/boot-study

spring boot study with Naver Open API

Primary LanguageJava

Spring Boot Study

  • study

환경

  • Spring version 5.x
  • Boot 2.2
  • Open JDK 11
  • Junit5 + AssertJ + Mockito

Mission


I learn

@SpringBootApplication

  • @SpringBootConfiguration
  • @ComponentScan
  • @EnableAutoConfiguration

    @SpringBootApplication 어노테이션은 위 3가지의 어노테이션을 사용하는것과 같다.

@EnableAutoConfiguration

  • 어플리케이션 등록시, 빈을 두 단계를 거쳐 등록 한다.

    1. @ComponentScan
    2. @EnableAutoConfiguration
  • 먼저 @ComponentScan으로 빈이 등록이 되는데, @SpringBootApplication 내부에 @ComponentScan을 살펴보면 filter로 AutoConfiguration을 통해 등록되는 빈들을 제외하라고 설정 되어 있다.

    @ComponentScan(
        excludeFilters = {@Filter(
            type = FilterType.CUSTOM,
            classes = {TypeExcludeFilter.class}
        ), @Filter(
            type = FilterType.CUSTOM,
            classes = {AutoConfigurationExcludeFilter.class} // <-
        )}
    )
  • @ComponentScan으로 빈이 등록 된 다음 @EnableAutoConfiguration이 추가적인 빈들을 등록 한다. (ex ServletWebServerFactory )
  • @EnableAutoConfiguration은 추가된 jar dependency 기반으로 Spring application을 자동으로 설정하는 것을 시도한다
  • @EnableAutoConfiguration이 빈을 등록하는 방법은
    org.springframework.boot:spring-boot-autoconfigure Library 안의 META-INF/spring.factories 라는 파일에 있는 값들을 읽어 등록 한다.
    spring.factories 파일 안에 키 밑에 설정되어있는 value(클래스들)들은 다 적용이 된다.
    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    ....
    ....
  • 파일 내용을 보면 다 클래스로 명시 되어 있고 각 클래스 내부에는 @Configuration 어노테이션을 통해 빈 설정파일로 등록 된다.
    다만 @ConditionalOn.... 어노테이션에 맞는 조건에 따라 등록되거나 등록이 안되기도 한다.
    ex) WebMvcAutoConfiguration.class

    • @ConditionalOnWebApplication(type = Type.SERVLET) : 웹 어플리케이션 타입이 servlet일 때만 AutoConfigure
    • @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
      : 클래스패스에 Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class가 있을 때 AutoConfigure
          @Configuration(
              proxyBeanMethods = false
          )
          @ConditionalOnWebApplication(type = Type.SERVLET)
          @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
          @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
          @AutoConfigureOrder(-2147483638)
          @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
          public class WebMvcAutoConfiguration {
            ...
          }
      
  • 만약 @ComponentScan과 @EnableAutoConfigure가 같은 빈을 등록한다면 @ComponentScan 때 등록한 빈을 @EnableAutoConfigure가 덮어 씌울 것이다.
    덮어쓰는 걸 방지하려면 @ConditionalOnMissingBean를 통해 빈을 등록 못했을때 등록하도록 설정해준다.




내장 웹서버

  • 스프링 부트는 내장 서블릿 컨테이너를 쉽게 사용할수 있게, 스프링 프레임워크를 쉽게 사용할 수 있게하는 Tool이다.
  • 내장 서블릿 컨테이너도 자동설정의 일부분이다.
  • 자바로 내장 웹서버인 톰캣 객체를 생성할 수 있다.
    Tomcat tomcat = new Tomcat(); // 톰캣 객체 생성
    tomcat.setPort(8081); // 포트 설정
    Context context = tomcat.addContext("/", "/"); // 톰캣에 컨텍스트 추가
    
    // 서블릿 만들기
    HttpServlet servlet = new HttpServlet() {
        @Override
        protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
            PrintWriter writer = resp.getWriter();
            writer.println("<html>");
            writer.println("<head>");
            writer.println("    <title>mission1</title>");
            writer.println("</head>");
            writer.println("<body>hello world</body>");
            writer.println("</html>");
        }
    };
    String servletName = "apiServlet";
    tomcat.addServlet("/", servletName, servlet); // 톰캣에 서블릿 추가
    context.addServletMappingDecoded("/api", servletName); // 컨텍스트에 서블릿 매핑
    tomcat.getConnector();
    tomcat.start(); // 톰캣 실행
    tomcat.getServer().await(); // 톰캣 요청 대기
  • 다만 별도의 서블릿들을 다 정의해야 되기 때문에 위 코드 과정을 자동 설정하고 실행하게 해주는 것이 스프링 부트의 기능 중 하나이다.
  • 그렇다면 위 소스코드 처럼 WAS 설정은 어디에 있고 어떤 원리로 스프링 부트가 실행할 수 있는건가?
    • 위에서 배웠던 @AutoConfiguration 를 통해 자동 설정이 된다.
    • Library org.springframework.boot:spring-boot-autoconfigure/META-INF/spring.factories
      • ServletWebServerFactoryAutoConfiguration.class -> 서블릿 웹서버 생성
        • ServletWebServerFactoryConfiguration
        • TomcatServletWebServerFactory.getWebServer() -> 톰캣 설정
        • TomcatServletWebServerFactoryCustomizer -> 톰캣 커스터마이징
      • DispatcherServletAutoConfigure.class -> 서블릿 만들고 등록
        • 내부에서 HttpServlet 을 상속받은 DispatcherServlet 을 생성하고 서블릿 컨테이너에 등록
    • 서블릿 컨테이너와 디스패처 서블릿 등록이 별도로 분리되어 있는 이유는
      • 서블릿 컨테이너는 설정에 따라 달라질 수 있지만, 서블릿은 변하지 않기에
      • 디스패처 서블릿이 어떤 서블릿 컨테이너를 사용하든 상관 없이 서블릿을 등록할 수 있도록 분리
    • 스프링 부트가 실행되면 자동으로 톰캣 객체를 생성하고, 내장 서블릿 컨테이너(톰캣)에 서블릿이 추가가 되고 Web MVC 설정이 되면서 어플리케이션이 동작한다.

언더토우

    
        configurations {
            compile.exclude module: 'spring-boot-starter-tomcat'
        }
        dependencies {
            implementation 'org.springframework.boot:spring-boot-starter-web'
            compile 'org.springframework.boot:spring-boot-starter-undertow'
        }
    
  • 만약 내 어플리케이션을 웹 어플리케이션으로 만들기 싫은데 의존성에 웹관련 의존성 모듈이 있다면 스프링 부트는 자동으로 웹 어플리케이션으로 만들려고 한다.
  • 프로퍼티 설정을 통해 클래스패스에 웹 관련 의존성이 있다 하더라도, 웹 어플리케이션으로 안바꿀 수 있다.
    • spring.main.web-application-type=none
  • 프로퍼티 설정을 통해 포트 변경 및 랜덤 포트로 변경할 수 있다.
    • server.port = 8081
    • server.port = 0 // 랜덤 포트
    • ApplicationListener<ServletWebServerInitializedEvent>를 통해 현재 포트를 얻어올 수 있다.