본문 바로가기

SpringBoot

ch3 03. Spring DI의 원리(2)

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

- 컨테이너는 객체 저장소 : Map을 저장소로 사용한다. 

AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engien");
class AppContext {
	Map map = new HashMap();
    
    AppContext() {
    	map.put("car", new SportsCar());
    	map.put("engine", new Engine());        
    }
    
    Object getBean(String id) {return map.get(id);}
}

이렇게 하드코딩하면 변경에 불리하다. 

[AppConfig.java]

class AppConfig {
	@Bean public Car car() {return new SportsCar();}
	@Bean public Engine engine() {return new Engine();}
}

이렇게 코드를 바꾸면 변경 사항이 생겼을 때 AppConfig.java만 수정하면 되기 때문에 변경에 유리하게 된다. 

public AppContext(Class clazz) throws Exception { // AppConfig.class
	Object appConfig = clazz.newInstance();
    for(Method m : clazz.getDeclaredMethods())
    	for(Annotation anno : m.getDeclaredAnnotations())
    		if(anno.annotationType() == Bean.class) // @Bean이 있으면
            	map.put(m.getName(), m.invoe(appConfig, null));
}
Key Value
"car" 0x100
"engine" 0x200
... ...

 

 

 

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

AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car"); // 이름(id)으로 찾기
Car car2 = (Car)ac.getBean(Car.class); // 타입으로 찾기
Object getBean(String id) { // 이름으로 찾기
	return map.get(id); 
}

 0x100 반환 -> 지정한 이름을 Map의 Key에서 찾는다. (Key)

Object getBean(Class clazz) { // 타입으로 찾기
	for(Object obj : map.values()) {
    	if(clazz.isInstance(obj)) // obj instanceof clazz
   			return obj;
        }
        return null;
     }

타입을 비교할 땐 isinstanceof를 이용해서 찾는다. instanceof가 true인 것을 반환한다. (Value)

 

 

 

 

<실습>

- 이름으로 찾기 : by Name

 

[AppContext.java]

package com.fastcampus.ch3.di2;

import java.util.HashMap;
import java.util.Map;

public class AppContext {
    Map map = new HashMap();

    AppContext() {
        map.put("car", new SportsCar());
        map.put("engine", new Engine());
        map.put("door", new Door());
    }

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

[Main.java]

package com.fastcampus.ch3.di2;


    class Car{
        Engine engine;
        Door door;

        @Override
        public String toString() {
            return "Car{" +
                    "engine=" + engine +
                    ", door=" + door +
                    '}';
        }

        public void setEngine(Engine engine) {
            this.engine = engine;
        }

        public void setDoor(Door door) {
            this.door = door;
        }
    }

    class SportsCar extends Car {
        @Override
        public String toString() {
            return "SportsCar{" +
                    "engine=" + engine +
                    ", door=" + door +
                    '}';
        }
    }
    class Engine {}
    class Door {}


public class Main {
    public static void main(String[] args) {
         AppContext ac = new AppContext();
         Car car = (Car)ac.getBean("car"); // by Name
        System.out.println("car = " + car);
    }
}

 

- 타입으로 찾기 : by Type

[Main.java 코드 추가]

public class Main {
    public static void main(String[] args) {
         AppContext ac = new AppContext();
         Car car = (Car)ac.getBean("car"); // by Name
         Car car2 = (Car)ac.getBean(Car.class); // by Name
        System.out.println("car = " + car);
        System.out.println("car2 = " + car2);
    }
}

[AppContext.java 코드 추가]

    AppContext() {
        map.put("car", new SportsCar());
        map.put("engine", new Engine());
        map.put("door", new Door());
    }
    public Object getBean(Class clazz) {
        for(Object obj : map.values())
            if(clazz.isInstance(obj))
                return obj;
        return null;
    }

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

둘 다 객체를 잘 가져오는 것을 확인할 수 있다. 

 

 

- 자바 설정파일을 이용해서 하드 코딩을 대체

[Main.java 수정]

public class Main {
    public static void main(String[] args) {
        // AppContext(Class clazz) - 생성파일 자바클래스 지정
         AppContext ac = new AppContext(AppConfig.class);
         Car car = (Car)ac.getBean("car"); // by Name
         Car car2 = (Car)ac.getBean(Car.class); // by Name
         Engine engine = (Engine) ac.getBean("engine"); // by Name
         Door door = (Door) ac.getBean(Door.class); // by Type

         System.out.println("car = " + car);
         System.out.println("car2 = " + car2);
         System.out.println("door = " + door);
         System.out.println("engine = " + engine);
    }
}

[AppContext.java]

package com.fastcampus.ch3.di2;

import org.springframework.context.annotation.Bean;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AppContext {
    Map map = new HashMap();

    AppContext() {
        map.put("car", new SportsCar());
        map.put("engine", new Engine());
        map.put("door", new Door());
    }

    AppContext(Class clazz) {
        Object config = null;
        try {
            config = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Method[] methods = clazz.getDeclaredMethods();

        for(Method m : methods) {
            System.out.println("m = " + m.getName());
            for(Annotation anno : m.getDeclaredAnnotations()){
                if(anno.annotationType() == Bean.class)
                    try {
                        map.put(m.getName(), m.invoke(config, null));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                // map.put("car", new Car());
            }
        }
    }

    public Object getBean(Class clazz) {
        for(Object obj : map.values())
            if(clazz.isInstance(obj))
                return obj;
        return null;
    }

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

[AppConfig.java]

package com.fastcampus.ch3.di2;

import org.springframework.context.annotation.Bean;

public class AppConfig {
    // <bean id="car" class="com.fastcampus.ch3.Car">
    @Bean public Car car() { // 메서드 이름이 Bean의 이름
        // map.put("car", new Car());
        Car car = new Car();
        return car;
    }
    @Bean public Engine engine() {return new Engine();}
    @Bean public Door door() {return new Door();}
}

Bean - 저장소가 관리하는 객체

 

 

 

5. 객체를 자동 연결 하기(1) - @Autowired

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

car.setEngine(engine); // car.engine = engine
cer.getDoor(door) // car.door = door

car.setEngine(engine); // car.engine = engine
cer.getDoor(door) // car.door = door

이 부분이 객체간의 관계를 설정해주는 부분인데, @Autowired를 이용해서 이를 자동 연결할 수 있다.

이걸 사용하면 해당 코드를 사용하지 않아도 된다. 

class Car {
	Engine engine;
    Door door;
}

=>

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

engine이라면 0x200을 넣어주고, door라면 0x300을 넣어준다. (예를 들어서!)

@Autowired는 by Type이다. 

변경 사항에 더 유리해지고, 작성해야 하는 코드도 줄어들게 된다. 

 

5. 객체를 자동 연결 하기(2) - @Resource

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

@Resource를 사용하면 by Name으로 연결한다. 

여기에도 @Resource(name="engine")이 생략된 것이다. 

 

만약 다른 객체를 사용하고 싶으면, 이름을 지정해줄 수 있다. 

class Car {
	@Resource(name="engine2") Engine engine;
    @Resource Door door;
}

이렇게 사용하면 해당 참조변수가 SuperEngine을 가리켜서 사용할 수 있게 된다. 

 

 

<실습>

수동으로 engine과 door를 넣어줄 수 있지만 해당 코드를 주석처리 하고도 자동으로 값이 들어가도록 한다. 


AppContext.java에 doAutowired메소드를 추가한다.

package com.fastcampus.ch3.di2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AppContext {
    Map map = new HashMap();

    AppContext() {
        map.put("car", new SportsCar());
        map.put("engine", new Engine());
        map.put("door", new Door());
    }

    AppContext(Class clazz) {
        Object config = null;
        try {
            config = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Method[] methods = clazz.getDeclaredMethods();

        for(Method m : methods) {
            System.out.println("m = " + m.getName());
            for(Annotation anno : m.getDeclaredAnnotations()){
                if(anno.annotationType() == Bean.class)
                    try {
                        map.put(m.getName(), m.invoke(config, null));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                // map.put("car", new Car());
            }
        }
        doAutowired(); // @Autowired를 찾아서 빈(객체)간의 자동 연결처리
    }

    private void doAutowired() {
        for(Object bean : map.values()) {
            for(Field fld : bean.getClass().getDeclaredFields()) {
                if(fld.getAnnotation(Autowired.class)!=null)
                    try {
                        fld.set(bean, getBean(fld.getType())); // car.engine = ob;
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
            }
        }
    }

    public Object getBean(Class clazz) {
        for(Object obj : map.values())
            if(clazz.isInstance(obj))
                return obj;
        return null;
    }

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

[Main.java]

package com.fastcampus.ch3.di2;


import org.springframework.beans.factory.annotation.Autowired;

class Car{
        Engine engine;
        @Autowired Door door;

        @Override
        public String toString() {
            return "Car{" +
                    "engine=" + engine +
                    ", door=" + door +
                    '}';
        }

        public void setEngine(Engine engine) {
            this.engine = engine;
        }

        public void setDoor(Door door) {
            this.door = door;
        }
    }

    class SportsCar extends Car {
        @Override
        public String toString() {
            return "SportsCar{" +
                    "engine=" + engine +
                    ", door=" + door +
                    '}';
        }
    }
    class Engine {}
    class Door {}


public class Main {
    public static void main(String[] args) {
        // AppContext(Class clazz) - 생성파일 자바클래스 지정
         AppContext ac = new AppContext(AppConfig.class);
         Car car = (Car)ac.getBean("car"); // by Name
         Car car2 = (Car)ac.getBean(Car.class); // by Name
         Engine engine = (Engine) ac.getBean("engine"); // by Name
         Door door = (Door) ac.getBean(Door.class); // by Type

        // 빈들끼리의 관계를 설정 - 수동
//        car.setEngine(engine);
//        car.setDoor(door);

         System.out.println("car = " + car);
         System.out.println("car2 = " + car2);
         System.out.println("door = " + door);
         System.out.println("engine = " + engine);
    }
}

구분하기 위해 Door에만 @Autowired를 붙인다. 

 

 

이번엔 doResource메소드를 생성해서 실습을 진행한다. 

[AppContext.java]

    private void doResource() {
        for(Object bean : map.values()) {
            for(Field fld : bean.getClass().getDeclaredFields()) {
                if(fld.getAnnotation(Resource.class)!=null)
                    try {
                        fld.set(bean, getBean(fld.getName())); // car.engine = ob;
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
            }
        }
    }

[Main.java]

class Car{
    // @Resource(name="door")
        @Resource Engine engine;
        @Autowired Door door;

빈을 잘 찾아서 주입한 것을 확인할 수 있다. 

'SpringBoot' 카테고리의 다른 글

ch3 05. Spring 애너테이션  (0) 2023.07.30
ch3 04. Bean과 ApplicationContext  (0) 2023.07.30
ch3 02. Java Reflection API  (0) 2023.07.19
ch3 01. Spring DI의 원리(1)  (0) 2023.07.18
ch2 16. thymeleaf 사용하기  (0) 2023.07.18