SpringBoot 원리 4)스프링 컨테이너 구성
6. 스프링 컨테이너 구성
코드를 Spring container의 Compenent 를 등록하고, 의존하고 있다면 어느 시점에 어떻게 주입해 줄 것인가를 스프링 컨테이너에 ‘구성정보’로 제공 (이전 : xml)
- 이전 : 클래스 정보를 registerBean
1. java 코드로 !
FactoryMethod : object 생성하는 로직을 갖고 있는 메서드 => 빈 생성 + 조립
@Configuration // 이게 Bean 등록정보(스프링 컨테이너 구성정보)를 가진 클래스임을 알려줌 => "아 여기 BeanAnnotaion이 붙은 Factory 메서드가 있겠구나)
public class HellobootApplication {
@Bean // 빈 객체 생성
public HelloController helloController(HelloService helloService){
return new HelloController(helloService); // 의존 정보 등록
}
@Bean
public HelloService helloService(){
//SimpleHelloService로 리턴은 좋은 코딩이 아님 (확장성을 반영한 interface 타입 리턴이 better)
return new SimpleHelloService();
}
public static void main(String[] args) {
// application context -> Spring container 생성
// GenericWebApplicationContext applicationContext = new GenericWebApplicationContext(){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(){
// 자바 코드를 설정정보로 인식하는 Spring Container
@Override
protected void onRefresh() {
super.onRefresh();
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory(8081);
WebServer webServer = serverFactory
.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet"
, new DispatcherServlet(this)).addMapping("/*"); // addServlet
}) ; // getWebServer
webServer.start();
} // onRefresh
};
// applicationContext.registerBean(HelloController.class);
// applicationContext.registerBean(SimpleHelloService.class);
// 이거 대신 java 코드 구성정보 담은 클래스를 등록해줘야함
applicationContext.register(HellobootApplication.class);
applicationContext.refresh(); // 템플릿 메서드 => 훅 메서드: onRefresh
} // main
} // HellobootApplication
중요 : @Configuration
=> 처음 등록됨 ! Bean Factory 메서드를 가지는 이상으로 전체 Spring Container 를 구성하는데 많은 정보를 담을 수 있음
Autowiring by type from bean name 'helloController' via factory method to bean named 'helloService'
2. 스캐너 : 클래스 위에 직접 붙여줌
빈객체로 등록하고 싶은 클래스 위에 직접 사용해줌 (위: 호출하는데서 작성)
=> @Component : “나는 Spring Container 에 들어가는 Component(빈객체)야”
1) 빈객체로 등록하고자 하는 클래스 위에 @Component
2) @ComponentScan : @Component 이 붙은 클래스를 찾아 빈객체로 생성+조립해라 !
(@Configuration: Container 구성정보 아래에 작성)
- => 간단,편리하지만 Bean으로 어떤 객체들이 등록되는지 찾지 못하는 단점
-
패키지 구성 잘하고 모듈 잘 나눠서 개발하면 ㄱㅊ
@MySpringBootApplication
public class HellobootApplication {
public static void main(String[] args) {
// application context -> Spring container 생성
// GenericWebApplicationContext applicationContext = new GenericWebApplicationContext(){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(){
// 자바 코드를 설정정보로 인식하는 Spring Container
@Override
protected void onRefresh() {
super.onRefresh();
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory(8081);
WebServer webServer = serverFactory
.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet"
, new DispatcherServlet(this)).addMapping("/*"); // addServlet
}) ; // getWebServer
webServer.start();
} // onRefresh
};
// applicationContext.registerBean(HelloController.class);
// applicationContext.registerBean(SimpleHelloService.class);
// 이거 대신 java 코드 구성정보 담은 클래스를 등록해줘야함
applicationContext.register(HellobootApplication.class);
applicationContext.refresh(); // 템플릿 메서드 => 훅 메서드: onRefresh
} // main
} // HellobootApplication
7. MetaAnnotation
: Annotaion (코드)위에 붙은 Annotaion
=> 이 MetaAnnotaion을 사용하면 그 아래에 있는 하위 Annotaion 들도 자동 반영됨
- => ‘계층형 아키텍처’
-
@Component 안쓰고 굳이? Spring Bean Object로 등록되는건 기본이고 이 객체가 어떤 종류(역할)인지도 나타내주고 싶어! (Controller, Service)
- => stereo Type Meta Annotation ex) RestController
-
선언부 들어가보면 @Component 있음 => 이거 사용해도 Spring Container가 CompentScan으로 찾아내 빈 객체 생성
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {}
ex) @RestController (Meta) - @Controller (Meta) - @Component
ㄴ @ResponseBody => 코드에 넣어줬던거 지워줘도 됨
// @RequestMapping("/hello") // 이 안에 매핑 정보가 담겨져있는 메서드가 있다 (/hello 요청을 처리하는 메서드가 있다)
// @MyComponent
@RestController
public class HelloController {
// SimpleHelloService simpleHelloService = new SimpleHelloService();
private final HelloService helloService; // 주입
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
// @GetMapping
@GetMapping("/hello")
// @ResponseBody
// return 된 String 값을 그대로 응답의 Body에 추가해주는 애노테이션 => @RestController 로 대체
public String hello(String name){
return helloService.sayHello(Objects.requireNonNull(name)); // 이렇게만 하면 에러! Controller에서 반환하는 String return 값을 스프링은 기본적으로 view 페이지 이름으로 인식
// null 이면 예외를 던지고 아니면 값을 그대로 넘김 (null이 아닌경우에만 사용되도록)
}
}
8. 서블릿 컨테이너도 빈으로 등록
-
TomcatServletWebServerFactory, DispatcherServlet => Application이 시작되려면 필요한 두 object
=> Spring Bean으로 등록해 Spring Container 가 관리
=>bean factory method 활용 (다음 자동 구성 기능 수업에서 수정 예정 )
@MySpringBootApplication
public class HellobootApplication {
@Bean
public ServletWebServerFactory serverFactory (){
return new TomcatServletWebServerFactory(8081);
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet() ; // 여기선 Spring Container (application Context)를 어떻게 넣어줌?
}
public static void main(String[] args) {
// application context -> Spring container 생성
// GenericWebApplicationContext applicationContext = new GenericWebApplicationContext(){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(){
// 자바 코드를 설정정보로 인식하는 Spring Container
@Override
protected void onRefresh() {
super.onRefresh();
// TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory(8081);
ServletWebServerFactory serverFactory = this.getBean(ServletWebServerFactory.class) ;
DispatcherServlet dispatcherServlet = this.getBean(DispatcherServlet.class) ;
// dispatcherServlet.setApplicationContext(this);
// => 이거 지정 안해줘도 동작 잘함 : Bean의 생명주기 메서드 때문!
WebServer webServer = serverFactory.getWebServer(servletContext -> {
//servletContext.addServlet("dispatcherServlet",new DispatcherServlet(this)).addMapping("/*");
servletContext.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/*");
}) ; // getWebServer
webServer.start();
} // onRefresh
};
// applicationContext.registerBean(HelloController.class);
// applicationContext.registerBean(SimpleHelloService.class);
// 이거 대신 java 코드 구성정보 담은 클래스를 등록해줘야함
applicationContext.register(HellobootApplication.class);
applicationContext.refresh(); // 템플릿 메서드 => 훅 메서드: onRefresh
} // main
} // HellobootApplication
ㄴ DispatcherServlet 에 Spring Container (application Context)를 어떻게 넣어줌?
자동으로 넣어짐 ! By lifecycle method
-
DispatcherServlet : ApplicationContextAware interface를 구현함
=> setApplicationContext()
public interface ApplicationContextAware extends Aware {
/**
* Set the ApplicationContext that this object runs in.
* Normally this call will be used to initialize the object.
* <p>Invoked after population of normal bean properties but before an init callback such
* as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
* or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
* {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
* {@link MessageSourceAware}, if applicable.
* @param applicationContext the ApplicationContext object to be used by this object
* @throws ApplicationContextException in case of context initialization errors
* @throws BeansException if thrown by application context methods
* @see org.springframework.beans.factory.BeanInitializationException
*/
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
- setApplicationContext()
-
lifecycle method’ - Bean을 Container가 등록하고 관리하는 중 Container가 관리하는 Object를 bean 에 주입
=> 이 interface를 구현한 클래스(dispatcherServlet)가 빈으로 등록되면, 어떻게 등록되던 스프링 컨테이너는 해당 인터페이스의 setter 메서드로 applicationContext를 알아서 주입해준거암
=> 이런 종류의 interface를 구현해놓으면 Spring Container는 이 객체가 등록되는 시점에 setter 메서드로 넣어줘야겠구나! (이 setter 메서드 자체를 Spring Container 가 호출하는 거임!
(명시적으로 setter 메서드 사용해서 주입 안시켜줘도 됨)
package tobyspring.helloboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
\
@RestController
public class HelloController implements ApplicationContextAware {
private final HelloService helloService;
private ApplicationContext applicationContext ; // 자기 자신이지만 Bean 처럼 등록해서 관리
// 생성자를 통해 HelloController가 초기화될 때 final 이면 그 필드도 초기화 되어야하는데
// ApplicationContextAware 는 객체가 생성된 후 메서드 호출(setter)을 통해 주입(조립)되기 때문에
public HelloController(HelloService helloService) { // 주입
this.helloService = helloService;
}
// setter 주입
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 스프링 컨테이너가 초기화 되는 시점에 이거 실행됨
System.out.println(applicationContext);
this.applicationContext=applicationContext;
}
// 생성자 주입
/*
private final ApplicationContext applicationContext
public HelloController(HelloService helloService, ApplicationContext applicationContext) { // 주입
this.helloService = helloService;
this.applicationContext=applicationContext;
System.out.println(applicationContext);
}
*/
@GetMapping("/hello")
// @ResponseBody
// 이거 추가해줘야함 : return 된 String 값을 그대로 응답의 Body에 추가해주는 애노테이션 => @RestController 로 대체
public String hello(String name){
if(name == null || name.trim().length() == 0)throw new IllegalArgumentException();
return helloService.sayHello(name); // 이렇게만 하면 에러! Controller에서 반환하는 String return 값을 스프링은 기본적으로 view 페이지 이름으로 인식
// null 이면 예외를 던지고 아니면 값을 그대로 넘김 (null이 아닌경우에만 사용되도록)
} // hello
}
9. Application 과 해당 Application을 실행시켜주는 코드 분리
- application - visible(웹 마다 다른 어플리케이션 실행부)
HelloBootApplication
@MySpringBootApplication
public class HellobootApplication {
@Bean
public ServletWebServerFactory serverFactory (){
return new TomcatServletWebServerFactory(8081);
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet() ; // 여기선 Spring Container (application Context)를 어떻게 넣어줌?
}
public static void main(String[] args) {
// MySpringApplication.run(HellobootApplication.class, args);
SpringApplication.run(HellobootApplication.class, args); // refactoring
} // main => 스프링 부트 최종코드 !
} // HellobootApplication
- => 각기 다른 설정정보를 갖고 있는 클래스
-
@Configuration 과 @ComponentScan 을 갖고있는 클래스
- application 실행시키는 클래스 - invisible (spring boot가 내부적으로 웹 어플리케이션을 다루는 공통 코드)
MySpringBootApplication
- 실행 메서드 분리 : run()
private static void run(Class<?> applicationClass, String... args) {
// 설정정보 클래스 (어플리케이션 실행부) 클래스 자체를 전달받음
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(){
@Override
protected void onRefresh() {
super.onRefresh();
ServletWebServerFactory serverFactory = this.getBean(ServletWebServerFactory.class) ;
DispatcherServlet dispatcherServlet = this.getBean(DispatcherServlet.class) ;
WebServer webServer = serverFactory
.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/*"); // addServlet
}) ; // getWebServer
webServer.start();
} // onRefresh
};
applicationContext.register(applicationClass);
applicationContext.refresh(); // 템플릿 메서드 => 훅 메서드: onRefresh
}
- 재사용해야함
-
스프링 컨테이너 + 서블릿 컨테이너 초기화해
-
어플리케이션의 설정정보를 스프링 컨테이너로부터 넘겨받아 기본적으로 웹을 실행시켜줌
=> 다른 main이 되는 클래스에서도 재사용 가능
:Spring Boot에선 형식에 맞는 각기다른 웹 어플리케이션을 넘겨받으면, 이렇게 동작시켜줌
<-> 추가적인 설정정보만 application에 적어주면,
-
우선 ComponentScan및 설정정보에 작성된 정보들로 Spring Bean 객체 등록해주고
-
이 메서드에서 서블릿 컨테이너와 스프링 컨테이너를 생성해 등록된 빈들로 구성해 실행시켜주고
-
요청을 받아 바인딩해 설정된 매핑 핸들러로 넘겨주고,
-
전달받은 응답 바디(Response)에 부가적인 정보를 붙여 클라이언트로 전달
=> 이걸 스프링 부트가 우리 안보이게 대신해줌
댓글남기기