본문 바로가기
Spring|Spring-boot

Spring Boot 좀 더 자세히.

by oncerun 2023. 2. 4.
반응형

도메인이 정해지면 개발자는 기술 스택에 대한 수많은 결정을 진행한다. 

 

우리는 언어와, 빌드툴을 선택하고 어떤 플랫폼인지 확인한다.

 

1. 웹인가?, 웹이면 서블릿 기반인가 리액티브 기반인가?

2. 데이터접근기술이 필요하다면  하나의 데이터접근 방식만 사용할 것인가?  아니면 여러 데이터접근 방식을 사용하게 될 것인지. 

3. 보안은 어떻게 할 것이며

4. 캐싱을 적용해야 하는지

5. API 문서는 어떻게 할 것인지

6. 클라우드 관련 기술을 무엇을 사용할 것인지 

7. 어떤 아키텍처를 따를 것인지

8. 테스트 코드의 비율은 어떻게 맞출 것인지

9. 타 시스템과의 의사소통을 위해 이벤트가 필요하다면 어느 정도의 수준으로 처리될 것인지? 별도의 메시징 중개인을 사용해야 하는지

10. 코드는 어디에 저장할 것인지

11. 팀원과의 의사소통은 어떻게 할 것인지

 

.. 등등 수많은 고민을 통해 기술을 선택했다면 이제 스프링 부트 프로젝트를 만든다. 

 

이 과정에서 스프링 부트는 템플릿을 제공한다. 

 

이 과정에서 선택한 기술 스택들의 클래스/라이브러리들이 스프링 부트 버전에 적합한 버전으로 포함이 된다. 

 

이제 스프링 부트의 자동 구성 설정이 시작된다.

 

@AutoConfiguration

스프링 부트가 제공하는 AutoConfiguration.imports 파일을 기반으로 수많은 자동 구성의 후보들을 로딩하는 과정을 거친다. 

 

@Conditional

이러한 후보들 중에서 기술 스택을 선택했던 실제 dependencies들과 추가적인 프로퍼티 설정 정보를 토대로 매칭해야 할 조건을 확인하는 과정을 거친다.

 

default infra bean  

선택된 인프라 빈들의 기본 상태를 가지고 만들어진다. 

 

application.properties

외부 환경을 추상화한 Envrioment로 다양한 외부 설정값을 읽어 온다. 

default 값을 사용자가 정의한 설정 값으로 주입하는 과정을 진행한다. 

 

 여기까지 기술스택을 스프링 부트가 자동 구성을 통해 만들어주는 과정이다. 

추가적으로 사용자가 정의한 Bean을 구성하는 과정도 진행해야 한다.

 

@ComponentScan

ComponentScan을 통해 사용자가 작성한 빈을 등록하는 과정을 진행한다.

 

@Configuration

스프링의 자동 구성 정보를 그대로 사용해도 되지만 이를 커스텀하여 스프링 부트 자동 구성의 인프라 빈을 대체할 수도 있다.  

스프링 부트는 @Configuration이 붙은 클래스를 확인하고 조건에 따라 사용자 정의한 인프라 빈으로 교체하는 과정을 거친다. 

또는 Querydsl과 같이 서드  파티 라이브러리를 손쉽게 사용하기 위해 추가적으로 인프라 빈을 등록하는 과정도 있다.

 

 

for toby

나중에 스프링 부트의 대한 내막이 궁금하다면 이 그림을 토대로 스프링 부트에서 어떠한 일들이 일어나는지 다시 한번 

상기시켜 보자.

 

 

 

 

개발할 때 스프링부트의 애노테이션을 타고 들어가는 경우가 생각보다 많았다. 

 

나는 Test 코드를 작성할 때 이러한 궁금증을 해소하기 위해 애노테이션을 타고 들어간 적이 많았다. 

 

다음과 같은 어노테이션을 본 적이 있을 것이다.

  • @JdbcTest
  • @DataJpaTest
  • @WebMvcTest
  • @AutoConfigureTestDatabase
  • @SpringbootTest
  • @AutoConfigurationMockMvc
  • @AutoConfigurationRestDocs

 

예를 들어 이러한 애노테이션을 설명하는 블로그에서는 간단하게 설명한다. 

 

"@WebMvcTest 어노테이션을 사용하면 MVC 관련 빈들만 콘텍스트에 로딩하여 테스트 시간이 짧아진다."

 

그래서 보통 어떤 빈들을 가져오는지 궁금해서 애노테이션을 타고 들어가면 주석으로 잘 설명되어 있는 경우도 있고 그렇지 않은 경우도 있다.

@WebMvcTest

보면 Spring Security와 MockMvc 기본 자동 구성으로 사용하며, @Controller, @ControllerAdvice. Converter와 같은 Bean들을 로딩하며 @Component, @Service, @Repository는 빈으로 등록하지 않는다.라고 쓰여있다. 

 

사실 강의를 들으면서 더 좋았던 것은 스프링부트 어노테이션의 컨벤션을 알게 되어서 좋은 점도 있다. 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(WebMvcTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(WebMvcTypeExcludeFilter.class)
@AutoConfigureCache
@AutoConfigureWebMvc
@AutoConfigureMockMvc
@ImportAutoConfiguration
public @interface WebMvcTest {...}

 

메타 어노테이션이 범벅되어 있어 각각이 무슨 역할을 하는지 한눈에 파악하기가 힘들 때가 많았다. 

 

하지만 AutoConfiguration의 prefix는 자동 구성설정을 위한 클래스이기 때문에 이 어노테이션 안에는 default 외부 속성 설정값이 있고 다양한 조건으로 사용되는 어노테이션이라는 것을 알게 된다. 

 

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.mockmvc")
public @interface AutoConfigureMockMvc

더 재밌는 것은 Import라는 prefix가 있다면 AutoConfigurationImportSelector을 통해 imports 파일을 읽어오는구나

 

뭐 이런 걸 한눈에 파악할 수 있게 되었다. 

 

별것이 아닌 것 같지만 실제 이를 위해 애노테이션을 찾아서 들어가서 설명이 없으면 공식문서를 찾고 자료 서칭을 하는 시간보다 타고 들어가서 실제 imports 파일을 보고 무엇이 빈으로 등록되는 후보인지 어떻게 조건으로 검사가 되는지 무슨 속성이 기본 값이고 어떠한 속성들이 있는지 코드로 확인하는 것이 매우 시간적으로 절약적이라고 생각합니다.

 

 

이렇게 코드레벨에서 스프링 구성을 찾아보는 방법도 상당히 좋은 방법 중 하나인 것 같습니다.

  • @AutoConfiguration
  • @Conditional
  • @Bean
  • ...

자동 구성 분석

 

 

1. debug 옵션.

 

-Ddebug, --debug

 

imports 파일 후보 중 Condition을 확인한 내용을 로그로 보여줍니다. 꽤 많은 정보를 전달하기 때문에 자세하지만 눈이 아픕니다. 

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

   AopAutoConfiguration.ClassProxyingConfiguration matched:
      - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)

   GenericCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type (CacheCondition)

   JmxAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.jmx.export.MBeanExporter' (OnClassCondition)
      - @ConditionalOnProperty (spring.jmx.enabled=true) matched (OnPropertyCondition)

   JmxAutoConfiguration#mbeanExporter matched:
      - @ConditionalOnMissingBean (types: org.springframework.jmx.export.MBeanExporter; SearchStrategy: current) did not find any beans (OnBeanCondition)

   JmxAutoConfiguration#mbeanServer matched:
      - @ConditionalOnMissingBean (types: javax.management.MBeanServer; SearchStrategy: all) did not find any beans (OnBeanCondition)

   JmxAutoConfiguration#objectNamingStrategy matched:
      - @ConditionalOnMissingBean (types: org.springframework.jmx.export.naming.ObjectNamingStrategy; SearchStrategy: current) did not find any beans (OnBeanCondition)

   LifecycleAutoConfiguration#defaultLifecycleProcessor matched:
      - @ConditionalOnMissingBean (names: lifecycleProcessor; SearchStrategy: current) did not find any beans (OnBeanCondition)

   NoOpCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration automatic cache type (CacheCondition)

   PropertyPlaceholderAutoConfiguration#propertySourcesPlaceholderConfigurer matched:
      - @ConditionalOnMissingBean (types: org.springframework.context.support.PropertySourcesPlaceholderConfigurer; SearchStrategy: current) did not find any beans (OnBeanCondition)

   SimpleCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)

   SpringApplicationAdminJmxAutoConfiguration matched:
      - @ConditionalOnProperty (spring.application.admin.enabled=true) matched (OnPropertyCondition)

   SpringApplicationAdminJmxAutoConfiguration#springApplicationAdminRegistrar matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar; SearchStrategy: all) did not find any beans (OnBeanCondition)

   SqlInitializationAutoConfiguration matched:
      - @ConditionalOnProperty (spring.sql.init.enabled) matched (OnPropertyCondition)
      - NoneNestedConditions 0 matched 1 did not; NestedCondition on SqlInitializationAutoConfiguration.SqlInitializationModeCondition.ModeIsNever @ConditionalOnProperty (spring.sql.init.mode=never) did not find property 'mode' (SqlInitializationAutoConfiguration.SqlInitializationModeCondition)

   TaskExecutionAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' (OnClassCondition)

   TaskExecutionAutoConfiguration#applicationTaskExecutor matched:
      - @ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) did not find any beans (OnBeanCondition)

   TaskExecutionAutoConfiguration#taskExecutorBuilder matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.task.TaskExecutorBuilder; SearchStrategy: all) did not find any beans (OnBeanCondition)

   TaskSchedulingAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler' (OnClassCondition)

   TaskSchedulingAutoConfiguration#taskSchedulerBuilder matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.task.TaskSchedulerBuilder; SearchStrategy: all) did not find any beans (OnBeanCondition)

 

 

조건이 매칭되면서 어떤 자동구성이 발생했는지 확인할 수 있다. 하지만 로그는 매우 길다. 따라서 이 부분은 등록된 부분만 가져온 것이다. 

 

 

2. ConditionEvaluationReport

 

자동 구성 클래스의 Condition 결과를 커스텀하여 볼 수 있다. 이는 자동적으로 등록되는 빈들 중 하나이다.

@Bean
ApplicationRunner run(ConditionEvaluationReport report) {
    return args -> {
        report.getConditionAndOutcomesBySource().entrySet().stream()
                .filter(co -> co.getValue().isFullMatch())
                .filter(co -> !co.getKey().contains("Jmx"))
                .forEach(co ->
                        {
                            System.out.println(co.getKey());
                            co.getValue().forEach(c -> {
                                System.out.println("\t" + c.getOutcome());
                            });
                            System.out.println();
                        }

                );

    };
}
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
	@ConditionalOnProperty (spring.aop.auto=true) matched

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
	@ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice'
	@ConditionalOnProperty (spring.aop.proxy-target-class=true) matched

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type

org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration automatic cache type

org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type

org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration#defaultLifecycleProcessor
	@ConditionalOnMissingBean (names: lifecycleProcessor; SearchStrategy: current) did not find any beans

org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration#propertySourcesPlaceholderConfigurer
	@ConditionalOnMissingBean (types: org.springframework.context.support.PropertySourcesPlaceholderConfigurer; SearchStrategy: current) did not find any beans

org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
	@ConditionalOnProperty (spring.sql.init.enabled) matched
	NoneNestedConditions 0 matched 1 did not; NestedCondition on SqlInitializationAutoConfiguration.SqlInitializationModeCondition.ModeIsNever @ConditionalOnProperty (spring.sql.init.mode=never) did not find property 'mode'

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
	@ConditionalOnClass found required class 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor'

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration#applicationTaskExecutor
	@ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) did not find any beans

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration#taskExecutorBuilder
	@ConditionalOnMissingBean (types: org.springframework.boot.task.TaskExecutorBuilder; SearchStrategy: all) did not find any beans

org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
	@ConditionalOnClass found required class 'org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler'

org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration#taskSchedulerBuilder
	@ConditionalOnMissingBean (types: org.springframework.boot.task.TaskSchedulerBuilder; SearchStrategy: all) did not find any beans


Process finished with exit code 0

 

Jmx를 제외하고 조건 매치에 성공한 것들을 별도로 커스텀하여 출력하였다. 

 

이 중에서 AopAutoConfiguration을 살펴보자.

@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration

 

matchIfMissing = true을 통해 별다른 설정하지 않아도 조건이 통과되도록 되어있는 것을 확인할 수 있다. 

 

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {

   @Configuration(proxyBeanMethods = false)
   @EnableAspectJAutoProxy(proxyTargetClass = false)
   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
   static class JdkDynamicAutoProxyConfiguration {

   }

   @Configuration(proxyBeanMethods = false)
   @EnableAspectJAutoProxy(proxyTargetClass = true)
   @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
         matchIfMissing = true)
   static class CglibAutoProxyConfiguration {

   }

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
      matchIfMissing = true)
static class ClassProxyingConfiguration {

   @Bean
   static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
      return (beanFactory) -> {
         if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
         }
      };
   }

}

이를 확인해 보면 Advice.class가 존재하면 AspectJAutoProxyingConfiguration이 자동 구성정보로 선택되고 

CglibAutoProxyConfiguration이 default 값으로 설정되는군요.

 

하지만 org.aspectj.weaver.Advice 클래스가 존재하지 않으면 ClassProxingConfiguration이 자동 구성정보로 설정되고 

BeanFactoryPostProcessor라는 후처리기가 등록되는 것을 볼 수 있습니다.

 


캐시 관련된 부분도 살펴볼 수 있습니다.

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type

org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration automatic cache type

org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
	Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type

 

기본적으로 SimpleCacheConfiguration이 등록되는군요. 따라서 로컬 캐시를 이용하는 경우 별다른 라이브러리를 의존하지 않아도 됩니다. 어차피 core에 들어있으니까요.

 

자동 구성을 통해 빈들이 등록되었다면 이는 스프링 부트에서 제시하는 기술입니다.

 

이를 보고 우리는 이것이 무엇인지? 확인해야 합니다. 

 

이를 위해선 SpringBoot Reference에서 관련 내용을 찾아볼 수 있습니다. 

 

공식 문서에는 이러한 빈을 왜 등록했고 어떻게 사용하는지 등등의 예제와 설명이 나와있을 확률이 높습니다. 

 

 

https://docs.spring.io/spring-boot/docs/2.7.8/reference/htmlsingle/#io

 

추가적으로 core에 동시성 관련한 자동 구성정보도 default로 등록됩니다.

 

@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@AutoConfiguration
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {

   /**
    * Bean name of the application {@link TaskExecutor}.
    */
   public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

   @Bean
   @ConditionalOnMissingBean
   public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
         ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
         ObjectProvider<TaskDecorator> taskDecorator) {
      TaskExecutionProperties.Pool pool = properties.getPool();
      TaskExecutorBuilder builder = new TaskExecutorBuilder();
      builder = builder.queueCapacity(pool.getQueueCapacity());
      builder = builder.corePoolSize(pool.getCoreSize());
      builder = builder.maxPoolSize(pool.getMaxSize());
      builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
      builder = builder.keepAlive(pool.getKeepAlive());
      Shutdown shutdown = properties.getShutdown();
      builder = builder.awaitTermination(shutdown.isAwaitTermination());
      builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
      builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
      builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
      builder = builder.taskDecorator(taskDecorator.getIfUnique());
      return builder;
   }

   @Lazy
   @Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
         AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
   @ConditionalOnMissingBean(Executor.class)
   public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
      return builder.build();
   }

}

 

빌더 빈이 따로 존재하는 것도 흥미롭습니다. 

 

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties

 

해당 클래스에서 task관련 프로퍼티들이 무엇이 있는지 확인할 수 있습니다.

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {

   private final Pool pool = new Pool();

   private final Shutdown shutdown = new Shutdown();

   /**
    * Prefix to use for the names of newly created threads.
    */
   private String threadNamePrefix = "task-";

   public Pool getPool() {
      return this.pool;
   }

   public Shutdown getShutdown() {
      return this.shutdown;
   }

   public String getThreadNamePrefix() {
      return this.threadNamePrefix;
   }

   public void setThreadNamePrefix(String threadNamePrefix) {
      this.threadNamePrefix = threadNamePrefix;
   }

   public static class Pool {

      /**
       * Queue capacity. An unbounded capacity does not increase the pool and therefore
       * ignores the "max-size" property.
       */
      private int queueCapacity = Integer.MAX_VALUE;

      /**
       * Core number of threads.
       */
      private int coreSize = 8;

      /**
       * Maximum allowed number of threads. If tasks are filling up the queue, the pool
       * can expand up to that size to accommodate the load. Ignored if the queue is
       * unbounded.
       */
      private int maxSize = Integer.MAX_VALUE;

      /**
       * Whether core threads are allowed to time out. This enables dynamic growing and
       * shrinking of the pool.
       */
      private boolean allowCoreThreadTimeout = true;

      /**
       * Time limit for which threads may remain idle before being terminated.
       */
      private Duration keepAlive = Duration.ofSeconds(60);

실제 이러한 설정에 대한 정보를 찾기가 매우 어려웠는데 다음과 같이 설정에 대한 기본값과 설명에 대해 참고할 수 있을 것 같습니다. 

 

 

 

3. ListableBeanFactory

이를 통해 실제로 등록된 빈을 확인할 수 있습니다. 스프링 컨테이너가 제공하는 확장 기능 중 하나로 보입니다. 

 

 

 

자동 구성을 통해 등록되는 빈들에 대해 알아보았다. 

 

이를 통해 하나의 주의사항을 깨달았다면 좋겠다.

스프링 자동 구성정보로 등록된 인프라 빈을 가볍게 재정의해서 빈으로 등록해서 사용하는 것이 생각보다 위험할 수 있다는 점이다. 

 

하나의 자동 구성정보로 등록된 빈을 들어가면서 코드를 보다 보면 생각보다 매우 복잡하게 default 설정과 의존관계들이 얽혀있다는 것을 알 수 있기 때문이다. 

 

예를 들어 spring-boot stater web을 dependencies 했다면 ObjectMapper가 기본적으로 빈으로 등록되어 있다. 

@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
   return builder.createXmlMapper(false).build();
}


builder를 통해 생성하는 것을 확인할 수 있다. 

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
      List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
   Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
   builder.applicationContext(applicationContext);
   customize(builder, customizers);
   return builder;
}

 

이 중 Jackson2 ObjectMapperBuilderCustomizer라는 타입이 존재한다. 

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
@EnableConfigurationProperties(JacksonProperties.class)
static class Jackson2ObjectMapperBuilderCustomizerConfiguration 

 

여기에는 JacksonProperties 클래스가 존재한다. 

@ConfigurationProperties(prefix = "spring.jackson")
public class JacksonProperties 

 

설정가능한 프로퍼티들이 나열되어 있다. 

 

이러한 설정 프로퍼티를 통해 빌더를 생성하고 최종적으로 ObjectMapper를 빈으로 등록하고 생성하는 과정이 있다는 것이다. 

 

이를 단순히 new ObjectMapper로 사용하게 되었을 때와 많은 차이가 있다는 것을 확인할 수 있다.

 

여기서 그럼 중요한 기억할 점이 생기는 것 같다.

 

자동 구성을 통해 설정된 빈들이 무엇이 있는지 알아야 한다. 

 

 자동 구성되는 빈을 알고 있다는 것은 스프링 부트가 제공하는 기능을 제대로 사용하고 커스터마이징을 할 수 있다는 것이다. 

실수를 통해 인프라 빈을 어이없는 설정을 통해 재정의 했다면 해당 빈을 주입받는 빈들도 고장 날 것이다. 

따라서 자신의 프로젝트에 자동 구성되는 빈들은 어떤 것들이 있나 전부는 아니더라도 변경 시 한번 확인해 보는 것은 어떨까?

 

사용하고 있는데 잘 모른다면 공식문서나 코드 레벨에서  어떤 일을 하고 어떻게 구성되고 변경을 하기 위해 어떻게 할 수 있는지 확인해 보는 것도 좋다고 생각한다.

 

 

 

스프링 부트는 생각보다 방대한 것 같다.

 

간단한 프로젝트를 독립형 애플리케이션으로 별다른 설정 없이 실행하는 것은 빙산의 일각에 불과했고

이 밑에서 스프링 부트가 자동으로 해주는 것들은 생각보다 많았고, 이를 제대로 사용하기 위해선 꽤 많은 노력을 해야 함을 알게 되었다. 

 

그래도 이러한 내용에 대해 알고 있기 때문에 필요할 때마다 관련 빈을 찾고 자동 구성으로 등록되는 빈을 분석하고 공식문서를 통해 내가 현재 필요한 정보를 더욱 빠르게 확인할 수 있을 것이라 믿는다. 

 

 

반응형

댓글