티스토리 뷰

Framework/Spring

auto-configuration에 대하여

DUCKBAE's 2024. 10. 23. 22:18

스프링부트의 자동 구성(auto-configuration) 기능은 개발자가 수동으로 필요한 설정을 하는 하는 번거로움을 줄여준다. 때문에 개발자는 복잡한 설정 과정을 하지 않고 비즈니스 로직에만 집중할 수 있다.
이 글에서 스프링부트에서 자동구성의 원리와 그 작동 방식에 대해 알아보려고 한다.

 


auto-configuration 정의

스프링부트에서 애플리케이션을 시작할 때 필요한 설정을 자동으로 구성해주는 기능을 제공한다.
즉, 개발자가 수동으로 필요한 설정을 하지 않아도 기본적인 설정이 자동으로 적용된다는 것이다.
예를들어, mysql 드라이버가 클래스패스에 존재하고 데이터베이스 연결과 관련된 설정을 하지 않더라도 application.properties에 관련 설정 정보만 작성해준다면 스프링부트는 자동으로 mysql 에 대해 자동으로 설정을 수행해준다.
 

auto-configuration의 사용 방법

@EnableAutoConfiguration 또는 @SpringBootApplication 어노테이션을 사용하여 auto-configuration을 활성화해야한다.
Spring 프로젝트를 만들면 main 메서드를 실행하는 메인 클래스가 있는데, 해당 클래스에는 @SpringBootApplication 어노테이션이 선언되어있다.
@SpringBootApplication = @EnableAutoConfiguration + @Configuration + @Component
이 중 @EnableAutoConfiguration 어노테이션을 통해 auto-configuration이 자동으로 활성화된다.
다음 코드는 SpringBootApplication 함수형 인터페이스 코드의 일부인데, 위에 언급한 어노테이션들이 선언되어있는 것을 알 수 있다.

@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 {
}

 

auto-configuration 클래스 비활성화

auto-configuration 클래스 비활성화

원치 않은 클래스에 auto-configuration이 적용되고 있는 경우 다음과 같이 SpringBootApplication의 exclude 속성을 사용하여 해당 클래스를 비활성화 할 수 있다. 만약 클래스가 클래스패스에 존재하지 않는다면 excludeName 속성을 사용할 수 있다. @EnableAutoConfiguration 도 동일하게 exclude, excludeName 속성을 사용할 수 있다.

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication {
	public static void main(String[] args) {
    	...
    }
}

 

auto-configuration 의 원리

@EnableAutoConfiguration
Spring Application Context의 자동 구성(auto-configuration)을 활성화한다.
이 어노테이션을 사용하면 스프링부트는 자동구성 기능을 시작한다.
 
spring.factories
spring-boot-autoconfigure 모듈의 JAR 파일 내 META-INF 디렉토리에 위치하는 파일로, 자동 구성(auto-configuration) 클래스를 정의하고 있다.
스프링부트는 ClassLoader를 통해 해당 파일을 읽어서 자동 구성(auto-configuration) 클래스를 로드한다.
클래스 로드 과정에서 각 클래스가 갖고 있는 특정 조건이 만족되면 빈으로 등록한다.
 
spring.factories 파일은 다음과 같이 key-value 구조로 되어있다.
key는 인터페이스이고, value는 해당 인터페이스에 대한 구현체로 스프링부트는 애플리케이션 컨텍스트를 초기화할 때 이 구현체들을 빈으로 등록하게 된다.

# ApplicationContext Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

 
조건 평가
자동 구성 클래스는 @Conditional 어노테이션을 사용하여 특정 조건을 평가한다.
특정 조건이 만족될 때만 빈(Bean)을 생성한다. 예를 들어 특정 클래스가 클래스패스에 존재하거나, 이미 빈이 등록되어있는 경우에만 빈을 생성하도록 조건을 설정할 수 있다.
 

auto-configuration 의 조건부인 @Conditional

지정된 모든 조건이 일치하는 경우에만 컴포넌트를 등록할 수 있음을 나타내는 어노테이션이다.
즉, 특정 조건에 따라 빈의 생성 여부를 결정할 수 있게 하는 어노테이션이다. - 어노테이션 참고
 
@Conditional은 value 인자로 Condition 인터페이스를 받는다.
(커스텀 조건을 생성할 때 해당 Condition 인터페이스를 구현한다. 구현 예제는 아래에서 확인할 수 있다.)

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}
@FunctionalInterface
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition 인터페이스는 Boolean 값을 반환하는 matches() 메서드 하나만 갖는 함수형 인터페이스이다.
조건이 일치해서 true를 반환하면 @Conditional이 붙어있는 대상으로부터 빈이 생성되고, false를 반환하면 @Conditional이 붙어있는 대상의 내용이 실행되지 않고 빈도 생성되지 않는다.
 

auto-configuration 과정

빈 탐색
스프링부트는 @EnableAutoConfiguration이 활성화되면 spring.factories 파일을 읽어 자동구성 클래스를 찾는다.
 
조건 평가
각 자동 구성(auto-configuration) 클래스는 특정 조건이 충족되는지 평가한다.
@Conditional 어노테이션을 통해 조건을 평가하는데, 조건이 충족되면 해당 클래스가 활성화되고 그렇지 않으면 무시된다.
 
빈 생성
조건이 충족된 자동 구성(auto-configuration) 클래스에서 정의된 빈을 생성한다.
 
애플리케이션 초기화
모든 자동 구성(auto-configuration) 클래스가 처리되면, 스프링 애플리케이션 컨텍스트가 완성된다.
생성된 모든 빈은 스프링 컨테이너에 등록되며, 애플리케이션이 실행 준비 상태가된다.
 

예제를 통해 auto-configuration 분석

데이터 소스와 관련된 트랜잭션 관리를 자동으로 설정하는 클래스인 DataSourceTransactionManagerAutoConfiguration.java 파일을 분석해본다.
더 다양한 auto-configuration 클래스 파일은 여기에서 확인할 수 있다.

@AutoConfiguration(before = TransactionAutoConfiguration.class,
		after = TransactionManagerCustomizationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class, TransactionManager.class }) (1)
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnSingleCandidate(DataSource.class) (2)
	static class JdbcTransactionManagerConfiguration {

		@Bean
		@ConditionalOnMissingBean(TransactionManager.class) (3)
		DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
				ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
			DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
			transactionManagerCustomizers
				.ifAvailable((customizers) -> customizers.customize((TransactionManager) transactionManager));
			return transactionManager;
		}

		private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
			return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
					? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
		}

	}

}

(1) 클래스패스에 DataSource, JdbcTemplate, TransactionManager 클래스가 존재할 경우 DatasourceTransactionManagerAutoConfiguration 빈을 생성한다.
 
(2) Datasource 빈이 한 개만 정의되어있으면 JdbcTransactionManagerConfiguration 빈을 생성한다. 다시말해, 두 개 이상의 Datasource 빈이 존재하면 JdbcTransactionManagerConfiguration 빈은 생성하지 않는다.
 
(3) 클래스패스에 TransactionManager 클래스가 존재하지 않을 경우 DatasourceTransactionManager 빈을 생성한다.
 

조건평가 예제 만들어보기

@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
@ConditionalOnProperty(name = "myapp.common.enabled", havingValue = "true", matchIfMissing = false)
public CommonRepository commonRepository() {
    return new CommonRepositoryImpl();
}

 

CommonRepository 빈을 생성하기 위한 조건은 다음과 같다.
1. @ConditionalOnclass 클래스패스에 mysql driver 가 존재해야한다.
2. @ConditionalOnProperty application.properties 파일에 myapp.common.enabled(name) 프로퍼티의 값은 true(havingValue) 여야 한다. matchIfMissing은 name 속성에 있는 프로퍼티명이 존재하지 않을 경우 빈을 생성하지 않는다는 뜻이다.

@SpringBootApplication
public class MyAppApplication implements CommandLineRunner {

	@Autowired
	private ApplicationContext applicationContext;

	public static void main(String[] args) {
		SpringApplication.run(MyAppApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		if (applicationContext.containsBean("commonRepository")) {
			System.out.println("commonRepository 빈이 생성되었습니다.");
		} else {
			System.out.println("commonRepository 빈이 생성되지 않았습니다.");
		}
	}
}

애플리케이션을 실행하여 실제로 ApplicationContext에 commonRepository 빈이 생성되는지 확인해 볼 수 있다. 위에서 언급했던 빈 생성 조건에 따라 프로퍼티의 값을 변경해보고, mysql 라이브러리를 추가해보면서 테스트를 할 수 있다. 두 조건 중 하나라도 충족하지 않으면 commonRepository 빈은 생성이 되지 않을 것이고, 두 조건 모두 충족한다면 commonRepository 빈은 생성될 것이다.


auto-configuration은 스프링 컨텍스트 초기화 시, 클래스패스의 라이브러리와 설정을 기반으로 특정 조건을 만족할 때 필요한 빈(Bean)을 자동으로 생성하고 구성한다.


 
참고
- 실전 스프링 부트
- https://docs.spring.io/spring-boot/reference/using/auto-configuration.html

'Framework > Spring' 카테고리의 다른 글

Spring Security 의 두 번째: 인증  (0) 2024.11.18
Spring Security 의 첫 걸음  (0) 2024.11.17
[Web] Servlet 과 Servlet Container  (0) 2022.12.26
[Spring] DispatcherServelt (Feat. Front Controller)  (0) 2022.12.16
[Spring] 순환 참조  (0) 2022.12.13
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함