6 분 소요

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
	}
  • 재사용해야함
  1. 스프링 컨테이너 + 서블릿 컨테이너 초기화해

  2. 어플리케이션의 설정정보를 스프링 컨테이너로부터 넘겨받아 기본적으로 웹을 실행시켜줌

=> 다른 main이 되는 클래스에서도 재사용 가능

:Spring Boot에선 형식에 맞는 각기다른 웹 어플리케이션을 넘겨받으면, 이렇게 동작시켜줌

<-> 추가적인 설정정보만 application에 적어주면,

  1. 우선 ComponentScan및 설정정보에 작성된 정보들로 Spring Bean 객체 등록해주고

  2. 이 메서드에서 서블릿 컨테이너와 스프링 컨테이너를 생성해 등록된 빈들로 구성해 실행시켜주고

  3. 요청을 받아 바인딩해 설정된 매핑 핸들러로 넘겨주고,

  4. 전달받은 응답 바디(Response)에 부가적인 정보를 붙여 클라이언트로 전달

=> 이걸 스프링 부트가 우리 안보이게 대신해줌

댓글남기기