-
[02] Spring boot 자동 설정Spring/Spring boot 2020. 5. 15. 09:28반응형
[@SpringBootApplication annotation에 대해]
Spring boot project를 생성하고 application을 실행하면 별다른 설정 없이도 실행이 가능합니다. 그 이유는 다음과 같이 @SpringBootApplication annotation안에 @EnableAutoConfiguration annotation이 선언되어 있기 때문입니다.
...(생략)... @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { ...(생략)...
따라서 main class의 @SpringBootApplication annotation은 다음과 같이 바꿔볼 수 있습니다.
@SpringBootConfiguration // ① @ComponentScan // ② @EnableAutoConfiguration // ③ public class Application { public static void main(String[] args) { //@EnableAutoConfiguration을 사용하지 않는 경우 web application이 아니기 때문에 spring application type에 web application이 아님을 지정해야 실행가능합니다. SpringApplication springApplication = new SpringApplication(Application.class); //springApplication.setWebApplicationType(WebApplicationType.NONE); springApplication.run(args); //SpringApplication.run(Application.class, args); } }
① @Configuration과 이름만 다르고 기능은 동일한 annotation입니다.
② annotation이 지정된 class의 package 밑으로 component scan을 진행하여 @Component 계열 annotation(@Controller, @RestController, @Configuration, @Repository, @Service)과 @Bean annotaion이 붙은 method return 객체를 모두 bean으로 등록합니다.
③ 이 annotation이 적용된 경우 application context에 ServletWebServerFactory bean을 등록하여 web application으로 만들어 줍니다.
@SpringBootApplication =
@SpringBootConfiguration
+
@ComponentScan
+
@EnableAutoConfiguration
[Spring boot에서의 bean 등록 과정]
Spring boot에서 bean의 등록은 두 번에 걸쳐 진행되며 그 순서는 다음과 같습니다.
- Component Scan에 의해 등록
- EnableAutoConfiguration으로 읽어온 bean을 등록
따라서 @EnableAuoConfiguration을 제외하고도 project를 실행할 수 있습니다. 다만 기본적으로 spring boot application은 web application으로 구동되는데, 이 post 상단의 code에서 언급했듯이 @EnableAutoConfiguration을 붙여야 web application으로 만들어주게 되므로 application type을 NONE으로 지정해야 실행이 가능합니다. code로 살펴보면 다음과 같습니다.
@SpringBootConfiguration @ComponentScan //@EnableAutoConfiguration public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.setWebApplicationType(WebApplicationType.NONE); // ① app.run(args); } }
① @EnableAutoConfiguration을 사용하지 않으면 ServletWebServerFactory bean이 등록되지 않습니다. ServletWebServerFactory bean은 application이 web application으로 실행되도록 해주는 bean입니다. 따라서 spring application type에 WebApplicationType.NONE enum value를 지정해서 실행되는 application이 web application이 아님을 명시해야 application을 정상적으로 실행할 수 있습니다.
위의 code로 application을 실행해보면 auto configuration 없이도 bean을 등록하고 실행할 수 있는 것을 확인할 수 있습니다.
[@ComponentScan에 의한 bean 등록]
@ComponentScan가 붙은 class의 package를 기준으로 하위에 존재하는 모든 class 중 @Component 계열 annotation(@Controller, @RestController, @Configuration, @Repository, @Service)이 붙어 있는 class와 @Bean annotaion이 붙은 method를 scan 해서 bean으로 등록합니다. (일부 filter에 등록된 class들은 등록 대상에서 제외됩니다.)
[@EnableAutoConfiguration에 의한 bean 등록]
Spring boot의 autoconfiguration file은 External Libraries 중 spring-boot-autoconfigure-X.X.X.RELEASE/META-INF/spring.factories file 입니다.
file의 내용을 살펴보면 key 중에 org.springframework.boot.autoconfigure.EnableAutoConfiguration이 있고 그에 대한 value로 여러 개의 configuration class들이 지정되어 있습니다. 이 configuration class들이 spring boot의 기본 설정들 즉, convention들입니다. value로 지정된 class 중 하나인 org.springframework.boot.autoconfigure.web.servelet.WebMvcAutoConfiguration을 살펴보도록 합시다.
...(생략)... @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 { public static final String DEFAULT_PREFIX = ""; public static final String DEFAULT_SUFFIX = ""; private static final String[] SERVLET_LOCATIONS = new String[]{"/"}; ...(생략)...
① Auto configuration 안에서 @Configuation annotation을 사용하고 있습니다. @Configuration을 사용해 java 설정 file을 읽어 들입니다. Spring은 읽어 들인 java 설정 file을 통해 bean을 등록합니다.
② application type이 SERVLET인 경우에만 이하의 bean 설정 file의 내용대로 bean을 등록합니다.
WebMvcAutoConfiguration class 외에도 모든 auto configuration class에는 @Configuration이 붙어있습니다. 그렇기 때문에 모든 목록이 java 설정 file이라 볼 수 있습니다. 하지만 java 설정 file의 모든 내용이 bean으로 등록되는 것은 아니고 설정에 따라 bean으로 등록하게 됩니다.
[Custom Auto Configruation 만들기]
하나의 project에서 사용자 정의 Auto configuration을 만들어 다른 project에서 사용하는 예를 확인해 보도록 합시다.
1. 먼저 Auto configuration 작성을 위한 project (이하 Project A)를 하나 생성합니다.
2. Project A의 pom.xml에 자동 설정에 필요한 의존성을 추가해줍니다.
<!-- 자동설정 관리에 필요한 의존성을 등록 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure-processor</artifactId> <optional>true</optional> </dependency> </dependencies> <!-- spring-boot-autoconfigure, spring-boot-autoconfigure-processor 두 의존성의 version 관리를 위해 dependencyManagement tage 사용. --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.0.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
3. src/main/java 아래 package를 하나 만들고 그 밑에 bean으로 사용할 class를 정의합니다. 본 예에서는 Product.class를 추가합니다. code는 다음과 같습니다. 상품명과 수량을 관리할 수 있는 간단한 class입니다.
public class Products { String productName; Integer productQuantity; public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public Integer getProductQuantity() { return productQuantity; } public void setProductQuantity(Integer productQuantity) { this.productQuantity = productQuantity; } @Override public String toString() { return "Products{" + "productName='" + productName + '\'' + ", productQuantity=" + productQuantity + '}'; } }
4. Product class를 bean으로 등록할 수 있도록 Product class와 동일한 경로에 java 설정 file인 ProductConfiguration.class를 작성합니다. 내용은 다음과 같습니다.
@Configuration // ① public class ProductsConfiguration { @Bean // ② public Products products() { Products products = new Products(); products.setProductName("Apple Pencil"); products.setProductQuantity(11); return products; } }
① 이 annotation이 지정된 class는 XML file에서 하던 bean 설정과 동일한 작업을 하는 class입니다.
② @Bean annotaiton은 method에 붙어 그 method가 반환하는 object를 bean으로써 AppicationContext에 등록합니다.
5. src/main/resources/ 에 META-INF directory를 생성하고 spring.factories file을 만들어 넣어줍니다. spring.factories file은 spring boot autoconfiguration과 관련된 내용이 기술되어 있습니다. 본 예에서 입력할 내용은 다음과 같습니다.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ me.dave.autoconfigure.ProductsConfiguration
위에 기술된 spring.factories file 내용은 auto configuration이 enable이면 me.dave.autoconfigure.ProductsConfiguration java 설정 file을 사용해서 bean을 등록하라는 것입니다.
6. Project A의 auto configuration을 다른 project에서도 사용할 수 있도록 Project A를 build 해서 jar file형태로 maven repository에 install 합니다. maven install은 maven tab의 Lifecycle 중 install을 double click 하거나 terminal에서 Project A 경로로 두고 mvn install 명령어를 입력하여 실행할 수 있습니다.
7. Project A의 auto configure를 사용할 다른 project(이하 Project B)를 만듭니다.
8. Project B에서 Project A의 auto configure를 사용하기 위해서 다음과 같이 Project B의 pom.xml에 Project A에 대한 의존성을 추가해 줍니다. maven 변경사항을 load 해줍니다.
<?xml version="1.0" encoding="UTF-8"?> <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> <groupId>me.dave</groupId> <artifactId>spring-boot-example_0517</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.7.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Project A의 auto configure 의존성을 추가 시작--> <dependency> <groupId>me.dave.autoconfigure</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--Project A의 auto configure 의존성을 추가 끝--> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Project tool window에서 External Libraries를 확인해보면 추가한 auto configure 의존성이 들어와 있는 것을 확인해볼 수 있습니다.
9. Project A의 auto configure를 통해 생성된 bean을 Project B에서 사용 특별한 선언 없이 사용 가능한지 확인해보기 위해 Runner class(ProductRunner.class)를 하나 만들어 확인해봅니다.
@Component public class ProductRunner implements ApplicationRunner { @Autowired Products products; @Override public void run(ApplicationArguments args) throws Exception { System.out.println(products); } }
실행을 위한 main class는 실행시간 단축을 위해 web application type을 NONE으로 지정합니다.
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.setWebApplicationType(WebApplicationType.NONE); app.run(args); } }
main mathod를 실행하면 Project B 어디에서도 Product bean을 등록하지 않았지만 사용할 수 있게 됩니다.
[auto configure와 동일한 bean name의 bean을 추가하기 원하는 경우]
Spring boot에서 bean을 등록하는 phase는 두 가지이며 각 실행 순서는 다음과 같습니다.
- Component Scan
- Auto Configuration
Project B의 Application class에서 Product A의 Products class type의 bean을 application context에 등록하도록 합니다.
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.setWebApplicationType(WebApplicationType.NONE); app.run(args); } //Spring Boot에서 bean을 등록하는 두 phase 중 Component scan이 먼저 일어나고 //두 번째로 auto configuration에서 일어납니다. //여기에서 @Bean annotaion을 붙여 bean을 생성하는 것은 Component scan에 의해 생성되고 //auto cofiguration을 통해 spring-boot-autoconfigure project의 bean 설정 file로 현재 생성된 //bean을 덮어 쓰기 때문에 원하는 결과를 얻을 수 없습니다. @Bean public Products product() { Products products = new Products(); products.setProductName("S-Pen"); products.setProductQuantity(80); return products; } }
이런 경우 component scan보다 auto configuration에 의한 bean 등록이 나중에 진행되므로 bean의 내용은 auto configuration을 통해 생성된 것을 가지고 있게 됩니다.
Component scan에 의해 등록된 bean이 이미 있는 경우에 auto configuration에서 제외하려면 Project A에서의 해당 bean 등록 method에 @ConditionalOnMissingBean을 붙여줍니다.
@Configuration public class ProductsConfiguration { @Bean @ConditionalOnMissingBean //등록하려는 bean이 없는 경우에 한해서만 등록 진행 public Products products() { Products products = new Products(); products.setProductName("Apple Pencil"); products.setProductQuantity(11); return products; } }
변경사항을 jar file에 적용하기 위해 maven install을 다시 진행합니다.
이제 다시 Project B application을 실행하면 component scan으로 등록한 bean을 우선하여 등록하게 됩니다.
기본 설정은 일종의 convention이고 내가 원하는 대로 등록한 bean이 기본 설정보다 우선적으로 사용됩니다. 이 것을 통해 Spring boot가 제공하는 여러 가지 기능들을 customizing 할 수 있습니다.
[Bean 선언을 application.properties key, value로 대체하기]
이미 Project A에서 bean의 class type을 선언했는데 다시 Project B에서 선언하여 사용하게 되면 code가 반복되고 의도하지 않은 실수가 나올 수도 있습니다. 이를 방지하기 위해 bean의 field들을 Project B의 src/resources/application.properties에 등록해놓고 Project A에서 이를 사용하여 bean 등록하도록 할 수 있습니다.
먼저 Project B의 application.properties의 내용을 다음과 같이 작성합니다.
products.name = Surface Pen products.quantity = 33
Project A에 configuration properties를 관리할 class(ProductsProperties.java)를 만듭니다. 그 내용은 다음과 같습니다.
@ConfigurationProperties("products") // ① public class ProductsProperties { private String name; private Integer quantity; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } }
① configuration properties를 사용하기 위한 annotation으로 매개 값으로는 가져오기 원하는 key의 prefix를 지정합니다. Project B의 application.properties에 입력된 key, value pair에 접근할 수 있게 됩니다.
auto configure class의 내용을 다음과 같이 수정합니다.
@Configuration @EnableConfigurationProperties(ProductsProperties.class) // ① public class ProductsConfiguration { @Bean @ConditionalOnMissingBean public Products products(ProductsProperties properties) { // ② Products products = new Products(); products.setProductName(properties.getName()); products.setProductQuantity(properties.getQuantity()); return products; } }
① configure properties class로 ProductsProperties.class을 지정합니다.
② bean을 return 하는 method의 매개 값으로 properties에 해당하는 type의 객체를 넣어줍니다. 이 객체의 method를 통해 Project B의 application.properties file의 내용에 접근할 수 있습니다.
auto configure class의 내용이 수정되면 maven install을 진행합니다.
Project B에서 properties만 지정해도 bean이 등록되는지 확인해보기 위해 bean 생성을 위한 code를 제거하고 실행해봅니다.
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.setWebApplicationType(WebApplicationType.NONE); app.run(args); } /* @Bean public Products product() { Products products = new Products(); products.setProductName("S-Pen"); products.setProductQuantity(80); return products; } */ }
Project B의 어느 곳에도 bean을 등록하는 code가 없지만 Project A의 auto configure에서 Project B의 application.properties 내용을 기준으로 bean을 등록한 것을 볼 수 있습니다.
'Spring > Spring boot' 카테고리의 다른 글
[05] Spring Application (0) 2020.05.28 [04] 독립으로 실행가능한 JAR file (0) 2020.05.26 [03] 내장 Web Application Server (0) 2020.05.20 [01] Spring boot 의존성 관리 (0) 2020.05.12 [00] Maven으로 Spring Boot Project 생성 (0) 2020.05.11