강좌 ch3. 6, 7강 요약
public static void main(String[] args) {
ApplicationContext ac = new GenericXmlApplicationContext("config.xml");
// Car car = ac.getBean("car", Car.class); // 타입을 지정하면 형변환 안해도됨. 아래의 문장과 동일
Car car = (Car) ac.getBean("car"); // 이름으로 빈 검색
Car car2 = (Car) ac.getBean(Car.class); // 타입으로 빈 검색
System.out.println("car = " + car);
System.out.println("car2 = " + car2);
System.out.println("ac.getBeanDefinitionNames() = " + Arrays.toString(ac.getBeanDefinitionNames())); // 정의된 빈의 이름을 배열로 반환
System.out.println("ac.getBeanDefinitionCount() = " + ac.getBeanDefinitionCount()); // 정의된 빈의 개수를 반환
System.out.println("ac.containsBeanDefinition(\"car\") = " + ac.containsBeanDefinition("car")); // true 빈의 정의가 포함되어 있는지 확인
System.out.println("ac.containsBean(\"car\") = " + ac.containsBean("car")); // true 빈이 포함되어 있는지 확인
System.out.println("ac.getType(\"car\") = " + ac.getType("car")); // 빈의 이름으로 타입을 알아낼 수 있음.
System.out.println("ac.isSingleton(\"car\") = " + ac.isSingleton("car")); // true 빈이 싱글톤인지 확인
System.out.println("ac.isPrototype(\"car\") = " + ac.isPrototype("car")); // false 빈이 프로토타입인지 확인
System.out.println("ac.isPrototype(\"door\") = " + ac.isPrototype("door")); // true
System.out.println("ac.isTypeMatch(\"car\", Car.class) = " + ac.isTypeMatch("car", Car.class)); // "car"라는 이름의 빈의 타입이 Car인지 확인
System.out.println("ac.findAnnotationOnBean(\"car\", Component.class) = " + ac.findAnnotationOnBean("car", Component.class)); // 빈 car에 @Component가 붙어있으면 반환
System.out.println("ac.getBeanNamesForAnnotation(Component.class) = " + Arrays.toString(ac.getBeanNamesForAnnotation(Component.class))); // @Component가 붙은 빈의 이름을 배열로 반환
System.out.println("ac.getBeanNamesForType(Engine.class) = " + Arrays.toString(ac.getBeanNamesForType(Engine.class))); // Engine 또는 그 자손 타입인 빈의 이름을 배열로 반환
}
메서드 | 반환결과 |
---|---|
getBean(bean name), getBean(bean type) | Bean 객체를 name이나 type으로 찾아 반환 |
getBeanDefinitionNames() | config 파일을 통해 정의된 beans + 컴포넌트 스캔과 애너테이션 config가 등록한 beans 반환 |
getBeanDefinitionCount() | 정의된 bean의 개수 반환 |
containsBeanDefinition(bean name) | bean이 등록되어 있는지 참/거짓 반환 |
containsBean(bean name) | 해당 ApplicationContext에 bean이 존재하는지 참/거짓 반환 |
getType(bean name) | bean의 패키지, 클래스 반환 |
isSingletone(bean name) | bean이 싱글톤인지 참/거짓 반환 |
isPrototype(bean name) | bean이 프로토타입인지 참/거짓 반환 |
isTypeMatch(bean name, class) | bean과 클래스가 일치하는지 참/거짓 반환 |
findAnnotationOnBean(bean name, Annotation class) | bean에 입력한 애너테이션이 붙어있으면 반환 |
getBeanNamesForAnnotation(Annotation class) | 해당 애너테이션이 붙은 bean을 배열로 반환 |
getBeanNamesForType(class) | 입력한 클래스 또는 그 자손 타입인 bean의 이름을 배열로 반환 |
애플리케이션 컨텍스트는 2개가 있다고 했다. 그 중 root AC를 컨트롤러에서 얻어오려면,
ServletContext sc = request.getSession().getServletContext(); // ApplicationContextFacade
WebApplicationContext rootAC = WebApplicationContextUtils.getWebApplicationContext(sc); // Root AC
이 두 줄이 필요하다. 원래는 requst.getServlet()으로 되지만 홈컨트롤러는 서블릿이 아니라 2줄이 필요하다.
강의에선 IntelliJ로 내장된 톰캣 서버를 켜서 이것저것 하는데 나는 Community 버전이라 따로 플러그인을 설치해야 했다.
IntelliJ Community 버전에서 톰캣 연결하기.
1. 톰캣을 받고 압축을 풀어준다.
2. Settings-Plugins-Smart Tomcat을 받는다.
3. Run/Debug - Configuration Edit - +를 눌러 Tomcat으로 Configuration을 생성한다.
4. 받아둔 톰캣의 디렉토리와 연결해준다.
톰캣 서버를 run 하면 Root WebApplicationContext가 먼저 초기화되고, 그 다음에 ServletContext가 초기화되는 것을 볼 수 있다. 하여 초기화 중 오류가 났다면 로그를 확인했을 때 root-context.xml에 오류가 있는지, servlet-context.xml에 오류가 있는지를 파악할 수 있다.
IoC (Inversion of Control)
제어의 역전; 제어의 흐름을 전통적인 방식과 다르게 뒤바꾸는 것을 말한다.
전통적인 흐름은 사용자 코드가 Framework 코드를 호출하는 식으로 흐른다. 사용자가 작성한 코드가 호출을 하고, 라이브러리에서 실행을 마치면 그 결과를 받아 다시 원래 위치로 돌아오는 식의 흐름이다.
// 사용자 작성코드
Car car = new Car();
car.turboDrive(); // turboDrive() 호출 -> 실행 끝나면 다시 돌아옴
// 라이브러리
void turboDrive(){
engine = new TurboEngine();
engine.start();
...
}
반면 IoC에서는 라이브러리에서 사용자 작성 코드를 호출한다.
// 사용자 작성코드
Car car = new Car();
car.drive(new SuperEngine());
// 라이브러리
void drive(Engine engine){
engine.start(); // new SuperEngine() 호출
...
turboDrive()에서 변하는 부분은 new 뒤에 오는 것 뿐이다. 코드의 분리는 관심사, 변하는 것과 변하지 않는 것, 그리고 중복 코드가 존재할 때 일어나는데, 여기서는 변하는 것과 변하지 않는 것을 분리하게 된다. 분리 과정에서 변하지 않는 부분만 남긴 것이 drive()이다. 만약 new SuperEngine()이 TurboEngine()으로 바뀐다고 해도 drive()는 변하지 않는다.
사용할 Engine은 사용자 코드에서 제공하며, 여기에는 사용할 객체를 외부에서 주입하는 DI가 사용되었다. 이런 설계가 조금 더 바람직하다고 평가된다.
이는 디자인 패턴에서 전략 패턴에 해당하는데, 직접 메서드를 구현하지 않고 메서드에 대한 전략 클래스를 생성하여 로직이 변경되거나 하면 필요에 따라 다른 전략을 사용할 수 있다는 뜻이다.
필요한 bean에 @Autowired가 붙어있을 경우 의존성이 자동으로 주입된다. @Autowired 애너테이션은 인스턴스 변수, setter, 메서드에 적용되며, 생성자 메서드에는 생략이 가능하다. 그러나 매개변수를 받지 않는 생성자 메서드가 존재할 때에는 어떤 생성자 메서드를 사용해야 할지 모르기 때문에 이 때는 애너테이션을 붙여주어야 한다.
주입해야 할 빈을 까먹을 수도 있기 때문에 필요한 변수가 전부 다 들어있는 생성자로 주입받는 것을 추천한다.
Sprimg 컨테이너는 먼저 타입으로 빈을 검색해서 참조변수에 자동 주입하며, n개가 검색되었다면 거기서 이름이 일치하는 것을 주입한다.
주입 대상이 변수 1개일 때, 검색된 빈이 1개가 아니라면 예외가 발생한다. 하지만 주입 대상이 배열이면 n개가 검색되어도 예외가 발생하지 않는다.
@Autowired에 (required = false) 옵션을 주면 주입할 빈이 없어도 예외가 발생하지 않고, 해당 변수는 null로 들어간다.
@Resource는 Container에서 이름으로 빈을 검색해 참조 변수에 자동 주입해주는 애너테이션으로, 일치하는 이름의 빈이 없으면 예외를 발생시킨다.
원래라면 @Resource 뒤에 (name="") 하고 이름을 써주어야 하지만, 생략시 참조변수의 이름을 빈의 이름으로 간주한다.
@Resource는 @Autowired와 @Qualifier("빈 이름") 을 함께 사용하는 것과 같다.
@Resource는 스프링 애너테이션이 아니라, 스프링 방식으로는 후자가 맞긴 하지만 별 차이는 없다.
@Component 애너테이션은 properties 파일에 <component-scan>으로 등록된 패키지를 뒤져 해당 애너테이션이 붙은 클래스들을 빈으로 저장해준다. @Component 애너테이션도 이름을 따로 받을 수 있지만, 생략할 경우에는 맨 첫글자를 소문자로 바꾼 이름을 찾아 빈으로 등록한다.
// <bean id = "superEngine" class = "패키지디렉토리"/>
// @Component("superEngine")
@Component
class SuperEngine extends Engine{}
@Controller, Service, Repository, ControllerAdvice 등의 애너테이션들 안에 포함되어 있다. 애너테이션을 만들 때 사용되는 애너테이션으로, 메타 에너테이션이라고 한다. 하여 이 애너테이션이 붙은 클래스들은 전부 자동으로 빈으로 등록이 된다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller { ... }
@PropertySource는 properties 파일에서 설정을 불러올 수 있다. 이러한 설정은 @Value 애너테이션을 사용해서 불러온다.
@Component
@PropertySource("setting.properties")
class SysInfo{
@Value("#{systemProperties['user.timezone']}")
String timeZone;
@Value("#{systemEnvironment['PWD']}")
String currDir;
...
혹은, setting.properties 파일을 읽어올 수도 있다. 등호를 구분자로 키와 값을 구분해놓은 상태이므로, @Value 애너테이션에 값으로 properties 파일의 키 값을 넣으면 해당하는 값을 불러올 수 있다.
// src/main/resources/setting.properties
autosaveDir = autosave
autosave = true
autosaveInterval = 30
// ---
@Value("${autosaveDir}")
String autosaveDir;
@Resource 말고는 스프링 애너테이션을 사용하고, 이런 것도 있구나 정도로 넘어가면 된다.
스프링 | 표준 | 비고 |
---|---|---|
@Autowired | @Inject | @inject에는 required 속성 x |
@Qualifier | @Qualifier, @Named | 스프링의 @Qualifier과 @Named는 유사 |
- | @Resource | 스프링에는 이름검색이 없음 |
@Scope("singleton") | @Singleton | 표준에서는 prototype이 디폴트 |
@Component | @Named, @ManagedBean | 표준에서는 반드시 이름이 필요 |