스프링 부트 - 자동구성 4
- Spring/Spring
- 2023. 9. 17.
자동 구성 이해1 - 스프링 부트의 동작
스프링 부트는 다음 경로에 있는 파일을 읽어서 스프링 부트 자동 구성으로 사용한다.
resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
직접 만든 memory-v2 라이브러리와 spring-boot-autoconfigure 라이브러리의 위 파일을 살펴보면 스프링 부트의 자동 구성을 확인할 수 있다.
// spring-boot-autoconfigure의 org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
...
- 스프링 부트는 어떤 방법으로 해당 파일들을 읽어서 자동 구성을 해주는 것일까?
- 스프링 부트의 자동 구성은 아래 계층구조를 바탕으로 동작하게 된다.
@SpringBootApplication → @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector.class)
어떻게 이렇게 동작하는 것일까?
@SpringBootApplication
public class ProjectV1Application {
public static void main(String[] args) {
SpringApplication.run(ProjectV1Application.class, args);
}
}
- 먼저 위 메인 메서드가 실행된다. 이 때 SpringApplication.Run()을 할 때, ProjectV1Application.class를 기준으로 실행하는 것을 볼 수 있다.
- ProjectV1Application 클래스를 설정 정보로 사용한다는 뜻이다.
- ProjectV1Application.class는 @SpringBootApplication 어노테이션을 가지고 있다.
...
@SpringBootConfiguration
@EnableAutoConfiguration
...
public @interface SpringBootApplication {}
@SpringBootApplication 어노테이션에는 여러 어노테이션이 붙어있다. 여기서 @EnableAutoConfiguration이라는 어노테이션이 있는데, 이 녀석 덕분에 스프링 부트는 자동 구성을 지원하게 된다.
...
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
- @EnableAutoConfiguration은 @Import(AutoConfigureationImportSelect.class) 어노테이션을 가진다.
- @Import는 주로 스프링 설정 정보(@Configuration)를 포함할 때 사용함.
- 그런데 AutoConfigurationImportSelector.class를 열어보면 @Configuration이 아니다.
이것만 봐서는 이해하기가 어렵다. 정확하게 이해하려면 ImportSelector에 대해서 먼저 이해해야한다.
자동 구성 이해2 - ImportSelector
@Import에 설정 정보를 추가하는 방법은 2가지가 있음.
- 정적 Import : @Import(클래스). 어떤 대상이 추가되어야 하는지 코드에 설정되어있음. 설정으로 사용할 대상을 동적으로 변경할 수 없다.
- 동적 Import : @Import(ImportSelector) 코드로 프로그래밍해서 설정으로 사용할 대상을 동적으로 선택할 수 있음.
정적인 방법
@Configuration
@Import( {AConfig.class, BConfig.class} )
public class AppConfig {...}
- 스프링에서 다른 설정 정보를 정적으로 추가하고 싶으면 다음과 같이 @Import를 사용하면 됨.
- 그런데 예제처럼 AConfig, BConfig가 코드에 딱 정해진 것이 아니라 특정 조건에 따라서 설정 정보를 선택해야하는 경우 어떻게 해야할까?
동적인 방법
스프링은 설정 정보 대상을 동적으로 선택할 수 있는 ImportSelector 인터페이스를 제공한다.
package org.springframework.context.annotation;
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
//..
}
- ImportSelector는 selectImports()의 결과로 스트링 배열을 반환한다.
- 스트링 배열에 내가 등록하고 싶은 스프링 빈의 이름을 반환해주면, 스프링 프레임워크가 빈으로 등록해준다.
ImportSelector 직접 구현해보기.
public class HelloImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"hello.selector.HelloConfig"};
}
}
- 설정 정보를 동적으로 선택할 수 있게 해주는 ImportSelector 인터페이스를 구현했다.
- 여기서는 단순히 `hello.selector.HelloConfig` 설정 정보를 반환한다.
- 이렇게 반환된 설정 정보는 선택되어서 사용된다. 여기에 설정 정보로 사용할 클래스를 동적으로 프로그래밍 하면 됨.
// 일반적으로 사용하는 방법
@Test
void staticConfig() {
// 이렇게 하면 스프링 컨테이너를 만들 때, HelloConfig.class를 참고함.
AnnotationConfigApplicationContext appContext =
new AnnotationConfigApplicationContext(StaticConfig.class);
HelloBean bean = appContext.getBean(HelloBean.class);
Assertions.assertThat(bean).isNotNull();
}
@Configuration
@Import(HelloConfig.class)
public static class StaticConfig {
}
- staticConfig()
- 이 코드는 스프링 컨테이너를 만들 때, StaticConfig.class를 초기 설정 정보로 사용함.
- 그 결과 HelloBean이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있다.
@Test
void selectorConfig() {
// 이렇게 하면 스프링 컨테이너를 만들 때, HelloConfig.class를 참고함.
AnnotationConfigApplicationContext appContext =
new AnnotationConfigApplicationContext(StaticConfig.class);
HelloBean bean = appContext.getBean(HelloBean.class);
Assertions.assertThat(bean).isNotNull();
}
@Configuration
@Import(HelloImportSelector.class)
public static class SelectorConfig{}
- selectorConfig()
- selectorConfig()는 SelectorConfig.class를 초기 설정 정보로 사용함.
- SelectorConfig는 @Import(HelloImportSelector.class)에서 ImportSelector의 구현체인 HelloImportSelector를 사용함.
- 스프링은 HelloImportSelector를 실행하고, "hello.selector.HelloConfig"라는 문자를 반환받음.
- 스프링은 이 문자에 맞는 대상을 설정 정보로 사용한다. 즉, hello.selector.HelloConfig.class를 설정 정보로 사용함.
- 그 결과 HelloBean이 스프링 컨테이너에 잘 등록된 것을 확인할 수 있음.
@EnableAutoConfiguration 동작 방식
- ImportSelector를 이해했으니 다음 코드를 이해할 수 있음.
// @EnableAutoConfiguration
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
- @EnableAutoConfiguration은 AutoConfigurationImportSelector.class를 @Import한다.
- AutoConfigurationImportSelector는 ImportSelector의 구현체다. 따라서 설정 정보를 동적으로 선택할 수 있음.
- AutoConfigurationImportSelector는 모든 라이브러리에 있는 아래 경로의 파일을 확인한다.
- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// org.springframework.boot.autoconfigure.AutoConfiguration.imports
...
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
...
그리고 파일의 내용을 읽어서 설정 정보로 선택한다.
- 결론은 아래 순서대로 동작한다.
- @SpringBootApplication → @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector.class) → resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일을 열어서 설정 정보 선택
- 해당 파일의 설정 정보를 이용해 스프링 컨테이너에 스프링 빈을 등록한다.
정리
스프링 부트의 자동 구성을 직접 만들어서 사용할 때는 다음을 참고하자.
- @AutoConfiguration에 자동 구성의 순서를 지정할 수 있다. before, after 같은 걸로 할 수 있음.
- @AutoConfiguration도 설정 파일이다. 내부에 @Configuration이 있기 때문임.
- 하지만 일반 스프링 설정과 라이프 사이클이 다르기 때문에 컴포넌트 스캔의 대상이 되면 안됨.
- 파일에 지정해서 사용해야 함.
- resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 그래스 스프링 부트가 제공하는 컴포넌트 스캔에서는 @AutoConfiguration을 제외하는 AutoConfigurationExcludeFilter가 포함되어있음.
// SpringBootApplication
...
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
- 위의 코드를 보면, SpringBootApplication 어노테이션이 실행될 때 컴포넌트 스캔을 실행한다. 그러나 이 때 @Filter 어노테이션을 이용해 AutoConfigurationExcludeFilter.class를 실행해 AutoConfiguration.class가 컴포넌트 스캔의 대상이 되는 것을 방지함.
- 자동 구성이 내부에서 컴포넌트 스캔을 사용하면 안됨. 매우 중요. 대신에 자동 구성 내부에서 @Import는 사용할 수 있음.
자동 구성을 언제 사용하는가?
- AutConfiguration은 라이브러리를 만들어서 제공할 때 사용하고, 그 외에는 사용하는 일이 거의 없다. 왜냐하면 보통 필요한 빈들은 컴포넌트 스캔하거나 직접 등록하기 때문임.
- 자동 구성을 알아야 하는 진짜 이유는 개발하다보면 사용하는 특정 빈들이 어떻게 등록된 것인지 확인이 필요할 때가 있음. 이럴 떄 스프링 부트의 자동 구성 코드를 읽을 수 있어야 한다.
남은 문제
- 이런 방식으로 스프링 빈이 자동 등록되면, 빈을 등록할 때 사용하는 설정 정보는 어떻게 변경해야 하는지 의문이 들 것이다. 예를 들어서 DB 접속 URL, ID, PW 같은 것들말이다.
- DataSource 스프링 빈을 등록할 때 이런 정보를 입력해야하는데, 어떻게 이런 정보를 동적으로 바꿀 수 있을까?
'Spring > Spring' 카테고리의 다른 글
Spring : 외부 설정이란? (0) | 2023.10.21 |
---|---|
스프링 부트 - 자동 구성 관련 요약 (0) | 2023.09.17 |
스프링 부트 - 자동구성 2 (0) | 2023.09.17 |
스프링 부트 - 자동구성 3 (2) | 2023.09.17 |
스프링 부트 - 자동구성 1 (0) | 2023.09.14 |