Spring

[Spring Boot] 01. Spring Boot Core (2) - 자동 구성과 외부 구성

Joo.v7 2024. 11. 6. 21:01

5. Spring Boot 코드 살펴보기

  • Spring Boot 동작 원리 알기 - 자동 생성 코드 살펴보기

6. 자동 구성과 조건

  • maven에 라이브러리 추가하기
  • Auto Configuration(자동 구성) 동작 원리
  • 라이브러리 추가시 자동 구성
  • Auto Configuration 에서 제외
  • @Conditional
  • @ConditionalOnXXX

7. 외부 구성

  • Externalized Configuration 사용 (바인딩)
  • Spring Profile

5. Spring Boot 코드 살펴보기

Spring Boot 동작 원리 알기

 (1) 자동 생성 코드 살펴보기 - pom.xml

  • spring-boot-starter-parent
    • spring-boot 버전별로 지원하는 라이브러리 의존성 목록.
    • spring-boot 버전을 업그레이드하면 라이브러리 의존성도 모두 자동 업그레이드 된다. (자동으로 버전 관리 해줌)
  • spring-boot-starter
    • core, context, logging 등 기본 스프링 기능을 담당하는 라이브러리.
    • 자주 사용하는 라이브러리를 자동으로 추가해준다.
  • spring-boot-maven-plugin
    • spring boot의 실행을 편하게 하기 위해서, build 할 때 간섭하는 플러그인.
더보기
더보기

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>
    
    
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>3.2.5</version>
</dependency>
    

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

 

(2) 자동 생성 코드 살펴보기 - Main.java

@SpringBootApplication

 - main() 메소드를 포함하는 클래스에 달리며, spring boot에서 매우 중요한 역할.

  • @SpringBootConfiguration
    • Spring Boot의 특수한 설정 클래스임을 나타내는 annotation.
  • @ComponentScan
    • Spring에게 지정된 패키지와 그 하위 패키지들을 스캔하도록 지시하는 annotation.
    • Spring이 @Component를 자동으로 찾아서 Spring ApplicationContext에 Bean으로 등록할 위치를 지정.
  • @EnableAutoConfiguration
    • Spring Bean의 자동 구성 기능을 활성화.
/*
Spring boot 프로젝트가 실행되면, SpringApplication.run()이 실행되고
실행을 따라가보면 applicationContext 생성 및 초기화 / 코드 해석 / 빈 초기화 등 여러 동작 수행.
*/

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

 

 

  • SpringApplication.java
    • Spring boot 프로젝트가 실행되면, SpringApplication.run()이 실행되고, 실행을 따라가보면 applicationContext 생성 및 초기화, 코드 해석, 빈 초기화 등 여러 동작을 수행한다.
더보기
더보기

SpringApplication.java

 

1. SpringApplication.run() 호출할 때, 자기 자신을 인자(argument)로 넘기고 sources에 추가한다.

SpringApplication.run(Main.class, args)

 

2. applicationContext 생성.

SpringApplication.run()
SpringApplication.createApplicationContext()
DefaultApplicationContextFactory.create()
DefaultApplicationContextFactory.createDefaultApplicationContext()

 

3. DefaultApplicationContextFactory.java

private ConfigurableApplicationContext createDefaultApplicationContext() {
    if (!AotDetector.useGeneratedArtifacts()) {
        return new AnnotationConfigApplicationContext();
    }
    return new GenericApplicationContext();
}

 

4. Bean Factory 생성.

SpringApplication.run()
SpringApplication.refreshContext()
SpringApplication.refresh()
AbstractApplicationContext.refresh()
AbstractApplicationContext.obtainFreshBeanFactory()
AbstractRefreshableApplicationContext.refreshBeanFactory()

---------------------------------------------------------------------------------------------------------------------

// AbstractRefreshableApplicationContext.java
{
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }
}

 

5. 코드(annotation) 해석

SpringApplication.run()
SpringApplication.refreshContext()
SpringApplication.refresh()
AbstractApplicationContext.refresh()
AbstractApplicationContext.invokeBeanFactoryPostProcessors()
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()
ConfigurationClassPostProcessor.processConfigBeanDefinitions()
ConfigurationClassParser.parse()
ConfigurationClassParser.processConfigurationClass()
ConfigurationClassParser.doProcessConfigurationClass()

---------------------------------------------------------------------------------------------------------------------

// ConfigurationClassParser.java
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
    processMemberClasses(configClass, sourceClass, filter);
    }
    
    ...
    
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
				MergedAnnotation::isDirectlyPresent);
}

 

6. Bean 생성 (초기화)

SpringApplication.run()
SpringApplication.refreshContext()
SpringApplication.refresh()
AbstractApplicationContext.refresh()
AbstractApplicationContext.finishBeanFactoryInitialization()
DefaultListableBeanFactory.preInstantiateSingletons()
AbstractBeanFactory.getBean()
AbstractBeanFactory.doGetBean()
AbstractAutowireCapableBeanFactory.createBean()
AbstractAutowireCapableBeanFactory.doCreateBean()

---------------------------------------------------------------------------------------------------------------------

// AbstractAutowireCapableBeanFactory.java
{
...
try {
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    if (logger.isTraceEnabled()) {
        logger.trace("Finished creating instance of bean '" + beanName + "'");
    }
        return beanInstance;
    }
}
...

 

@ComponentScan

  • Spring Framework에서 자동으로 StereoType annotation이 붙은 클래스들을 검색하고 Bean으로 등록하는데 사용.
    (StereoType annotation: @Component, @Service, @Repository, @Controller ...)
  • 기본적으로, 선언된 클래스가 속한 패키지와 그 하위 패키지들을 검색 범위로 한다.
  • 이걸 통해서 Spring Boot 프로젝트가 코드를 어디까지 해석할지 제어할 수 있다.
  • 주요 옵션
    • basePackages: 특정 패키지를 스캔의 시작점으로 지정. 여러 패키지를 지정할 수 있다.
    • basePackageClasses: 스캔 시작점으로 사용할 클래스를 지정한다. 여러 클래스 지정 가능
    • includeFilters와 excludeFilters: 특정 조건에 맞는 컴포넌트를 포함시키거나 제외시킬 때 사용.
    • useDefaultFilters: 기본적으로 활성화되어 있는 스테레오타입 어노테이션을 기반으로 한 필터 사용 여부를 설정. 기본값은 true (@Component, @Service, @Repository, @Controller 등)

 

@Autowired

  • Spring Framework에서 DI를 위해 사용되는 annotation. (Bean 주입에 사용)
  • 이걸 사용하면 Spring 컨테이너가 자동으로 지정된 타입의 Bean을 해당 필드, 메소드, 생성자에 주입.
  • 주입할 Bean이 없거나 모호할 때 오류 발생.
  • 옵션 - required: 빈의 null 여부에 대한 설정, 기본값은 true다.
  • Spring Boot는 @Autowired를 보고, Spring Bean이 적절한 위치에 주입될 수 있도록 설정한다.

 

요약

더보기
더보기

Spring Boot의 기본 설정에 대한 동작.

  1. Main 클래스를 인자로 넘기기 때문에 해당 클래스를 해석한다.
  2. Spring Boot는 @ComponentScan을 통해서 코드를 해석한다.
  3. Main 클래스는 기본적으로 @SpringBootApplication를 가지고 있고, 얘는 @ComponentScan을 가지고 있다.
  4. @ComponentScan의 옵션에 따라서 Spring Boot는 전체 패키지에서 클래스들의 코드를 해석한다.
  5. 해석하는 도중에 @Component, @Autowired 등 annotation이 달린 클래스를 만나면 다르게 동작하도록 내부적으로 설정.

버전 관리

  • Spring BOM(Bill of Materials): 여러 라이브러리와 의존성 간의 버전 충돌 문제를 해결.

SpringApplication

  • Spring은 SpringApplication.run()을 통해서 실행되고, 이 코드를 보면 applicationContext, Bean 생성 등 많은 작업을 함

@SpringBootApplication

  • 이 annotation 내부의 또 다른 annotation들을 통해 Spring Boot에서 많은 일을 하는걸 알 수 있다.

@ComponentScan

  • Spring에게 지정된 패키지와 그 하위 패키지들을 스캔하도록 지시하는 어노테이션. 스캔을 통해 Bean을 생성한다.

@Component

  • @ComponentScan으로 인해서 찾아지는 클래스로, Spring에서 Bean으로 등록한다.

@Autowired

  • 생성한 Bean을 적절한 위치에 넣기 위해 사용되는 annotation.

 


6. 자동 구성과 조건

maven에 라이브러리 추가하기

  • Spring Boot를 이용해서 Web Application을 동작하게 하고 싶다.
  • pom.xml에 web 의존성 추가하면, 자동으로 Web Application으로 동작함. -> 자동 구성(Auto Configuration)
<!-- pom.xml에 Web 의존성 추가 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 

Auto Configuration(자동 구성) 동작 원리

 @EnableAutoConfiguration

  • 해당 annotation을 추가하면 Application은 자동으로 계속 특정 경로의 클래스를 찾으려고 한다.
  • Spring Boot에서 자동 구성(Auto Configuration)을 활성화하기 위해 사용되는 annotation.
더보기
더보기

@EnableAutoConfiguration

  • @Import가 있을 경우, 지정된 클래스 Application의 설정 컨텍스트에 명시적으로 포함시킨다.
  • AutoConfigurationImportSelector는 "META-INF/spring/%s.imports" 경로의 파일을 해석해서 해당하는 클래스를 로드하려고 시도함.
@Import(AutoConfigurationImportSelector.class)
ConfigurationClassParser.doProcessConfigurationClass()
ConfigurationClassParser.processImports()
AutoConfigurationImportSelector.selectImports()
AutoConfigurationImportSelector.selectImports()
AutoConfigurationImportSelector.getCandidateConfigurations()
ImportCandidates.load()
ImportCandidates.LOCATION

 

ImportCandidates.java

private static final String LOCATION = "META-INF/spring/%s.imports";
{
	protected List<String> getExcludeAutoConfigurationsProperty() {
		Environment environment = getEnvironment();
		if (environment == null) {
			return Collections.emptyList();
		}
		if (environment instanceof ConfigurableEnvironment) {
			Binder binder = Binder.get(environment);
			return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
				.map(Arrays::asList)
				.orElse(Collections.emptyList());
		}
		String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
		return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
	}
}

 

org.springframework.boot.autoconfigure.AutoConfiguration.imports

...
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
...

 

WebMvcAutoConfiguration.java

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {
...
}

 

위 파일들의 상위에 @ConditionalXXX annotation이 붙어있는데, 이는 특정 조건에서 동작한다는 뜻이다.

ex) @ConditionalOnClass({Servlet.class, DispatcherServlet.class. ... })는 이 클래스들이 존재할 때만 동작한다는 뜻이다.

 

라이브러리 추가시 자동 구성

  1. pom.xml에 dependency를 추가하면, maven은 해당 라이브러리 파일을 프로젝트에 추가한다.
  2. 라이브러리가 추가되면서 특정 조건이 만족되어 작동하지 않던 빈들이 동작한다.

Auto Configuration 에서 제외

  • @EnableAutoConfiguration의 exclude를 설정한다.
    (@SpringBootApplication을 사용한 경우도 동일한 방법으로 제외 가능)
@SpringBootApplication(exclude = RedisAutoConfiguration.class)
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

 

 

참고: 2024.11.09 - [NHN Java 백엔드 8기/Spring Boot] - [Spring Boot] 서비스 추상화 (Portable Service Abstraction)

 

[Spring Boot] 서비스 추상화 (Portable Service Abstraction)

1. 서비스 추상화 (PSA, Poratble Service Abstraction)개발자가 특정 환경이나 기술에 종속되지 않고, 일관된 방식으로 서비스를 사용할 수 있도록 추상화를 제공.Interface로 사용하고, Interface의 구현부(impl

lightningtech.tistory.com

 

@Conditional

  • 설정된 모든 Condition 인터페이스의 조건이 TRUE인 경우 동작.
  • 특정 조건에서만 Bean이 동작하길 원할 때 사용.
  • Auto Configuraton(자동 구성)도 이 기능을 이용해서 동작.

 

@ConditionalOnXXX

  • Spring-boot가 제공하는 @Conditional의 확장.
구분 내용 비고
@ConditionalOnWebApplication 프로젝트가 웹 애플리케이션이면 설정 동작 -
@ConditionalOnBean 해당 Bean 이 Spring Context 에 존재하면 동작 Auto configuration only
@ConditionalOnMissingBean 해당 Bean 이 Spring Context 에 존재하지 않으면 동작 Auto configuration only
@ConditionalOnClass 해당 클래스가 존재하면 자동 설정 등록 -
@ConditionalOnMissingClass 해당 클래스가 존재하지 않으면 자동 설정 등록 -
@ConditionalOnResource 자원(file 등)이 존재하면 동작 -
@ConditionalOnProperty 특정 프로퍼티가 존재하면 동작 -
@ConditionalOnJava JVM 버전에 따라 동작 여부 결정 -
@ConditionalOnWarDeployment 전통적인 WAR 배포 방식에서만 동작 -
@ConditionalOnExpression SpEL (Spring Expression Language)의 결과에 따라 동작 여부 결정 -

 

* 주의

Spring은 Bean을 등록 할 때, 패키지를 위에서부터 읽으면서 등록하므로, 빈 등록 시 우리가 원하는 대로 동작하지 않을 수 있다. 따라서 주의해서 사용이 필요하다


참고

2024.11.08 - [NHN Java 백엔드 8기/Spring Boot] - [Spring Boot] @ConfigurationProperties 활성화시 @Profile 무시 에러

 

[Spring Boot] @ConfigurationProperties 활성화시 @Profile 무시 에러

1. 에러 발생의도문제문제 원인 파악문제가 발생한 코드2. @ConfigurationProperties란?Externalized Configuration (외부 구성) Externalized Configuration 사용 (바인딩)@ConfigurationProperties3. 해결Bean 등록 방법 2가지@Co

lightningtech.tistory.com

 


7. 외부 구성 (Externalized Configuration)

  • Spring-boot는 같은 소스 코드로 여러 환경에서 동작할 수 있도록 외부화 설정을 제공한다.
  • java properties, YAML, 환경변수, 실행 인자로 설정 가능하다.
  • 전체 프로젝트의 설정은 .properties, .yaml 중 하나만 사용하는 것을 권장.
  • 같은 곳에 application.propreties, application.yaml 이 동시에 존재하면 application.propreties 가 우선한다.
  • 외부 구성만 수정했지만, application의 동작이 바뀌는 것도 자동 구성(Auto Configuration)의 일부이다.

 

Externalized Configuration 사용 (바인딩)

  • Spring Boot는 설정값을 바인딩 하기 위한 2가지 방법을 제공.

(1) @Value바인딩

  • 속성값(properties) @Value annotation으로 바인딩하여 사용.
  • Spel 표현식을 지원.
@Component
public class AppStartupRunner implements ApplicationRunner {

    @Value("${greeting.english}")
    private String english;

    @Value("${greeting.korean}")
    private String korean;

    @Override
    public void run(ApplicationArguments args) {
        System.out.println(english);
        System.out.println(korean);
    }
}

 

(2) @ConfigurationProperties 바인딩

  1. properties를 이 annotation으로 바인딩하여 사용.
  2. 이 annotation으로 설정된 클래스 Dependency Injection(의존성 주입)로 참조하여 사용
    (DI 3가지 방법: 생성자/필드/Setter 주입)
  • 사용법
    • @ConfigurationProperties 클래스 선언
    • @ConfigurationProperties 활성화(2가지 방법)
    • 활성화가 되면서 Bean 등록됨. 
      • @EnableConfigurationProperties("GreetingProperties.class")
        (활성화가 필요한 클래스를 직접 등록)
      • @ConfigurationPropertiesScan
        (@ComponentScan과 비슷하게 패키지 단위로 스캔 -> 1번 방법이 너무 길어지는 경우 사용)
/* @ConfigurationProperties 클래스 선언 */
@AllArgsConstructor
@Getter
@ConfigurationProperties("greeting")
public class GreetingProperties {
    private String english;
    private String korean;
}

/* @ConfigurationProperties 활성화 */
// 방법 1
@EnableConfigurationProperties(GreetingProperties.class)

// 방법 2
@ConfigurationPropertiesScan

 

Spring Profile

  • 프로그램을 띄우는 환경마다 동작을 다르게 하고 싶을 때 사용.
  • 환경마다 다른 설정파일을 참조할 수 있고, 이를 Profile이라 부른다.
  • spring.profiles.active 실행 인자로, 프로필 지정 설정 파일의 로딩 여부가 결정된다.
  • ex) spring.profiles.active=prod 는 application.properties 와 application-prod.properties 를 모두 로딩한다.
  • 프로필은 여러 개 지정 가능하다. (중복될 경우, 덮어쓰기(override)한다)

Profile에 따라서 다른 Bean이 동작하게 하기

(1) @Configuration + @Bean 

@Configuration
public class GreetingConfig {

    //TODO-1 eng profile 인 경우 빈이 활성화 된다.
    @Profile("eng")
    @Bean
    Greeting englishGreeting() {
        return new EnglishGreeting();
    }

    //TODO-2 eng profile 이 아닌 경우 빈이 활성화 된다.
    @Profile("!eng")
    @Bean
    Greeting koreanGreeting() {
        return new KoreanGreeting();
    }

}

 

(2) @Component

//TODO-3 eng profile 인 경우 빈이 활성화 된다.
@Profile("eng")
@Component
public class EnglishFarewell implements Farewell {

    @Override
    public void sayGoodBye() {
        System.out.println("good bye");
    }
}

 

 

요약

더보기
더보기

외부 구성

  • Spring Boot 코드 중간에 외부 속성 값을 바인딩 할 수 있다.
    (내부에 있는 값들을 환경변수라는 application.properties 값을 변경함으로써 바꿀 수 있다)
  • 바인딩 2가지 방법: @Value, @ConfigurationProperties

Profile

  • Spring 에서는 profile을 이용해 상황에 따라서 다른 환경 변수를 보거나, 다른 방식으로 동작하게 할 수 있다.

Spel

  • 런타임에서 객체에 대한 쿼리와 조작(querying and manipulating)을 지원하는 강력한 표현 언어.
  • @Value에서 지원함.

yaml

  • 데이터를 표현하기 위한 형식의 한 종류. (json과 같은 레이어)

 


 

출처: https://nhnacademy.dooray.com/share/pages/ABFgz8KgRv62A85x9KClUw

 

Spring Boot Core

 

nhnacademy.dooray.com