스프링부트에서 테스트는 일단 spring-boot-starter-test 의존성을 추가하고, @SpringBootTest가 마킹된 테스트 클래스를 작성한다. 이때 컨트롤러에 대한 테스트라면 MockMvc나 내장톰캣을 구동하여 테스트를 진행할 수 있으며, @WebMvcTest 등을 활용하여 특정 레이어만 테스트할 수도 있다.

 

하나씩 알아본다.

spring-boot-starter-test 스프링부트에서 테스트를 위해 추가하는 의존성

스프링부트에서는 테스트를 위해 제공하는 라이브러리가 있으며 코어 라이브러리와 Auto-Configuration을 포함하는 Starter(라이브러리 및 자동설정을 포함)를 제공한다. Stater 안에는 Spring 테스트모듈을 포함하여 JUnit, Jupiter, AssertJ, Hamcrest 등의 테스트시 유용한 라이브러리가 포함되어 있다.

 

메이븐을 사용하다면 아래와 같이 pom.xml spring-boot-starter-test를 다음과 같이 추가한다.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

Test Class 작성

스프링부트 어플리케이션 테스트를 작성할 클래스에는 테스트의 성격에 따라서 스프링에서 제공하는 애노테이션을 조합하여 마킹할 수 있다.
가장 기본적으로는 @SpringBootTest가 있는데, 이는 스프링의 @ContextConfiguration 애노테이션을 대체하면서 몇 가지 기능을 제공한다. (스프링에서는 테스트를 위한 환경설정을 위해 @ContextConfiguration를 사용한다.)

 

아래는 기본적인 스프링부트 테스트 클래스이다.

Junit의 스프링 테스트를 위해 @Runwith가 마킹되어 있고, @SpringBootTest는 테스트를 위한 환경설정 및 부가기능을 제공한다. 각 @Test 메서드마다 단위 테스트를 작성한다.

@RunWith(SpringRunner.class)
@SpringBootTest // 테스트를 위한 환경설정 및 부가기능을 제공한다.
public class SampleTest {

    @Test
    public void test1(){
        //
    }

    @Test
    public void test2(){
        //
    }
}

@SpringBootTest의 기능

  • 기본 ContextLoader로써 SpringBootContextLoader를 사용한다.
  • 자동으로 @SpringBootConfiguration을 찾는다. @SpringBootConfiguration은 @SpringBootApplication(메인 클래스) 안에 있다.
  • 커스텀 Environment 프로퍼티를 정의할 수 있다.
  • 어플리케이션 구동 시 설정하는 application argument를 테스트 프로그램에서 정의할 수 있다.
    @SpringBootTest(args="--app.test=one")
  • WebEnvironment를 지정할 수 있다.
    • WebEnvironment.MOCK : 아무런 설정이 없을 시 적용되는 디폴트 설정이다. mock 서블릿 환경으로 내장톰캣이 구동되지 않는다.(브라우저에서 접속되지 않는다.)
    • WebEnvironment.RANDOM_PORT : 스프링부트를 직접 구동시킨 것처럼 내장톰캣이 구동되나 랜덤포트로 구동된다.
    • WebEnvironment.DEFINED_PORT : 정의된 포트로 내장톰캣이 구동된다.
    • WebEnvironment.NONE : WebApplicationType.NONE으로 구동된다.
  • 테스트를 위한 TestRestTemplate 빈과 WebTestClient 빈을 등록할 수 있다.
    만일 WebEnvironment.MOCK 이라면 TestRestTemplate과 WebTestClient빈은 등록되지 않는다. 실제 내장톰캣이 구동되지 않으므로 사용할 여지가 없고 어플리케이션에 요청을 보내고 싶다면 MockMvc로 테스트하면 되기 때문이다.

WebEnvironment 설정에서 디폴트 값이 MOCK 이므로 내장톰캣이 구동되지 않고 테스트가 수행되는 것에 유의한다.


@AutoConfigureMockMvc

디폴트 설정이 서버가 실행되지 않고 Mock 환경에서 진행되므로 컨트롤러처럼 엔드포인트가 있는 테스트를 진행할 때는 MockMvc를 활용한다.
MockMvc는 가상의 클라이언트로 어플리케이션에 요청을 날리는 역할을 한다. MockMvc를 생성하는 방법에는 여러 가지 있지만 테스트클래스에 @AutoConfigureMockMvc을 마킹하고 주입받는 것이 간단하다.

MockMvc를 사용하여 mock 환경에서 컨트롤러를 테스트하는 코드이다.

@Controller
public class SampleController { // 테스트 대상의 컨트롤러
    @GetMapping("/sample")
    @ResponseBody
    public String sample(){
        return "sample's return";
    }
}
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc // MockMvc를 생성한다.
public class SampleTest {
    @Autowired
    MockMvc mockMvc; // 생성된 MockMvc 빈을 주입받아 테스트에 사용한다.

    @Test
    public void sampleController_Test() throws Exception {
        mockMvc.perform(get("/sample"))
                .andExpect(handler().handlerType(SampleController.class))
                .andExpect(handler().methodName("sample"))
                .andExpect(status().isOk())
                .andExpect(content().string("sample's return"))
                .andDo(print());
    }

}

요청을 받는 클래스나 메서드명부터 결과 상태코드, 리턴값 등 다양한 검증이 가능하다. 


내장톰캣 구동 테스트

Mock서블릿 환경이 아닌 내장톰캣을 구동하여 테스트를 하기 위해서는 @SpringBootTest에 WebEnvironment를 설정한다.

 

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

 

내장 톰캣으로 구동이 되면 MockMvc 로는 테스트할 수 없으니 테스트를 위한 HTTP 클라이언트를 사용해야 한다. 여기서 사용할 수 있는 클라이언트가 TestRestTemplate다. 여기에 더해서 여러 레이어 간의 동작을 보는 통합 테스트가 아니라 단위 테스트인 경우에는 MockBean을 실제 빈 대신에 주입한 고립 테스트가 가능하다.

아래 컨트롤러를 테스트해보자.

@Controller
public class SampleController {
    @Autowired
    SampleService service; // 테스트대상(SampleController)이 다른 빈에 의존성을 가진다.

    @GetMapping("/sample")
    @ResponseBody
    public String sample(){
        return service.service(); // 서비스빈 호출
    }
}

SampleService가 어떻게 동작하는지 상관없이 SampleController의 구현을 대상으로 하는 테스트라면 @MockBean을 활용하여 실제 빈을 대체할 수 있다.

import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleTest {
    @Autowired
    TestRestTemplate testRestTemplate;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void sampleController_mockService_Test() throws Exception {
        // SampleService의 리턴을 사전에 정의한다.
                when(mockSampleService.service()).thenReturn("mock service");

        String result = testRestTemplate.getForObject("/sample", String.class);
        Assert.assertEquals(result, "mock service");
    }
}

Mock 서블릿 환경이 아닌 실제 내장톰캣이 랜덤포트로 구동됐으므로MockMvc는 사용할 수 없다. 그 대신 주입받은 테스트용 클라이언트인 TestRestTemplate를 활용하여 테스트 요청을 보내고 정의한 리턴 값을 확인한다.


Slice Test

@SpringBootTest는 자동 설정을 포함한 프로젝트의 전체 빈을 모두 등록하여 테스트한다.(그러므로 무겁다.) 만일 컨트롤러처럼 MVC만 테스트하고 싶거나, JPA만 테스트하고 싶은 경우 특정 레이어의 빈만 등록하여 가볍게 테스트할 수 있도록 애노테이션을 제공하고 있다.

@WebMvcTest, @WebFluxTest, @DataJpaTest 등 제공되는 애노테이션들이 있지만 @WebMvcTest만 살펴본다.

@WebMvcTest

스프링 MVC 컨트롤러에 관련된 환경구성만 진행하므로 @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, 그리고 HandlerMethodArgumentResolver 만 빈으로 등록된다.

다른 의존성이 있다면 테스트에서 빈등록 위해 명시해주거나 MockBean으로 대체해야 한다.

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class) // 테스트대상 클래스만 빈으로 등록한다.
public class SampleTest {
    @MockBean
    SampleService mockSampleService;

    @Autowired
    MockMvc mockMvc;

    @Test
    public void sampleController_Test() throws Exception {
        // SampleService의 리턴을 사전에 정의한다.
        when(mockSampleService.service()).thenReturn("mock service");

        mockMvc.perform(get("/sample"))
                .andExpect(handler().handlerType(SampleController.class))
                .andExpect(handler().methodName("sample"))
                .andExpect(status().isOk())
                .andExpect(content().string("mock service"))
                .andDo(print());
    }
}

요약하면

  • 스프링부트에서는 테스트를 위해 starter 라이브러리를 제공하고 있다.
  • classpath에 starter 의존성만 추가하면 자동 설정까지 완료된다.
  • 스프링부트 테스트에서는 @SpringBootTest를 활용하여 테스트를 위한 초기설정이나 환경설정을 추가/변경 할 수 있다.
  • 테스트하고자 하는 대상이 다른 빈을 참조하고 있다면 Mock 빈의 사용을 고려하자.
  • @SpringBootTest와 다르게 일부 레이어 테스트를 위한 @WebMvcTest 등을 제공하고 있다.

스프링에서는 Profile을 지정하여 어플리케이션의 구성(Configuration)을 Profile별로 가져갈 수 있도록 한다. 즉, 같은 소스코드를 두고 로컬환경, 개발환경, 운영환경별로 다른 구성(DB 접속정보, 업로드 파일의 저장위치 등)으로 실행할 수 있다.


@Profile

다르게 가져갈 구성은 @Component, @Configuration, @ConfigurationProperties 애노테이션에 @Profile을 마킹하여 지정하고, 어플리케이션 실행시 spring.profiles.active 프로퍼티로 프로파일을 지정하면 해당 프로파일이 적용된 빈만 등록된다.

아래는 test 프로파일을 적용해본 예시이다. @Component를 마킹하여 빈으로 등록될 클래스에 @Profile을 마킹했다. 이러면 정의한 프로파일에서만 빈으로 등록된다.

@Component
@Profile("test") // 이 클래스는 [test]프로파일에서만 빈으로 등록된다.
public class TestBean {
}
@Component
public class Sample implements ApplicationRunner {
    @Autowired(required = false)
    TestBean testBean;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("=====================================================");
        System.out.println("test 프로파일에서만 등록되는 빈 : " + testBean);
        System.out.println("=====================================================");
    }
}

테스트시 --spring.profiles.active=test 옵션을 주고 실행시 다음과 같이 콘솔에 남으며 TestBean 빈이 null 값이 아니라 등록 되었음을 확인할 수 있다.

=====================================================
test 프로파일에서만 등록되는 빈 : com.yang.wind.TestBean@50fb33a
=====================================================

만일 --spring.profiles.active=test 옵션을 빼고 실행하여 디폴트 프로파일로 실행하거나 --spring.profiles.active=dev 처럼 다른 프로파일을 지정하면 null 값으로 콘솔에 남는다.


Profile - Property File

스프링부트는 활성화 된 프로파일에 따라서 지정된 프로퍼티 파일을 읽는다.
즉 어떠한 프로파일도 활성화 하지 않으면 디폴트 프로퍼티 파일로 application.properties를 읽지만 test 프로파일을 활성화 하면 프로퍼티 파일로 application-test.properties를 읽어온다. 하지만 application-test.properties만 읽는것이 아니라 application.properties 파일도 같이 읽으며 중복된 값이 profile이 정의된 파일로 오버라이드 된다.

  • XXX 프로파일 활성화시 설정파일 : application.properties, application.yml, application-XXX.properties, application-XXX.yml

프로파일에 따라 어떻게 설정파일을 읽는지 알기위해 간단한 테스트를 해본다.

3개의 프로퍼티 파일을 작성 후 활성화한 프로파일에 따라 어떻게 값이 오버라이드 되는지 알아본다.

# application.yml 파일 -> 프로파일이 설정되지 않음. 우선순위가 낮아 프로파일 설정시 오버라이드 된다.

profile-common:
  name: defaultnameCommon

defaultonly:
  name: defualtname
# application-dev.yml 파일 -> dev 프로파일이 활성화시 우선순위가 높은 프로퍼티 파일

profile-common:
  name: devnameCommon

devonly:
  name: devname
# application-test.yml -> test 프로파일이 활성화시 우선순위가 높은 프로퍼티 파일

profile-common:
  name: testnameCommon

testonly:
  name: testname
  • profile-common.name는 모든 프로퍼티 파일이 공통으로 가진 프로퍼티이고, 각 파일마다 개별로 가지고있는 프로퍼티도 있다.
  • test 프로파일을 활성화하고 어플리케이션을 기동하면 공통속성인 profile-common.name은 프로파일 프로퍼티 파일(application-test.yml)이 나머지를 오버라이드하여 testnameCommon이 값이 된다.
  • application-test.yml 뿐만 아니라 디폴트 프로파일 파일인 application.yml 또한 읽으므로 defaultonly.name은 defaultname이 값이 된다.
  • 활성화 되지 않은 application-dev.yml 파일의 프로퍼티들은 읽지 않는다.

간단한 ApplicationRunner를 작성하여 프로퍼티 값을 콘솔에 찍어본다.

@Component
public class Sample implements ApplicationRunner {

    @Autowired
    Environment env;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("=====================================================");
        System.out.println("spring.profiles.active : " + args.getOptionValues("spring.profiles.active"));
        System.out.println("Enviroment's Active Profile : " + Arrays.toString(env.getActiveProfiles()));
        System.out.println("defaultonly.name : " + env.getProperty("devonly.name"));
        System.out.println("testonly.name : " + env.getProperty("testonly.name"));
        System.out.println("devonly.name : " + env.getProperty("devonly.name"));
        System.out.println("profile-common.name : " + env.getProperty("profile-common.name"));
        System.out.println("=====================================================");
    }
}
=====================================================
spring.profiles.active : [test]
Enviroment's Active Profile : [test]
defaultonly.name : defualtname
testonly.name : testname
devonly.name : null
profile-common.name : testnameCommon
=====================================================

 

 

 

외부설정(Externalized Configuration)

스프링부트는 외부설정을 통해 같은 소스코드로 각기 다른 환경에서 실행될 수 있도록 해준다.
코드의 외부설정으로는 properties 파일, YAML 파일, 환경변수, 커맨드라인 아규먼트 등으로 설정이 가능하다.

 

외부설정(Property)은 다음과 같이 소스코드에서 사용할 수 있다.

  • @Value : 소스코드에서 바로 프로퍼티에 주입받아 사용
  • Environment : 외부설정이 바인딩 된 Environment 를 주입받아 사용
  • @ConfigurationProperties : 외부설정이 바인딩 될 Bean(객체)을 생성하여 사용

외부설정을 할 수 있는 방법이 여러 가지가 있으므로 이들 사이에 우선순위도 존재한다.
같은 프로퍼티를 여기저기서 설정했다면 정의된 우선순위에 따라서 가장 높은 우선순위가 나머지를 오버라이드 한다.

 

스프링부트 공식문서에 소개된 우선순위는 17가지이므로 우선순위에 대한 개념만 알고 넘어가며 상황에 따라 찾아보면 될 듯하다.
기본적인 classpath 이하에 application.properties 파일을 두는 것은 우선순위가 15위로 하위권이다. 당연히 3순위인 Command line argument를 부여하면 오버라이드 된다.
예를 들어application.properties foo = bar1로 작성하고 java -jar myproject.jar --foo=bar2 로 애플리케이션을 실행하면 애플리케이션에서 foo에 맵핑된 프로퍼티 값은 ‘bar2’가 된다.

- 외부설정 및 우선순위에 대한 스프링 문서 :

https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config

 

Spring Boot Features

If you need to call remote REST services from your application, you can use the Spring Framework’s RestTemplate class. Since RestTemplate instances often need to be customized before being used, Spring Boot does not provide any single auto-configured Res

docs.spring.io


외부 프로퍼티 파일

여러 가지 방법으로 외부설정이 가능하다 하더라도 가장 많이 사용할 외부설정 방식은 프로퍼티 파일일 것이다.
주로 classpath:/application.properties 경로에 설정파일을 두고 사용하고 있지만 여러 경로에 두는 것이 가능하다.

프로퍼티 파일의 위치에 따라서 정의된 우선순위가 있다. 마찬가지로 같은 프로퍼티가 여러 파일에 정의되어 있다면 우선순위가 높은 경로에 있는 파일이 오버라이드 한다.

공식문서에서 소개하는 우선순위이다. classpath 바로 이하에 두는 것은 우선순위가 제일 낮은 6위이다. classpath 이하에 디폴트 설정 파일을 두고 필요에 따라 실제 프로퍼티 값을 가진 파일을 상위 우선순위 위치에 두는 전략을 생각해볼 수 있다.

  1. file:./custom-config/
  2. classpath:custom-config/
  3. file:./config/
  4. file:./
  5. classpath:/config/
  6. classpath:/

위 경로 중에custom-config 디렉터리는 개발자가 직접 설정이 가능한 경로라는 의미이다.
spring.config.name와 spring.config.location 프로퍼티로 커스텀한 파일경로를 지정한다. 프로퍼티 파일경로에 대한 설정은 기동시 이른 시점에 결정되어야 하므로 OS환경변수나 시스템 프로퍼티 또는 커맨드라인 아규먼트로 지정해줘야 한다.
아래는 커맨드라인 아규먼트로 지정한 예시이다. 경로(파일도 가능)는 컴마(,)를 구분자로 여러 개를 지정할 수 있다.

 

spring.config.name 프로퍼티로 파일명만 지정한다.

$ java -jar myproject.jar --spring.config.name=myproject

 

spring.config.location 프로퍼티로 경로와 파일명까지 지정한다.

$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

요약하면

  • 스프링부트는 코드 밖에서 외부 프로퍼티 설정이 가능하다.
  • 외부는 properties 파일, YAML 파일, 환경변수, 커맨드라인 아규먼트 등을 말하며 이들 사이에는 우선순위가 있어 프로퍼티 값이 오버라이드 된다.
  • 외부설정 중 많이 쓰이는 ‘파일 외부설정’은 파일이 위치하는 경로에 따라 우선순위가 있어 프로퍼티 값이 오버라이드 된다.
  • 파일 외부설정시 디폴트 파일명, 경로 외에 spring.config.name, spring.config.location 프로퍼티 설정으로 커스텀한 파일명 또는 경로를 지정할 수 있다.

스프링부트에서 의존성을 추가하고 싶을 때 주로 스프링부트에서 제공하는 starter 라이브러리를 의존성 설정파일에 추가한다.
예를들어 Mybatis 라이브러리를 추가하고자 하면 제공되는 starter 라이브러리를 의존성설정파일(메이븐의 경우 pom.xml)에 아래처럼 추가한다. starter 라이브러리는 단순히 의존성만 추가되는 것이 아니라 자동설정을 포함 하는데, 이것에 대해 자세히 알아본다.

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.1</version>
</dependency>

mybatis starter 라이브러리를 기준으로 포스팅할 예정이며 다른 라이브러리도 비슷하다.


mybatis-spring-boot-starter

스프링부트 프로젝트의 pom.xml에 추가한 mybatis-spring-boot-starter 자체에는 많은 파일이 있지는 않다.
META-INF 폴더에 pom.xml과 pom.properties 파일만 있는 정도이다.
아래는 mybatis-spring-boot-starter의 파일구성과 pom.xml이다. starter의 pom.xml에는 사용될 다른 라이브러리와 autoconfigure(mybatis-spring-boot-autoconfigure) 라이브러리가 포함되어 있는 것을 볼 수 있다.

mybatis-spring-boot-starter

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>2.1.1</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <properties>
    <module.name>org.mybatis.spring.boot.starter</module.name>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

mybatis-spring-boot-autoconfigure

pom.xml 중간에 추가된 autoconfigure는 자동설정 클래스를 포함하고 있다.(spring.factories  XxxAutoConfiguration.class 포함)

스프링부트 프로젝트 기동시 메인 클래스의 @EnableAutoConfiguration 애노테이션이 spring.factories 파일을 읽어들여 자동설정을 진행하는데 자세한 내용은 이전 포스팅을 참고하자.

이전 포스팅 링크 :

2020/12/01 - [Dev/스프링부트] - @SpringBootApplication에 대해서(Springboot 기본구조)

 

@SpringBootApplication에 대해서(Springboot 기본구조)

스프링부트 기본 프로젝트를 생성하면 main메서드를 포함하는 시작 클래스가 있고, 이 클래스에는 @SpringBootApplication이 마킹되어 있다. @SpringBootApplication는 다수의 애노테이션으로 이루어진 메타

yangbox.tistory.com

mybatis-spring-boot-autoconfigure

mybatis-spring-boot-autoconfigure의 내용(위 사진)을 보면 MybatisAutoConfiguration 자동설정클래스도 보이고, MybatisProperties 프로퍼티 클래스도 보인다. 이 중 프로퍼티 클래스에 대해 좀 더 살펴보면 보통 어플리케이션에 프로퍼티 설정을 위해 application.yml, application.properties와 같은 파일을 두고 키-값으로 프로퍼티를 작성한다. 이 값들을 스프링부트가 읽어 들여 변수에 세팅하는 클래스가 MybatisProperties 같은 프로퍼티 클래스이다.

좀 더 자세히 보자. MybatisProperties의 소스코드 일부이다. @ConfigurationProperties는 외부 프로퍼티를 사용하는 클래스라는 의미이고 prefix값을 지정할 수 있다. 예를들어 application.properties에서 'mybatis'로 시작하는 프로퍼티를 작성하면 읽어 들여 클래스의 변수에 바인딩된다.
mybatis.configLocation 프로퍼티 작성 -> MybatisProperties 클래스의 configLocation 변수에 바인딩

  @ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
  public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    private String configLocation; // 프로퍼티 설정파일에서 값을 읽어들여 바인딩된다.
    // 생략
  }

변수가 바인딩된 프로퍼티 클래스를 AutoConfiguration 자동설정 클래스에서 @EnableConfigurationProperties(MybatisProperties.class) 애노테이션을 마킹하여 바인딩된 변수(프로퍼티 설정파일의 값)를 이용하여 자동설정 한다.

  @org.springframework.context.annotation.Configuration
  @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
  @ConditionalOnSingleCandidate(DataSource.class)
  @EnableConfigurationProperties(MybatisProperties.class)
  @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
  public class MybatisAutoConfiguration implements InitializingBean {
      private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
      private final MybatisProperties properties; // 바인딩된 properties
      // 생략
  }

요약

스프링부트에서 의존성을 추가하고 싶다면 제공되는 stater가 있는지 알아본다.
starter는 단순히 라이브러리를 추가하는 것에 더해서 autoconfigure 라이브러리를 참조한다.
autoconfigure의 spring.factories에 정의된 자동설정 클래스들이 실행되고 스트링부트 프로젝트의 application.properties 같은 프로퍼티 설정파일에서 값을 읽어 들여 자동설정에 사용한다.

 

스프링부트 기본 프로젝트를 생성하면 main메서드를 포함하는 시작 클래스가 있고, 이 클래스에는 @SpringBootApplication이 마킹되어 있다.

@SpringBootApplication는 다수의 애노테이션으로 이루어진 메타애노테이션으로 상세 애노테이션들이 실행되어 빈 등록 및 자동설정을 수행한다. 주요 기능을 수행하는 상세 애노테이션은 3가지다.


@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

 

아래는 @SpringbootApplication의 코드 일부로 구성하는 3개의 애노테이션을 확인할 수 있다. @SpringbootApplication은 이 3가지 애노테이션을 동작시켜 스프링부트 기동시 기반작업(빈등록 등)을 수행하게 하는 역할이다.

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
	@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

@SpringBootConfiguration은 그저 자바 설정파일임을 마킹하는 애노테이션이고 아래 두개의 애노테이션이 스프링 Bean등록의 핵심이 된다. Bean 등록은 두 단계로 나뉘어져 진행되는데 다음과 같다.

 

1단계 @ComponentScan : 개발자가 지정한(애노테이션으로 마킹한) 클래스를 빈으로 등록

2단계 @EnableAutoConfiguration : 기타 라이브러리의 클래스를 자동으로 빈 등록

 

하나씩 살펴본다.

@ComponentScan

해당 애노테이션이 마킹된 자바파일의 패키지를 기본패키지로 하위 패키지의 컴포넌트들을 모두 빈으로 등록한다. 빈 등록 대상은 개발자가 애노테이션을 마킹한 클래스들이고, 이 때 마킹에 사용되는 주요 애노테이션은 아래와 같다.

클래스의 성격에 따라 맞는 애노테이션을 마킹한다.

 

- @Component

- @Configuration, @Repository, @Service, @Controller, @RestController 등


@EnableAutoConfiguration

@EnableAutoConfiguration은 @ComponentScan이 동작한 이후 자동설정이 동작한다. 이름에서 알 수 있듯이 라이브러리에 추가한 클래스들을 빈으로 자동등록 및 추가 자동설정을 해준다.

spring-boot-starter 안에는 자동설정을 위해 spring-boot-autoconfigure 라이브러리가 포함되어 있다. @EnableAutoConfiguration도 여기 라이브러리에 포함되어 있고, 다른 주요파일로는 META-INF/spring.factories 파일이 있다.

META-INF 디렉터리에 대해서는 이전에 정리한 글을 참고한다.

2018/04/12 - [Dev/기타] - META-INF 디렉터리에 대하여

spirng-boot-autoconfgure 라이브러리 및 spring.factories 파일의 일부

spring.factories을 열어보면 여러 클래스들이 나열되어 있는데, 그 중 org.springframework.boot.autoconfigure.EnableAutoConfiguration 프로퍼티값으로 작성된 AutoConfiguration 클래스들이 모두 자동설정의 대상이 된다. 살펴보면 tomcat 자동설정, DispatcherServlet 자동설정 등 많은 수의 자동설정을 기본으로 제공하는 걸 볼 수 있다.

spring.factories에 작성된 자동설정 대상이 되는 클래스들은 모두 @Cofiguration이 마킹되어 있다. 당연히 스프링부트 기동시 설정파일로 읽어들여 실행된다.

일반적으로 스프링부트 환경에서 A 라이브러리를 추가한다고하면, A라이브러리의 자동설정파일이 spring.factories에 정의되어 있고, 이 자동설정파일을 스프링부트가 기동시 실행하여 빈등록이나 추가 초기화 작업을 수행하게 된다. 라이브러리만 추가했을 뿐인데 빈등록이 되었다면 이 과정이 수행되었을 가능성이 크다.

org.springframework.boot.autoconfigure.EnableAutoConfiguration 프로퍼티값이 선언되어 있는 자동설정

AutoConfiguration 클래스에는 @ConditionalOnXxxYyyZzz 과 비슷한 유형의 애노테이션들이 작성되어 있다. 이는 모든 클래스들이 다 적용되는 것이 아니라 자동설정 클래스별로 조건에 따라 자동설정 되거나 무시된다. 예를들어 @ConditionalOnMissingBean 조건에는 특정 빈이 등록되어 있지 않은 경우에만 해당 설정파일이 적용된다.


정리하면 스프링부트의 시작점인 메인 클래스에는 @SpringBootApplication라는 메타 애노테이션이 마킹되어 있다.

이 애노테이션을 통해 개발자가 작성한 클래스들이 자동으로 읽어져 빈으로 등록되고,(@ComponentScan의 기능)

의존성에 추가한 라이브러리의 자동 설정 및 빈등록도 진행된다.(@EnableAutoConfiguration의 기능)

+ Recent posts