WEB/Spring Boot

자동 구성(Auto Configuration)

Tony Lim 2023. 5. 13. 17:41
728x90
@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 = 자동구성을 사용하려면 이 애노테이션을 등록해야한다.

  • 자동 구성도 내부에 @Configuration 이 있어서 빈을 등록하는 자바 설정 파일로 사용할 수 있다.

after = DataSouceAutoConfiguration.lcass 

  • 자동 구성이 실행되는 순서를 지정할 수 있다. JdbcTemplate은 DataSouce가 필요하기 때문에 DataSource를 자동으로 등록해주는 DataSourceAutoConfiguration 다음에 실행하도록 설정되어 있다.

@ConditionOnClass({DataSouce.class, JdbcTemplate.class})

  • 괄호안에 클래스가 있는 경우에만 설정이 동작하게 된다.

 

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

JdbcOperation은 jdbctemplate의 인터페이스이고 
@ConditionalOnMissingBean에서 해당 인터페이스르 구현한 빈이 없을시에만 JdbcTemplate 자동구성을 진행하게 된다.
개발자가 이미 등록했으면 안동작함


@Conditional

같은 소스 코드인데 특정 상황일 때만 특정 빈들을 등록해서 사용하도록 도와주는 기능

@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);
    }
}
@Configuration
@Conditional(MemoryCondition.class)
public class MemoryConfig {

-Dmemory=on jvm option을 주면 해당 Configuration에 정의된 빈들을 등록해라

@ConditionalOnProperty(name = "memroy", havingValue = "on")
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

위와같이 이미 스프링에서 이미 구현해 놓은것이다.

얘도 똑같이 OnPropertyCondition 클래스에서 Condition 인터페이스를 구현하고 있다.


순수 라이브러리 만들기

plugins {
    id 'java'
}

group = 'memory'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:3.0.2'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    annotationProcessor 'org.projectlombok:lombok:1.18.24'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:3.0.2'
}

test {
    useJUnitPlatform()
}

스프링 부트 플러그인을 사용하게 되면 앞에 설명한 실행 가능한 jar를 만들기 떄문에 plugin에 java만 기본으로 있다.
스프링 부트 플러그인이 없기에 버전명시도 되어있다.

 

plugins {
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation files('libs/memory-v1.jar')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

사용하고자 하는곳에 해당 라이브러리를 등록해줘야한다.

또한 사용자입장에서는 어떤 클래스들을 빈으로 등록을 해줘야하는지 알 수 가 없다.
MemoryConfig를 구성하는 방법을 알아내야함


자동 구성 라이브러리 만들기

@AutoConfiguration
@ConditionalOnProperty(name = "memory", havingValue = "on")
public class MemoryAutoConfig {
    
    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }
    
    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }
}

위에서 했던 @AutoConfiguration , @ConditionalOnProperty를 추가해서 라이브러리르 만든다.

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

memory.MemoryAutoConfig

인 클래스 풀네임을 해당 폴더에 적어줘야한다. 

이제 이것을 library jar으로 만들어서 실제로 쓰는 스프링부트앱에서 dependency 등록만하면 부팅시점에 필요한 빈들을 알아서 등록하게 된다.


자동 구성 이해 - 스프링 부트의 동작

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@SpringBootApplication 의 자식 Annotation (meta annotation) 에 selector가 존재한다.

@Import 에 설정정보를 추가하는 방법은 2가지이다.

정적인 방법 = @Import(클래스) , 설정으로 사용할 대상을 동적으로 변경할 수 없다.
동적인 방법 = @Import ( 'ImportSelector' 인터페이스 구현체 ) 를 사용해 동적으로 선택할 수 있다.

@Test
void selectorConfig() {
    AnnotationConfigApplicationContext appContext =
            new AnnotationConfigApplicationContext(SelectorConfig.class);
    HelloBean bean = appContext.getBean(HelloBean.class);
    Assertions.assertThat(bean).isNotNull();
}

@Configuration
@Import(HelloImportSelector.class)
public static class SelectorConfig {

}
public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"hello.selector.HelloConfig"};
    }
}

@Import에 바로 class 이름 넣는것은 바로 인식하는것이지만 ImportSelector 구현체를 넣으면 selectImport 메소드를 실행 시켜서 나오는 String 기준으로 classpath에서 찾아보게 된다.

ImportCandidates#load 에서 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 해당 파일에 적혀있는것을 다 읽어 오게 된다.

 

728x90

'WEB > Spring Boot' 카테고리의 다른 글

액츄에이터  (0) 2023.05.16
외부설정과 프로필  (0) 2023.05.14
스프링 부트 스타터와 라이브러리 관리  (0) 2023.05.11
스프링 부트와 내장 톰캣  (0) 2023.05.10
웹 서버와 서블릿 컨테이너  (0) 2023.05.08