본문 바로가기

백엔드 개발/SpringMVC

Spring Di (클래스 안의 인스턴스 변수가 객체일 때 자동으로 연결)

<개요>

저번 시간에 배웠던 내용은 (main2.java)클래스의 경로주소가 들어가 있던 외부 저장 파일 (.porperties)에서 Key= 객체이름, Value= 클래스의 경로주소로 값 뽑아내서 객체 저장소란 map에 저장했다. 해당 map에서 다시 value의 경로주소를 통해 진짜 객체를 만들어 내서 value 값들을 갱신 했다. 

결국 map은 (K(객체이름), V(객체))가 되어서 사용자가 어떤 객체를 선언하려하면, new를  이용해 동적할당 하지 않고 map에서 찾아 꺼내 썼다. 

그다음 main3.java에서 배운 내용은 컴포넌트 어노테이션을 이용하는 것이였다. 먼저 우리가 사용할 클래스들에는 @Component를 붙인다. 그리고 doComponentScan이라는 함수를 만든다. 해당 함수는 패키지 내의 모든 클래스를 읽어서 set이란 컬렉션 프레임워크에 저장하고, set에 저장된 클래스를 하나씩 읽어서 컴포넌트가 있는지 확인한 후, 컴포넌트가 붙어 있으면 map에 (K(Class이름의 앞글자만 소문자로 바꾼 이름), V(해당 클래스의 객체))로 만들어 저장했다. 

그 후 사용자가 객체를 생성할 일이 있으면 해당 Map에서 찾아내서 썼다. 

 

이번에 배울 내용은 main3.java에서 한 단계 더 나아간 것으로 Car라는 객체가 iv로 다른 클래스의 객체들을 지니고 있는 경우이다. 이 경우, 그저 객체들을 생성한 것에서 끝나지 않고, Car의 iv 인 객체들에 생성된 객체들을 연결해주어야 한다. 

물론 수동으로 할 수 있지만, 이를 자동으로 하는 법에 대해서 배우겠다. 자동으로 하는 법은 총 2가지인데 하나는 

@AutoWired 어노테이션을 사용하는 법, 하나는 @Resource 어노테이션을 사용하는 법이다. 

 

1. Map에서 클래스 타입으로 객체 검색

 Object getBean(String key) {return map.get(key);} // by name
 
 
 Car car = (Car)ac.getBean("car");   // byName으로 객체를 검색

위 함수는Map의 Key를 이용해 객체를 찾는 Method이다. 해당 매소드는 Key 값 ,그러니까 객체의 이름을 주면 해당 이름을 Key로 가진 요소의 Value 값(객체)을 리턴해주었다.

이번에는 Map의 Value 값 자체를 뒤져서 일치하는  객체를 찾아내는 방법을 알아보겠다. 

    Object getBean(Class clazz ) { // by type
        for (Object obj :
                map.values()) {
       // obj가 clazz 메타데이터를 구현한 클래스의 객체라면, (여기서 객체는 자손 클래스의 객체도 상관 무)         
            if (clazz.isInstance(obj)) 
                return obj;
        }
        return null;
    }

/* 	어떤 클래스의 메타데이터가 인수로 들어오면, 
	map 내에서 해당 클래스의 객체이거나 자손인 녀석이 있는지 차례대로 찾고,
    만약 발견하면 그녀석을 return */
    
    
    
    
    /*사용하는 예시*/
    // 어떤 클래스의 메타데이터로 검색
    Door door = (Door)ac.getBean(Door.class);  
	//map 내에 door라는 타입의 객체가 있는지 뒤진다.

2. @AutoWired 어노테이션 활용

Map에서 객체를 찾는 2가지 방법은 이제 알아봤고, 객체 사이의 관계를 잇는 2가지 방법을 알아보겠다. 

먼저 예시 상황이다. 

@Component class Car {
    Engine engine;
    Door door;
    }
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car{}
@Component class Engine {}
@Component class Door {}

Car라는 객체에 인스턴스 변수로 Engine과 Door의 객체가 들어가 있다. 

@Component 방식을 썼으므로, Map을 이용한 객체 생성은 원활하게 되었다. 

        Car car = (Car)ac.getBean("car");   // byName으로 객체를 검색
        Engine engine = (Engine)ac.getBean("engine");
        Door door = (Door)ac.getBean(Door.class);  // by Type으로 객체 검색

하지만 이 경우 선언만 한 것이지 Car의 iv로 두 객체가 대입된 것은 아니다. 

이걸 수동으로 해주는 방법도 있다.

        // 수동으로 객체를 연결
//        car.engine = engine;
//        car.door = door;

이제 AutoWired를 이용해 자동으로 하는 법을 배워보겠다. 

먼저 Car 객체의 인스턴스 변수 중 객체인 것에 @AutoWired 태그를 붙인다. 

@Component class Car {
    @Resource
    Engine engine;
    @Resource
    Door door;
    }

객체 저장소 생성자에 

생성될 시 실행할 매소드를 하나 더 늘린다. 

class AppContext {
    Map map ; //객체 저장소

    AppContext(){
    	// 1. 저장소 생성
       map = new HashMap();
       // 2. @Component 어노테이션 붙은 클래스들 자동 객체 생성 해서 (객체이름, 객체)로 Map에 저장
       //	우린 이제 직접 객체 선언 할당할 필요 없이, Map에서 필요한 객체를 뒤져서 사용할 수 있게 됨.
       doComponentScan();
       
       // 3. 만약 A 클래스 안에 인스턴스 변수로 B 클래스의 객체가 있을 시 
       // A 클래스가 생성되고 B 클래스가 생성되었을 때, 둘의 관계를 연결해주는 것
       // B가 자동으로 A의 인스턴스 변수로 들어감.
       doAutoWired();
       doResource();

    }
    private void doAutoWired() {
        // map에 저장된 객체의 iv 중에 @AutoWired가 붙어 있으면
        // map에서 iv의 타입에 맞는 객체를 찾아서 연결 (연결한다는 것은? 해당 객체의 주소를 iv에 저장)

        // bean은 Map에 있는 모든 Value
        for (Object bean :
                map.values()) {
            // fid는 필드 클래스로 클래스의 모든 변수들이 들어갈 수 있는 곳이고,
            // Map의 Value에는 객체가 있으니까, 
            // 그 객체를 메타데이터로 분해해서 접근제어자 상관없이 객체 안의 모든 필드에 접근함.
            for (Field fid : bean.getClass().getDeclaredFields()) {
                // 만약 접근한 필드 중 @Autowired라는 어노테이션 가진 필드가 있다면
                if(fid.getAnnotation(Autowired.class) != null){ // by type
                    try {
                        //인스턴스 변수로 있는 클래스의 메타데이터를 getBean의 인수로 집어 넣음.
                        fid.set(bean, getBean(fid.getType())); //car.engine = obj ; 대입 연산
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

3. @Resource 어노테이션 활용 

@AutoWired가 인스턴스 변수로 있는 클래스의 메타데이터를 활용해 Map의 Value(객체)에서 일치하는 Type의 객체를 찾았다면 @Resource는 인스턴스 변수로 있는 클래스의 객체 이름을 이용하여 Map의 Key(객체 이름) 중에서 이와 일치하는 객체를 찾는다.

그렇다면, 객체 이름을 어떻게 알까? @Resource Engine이면, 해당 클래스의 첫 대문자를 소문자로 바꾼 이름을 객체 이름으로 간주하고 찾는다. (default) 사실 @Resource Engine은 @Resource (name = "engine") Engine의 줄임말이다. 

우리가 (name = "")를 중간에 써서 객체 이름을 지정할 수 도 있다. 

    private void doResource() {
        // map에 저장된 객체의 iv 중에 @Resource가 붙어 있으면
        // map에서 iv의 이름에 맞는 객체를 찾아서 연결 (연결한다는 것은? 해당 객체의 주소를 iv에 저장)
        for (Object bean :
                map.values()) {
            // bean이라는 객체를 메타데이터로 분해해서 해당 객체의 모든 필드(변수)에 접근
            for (Field fid : bean.getClass().getDeclaredFields()) {
                // 필드들 중에, resource란 어노테이션 가지고 있다면 밑을 실행
                if(fid.getAnnotation(Resource.class) != null){ // by name
                    try {
                        //field.set(해당 변수가 속한 객체, 해당 변수에 넣을 값)
                        // fid.getName은 fid가 객체이므로, 해당 객체의 클래스 명을 반환한다.
                        // 그리고 getBean이 그 클래스명과 같은 Key를 Map에서 찾아서 그 Key에 해당하는 Value를 반환한다.
                        fid.set(bean, getBean(fid.getName())); //car.engine = obj ; 대입 연산
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

4. 스스로 해보기 

내용이 어려워서 안 보고 적지는 못 했다. 그 대신 @AutoWired @Resource들을 toggle 시켜 보았다. 

 

'백엔드 개발 > SpringMVC' 카테고리의 다른 글

Spring Di 활용하기 - 이론 (1)  (0) 2023.03.31
DI 활용하기 - 실습  (0) 2023.03.30
DI 흉내내기(2)  (0) 2023.03.21
DI 흉내내기  (0) 2023.03.21
Intellij 단축기 모음집 (계속 추가)  (0) 2023.03.19