BackEnd/Spring Boot

Spring Boot 핵심 기능 3 - 자동 구성(Auto Configuration)

hanseom 2023. 4. 20. 07:00
반응형

스프링 부트의 자동 구성

  스프링 부트는 자동 구성(Auto Configuration) 기능을 제공합니다. 일반적으로 자주 사용하는 수 많은 빈들을 자동으로 등록해주는 기능입니다. JdbcTemplate, DataSource, TransactionManager 등 스프링 부트가 자동 구성을 제공해서 자동으로 스프링 빈으로 등록됩니다.

 

  스프링 부트 자동 구성이 동작하는 원리는 다음 순서로 확인할 수 있습니다.

  1. @SpringBootApplication
  2. @EnableAutoConfiguration
  3. @Import(AutoConfigurationImportSelector.class)
  4. resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일의 설정 정보가 스프링 컨테이너에 등록되고 사용

 

  스프링 부트는 spring-boot-autoconfiguration 프로젝트 안에서 수 많은 자동 구성을 제공합니다. JdbcTemplate을 설정하고 빈으로 등록해주는 자동 구성은 다음과 같습니다.

package org.springframework.boot.autoconfigure.jdbc;

import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
		NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}
  • @AutoConfiguration: 자동 구성을 사용하려면 이 애노테이션을 등록해야 합니다.
  • after = DataSourceAutoConfiguration.class: 자동 구성이 실행되는 순서를 지정할 수 있습니다. JdbcTemplate은 DataSource가 필요하기 때문에 DataSource를 자동으로 등록해주는 DataSourceAutoConfigration 다음에 실행하도록 설정되어 있습니다.
  • @ConditionalOnClass({ DataSource.class, JdbcTemplate.class }): IF문과 유사한 기능을 제공합니다. DataSource, JdbcTemplate 클래스가 있는 경우에만 설정이 동작합니다. 만약 없으면 여기 있는 설정들이 모두 무효화 되고, 빈도 등록되지 않습니다.
  • @Import: 스프링에서 자바 설정을 추가할 때 사용합니다.

 

  @Import의 대상이 되는 JdbcTemplateConfiguration은 다음과 같습니다.

package org.springframework.boot.autoconfigure.jdbc;

import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {

	@Bean
	@Primary
	JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		JdbcProperties.Template template = properties.getTemplate();
		jdbcTemplate.setFetchSize(template.getFetchSize());
		jdbcTemplate.setMaxRows(template.getMaxRows());
		if (template.getQueryTimeout() != null) {
			jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
		}
		return jdbcTemplate;
	}

}
  • @Configuration: 자바 설정 파일로 사용됩니다.
  • @ConditionalOnMissingBean(JdbcOperations.class): JdbcOperation 빈이 없을 때 동작합니다. JdbcOperations는 JdbcTemplate의 부모 인터페이스입니다. 즉, JdbcTemplate가 빈으로 등록되어 있지 않은 경우에만 동작합니다. 만약 이런 기능이 없으면 내가 등록한 JdbcTemplate과 자동 구성이 등록하는 JdbcTemplate가 중복 등록되는 문제가 발생할 수 있습니다. 개발자가 직접 빈을 등록하면 개발자가 등록한 빈을 사용하고, 자동 구성은 동작하지 않습니다.

 

자동 등록 설정

  다음과 같은 자동 구성 기능들이 다음 빈들을 등록해줍니다.

  • JdbcTemplateAutoConfiguration : JdbcTemplate
  • DataSourceAutoConfiguration : DataSource
  • DataSourceTransactionManagerAutoConfiguration : TransactionManager

 

스프링 부트가 제공하는 자동 구성(AutoConfiguration)

  스프링 부트는 수 많은 자동 구성을 제공하고 spring-boot-autoconfiguration에 자동 구성을 모아둡니다. 스프링 부트 프로젝트를 사용하면 spring-boot-autoconfigure 라이브러리는 기본적으로 사용됩니다.

 

@Conditional

  같은 소스 코드인데 특정 상황일 때(예: 개발 환경에서는 Swagger를 사용하고 운영 환경에서는 Swagger를 사용하지 않을 때)만 특정 빈들을 등록해서 사용하도록 도와주는 기능입니다. 스프링 부트 자동 구성에서 자주 사용합니다. 이 기능을 사용하려면 Condition 인터페이스를 구현해야 합니다.

@FunctionalInterface
public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked
	 * @return {@code true} if the condition matches and the component can be registered,
	 * or {@code false} to veto the annotated component's registration
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
  • matches(): true를 반환하면 조건에 만족해서 동작하고, false를 반환하면 동작하지 않습니다.
  • ConditionContext: 스프링 컨테이너, 환경 정보 등을 담고 있습니다.
  • AnnotatedTypeMetadata: 애노테이션 메타 정보를 담고 있습니다.

 

  다음은 환경 정보가 memory=on이라고 되어 있을 때만 동작하는 샘플 코드입니다.

package memory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Slf4j
public class MemoryCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //-Dmemory=on
        String memory = context.getEnvironment().getProperty("memory");
        log.info("memory={}", memory);
        return "on".equals(memory);
    }
}
package hello.config;

import memory.MemoryCondition;
import memory.MemoryController;
import memory.MemoryFinder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
@Conditional(MemoryCondition.class)
public class MemoryConfig {

    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }

    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }

}

 

  스프링은 대부분의 필요한 Condition 인터페이스 구현체를 만들어 제공합니다. 위의 코드는 아래와 같이 사용할 수 있습니다. 별도의 MemoryCondition.class 를 구현할 필요 없이 @ConditionalOnProperty을 사용하면 됩니다.

@Configuration
//@Conditional(MemoryCondition.class)
@ConditionalOnProperty(name = "memory", havingValue = "on") //추가
public class MemoryConfig {
    ... 생략 ...
}
  • @ConditionalOnClass , @ConditionalOnMissingClass: 클래스가 있는 경우 동작한다. 나머지는 그 반대
  • @ConditionalOnBean , @ConditionalOnMissingBean: 빈이 등록되어 있는 경우 동작한다. 나머지는 그 반대
  • @ConditionalOnProperty: 환경 정보가 있는 경우 동작한다.
  • @ConditionalOnResource: 리소스가 있는 경우 동작한다.
  • @ConditionalOnWebApplication , @ConditionalOnNotWebApplication: 웹 애플리케이션인 경우 동작한다.
  • @ConditionalOnExpression: SpEL 표현식에 만족하는 경우 동작한다.

Condition Annotations 공식 메뉴얼

 

[참고정보]

스프링 부트 - 핵심 원리와 활용

반응형