3. Back-end/3-2. Spring MVC - 남궁성

Spring MVC - [ Spring DI ]

yunyj99 2023. 2. 8. 03:43

1. 변경에 유리한 코드(1) - 다형성, factory method

아래와 같이 코드를 작성하면 SportsCar를 Truck으로 변경할 때 변경해야 할 포인트는 2군데이다.

SportsCar car = new SportsCar();

Truck car = new Truck();

다형성을 이용하면 변경 포인트를 한 군데로 줄일 수 있다.

Car car = new SportsCar();

Car car = new Truck();

그리고 아래처럼 메서드로 반환받을 경우, 객체를 생성하는 코드는 손 댈 필요가 없고 메서드에서 반환 타입만 변경해주면 된다.

Car car = getCar();
static Car getCar() {
	return new SportsCar();
}

static Car getCar() {
	return new Truck();
}

 

 

 


2. 변경에 유리한 코드(2) - Map과 외부 파일

아래와 같이 파일에서 클래스 정보를 받아오는 경우, 파일만 변경하고 코드는 변경하지 않아도 된다. 프로그램의 변경을 최소화 한다는 것은 엄청난 장점이다.

Car car = getCar();

static Car getCar() throws Exception {
    // config.txt를 읽어서 properties에 저장
    Properties p = new Properties();
    p.load(new FileReader("config.txt"));
    
    // 클래스 설계(설계도)를 얻어서
    Class clazz = Class.forName(p.getProperty("car"));
    
    return (Car)clazz.newInstance(); // 객체를 생성해서 반환
}
[config.txt]
car = com.fastcampus.ch3.SportsCar

Properties는 Map과 비슷하다고 보면 된다. config.txt 파일을 로드하면 Key(car) : Value(com.fastcampus.ch3.SportsCar) 와 같이 값이 들어간다고 생각하면 됨

 

아래와 같이 Object타입으로 변경하면 조금 더 확장해서 사용할 수 있다.

Car car = (Car)getObject("car");
Engine engine = (Engine)getObject("engine");

static Object getObject(String key) throws Exception {
    // config.txt를 읽어서 properties에 저장
    Properties p = new Properties();
    p.load(new FileReader("config.txt"));
    
    // 클래스 설계(설계도)를 얻어서
    Class clazz = Class.forName(p.getProperty(key));
    
    return clazz.newInstance(); // 객체를 생성해서 반환
}
[config.txt]
car = com.fastcampus.ch3.SportsCar
engine=com.fastcampus.ch3.Engine

 

 

 

 


3.객체 컨테이너(ApplicationContext) 만들기

class AppContext {
    // Map이 객체 저장소 = 객체 컨테이너 역할
    Map map;
    
    AppContext() {
        // 하드코딩 했을 경우 
        /*
        map = new HashMap();
        map.put("car", new SportsCar()); 
        map.put("engine", new Engine());
        */
        
        // properties 이용해 불러오기 
        Properties p = new Properties();
        p.load(new FileReader("config.txt");
        // config.txt 파일 map에 저장
        // properties는 (String, String)  Map은 (String, Object)
        map = new HashMap(p); 
        
        for(Object key : map.keySet()) {
            Class clazz = Class.forName((String)map.get(key)); // 클래스 이름 가져오기
            map.put(key, clazz.newInstance()); // 객체 생성해서 map에 저장
        }
    }

    Object getBean(String id) { return map.get(id); }
}
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");

 

 

 


4. 자동 객체 등록 - Component Scanning

클래스 앞에 @Component 애노테이션을 붙이고, Properties를 이용해 외부 파일에서 클래스 이름을 불러와 map에 객체 인스턴스를 담는 코드를 아래와 같이 바꿀수 있다.

즉, @Component 애노테이션이 붙은 클래스들은, map안에 객체명의 첫 글자를 소문자로 바꾼 단어를 key로 해서 담기게 된다. 따로 외부파일에 클래스 정보를 작성할 필요가 없다. (보통은 config.txt 파일에 공통 객체를 지정하고 개인이 만든 클래스 파일에 @Component 애노테이션을 붙인다.)

=> Component Scanning : 애노테이션을 이용해서 객체 저장소에 저장할 빈들을 지정해 주는 것

@Component class Car {}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
// -- guava 라이브러리 이용 --

ClassLoader classloader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classloader);

// 패키지 내의 모든 클래스를 읽어서 Set에 저장
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("com.fastcampus.ch3");

for(ClassPath.ClassInfo classInfo : set) { // 패키지 내에 @Component 붙은 클래스 찾아서 자동으로 등록
    Class clazz = classInfo.load();
    Component component = (Component)clazz.getAnnotation(Component.class);
    if(component!=null) ( // 만약 Component 애노테이션 붙어있으면
        String id = StringUtils.uncapitalize(classInfo.getSimpleName()); // Car -> car (클래스 이름만 가져와 소문자로 변경)
        map.put(id, clazz.newInstance()); // 객체 생성해서 map에 저장
    }
}

 

 

 


5. 객체 찾기 - by Name, by Type

객체를 찾을 때 이름으로 찾는 걸 by Name, 타입으로 찾는 걸 by Type 이라고 한다.

AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car"); //이름(id)으로 찾기
Car car2 = (Car)ac.getBean(Car.class); //타입으로 찾기
// 이름으로 찾기
// = map에서 key값에 해당하는 값 찾아서 반환
Object getBean(string id) { 
	return map.get(id);
}

// 타입으로 찾기
// = map의 value에서 해당하는 인스턴스 찾아서 반환
Object getBean(Class clazz) {// 타입으로 찾기
	for (Object obj : map.values()) {
		if (clazz.isInstance(obj)) // obj instanceof clazz
		return obj;
	}
	return null;
}

 

 

 


6. 객체 자동 연결 (1) - @Autowired

만약 아래와 같은 Car 클래스가 있다면, 

class Car {
	Engine engine;
	Door door;
}

수동으로  멤버변수로 있는 Engine과 Door에 객체를 생성해서 값을 줘야한다.

AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");
Door door = (Door)ac.getBean("door");

car.engine = engine;
car.door = door;

 

그러나 @Autowired 애노테이션을 사용하면 map에서 byType형식으로 객체를 찾아 자동 연결시켜준다.

* Autowired는 타입으로 먼저 검색 후, 여러개일 경우 이름으로 검색해서 찾는다.

class Car {
    @Autowired Engine engine;
    @Autowired Door door;
}

 

 

 


6. 객체 자동 연결(2) - @Resource

@Resource도 마찬가지로 객체를 자동으로 연결시키는 애노테이션인데 by Name 즉 이름으로 찾아준다는 점이 @Autowired와 다르다. 찾을 땐 클래스 이름의 첫글자를 소문자로 바꾼값을 key로 map에서 찾는다.

class Car {
    @Resource Engine engine; // 사실은 @Resource(name="engine") 임. 뒷부분은 생략되어있음
    @Resource Door door;
}



class Car {
    @Resource(name="engine2") Engine engine; // 혹은 다른 이름 직접 지정해도 OK
    @Resource Door door;
}

 


참조

https://fastcampus.co.kr/dev_academy_nks

 

스프링의 정석 : 남궁성과 끝까지 간다 | 패스트캠퍼스

국비지원 조기 마감 신화, 베스트셀러 'JAVA의 정석'의 저자 남궁성의 Spring 강의입니다! 오픈톡방과 카페에서 평생 AS를 제공하며 완강과 취업까지 도와드립니다. 지금 할인가로 확인하세요!

fastcampus.co.kr