Spring - 12. Spring DI

병호·2023년 8월 21일
0

Spring

목록 보기
12/16
post-thumbnail

변경에 유리한 코드(1) - 다형성

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

위와 같은 클래스들의 상속관계가 있다고 할 때

SportsCar car = new SportsCar();

Truck car = new Truck();

위 코드를

Car car = new SportsCar();

Car car = new Truck();

다형성을 이용하면 다음과 같이 바꿀 수 있고 객체를 SportsCar에서 Truck으로 변경하게 될 경우, 다형성을 사용하지 않은 코드에서는 참조변수타입과 객체, 2부분을 바꾸어야 되는 반면, 다형성을 사용하는 코드는 객체부분만 바꾸면 된다는 점에서 변경포인트가 하나 줄게되어 변경에 용이하다. 이를 더 변경에 용이하게 바꾸면 다음과 같다.

Car car = getCar();

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

이와 같이 getCar 메서드를 만들고 Car타입의 변수에 SportsCar 객체의 내용을 넣을 수도 있다. 이렇게하면 SportsCar가 아닌 Truck의 객체를 생성하여도 getCar메서드의 내용만 바꾼다면 getCar를 무수히 많이 호출하여도 모두 변경에 용이한 점이 있다.

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

Car car = getCar();

static Car getCar() throws Exception {

	Properties p = new Properties(); //config.txt를 읽어서 Properties에 저장
    p.load(new FileReader("config.txt"));
    
    Class clazz = Class.forName(p.getProperty("car")); //클래스 객체를 얻어서
    
    return (Car)clazz.newInstance(); //객체를 생성해서 반환

위 코드에서 Properties객체를 생성한다. Properties객체는 Map과 같이 key, value로 되어있고 모두 String형을 받는다.

[config.txt]
car=com.fastcampus.ch3.SportsCar

위 txt파일에서 car는 key, com.fastcampus.ch3.SportsCar는 value이다. Properties객체에서 이를 읽어 key, value로 저장한다. SportsCar가 아닌 Truck 클래스의 내용을 사용하고 싶으면 txt파일만 바꾸어주면 된다. 이는 본 코드는 전혀 바꾸지 않고, txt파일만 변화함으로써 프로그램을 변경하지 않아도 된다는 큰 장점이 있다.

위를 더 변경에 유리하게 바꿀 수 있다.

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

위 코드에서는 car, engine을 key, value형태로 p에 저장한다. Object형을 써서 Car, Engine형 둘다 다룰수 있다.

객체 컨테이너 만들기

객체 저장소

class AppContext {
	Map map; //객체 저장소
    
    AppContext() { //초기화
    	map = new HashMap();
        map.put("car", new SportsCar());
        map.put("engine", new Engine());
    }
    
    Object getBean(String id) {return map.get(id); }
}
AppContext ac = new AppContext(); //ac객체 생성
Car car = (Car)ac.getBean("car"); //key값 주기
Engine engine = (Engine)ac.getBean("engine");

자동객체 등록하기 - Component Scanning

@Component class Car {}
@Component class SportsCar extends Car{}
@Component class Truck extends Car{}
@Component class Engine{}
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) {
    	String id = StringUtils.uncapitalize(classInfo.getSimpleName());
        map.put(id, clazz.newInstance()); //객체 생성해서 map에 저장
    }
}

@Component가 붙어있는 클래스를 순회. 붙어있지 않으면 순회안함.

객체 찾기 - by Name, by Type

AppContext ac = new AppContext();
Car car = (Car)ac.getbean("car"); //이름(id)으로 찾기
Car car2 = (Car)ac.getbean(Car.class); //타입으로 찾기

이름으로 찾으면 key값에서 일치하는 값을 찾는것이고, 타입으로 찾으면 value에서 일치하는 값을 찾는것이다.

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

--이름으로 찾기--

Object getBean(Class clazz) {
	for(Object obj: map.values()) {
    	if(clazz.isInstance(obj)) // obj instanceof clazz를 이용해서 타입 일치확인
        	return obj;
    }
    return null;
}

--타입으로 찾기--

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

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;
class Car {
	Engine engine;
    Door door;
}
class Car {
	@Autowired Engine engine;
    @Autowired Door door;
 }

@Autowired를 붙이면 맵에 저장되있는 값을 찾아 자동연결을 해준다.
(타입으로 찾는다)

class Engine{}
@Component class SuperEngine extends Engine {}
@Component class TurboEngine extends Engine {}

@Component
class Car {
	@Value("red") String color;
    @Value("100") int oil;
    @Autowired
    @Qualifier("superEngine")
    Engine engine;

@Autowired에서 검색한 타입이 여러개일 경우, 일치하는 것으로 찾는다. 하지만 위 코드와 같이 @Autowired에서 검색한 타입이 2개인데 둘 다 일치하지 않는다면 @Qualifier를 사용해 직접 지정해주어야 한다.

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

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;
class Car{
	Engine engine;
    Door door;
}
class Car {
	@Resource Engine engine; //Engine의 첫글자 E를 소문자로 바꾼것이 이름으로 사용됨(name = "engine")생략
    @Resource Door door;
}

@Resource를 붙이면 맵에 저장되어있는 값을 찾아 자동연결을 해준다.
(이름으로 찾는다)

빈(bean) 이란?

Spring Container가 관리하는 객체

Spring container - Bean 저장소, Bean을 저장, 관리(생성, 소멸, 연결(@Autowired))

IoC가 적용된 경우에는 사용자가 하던 new연산을 통한 객체 생성과 사용자의 제어권을 스프링에게 넘긴다. 사용자는 직접 생성한 객체가 아닌 스프링에 의하여 관리당하는 자바 객체를 사용한다. 이를 빈(Bean) 이라고 한다.

  1. BeanFactory - Bean을 생성, 연결 등의 기본 기능을 정의
  2. ApplicationContext - BeanFactory를 확장해서 여러 기능을 추가 정의

IoC와 DI

제어의 역전 IoC - 제어의 흐름을 전통적인 방식과 다르게 뒤바꾸는 것

[전통적인 방식] 사용자 코드가 Framework 코드를 호출
Car car = new Car();
car.turboDrive(); // 호출
void turboDrive() {
	engine = new TurboEngine();
    engine.start();
[IoC] Framework 코드가 사용자 코드를 호출
Car car = new Car();
car.drive(new SuperEngine()); //사용자 코드
void drive(Engine engine) {
	engine.start(); //호출
}

호출 방향이 반대.

IoC를 사용하는 이유

위 코드의 전통적인 방식에서 turboDrive 메서드는 잘 변하지 않는 코드인데반해, TurboEngine 메서드는 잘변하는 코드이다. IoC방식은 자주 변하는 SuperEngine() 메서드를 빼냄으로써 drive 메서드가 자주 변하지 않는 메서드로 바꾸어주는것이다.

의존성 주입 DI - 사용할 객체를 외부에서 주입받는 것

profile
안녕하세요

0개의 댓글