Ch. 03 Spring DI 흉내내기

2023. 1. 11. 16:18

1. Spring DI 흉내내기

 

 

- 다형성, factory method 으로 변경에 유리한 코드짜기

- getObject를 사용하여 변경을 할때 코드에는 손을 대지않고 config.txt만 바꿔서 객체 생성이 가능 

 

<config.txt>

car=com.fastcampus.ch3.diCopy1.Truck
engine=com.fastcampus.ch3.diCopy1.Engine

 

<Main.java>

class Car {}
class SportsCar extends Car{}
class Truck extends Car{}
class Engine{}

public class Main1 {
    public static void main(String[] args) throws Exception{
        Car car = getCar();
        Car car2 = (Car)getObject("car");
        Engine engine = (Engine)getObject("engine");
        // getObject할때 해당 class가 생성되서 반환
        System.out.println("car = "+ car);
        System.out.println("car2 = "+ car2);
        System.out.println("engine = "+ engine);

    }

    static Car getCar() throws Exception{
        //config.txt내용을 읽어서 Properties에 map으로 저장 (String,String)
        Properties p = new Properties();
        p.load(new FileReader("config.txt"));

        Class clazz = Class.forName(p.getProperty("car"));

        return (Car)clazz.newInstance();
    }

    static Object getObject(String key) throws Exception{
        //config.txt내용을 읽어서 Properties에 map으로 저장
        Properties p = new Properties();
        p.load(new FileReader("config.txt"));

        //클래스 객체(설계도)를 얻어서
        Class clazz = Class.forName(p.getProperty(key));

        // 객체를 생성해서 반환
        return clazz.newInstance();
    }

}

 

 

<객체컨테이너 (ApplicationContext)>

- class의 map(객체 저장소)에다가 객체를 저장함
- 위의 properties에는 (String, String)이었는데 여기서는 객체를 저장해야하므로 (String, Object)이다.
(String으로 받은 다음 반복문으로 객체생성해서 map에 넣어줌)

 

<Main2.java>

...
class AppContext {
    Map map; // 저장소
    AppContext(){
        try {
        
            // 1. 먼저 Properties를 받음
            Properties p = new Properties();
            p.load(new FileReader("config.txt"));

            // 2. Properties 내용을 map에 저장
            map = new HashMap(p);

            // 3. 반복문으로 map에 저장되어 있는 Class이름을 얻어서 객체를 생성한 후 
            // 다시 map에 저장
            for(Object key : map.keySet()){
                Class clazz = Class.forName((String) map.get(key));
                map.put(key,clazz.newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    Object getBean(String key){
        return map.get(key);
    }
}

public class Main2 {
    public static void main(String[] args) throws Exception{

        AppContext ac = new AppContext();

        Car car2 = (Car)ac.getBean("car");
        Engine engine = (Engine)ac.getBean("engine");
        // getBean할때 해당 class가 생성되서 반환
        System.out.println("car2 = "+ car2);
        System.out.println("engine = "+ engine);

    }
}

 

 

<Component Scanning> - 자동 객체 등록하기

- 지금까지 config.txt에 사용할 객체를 등록해놓고 그걸 AppContext에서 읽어다가 map에 등록 (공통 객체만)
- 그외에도 @Component 방법이 있음
- class 앞에다가 @Component를 붙이면 반복문으로 @Component 붙은거 다찾아서 객체 생성해서 map에 저장
(guava 라이브러리)

 

<Main3.java>

@Component class Car {}
@Component class SportsCar extends Car{}
@Component class Truck extends Car{}
@Component class Engine{}

class AppContext {
    Map map; // 저장소
    AppContext(){
        map = new HashMap();
        doComponentScan();

    }

    private void doComponentScan() {
        try {
            // 1. 해당 패키지 내의 클래스 목록 set에 가져온다.
            // 2. 반복문으로 클래스 하나씩 읽어와서 @Component이 붙어 있는지 확인
            // 3. @Component가 붙어 있으면 객체를 생성해서 map에 저장
            ClassLoader classLoader= AppContext.class.getClassLoader();
            ClassPath classPath = ClassPath.from(classLoader);

            Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("com.fastcampus.ch3.diCopy3");

            for(ClassPath.ClassInfo classInfo : set){
                Class clazz = classInfo.load();
                Component componet = (Component) clazz.getAnnotation(Component.class);
                if(componet!=null){
                    String id = StringUtils.uncapitalize(classInfo.getSimpleName());
                    map.put(id, clazz.newInstance());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    Object getBean(String key) {return map.get(key);}
}
    ...

 

 

<by Name, by Type> - 객체 찾기

- 위에서 한게 이름(id)으로 찾기 이고 value를 돌면서 타입으로 찾기도 가능함 (instanceof 사용)
    //byName
    Object getBean(String key) {
        return map.get(key);
    }

    //byType
    Object getBean(Class clazz) {
        for (Object obj : map.values()) {
            // 이 obj가 해당 class의 자손이면 true
            if (clazz.isInstance(obj)) return obj;
        }

    }
        AppContext ac = new AppContext();
        Car car = (Car)ac.getBean("car"); // byName으로 객체 검색
        Car car2 = (Car)ac.getBean(Car.class); // byType으로 객체 검색

 

 

<Autowired> - 객체 자동 연결 (byType)

- Car, Engine, Door 객체를 생성하고 수동으로 car.engine=engine; car.door=door; 연결해줘야함
- class안에 인스턴스 변수 앞에 @AutoWired를 적어주면 맵에서 자동으로 찾아서 연결시켜줌
@Component class Car {

    @Autowired Engine engine;
    @Autowired Door door;
class AppContext {
    Map map; // 저장소

    AppContext() {
        map = new HashMap();
        doComponentScan();
        doAutowired();
    }
    
    private void doAutowired() {
            // map 에 저장된 객체의 iv중에 @Autowired가 붙어 있으면
            // map 에 iv의 타입에 맞는 객체를 찾아서 연결(객체의 주소를 iv 저장)
            try{
                for(Object bean : map.values()){
                    for( Field fld : bean.getClass().getDeclaredFields()) {
                        if (fld.getAnnotation(Autowired.class) != null) { // byType
                            fld.set(bean, getBean(fld.getType())); // car.engine = obj
                            }
                        }
                    }
            } catch (Exception e){
                e.printStackTrace();
            }
        }

 

 

<Resource> - 객체 자동 연결 (byName)

- AutoWired랑 역할은 같음.
- class안에 인스턴스 변수 앞에 @Resource(name="engine2")로 연결 가능
- name 안적어주면 인스턴스 변수의 Type의 첫글자를 소문자로 바꿔주고 이름으로 넣음
@Component class Car {

    @Resource Engine engine;
    @Resource Door door;
class AppContext {
    Map map; // 저장소

    AppContext() {
        map = new HashMap();
        doComponentScan();
        doAutowired();
        doResouce();
    }
    
	private void doResouce() {
        // map 에 저장된 객체의 iv중에 @Resource가 붙어 있으면
        // map 에 iv의 이름에 맞는 객체를 찾아서 연결(객체의 주소를 iv 저장)
        try{
            for(Object bean : map.values()){
                for( Field fld : bean.getClass().getDeclaredFields()) {
                    if (fld.getAnnotation(Resource.class) != null) { // byName
                        fld.set(bean, getBean(fld.getName())); // car.engine = obj
                    }
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

 

'Spring > 스프링의 정석' 카테고리의 다른 글

Ch. 03 Spring DB연결, DAO  (0) 2023.01.13
Ch. 03 Spring DI  (0) 2023.01.12
Ch. 02 Spring DispatcherServlet, 데이터변환과 검증  (0) 2023.01.10
Ch. 02 Spring 예외처리  (0) 2023.01.09
Ch. 02 Spring 쿠키와 세션  (0) 2023.01.09

BELATED ARTICLES

more