1. 변경에 유리한 코드(1) - 다형성, factory method
아래와 같이 코드를 작성하면 SportsCar를 Truck으로 변경할 때 변경해야 할 포인트는 2군데이다.
SportsCar car = new SportsCar();
Truck car = new Truck();
다형성을 이용하면 변경 포인트를 한 군데로 줄일 수 있다.
Car car = new SportsCar();
Car car = new Truck();
그리고 아래처럼 메서드로 반환받을 경우, 객체를 생성하는 코드는 손 댈 필요가 없고 메서드에서 반환 타입만 변경해주면 된다.
Car car = getCar();
static Car getCar() {
return new SportsCar();
}
static Car getCar() {
return new Truck();
}
2. 변경에 유리한 코드(2) - Map과 외부 파일
아래와 같이 파일에서 클래스 정보를 받아오는 경우, 파일만 변경하고 코드는 변경하지 않아도 된다. 프로그램의 변경을 최소화 한다는 것은 엄청난 장점이다.
Car car = getCar();
static Car getCar() throws Exception {
// config.txt를 읽어서 properties에 저장
Properties p = new Properties();
p.load(new FileReader("config.txt"));
// 클래스 설계(설계도)를 얻어서
Class clazz = Class.forName(p.getProperty("car"));
return (Car)clazz.newInstance(); // 객체를 생성해서 반환
}
[config.txt]
car = com.fastcampus.ch3.SportsCar
Properties는 Map과 비슷하다고 보면 된다. config.txt 파일을 로드하면 Key(car) : Value(com.fastcampus.ch3.SportsCar) 와 같이 값이 들어간다고 생각하면 됨
아래와 같이 Object타입으로 변경하면 조금 더 확장해서 사용할 수 있다.
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
3.객체 컨테이너(ApplicationContext) 만들기
class AppContext {
// Map이 객체 저장소 = 객체 컨테이너 역할
Map map;
AppContext() {
// 하드코딩 했을 경우
/*
map = new HashMap();
map.put("car", new SportsCar());
map.put("engine", new Engine());
*/
// properties 이용해 불러오기
Properties p = new Properties();
p.load(new FileReader("config.txt");
// config.txt 파일 map에 저장
// properties는 (String, String) Map은 (String, Object)
map = new HashMap(p);
for(Object key : map.keySet()) {
Class clazz = Class.forName((String)map.get(key)); // 클래스 이름 가져오기
map.put(key, clazz.newInstance()); // 객체 생성해서 map에 저장
}
}
Object getBean(String id) { return map.get(id); }
}
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car");
Engine engine = (Engine)ac.getBean("engine");
4. 자동 객체 등록 - Component Scanning
클래스 앞에 @Component 애노테이션을 붙이고, Properties를 이용해 외부 파일에서 클래스 이름을 불러와 map에 객체 인스턴스를 담는 코드를 아래와 같이 바꿀수 있다.
즉, @Component 애노테이션이 붙은 클래스들은, map안에 객체명의 첫 글자를 소문자로 바꾼 단어를 key로 해서 담기게 된다. 따로 외부파일에 클래스 정보를 작성할 필요가 없다. (보통은 config.txt 파일에 공통 객체를 지정하고 개인이 만든 클래스 파일에 @Component 애노테이션을 붙인다.)
=> Component Scanning : 애노테이션을 이용해서 객체 저장소에 저장할 빈들을 지정해 주는 것
@Component class Car {}
@Component class SportsCar extends Car {}
@Component class Truck extends Car {}
@Component class Engine {}
// -- guava 라이브러리 이용 --
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) ( // 만약 Component 애노테이션 붙어있으면
String id = StringUtils.uncapitalize(classInfo.getSimpleName()); // Car -> car (클래스 이름만 가져와 소문자로 변경)
map.put(id, clazz.newInstance()); // 객체 생성해서 map에 저장
}
}
5. 객체 찾기 - by Name, by Type
객체를 찾을 때 이름으로 찾는 걸 by Name, 타입으로 찾는 걸 by Type 이라고 한다.
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car"); //이름(id)으로 찾기
Car car2 = (Car)ac.getBean(Car.class); //타입으로 찾기
// 이름으로 찾기
// = map에서 key값에 해당하는 값 찾아서 반환
Object getBean(string id) {
return map.get(id);
}
// 타입으로 찾기
// = map의 value에서 해당하는 인스턴스 찾아서 반환
Object getBean(Class clazz) {// 타입으로 찾기
for (Object obj : map.values()) {
if (clazz.isInstance(obj)) // obj instanceof clazz
return obj;
}
return null;
}
6. 객체 자동 연결 (1) - @Autowired
만약 아래와 같은 Car 클래스가 있다면,
class Car {
Engine engine;
Door door;
}
수동으로 멤버변수로 있는 Engine과 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 애노테이션을 사용하면 map에서 byType형식으로 객체를 찾아 자동 연결시켜준다.
* Autowired는 타입으로 먼저 검색 후, 여러개일 경우 이름으로 검색해서 찾는다.
class Car {
@Autowired Engine engine;
@Autowired Door door;
}
6. 객체 자동 연결(2) - @Resource
@Resource도 마찬가지로 객체를 자동으로 연결시키는 애노테이션인데 by Name 즉 이름으로 찾아준다는 점이 @Autowired와 다르다. 찾을 땐 클래스 이름의 첫글자를 소문자로 바꾼값을 key로 map에서 찾는다.
class Car {
@Resource Engine engine; // 사실은 @Resource(name="engine") 임. 뒷부분은 생략되어있음
@Resource Door door;
}
class Car {
@Resource(name="engine2") Engine engine; // 혹은 다른 이름 직접 지정해도 OK
@Resource Door door;
}
참조
https://fastcampus.co.kr/dev_academy_nks
스프링의 정석 : 남궁성과 끝까지 간다 | 패스트캠퍼스
국비지원 조기 마감 신화, 베스트셀러 'JAVA의 정석'의 저자 남궁성의 Spring 강의입니다! 오픈톡방과 카페에서 평생 AS를 제공하며 완강과 취업까지 도와드립니다. 지금 할인가로 확인하세요!
fastcampus.co.kr
'3. Back-end > 3-2. Spring MVC - 남궁성' 카테고리의 다른 글
Spring MVC - [ AOP ] (0) | 2023.03.09 |
---|---|
Spring MVC - [ Transaction, commit, Rollback ] (0) | 2023.03.03 |
Spring MVC - [ 데이터의 검증 ] (0) | 2023.02.02 |
Spring MVC - [ 데이터의 변환 ] (0) | 2023.02.02 |
Spring MVC - [ DispatcherServlet ] (0) | 2023.02.01 |