Spring DI 흉내내기
1. 변경에 유리한 코드(1) - 다형성, factory method
class Car{}
class SportsCar extends Car {}
class Truck extends Car {}
다형성을 이용하면 코드를 작성할 때 실수가 줄어들고, 코드의 활용과 수정이 편리해진다.
변경에 유리한 코드(2) - Map과 외부 파일
(Properties는 Map과 거의 동일하나, 파일 내용을 불러오는 것이 더 편리함)
[config.txt] car=com.fastcampus.ch3.SportsCar engine=com.fastcampus.ch3.Engine |
config.txt 파일의 내용이 다음과 같이 구성되어 있다.
p.load(new FileReader("config.txt")); 의 결과로
Properties 객체에 Key와 Value에 각각 '='을 구분자로 하여 저장함.
Key | Value |
"car" | "com.fastcampus.ch3.SportsCar" |
"engine" | "com.fastcampus.ch3.Engine" |
프로그램의 변경이 필요없이, config.txt 파일의 내용만 수정하면 변경이 가능하다는 장점이 있다.
2. 객체 컨테이너(ApplicationContext) 만들기
ApplicationContext는 저장소라고 생각하면 된다.
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();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");
(Java Bean = 객체라고 이해하면 됨)
AppContext의 생성자를 통해 Map에 직접 객체를 생성하여 저장한다.
AppContext의 인스턴스를 만들어 원하는 객체를 사용할 수 있다.
이 과정을 Properties와 접목시키면 다음과 같다.
Properties p = new Properties();
p.load(new FileReader("config.txt"));
map = new HashMap(p);
for(Object key : map.keySet()) {
Class clazz = Class.forName((String)map.get(key));
map.put(key, clazz.newInstance());
}
config.txt 파일에 원하는 객체의 정보를 Properties 객체에 불러와 저장한 후 다시 map에 저장한다.
반복문을 통해 객체의 정보를 꺼낸 후 인스턴스를 만들어 다시 동일한 key에다 객체를 생성해 저장한다.
즉, config 파일의 내용(객체의 정보)만 바꾸면 원하는 객체를 코드의 수정 없이 저장이 가능하게 된다.
3. 자동 객체 등록하기 - Component Scanning
@Component
package com.fastcampus.ch3.diCopy3;
import com.google.common.reflect.ClassPath;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@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. 패키지 내의 클래스 목록을 가져온다.
// 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 component = (Component) clazz.getAnnotation(Component.class);
if (component != null) {
String id = StringUtils.uncapitalize(classInfo.getSimpleName());
map.put(id, clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Object getBean(String key) {
return map.get(key);
}
}
public class Main3 {
public static void main(String[] args) throws Exception {
AppContext ac = new AppContext();
Car car = (Car) ac.getBean("car");
Engine engine = (Engine) ac.getBean("engine");
System.out.println("car = " + car);
System.out.println("engine = " + engine);
}
}
4. 객체 찾기 - by Name, by Type
Object getBean(String key) { // byName
return map.get(key);
}
Object getBean(Class clazz) { // byType
for(Object obj : map.values()) {
if(clazz.isInstance(obj))
return obj;
}
return null;
}
기존의 방법은 Map에 저장된 key의 name으로 객체를 생성했다면(byName)
Class의 타입으로 객체를 생성할 수도 있다. (byType)
Class 타입을 입력하면 map에 저장된 모든 value들(객체)을 하나씩 꺼내서
입력했던 Class와 같은 클래스 타입이면 그 객체를 반환한다.
public class Main3 {
public static void main(String[] args) throws Exception {
AppContext ac = new AppContext();
Car car = (Car) ac.getBean("car"); // byName으로 객체를 검색
Car car2 = (Car) ac.getBean(Car.class); // byType으로 객체를 검색
Engine engine = (Engine) ac.getBean("engine");
System.out.println("car = " + car);
System.out.println("car2 = " + car2);
System.out.println("engine = " + engine);
}
}
car2 = com.fastcampus.ch3.diCopy3.SportsCar@50cbc42f
로 나오게 되는데, 그 이유는 SportsCar가 car의 자손이기 때문이다.
5. 객체를 자동 연결하기(1) - @Autowired
(by Type)
class Car {
Engine engine;
Door 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 애너테이션을 사용하면 객체를 자동으로 연결할 수 있다.
(byType // Map의 value에서 해당 Type을 찾는다)
class Car {
@Autowired Engine engine;
@Autowired Door door;
}
6. 객체를 자동 연결하기(2) - @Resource
(by Name)
class Car {
@Resource Engine engine;
@Resource Door door;
}
@Resource(name="engine") Engine engine; 는 @Resource Engine engine; 과 같다.
name을 생략하면 클래스의 첫 글자를 소문자로 바꿔서 engine에 해당하는 객체의 주소를 저장하게 된다.
생략하지 않고 원하는 Name 객체의 주소를 저장할 수도 있다.
@Resource(name="engine2") Engine engine;
-> engine2라는 이름을 가진 engine객체를 Car의 engine의 주소로 저장하게 된다.
ex) @Autowired, @Resource 실습
package com.fastcampus.ch3.diCopy4;
import com.google.common.reflect.ClassPath;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component class Car {
@Autowired // 또는 @Resource 사용 가능
Engine engine;
@Autowired // 또는 @Resource 사용 가능
Door door;
@Override
public String toString() {
return "Car{" +
"engine=" + engine +
", door=" + door +
'}';
}
}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
@Component class Door {}
class AppContext {
Map map; // 객체 저장소
AppContext() {
map = new HashMap();
doComponentScan();
doAutowired();
doResource();
}
private void doResource() {
// map에 저장된 객체의 iv중에 @Resource가 붙어 있으면
// map에서 iv의 이름에 맞는 객체를 찾아서 연결(객체의 주소를 iv에 저장)
try {
for(Object bean : map.values()) {
for(Field fld : bean.getClass().getDeclaredFields()) {
if(fld.getAnnotation(Resource.class)!=null) // btName
fld.set(bean, getBean(fld.getName())); // car.engine = obj;
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
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) // btType
fld.set(bean, getBean(fld.getType())); // car.engine = obj;
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void doComponentScan() {
try {
// 1. 패키지 내의 클래스 목록을 가져온다.
// 2. 반복문으로 클래스를 하나씩 읽어와서 @Component 애너테이션이 붙어있는지 확인
// 3. @Component가 붙어있으면 객체를 생성해서 map에 저장
ClassLoader classLoader = AppContext.class.getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
Set<ClassPath.ClassInfo> set = classPath.getTopLevelClasses("com.fastcampus.ch3.diCopy4");
for(ClassPath.ClassInfo classInfo : set) {
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());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Object getBean(String key) {
return map.get(key);
} // byName
Object getBean(Class clazz) { // byType
for(Object obj : map.values()) {
if(clazz.isInstance(obj))
return obj;
}
return null;
}
}
public class Main4 {
public static void main(String[] args) throws Exception {
AppContext ac = new AppContext();
Car car = (Car) ac.getBean("car"); // byName으로 객체를 검색
Door door = (Door) ac.getBean(Door.class); // byType으로 객체를 검색
Engine engine = (Engine) ac.getBean("engine");
// 수동으로 객체를 연결
// car.engine = engine;
// car.door = door;
System.out.println("car = " + car);
System.out.println("engine = " + engine);
System.out.println("door = " + door);
}
}
doAutowired와 doResource 메소드를 추가하였다.
doAutowired()는 byType 으로, doResource()는 byName 으로
해당 애너테이션이 붙어있는 클래스에 각각 객체를 찾아서 연결해주는 메소드이다.
출처 : 스프링의 정석 : 남궁성과 끝까지 간다
'Spring & SpringBoot > Spring' 카테고리의 다른 글
220503 Spring - Chapter 3. Spring DI와 AOP (Part. 3) (0) | 2022.05.03 |
---|---|
220502 Spring - Chapter 3. Spring DI와 AOP (Part. 2) (0) | 2022.05.02 |
220428 Spring - Chapter 2. Spring MVC (Part 7) (0) | 2022.04.29 |
220427 Spring - Chapter 2. Spring MVC (Part 6) (0) | 2022.04.27 |
220426 Spring - Chapter 2. Spring MVC (Part 5) (0) | 2022.04.26 |