강좌 ch 3. 3강 요약
객체를 찾을 때에는 객체 이름(id)이나, 타입으로 찾는 방법 2가지가 있다. map으로 생각해보면, 이름으로 찾는 것은 키를 가지고 찾는 것이고, 타입으로 찾는 것은 값을 가지고 찾는 것이다. 이를 각각 by name, by type 이라고 한다.
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car"); // by name
Car car2 = (Car)ac.getBean(Car.class); // by type
Object getBean(String id){
return map.get(id);
Object getBean(Class clazz){
for (Object obj:map.values()){
if (clazz.isInstance(obj)){ // obj가 clazz의 객체거나 자손이면 true
return obj;
}
return null;
}
AppContext 내부에 Car, Engine, Door 클래스가 생성되고, Car 클래스가 Engine과 Door를 프로퍼티로 가진다고 하자. Car, Engine, Door을 생성한 후 참조연산자를 이용해 객체를 직접 지정해주는 것은 수동으로 객체를 참조해(=객체의 메모리 번지를 저장) 연결하는 것이다.
객체를 자동으로 연결하려면 @Autowired 라는 애너테이션을 사용한다. 이 애너테이션은 application context 내부에 저장된 객체들 중에서 연결된 객체를 찾아(byType) 자동으로 연결을 해준다.
먼저 수동연결된 예시이다.
// Car class
@Component class Car{
Engine engine;
Door door;
}
// byName, byType
Object getBean(String id) {
return map.get(id);
}
Object getBean(Class clazz) {
for (Object obj : map.values()) {
if (clazz.isInstance(obj)) // obj가 clazz의 객체거나 자손이면 true
return obj;
}
return null;
}
// main
public class Main4 {
public static void main(String[] args) throws Exception{
AppContext ac = new AppContext();
Car car = (Car)ac.getBean("car"); // Truck
Engine engine = (Engine)ac.getBean("engine");
Door door = (Door)ac.getBean(Door.class);
// 수동으로 객체 연결
car.engine = engine;
car.door = door;
System.out.println("car = " + car);
System.out.println("engine = " + engine);
System.out.println("door = " + door);
}
}
@Autowired를 사용하려면, 애너테이션을 추가하고, App Context에 doAutowired()라는 메서드를 만들어준다.
// Car class
@Component class Car{
@Autowired Engine engine;
@Autowired Door door;
}
// AppContext
class AppContext {
Map map;
AppContext() {
map = new HashMap();
doComponentScan();
doAutowired();
}
private void doAutowired(){
// map에 저장된 객체의 프로퍼티 중 @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 (IllegalAccessException e){
e.printStackTrace();
}
}
이렇게 실행하면, 수동으로 객체를 연결하는 부분을 주석처리해도 객체가 자동으로 연결되는 것을 확인할 수 있다.
@Autowired가 byType으로 객체를 자동연결한다면, @Resource는 byName으로 객체를 자동연결한다. 이때 이름을 정해주지 않는데, 이 경우 클래스 이름의 첫글자를 소문자로 바꾼 것을 이름으로 사용한다. (name="engine2)라고 애너테이션 뒤에 이름을 지정해 줄 수도 있다.
같은 타입의 프로퍼티가 여러 개 있을 때는 이름으로 구분해줄 수 있다.
@Resource 애너테이션은 J2EE에 있어 import하려면 빨간 줄이 쳐진다. 사용하려면 maven repository에서 가져오거나 file structure에서 apache-tomcat의 lib를 추가한다(annotations.api에 있다).
// Car class
@Component class Car{
@Resource Engine engine;
@Resource Door door;
}
// doResource()
AppContext() {
map = new HashMap();
doComponentScan();
doAutowired();
doResource();
}
private void doResource(){
// map에 저장된 객체의 프로퍼티 중 @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 (IllegalAccessException e){
e.printStackTrace();
}
}
* 만약 null이 나온다면 config.txt에 패키지가 맞게 들어있는지 확인한다.
역시 자동으로 객체가 잘 연결된다.
이러한 @Autowired, @Resource 같은 자동화 방법은 우리가 관리해야 하는 코드를 줄여준다. 변경사항이 발생하더라도 변경할 부분이나, 테스트할 코드의 양을 줄여주는 것이다.
사실 아직까지는 Spring이 익숙하지 않아서 코드가 어렵다. 처음에는 애너테이션들이 어떻게 돌아가는지만 알고 넘어가고, 조금 익숙해질 때마다 다시 복습을 하는 게 좋겠다.