Spring

[Spring] Spring Framework Overview - 2

kahnco 2024. 8. 22. 11:40
반응형

지난 시간에 이어서 IoC Container 관련 내용들을 마무리지어보겠습니다.

지금부터 서술될 내용들은 전부 스프링 공식문서(https://docs.spring.io/spring-framework/reference/overview.html) 에서 발췌된 것임을 알립니다.


Customizing the Nature of a Bean in Spring

Spring 프레임워크는 빈의 동작을 사용자 정의할 수 있는 다양한 인터페이스와 방법들을 제공합니다. 이러한 인터페이스와 방법들을 사용하면 빈의 생명 주기(lifecycle)를 제어하고, 특정 이벤트가 발생했을 때 맞춤형 작업을 수행할 수 있습니다. 이 섹션에서는 생명 주기 콜백과 Aware 인터페이스에 대해 설명합니다.

 

Lifecycle Callbacks (생명 주기 콜백)

빈의 생명 주기에 개입하고 싶다면, InitializingBean과 DisposableBean 인터페이스를 구현할 수 있습니다. 이 인터페이스들은 빈이 초기화되거나 소멸될 때 특정 작업을 수행할 수 있게 해줍니다.

  • InitializingBean: 이 인터페이스는 afterPropertiesSet() 메서드를 제공하며, 빈이 초기화된 후 호출됩니다.
  • DisposableBean: 이 인터페이스는 destroy() 메서드를 제공하며, 빈이 소멸되기 전에 호출됩니다.

하지만 Spring과의 결합을 최소화하기 위해, 이러한 인터페이스 대신 JSR-250 표준의 @PostConstruct와 @PreDestroy 어노테이션을 사용하는 것이 권장됩니다. 이러한 어노테이션을 사용하면 코드가 Spring에 종속되지 않으며, 순수 자바 애플리케이션에서도 적용할 수 있습니다.

 

또한, XML 기반 설정에서는 init-method와 destroy-method 속성을 사용해 초기화 및 소멸 메서드를 지정할 수 있습니다.

 

Initialization Callbacks (초기화 콜백)

  • InitializingBean 인터페이스: 빈의 모든 속성이 설정된 후에 초기화 작업을 수행할 수 있습니다.
  • @PostConstruct 어노테이션: 빈이 초기화된 후에 호출됩니다.
  • XML 기반 설정: init-method 속성을 사용하여 초기화 메서드를 지정할 수 있습니다.
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>

public class ExampleBean {
    public void init() {
        // 초기화 작업 수행
    }
}

 

Destruction Callbacks (소멸 콜백)

  • DisposableBean 인터페이스: 빈이 소멸되기 전에 호출됩니다.
  • @PreDestroy 어노테이션: 빈이 소멸되기 전에 호출됩니다.
  • XML 기반 설정: destroy-method 속성을 사용하여 소멸 메서드를 지정할 수 있습니다.
<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>

public class ExampleBean {
    public void cleanup() {
        // 자원 정리 작업 수행
    }
}

 

 

Default Initialization and Destroy Methods (기본 초기화 및 소멸 메서드)

Spring은 빈의 모든 정의에 대해 기본적으로 초기화 및 소멸 메서드를 호출할 수 있도록 설정할 수 있습니다. 이를 통해 모든 빈에서 동일한 초기화 및 소멸 메서드 이름을 사용할 수 있습니다.

<beans default-init-method="init" default-destroy-method="destroy">
    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>
</beans>

 

 

Combining Lifecycle Mechanisms (생명 주기 메커니즘의 결합)

Spring 2.5 이후로는 InitializingBean, DisposableBean, 사용자 정의 초기화/소멸 메서드, 그리고 @PostConstruct, @PreDestroy 어노테이션을 함께 사용할 수 있습니다. 다만 동일한 메서드 이름이 중복되면 해당 메서드는 한 번만 실행됩니다.

 

메서드 실행 순서

  • 초기화 순서:
    1. @PostConstruct
    2. afterPropertiesSet() (from InitializingBean)
    3. 사용자 정의 초기화 메서드 (init())
  • 소멸 순서:
    1. @PreDestroy
    2. destroy() (from DisposableBean)
    3. 사용자 정의 소멸 메서드 (destroy())

 

Startup and Shutdown Callbacks (시작 및 종료 콜백)

Lifecycle 인터페이스는 백그라운드 프로세스의 시작과 종료를 관리해야 하는 객체에서 사용할 수 있는 인터페이스입니다. 이 인터페이스는 다음과 같은 메서드를 정의합니다:

  • void start(): 객체의 시작 작업을 수행합니다.
  • void stop(): 객체의 종료 작업을 수행합니다.
  • boolean isRunning(): 객체가 실행 중인지 여부를 반환합니다.

스프링 컨텍스트는 시작 또는 종료 신호를 받으면 해당 컨텍스트 내에서 정의된 모든 Lifecycle 구현체에 이 신호를 전달합니다. 이 작업은 LifecycleProcessor라는 인터페이스를 통해 수행되며, 이 인터페이스는 다음과 같은 추가 메서드를 정의합니다:

  • void onRefresh(): 컨텍스트가 새로 고침되었을 때 호출됩니다.
  • void onClose(): 컨텍스트가 닫힐 때 호출됩니다.

 

SmartLifecycle 인터페이스

SmartLifecycle 인터페이스는 Lifecycle 인터페이스의 확장판으로, 자동 시작 및 종료의 순서를 세밀하게 제어할 수 있는 기능을 제공합니다. 주요 메서드는 다음과 같습니다:

  • int getPhase(): 객체의 시작 및 종료 순서를 지정합니다. 낮은 값이 먼저 시작되고, 높은 값이 나중에 시작됩니다.
  • boolean isAutoStartup(): 컨텍스트가 새로 고침될 때 자동으로 시작할지 여부를 지정합니다.
  • void stop(Runnable callback): 비동기 종료 처리를 지원하는 메서드로, 종료 작업이 완료된 후 callback.run()을 호출해야 합니다.

SmartLifecycle 인터페이스를 구현하면 빈의 시작 및 종료 순서를 보다 정교하게 제어할 수 있으며, 비동기 종료 작업을 처리할 수 있습니다.

 

 

Shutting Down the Spring IoC Container Gracefully in Non-Web Applications (비 웹 애플리케이션에서의 IoC 컨테이너 종료)

웹 애플리케이션이 아닌 환경에서 스프링 IoC 컨테이너를 사용하는 경우, JVM의 종료 훅(shutdown hook)을 등록하여 애플리케이션 종료 시 빈의 소멸 메서드가 호출되도록 할 수 있습니다. 이를 위해 registerShutdownHook() 메서드를 호출하면 됩니다.

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
ctx.registerShutdownHook();
 

 

Thread Safety and Visibility (스레드 안전성과 가시성)

스프링 IoC 컨테이너는 싱글톤 빈의 생성 및 게시 작업을 스레드 안전하게 처리합니다. 빈이 초기화되는 동안 싱글톤 잠금을 통해 스레드 안전성을 보장하며, 다른 스레드에서도 해당 빈을 안전하게 사용할 수 있도록 가시성을 제공합니다.

 

ApplicationContextAware와 BeanNameAware

ApplicationContextAware 인터페이스를 구현하면 빈은 자신을 생성한 ApplicationContext 인스턴스에 접근할 수 있습니다. 이 인터페이스는 다음과 같은 메서드를 정의합니다:

public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

 

이를 통해 빈은 ApplicationContext를 통해 다른 빈을 프로그래밍 방식으로 조회하거나, 애플리케이션 이벤트를 게시할 수 있습니다. 하지만 이러한 방식은 스프링 프레임워크에 대한 의존성을 높이므로, 일반적으로는 사용하지 않는 것이 좋습니다.

BeanNameAware 인터페이스는 빈이 자신이 정의된 이름을 알 수 있게 합니다.

public interface BeanNameAware {
    void setBeanName(String name) throws BeansException;
}

 

이 인터페이스를 구현하면 빈의 이름이 주입되며, 초기화 콜백 메서드가 호출되기 전에 빈의 이름을 사용할 수 있습니다.

 

Other Aware Interfaces

스프링은 빈이 특정 인프라 의존성을 필요로 할 때 이를 처리하기 위한 여러 Aware 인터페이스를 제공합니다. 주요 Aware 인터페이스는 다음과 같습니다:

  • ApplicationEventPublisherAware: 애플리케이션 이벤트 게시자에 대한 참조를 제공합니다.
  • BeanClassLoaderAware: 빈 클래스를 로드하는 클래스 로더에 대한 참조를 제공합니다.
  • BeanFactoryAware: BeanFactory에 대한 참조를 제공합니다.
  • MessageSourceAware: 메시지 리졸버에 대한 참조를 제공합니다.

이러한 인터페이스들은 빈이 스프링 컨테이너의 특정 인프라를 필요로 할 때 사용되며, 주로 인프라 빈에서 사용됩니다.


빈 정의 상속 (Bean Definition Inheritance)

빈 정의 상속은 스프링 프레임워크에서 제공하는 강력한 기능으로, 자식 빈 정의가 부모 빈 정의로부터 구성 데이터를 상속받을 수 있게 해줍니다. 이를 통해 구성 정보의 중복을 줄이고, 설정을 보다 효율적으로 관리할 수 있습니다.

 

핵심 개념

 

부모 및 자식 빈 정의

  • 자식 빈은 부모 빈 정의로부터 속성 값, 생성자 인자, 스코프 등을 상속받을 수 있습니다.
  • 자식 빈은 상속받은 값을 덮어쓰거나 새로운 값을 추가할 수 있습니다.
  • 이를 통해 빈 구성에서 템플릿 역할을 하는 부모 빈을 정의하고, 자식 빈이 이를 상속받아 사용할 수 있습니다.

 

부모 및 자식 빈 정의 선언

  • 자식 빈 정의는 XML에서 parent 속성을 사용해 부모 빈을 지정하여 설정합니다.
<bean id="parentBean" abstract="true" class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="childBean" parent="parentBean">
    <property name="name" value="child"/>
    <!-- 'age' 속성은 부모로부터 상속 -->
</bean>

 

 

추상 부모 빈

  • 부모 빈은 abstract="true"로 명시적으로 설정하여 추상 빈으로 만들 수 있습니다. 이렇게 하면 해당 빈은 직접 인스턴스화될 수 없으며, 오직 템플릿으로만 사용됩니다.
  • 부모 빈에 클래스가 정의되어 있지 않다면, 추상 속성을 반드시 설정해야 합니다.
<bean id="abstractParentBean" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

 

 

클래스 상속

  • 자식 빈 정의가 클래스 정보를 명시하지 않으면, 부모 빈 정의의 클래스를 상속받습니다.
  • 자식 빈이 자체 클래스를 지정할 수도 있지만, 이 경우 부모의 속성을 수용할 수 있는 호환 가능한 클래스여야 합니다.

 

상속되는 항목

  • 부모로부터 상속:
    • class (자식이 명시하지 않은 경우)
    • scope, 생성자 인자, 속성 값, 메서드 오버라이드
  • 상속되지 않는 항목:
    • depends-on, autowire 모드, dependency 체크, singleton, lazy-init 등은 자식 빈 정의에서 설정된 값을 따릅니다.

 

스코프 및 초기화

  • 자식 빈이 스코프, 초기화 메서드, 소멸 메서드, 정적 팩토리 메서드를 명시하지 않은 경우, 부모로부터 해당 설정을 상속받습니다.
  • 자식 빈은 필요에 따라 이러한 설정을 덮어쓸 수 있습니다.

 

추상 빈 처리

  • 추상 빈은 인스턴스화될 수 없기 때문에, preInstantiateSingletons() 메서드 호출 시 스프링 컨테이너에서 무시됩니다.
  • 부모 빈을 템플릿으로만 사용하려면 abstract="true"를 반드시 설정해야 합니다.

 

빈 정의 상속 예시

<!-- 부모 빈 정의 -->
<bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<!-- 부모로부터 상속받는 자식 빈 정의 -->
<bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!-- 'age' 속성은 부모로부터 상속됨 -->
</bean>

 

이와 같은 빈 정의 상속 기능을 통해 설정 파일에서 중복된 코드를 줄일 수 있으며, 유지보수성도 크게 향상됩니다.


컨테이너 확장 지점 (Container Extension Points)

스프링 IoC 컨테이너는 특별한 통합 인터페이스를 구현하여 확장할 수 있습니다. 이 섹션에서는 이러한 통합 인터페이스에 대해 설명합니다.

 

BeanPostProcessor를 사용한 빈 커스터마이징

BeanPostProcessor 인터페이스는 콜백 메서드를 정의하여 빈의 인스턴스화 논리나 의존성 해결 논리 등을 커스터마이징할 수 있습니다. 빈이 스프링 컨테이너에서 인스턴스화되고 초기화가 끝난 후에 추가적인 작업을 수행하고 싶다면, 커스텀 BeanPostProcessor 구현을 하나 이상 플러그인 방식으로 추가할 수 있습니다.

 

  • BeanPostProcessor 작동 방식: 스프링 컨테이너는 빈 인스턴스를 생성한 후 BeanPostProcessor가 이를 후처리합니다. 이를 통해 빈의 생성 전후에 추가 로직을 삽입할 수 있습니다.
  • 등록 방법: BeanPostProcessor는 스프링 컨테이너에서 자동으로 감지됩니다. XML 구성 파일이나 자바 구성 클래스에서 BeanPostProcessor를 정의하면, 애플리케이션 컨텍스트는 이를 빈 생성 시 호출하도록 등록합니다.
  • 정렬 및 순서: 여러 개의 BeanPostProcessor가 있을 경우 순서를 제어할 수 있으며, Ordered 인터페이스를 구현하면 순서를 명시할 수 있습니다.

 

BeanPostProcessor 인터페이스

BeanPostProcessor 인터페이스는 두 개의 콜백 메서드로 구성됩니다:

  1. postProcessBeforeInitialization(Object bean, String beanName): 빈 초기화 이전에 호출됩니다.
  2. postProcessAfterInitialization(Object bean, String beanName): 빈 초기화 이후에 호출됩니다.

이 메서드들은 각각 빈의 초기화 전에 또는 후에 어떤 작업을 수행할지 정의할 수 있습니다. 예를 들어, 빈을 래핑하거나 로깅하는 등의 작업을 이 단계에서 처리할 수 있습니다.

 

 

BeanPostProcessor 사용 예시

다음 예시는 모든 빈이 생성될 때 해당 빈의 toString() 메서드를 호출하고, 결과를 시스템 콘솔에 출력하는 커스텀 BeanPostProcessor를 구현한 것입니다.

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

# XML 설정
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>
    
</beans>

 

 

이 예시는 커스텀 BeanPostProcessor가 빈이 인스턴스화된 이후에 toString() 메서드를 호출하여 로그를 출력하는 방식으로 동작합니다.

 

AutowiredAnnotationBeanPostProcessor 예시

스프링에서 제공하는 AutowiredAnnotationBeanPostProcessor는 BeanPostProcessor의 구현체로, 자동으로 필드, setter 메서드 및 기타 구성 메서드에 의존성을 주입하는 역할을 합니다. 이 구현체는 스프링에서 흔히 사용되는 자동 와이어링 기능을 지원하는 핵심 구성 요소 중 하나입니다.

 

 

 

BeanFactoryPostProcessor를 사용한 설정 메타데이터 커스터마이징

BeanFactoryPostProcessor는 스프링 IoC 컨테이너가 빈을 인스턴스화하기 전에 빈의 설정 메타데이터를 읽고 수정할 수 있도록 해줍니다. BeanPostProcessor와 유사한 동작을 하지만, BeanFactoryPostProcessor는 실제 빈 인스턴스가 아니라 빈의 설정 메타데이터를 처리합니다. 이로 인해 빈이 생성되기 전에 설정 정보를 조작할 수 있습니다.

 

주요 특징

  1. 빈 설정 메타데이터에 작용: 빈 인스턴스가 생성되기 전에 설정 정보를 조작할 수 있습니다.
  2. 컨테이너별로 작동: 각 컨테이너 내에서만 작동하며, 컨테이너 계층이 존재할 경우 다른 컨테이너에 정의된 빈에는 영향을 미치지 않습니다.
  3. 자동 실행: ApplicationContext에 선언된 BeanFactoryPostProcessor는 자동으로 실행되어 빈 설정 메타데이터에 적용됩니다.

 

사용 사례

스프링은 PropertyOverrideConfigurer와 PropertySourcesPlaceholderConfigurer 같은 여러 사전 정의된 BeanFactoryPostProcessor 구현체를 제공합니다. 이러한 구현체들은 외부 설정 파일에서 속성 값을 읽어와 빈 설정에 적용하는 등의 작업을 수행합니다. 예를 들어, 데이터베이스 URL이나 비밀번호와 같은 환경별 속성을 설정 파일에서 관리할 수 있습니다.

 

 

예제: PropertySourcesPlaceholderConfigurer

이 예제에서는 외부 파일에서 JDBC 설정을 읽어와 데이터 소스 빈에 적용하는 방법을 보여줍니다.

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

 

여기서 PropertySourcesPlaceholderConfigurer는 jdbc.properties 파일을 읽고, ${jdbc.driverClassName}, ${jdbc.url} 등의 플레이스홀더를 해당 속성 값으로 치환합니다.

 

 

예제: PropertyOverrideConfigurer

PropertyOverrideConfigurer는 기본적으로 정의된 빈의 속성을 외부 파일에서 가져온 값으로 덮어씌웁니다.

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

 

이 설정 파일은 dataSource 빈의 driverClassName과 url 속성을 덮어씌웁니다. 이 방식은 기존 XML 파일에서 속성을 정의하지 않더라도 외부 설정 파일에서 필요한 값을 제공할 수 있다는 장점이 있습니다.

 

FactoryBean을 사용한 인스턴스화 로직 커스터마이징

복잡한 인스턴스화 로직이 필요할 경우, FactoryBean 인터페이스를 구현할 수 있습니다. 이를 통해 복잡한 초기화 로직을 자바 코드로 처리하고, 이를 컨테이너에 플러그인 방식으로 삽입할 수 있습니다.

 

FactoryBean은 세 가지 주요 메서드를 제공합니다:

  1. getObject(): 이 팩토리가 생성하는 객체를 반환합니다.
  2. isSingleton(): 이 팩토리가 싱글톤을 반환하는지 여부를 반환합니다.
  3. getObjectType(): 생성되는 객체의 타입을 반환합니다.
<bean id="serviceStrategy" class="${custom.strategy.class}"/>

 

이 경우 custom.strategy.class 속성이 런타임에 유효한 클래스 이름으로 치환되지 않으면 빈 생성이 실패하게 됩니다.


어노테이션 기반 컨테이너 구성 (Annotation-based Container Configuration)

스프링은 어노테이션 기반 구성에 대한 포괄적인 지원을 제공합니다. 이 방식은 클래스, 메서드 또는 필드 선언에 애노테이션을 사용하여 구성 메타데이터를 직접 컴포넌트 클래스에 배치하는 방식입니다. 이는 XML 기반 구성의 대안으로, 보다 간결하고 가독성이 높은 코드를 작성할 수 있게 하며, 여전히 스프링 IoC 컨테이너에 대한 완전한 제어를 유지할 수 있습니다.

 

BeanPostProcessor와 어노테이션의 상호 작용

스프링은 BeanPostProcessor를 어노테이션과 함께 사용하여 IoC 컨테이너가 특정 어노테이션을 인식할 수 있도록 합니다. 예를 들어, @Autowired 어노테이션은 Autowiring Collaborators에서 설명한 것과 동일한 기능을 제공하지만, 더 세밀한 제어와 더 넓은 적용성을 가집니다.

 

또한, 스프링은 JSR-250 어노테이션(예: @PostConstruct, @PreDestroy)과 JSR-330(Java 의존성 주입) 어노테이션(jakarta.inject 패키지에 포함, 예: @Inject, @Named)을 지원합니다. 이러한 어노테이션에 대한 자세한 내용은 관련 섹션에서 확인할 수 있습니다.

 

어노테이션과 외부 설정의 관계

스프링에서 어노테이션 주입은 외부 속성 주입 전에 수행됩니다. 따라서 외부 구성(예: XML로 지정된 빈 속성)은 혼합 접근 방식을 사용할 때 어노테이션으로 설정된 속성보다 우선적으로 적용됩니다. 이는 외부 설정이 더 높은 우선순위를 갖는다는 의미입니다.

 

AnnotationConfigApplicationContext와 어노테이션 구성

AnnotationConfigApplicationContext에서는 이러한 포스트 프로세서들이 자동으로 등록됩니다. 즉, 어노테이션 기반 구성 방식에서는 별도의 설정 없이도 스프링이 내부적으로 필요한 BeanPostProcessor를 등록하고 사용하게 됩니다.

 

XML 기반 설정에서 어노테이션 지원 활성화

만약 XML 기반의 스프링 설정을 사용하면서도 어노테이션 기반 구성을 혼합하여 사용하고 싶다면, 다음과 같은 설정 태그를 추가해야 합니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
</beans>

 

이 <context:annotation-config/> 요소는 다음과 같은 중요한 BeanPostProcessor들을 자동으로 등록합니다:

 

  • ConfigurationClassPostProcessor: 스프링 설정 클래스 내부에서 자바 기반 설정을 처리하는 역할을 합니다. 주로 @Configuration 및 @Bean과 같은 어노테이션을 처리합니다.
  • AutowiredAnnotationBeanPostProcessor: @Autowired 어노테이션을 처리하며, 스프링 빈 간의 의존성 주입을 자동으로 수행합니다.
  • CommonAnnotationBeanPostProcessor: JSR-250 어노테이션(@PostConstruct, @PreDestroy 등)을 처리합니다.
  • PersistenceAnnotationBeanPostProcessor: JPA나 스프링 데이터와 같은 영속성 관련 어노테이션(@PersistenceContext, @PersistenceUnit 등)을 처리합니다.
  • EventListenerMethodProcessor: @EventListener 어노테이션을 처리하며, 이벤트 리스너 메서드를 관리합니다.

이러한 포스트 프로세서들은 스프링 애플리케이션에서 빈의 생성 및 초기화 과정에서 중요한 역할을 수행하며, 어노테이션 기반 구성을 활성화하기 위해 필수적으로 등록되어야 하는 요소들입니다.

 

어노테이션 기반 구성은 스프링의 유연성과 간결성을 제공하는 중요한 기능입니다. 이를 통해 개발자는 XML 설정 파일에 대한 의존성을 줄이고, 더 직관적이고 유지 관리가 쉬운 코드를 작성할 수 있습니다. XML 설정에서 <context:annotation-config/> 태그를 사용하면 어노테이션 기반 구성을 활성화할 수 있으며, 스프링은 내부적으로 필요한 BeanPostProcessor들을 자동으로 관리하여 개발자의 부담을 줄입니다.


@Autowired 어노테이션 사용

Spring Framework에서는 @Autowired 어노테이션을 사용하여 의존성을 자동으로 주입할 수 있습니다. 이 어노테이션은 Spring의 의존성 주입(Dependency Injection, DI) 메커니즘을 사용하여 구성 요소 간의 의존성을 해결하는 데 사용됩니다. 또한 JSR 330 표준의 @Inject 어노테이션도 동일한 방식으로 사용될 수 있으며, 두 어노테이션은 거의 동일한 기능을 제공합니다.

 

생성자 주입(Constructor Injection)

@Autowired 어노테이션을 생성자에 적용할 수 있습니다. 이를 통해 스프링 컨테이너는 빈을 생성할 때 해당 생성자를 사용하여 필요한 의존성을 주입합니다.

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

 

 

Spring Framework 4.3부터는 생성자가 하나만 있는 경우에는 @Autowired 어노테이션을 생략할 수 있습니다. 그러나 여러 생성자가 존재하고 기본 생성자가 아닌 특정 생성자를 사용해야 하는 경우에는 적어도 하나의 생성자에 @Autowired를 명시해 주어야 합니다.

 

세터 주입(Setter Injection)

@Autowired 어노테이션은 세터 메서드에 적용할 수도 있습니다. 이를 통해 스프링 컨테이너는 빈을 생성한 후 세터 메서드를 호출하여 의존성을 주입합니다.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

 

메서드 주입(Method Injection)

@Autowired 어노테이션은 여러 인자를 받는 일반 메서드에도 적용할 수 있습니다. 이를 통해 복수의 의존성을 한 번에 주입할 수 있습니다.

public class MovieRecommender {

    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

 

Setter Injection 및 Method Injection 방식으로 의존성을 주입하는 경우, 스프링의 IoC 컨테이너가 해당 클래스를 빈으로 관리하게 되면, 클래스가 처음 인스턴스화되고 빈으로 등록될 때 설정된 setter 메서드나 일반 메서드를 통해 의존성이 주입됩니다. 이때 중요한 점은 다음과 같습니다:

  • 자동 호출 여부: IoC 컨테이너는 빈을 초기화할 때, 의존성을 주입하기 위해 setter 메서드 또는 method가 자동으로 호출됩니다. 개발자가 직접 호출하지 않아도 됩니다.
  • 싱글톤 방식: 빈이 기본적으로 싱글톤(singleton) 스코프로 설정된 경우, 해당 빈은 컨테이너 내에서 단 하나의 인스턴스만 생성됩니다. 즉, setter 메서드나 method를 통해 의존성이 주입된 후, 그 클래스는 싱글톤으로 인스턴스화되어 컨테이너 내에 저장되고, 이후 빈을 요청할 때 동일한 인스턴스가 반환됩니다.
  • 초기화 시점: 스프링 컨테이너는 빈을 초기화할 때 의존성 주입이 이루어지며, 초기화 후에 setter나 method가 호출됩니다. 빈의 라이프사이클 중 처음 인스턴스화될 때 이 메서드들이 호출되어 의존성을 주입하게 되므로, 이후 동일한 빈에 대한 요청은 주입된 의존성을 가진 동일한 객체를 반환하게 됩니다(싱글톤 스코프의 경우).
  • 싱글톤 이외의 스코프: 만약 해당 빈이 prototype 스코프로 설정되었다면, 매번 새로운 인스턴스가 생성되며 setter 메서드나 method가 주입 시마다 호출됩니다.

 

 

필드 주입(Field Injection)

@Autowired 어노테이션은 필드에도 적용할 수 있으며, 생성자 주입과 혼합하여 사용할 수도 있습니다.

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

 

이때 중요한 점은 @Autowired가 적용된 필드나 메서드가 주입하려는 빈의 타입과 일치해야 한다는 것입니다. 빈의 타입이 일치하지 않으면 주입이 실패할 수 있습니다.

 

배열, 컬렉션 및 맵 주입

Spring은 배열, 컬렉션, 맵 등의 타입에 대해 해당 타입의 모든 빈을 주입할 수 있습니다.

 

 

배열 주입

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

 

컬렉션 주입

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

 

이러한 경우, 주입되는 빈들의 순서는 일반적으로 빈이 컨테이너에 등록된 순서를 따르며, @Order 또는 @Priority 어노테이션을 사용하여 순서를 제어할 수 있습니다.

 

맵 주입

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

 

맵 주입에서는 맵의 키가 빈이름이며, 값이 해당 타입의 빈이 됩니다.

 

 

Optional 및 Nullable 주입

 

Java 8의 Optional 또는 @Nullable 어노테이션을 사용하여 주입이 선택적인 의존성일 경우에도 대응할 수 있습니다.

 

Optional 사용 예시

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}

 

@Nullable 사용 예시

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}

 

 

특수 인터페이스의 자동 주입

@Autowired는 특정 스프링 인프라스트럭처 빈에도 사용할 수 있습니다. 예를 들어, ApplicationContext, BeanFactory, Environment와 같은 스프링 컨텍스트 관련 인터페이스를 자동으로 주입할 수 있습니다.

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    // ...
}

 

이 경우, 별도의 설정 없이 스프링 컨테이너가 자동으로 해당 의존성을 주입합니다.

 

 

필수 의존성 여부 설정

@Autowired 어노테이션은 기본적으로 주입이 불가능할 경우 예외를 발생시킵니다. 그러나 required=false 속성을 사용하여 의존성이 없을 경우 주입을 생략하도록 설정할 수 있습니다.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

 

이 경우, 주입이 불가능할 경우 해당 필드는 초기화되지 않고 기본값으로 남게 됩니다.


Fine-tuning Annotation-based Autowiring with @Primary

Spring에서는 타입 기반의 의존성 주입을 사용할 때, 여러 개의 후보 빈이 있을 수 있습니다. 이 경우 어느 빈을 주입해야 할지 명확하지 않아서 충돌이 발생할 수 있습니다. 이때 @Primary 어노테이션을 사용하면 특정 빈에 우선 순위를 부여하여 충돌을 방지할 수 있습니다.

 

@Primary 어노테이션은 빈 중에서 우선적으로 선택되어야 할 빈임을 나타냅니다. 주입하려는 동일한 타입의 빈이 여러 개 있을 때, @Primary로 지정된 빈이 우선적으로 주입됩니다.

 

@Primary 어노테이션 사용 예시

Java 기반 구성

@Configuration
public class MovieConfiguration {

    // @Primary 어노테이션이 붙어있는 firstMovieCatalog 빈이 우선적으로 주입됩니다.
    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() {
        return new FirstMovieCatalog();
    }

    @Bean
    public MovieCatalog secondMovieCatalog() {
        return new SecondMovieCatalog();
    }
}

 

위 구성에서 MovieRecommender 클래스에 MovieCatalog 타입의 빈이 주입되면, @Primary로 지정된 firstMovieCatalog 빈이 주입됩니다.

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // firstMovieCatalog가 주입됩니다.
}

 

XML 기반 구성

Spring에서는 XML 구성에서도 primary 속성을 사용하여 동일한 기능을 제공합니다. 다음은 XML 기반 구성 예시입니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 우선적으로 주입될 빈을 primary 속성으로 지정 -->
    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- 해당 빈의 의존성 주입 -->
    </bean>

    <!-- 두 번째 빈 정의 -->
    <bean class="example.SimpleMovieCatalog">
        <!-- 해당 빈의 의존성 주입 -->
    </bean>

    <!-- movieRecommender 빈 정의 -->
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

 

위 구성에서 movieRecommender 빈이 생성될 때, SimpleMovieCatalog 타입의 빈이 여러 개 존재하지만 primary="true"로 설정된 빈이 주입됩니다.


Fine-tuning Annotation-based Autowiring with Qualifiers

@Primary는 여러 빈 후보 중 하나를 주입할 때 우선순위를 지정하는 간단한 방법이지만, 여러 개의 후보 중 특정 빈을 명시적으로 선택하고자 할 때는 @Qualifier 어노테이션을 사용해야 합니다. @Qualifier를 사용하면, 주입할 빈을 더 구체적으로 명시할 수 있습니다.

 

기본 사용 예시

아래는 @Qualifier 어노테이션을 사용하여 main이라는 이름을 가진 빈을 명시적으로 선택하는 예시입니다.

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

 

위 코드는 @Qualifier("main")을 통해 MovieCatalog 타입의 빈 중에서 main이라는 이름을 가진 빈을 주입하도록 지정하고 있습니다.

 

생성자 또는 메서드 매개변수에서의 사용

@Qualifier는 생성자 인자나 메서드 파라미터에 대해서도 사용할 수 있습니다. 아래 예시는 생성자와 메서드 파라미터에서 @Qualifier를 사용하는 예입니다.

public class MovieRecommender {

    private final MovieCatalog movieCatalog;
    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(@Qualifier("main") MovieCatalog movieCatalog,
                            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

 

위와 같이 @Qualifier 어노테이션을 사용하여 생성자 인자의 특정 빈을 지정할 수 있습니다.

 

XML 기반 구성에서의 사용

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <!-- main이라는 qualifier를 가진 빈 정의 -->
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 
    </bean>

    <!-- action이라는 qualifier를 가진 빈 정의 -->
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 
    </bean>

    <!-- movieRecommender 빈 정의 -->
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

 

위 XML에서는 각 빈에 qualifier 태그를 추가하여 해당 빈의 @Qualifier 값을 지정하고 있습니다.

 

@Qualifier를 사용한 컬렉션 주입

@Qualifier는 컬렉션 주입 시에도 유용하게 사용될 수 있습니다. 예를 들어, 특정 @Qualifier 값을 가진 여러 개의 빈을 Set이나 List 형태로 주입할 수 있습니다.

public class MovieRecommender {

    @Autowired
    @Qualifier("action")
    private Set<MovieCatalog> movieCatalogs;

    // ...
}

 

위 예시에서는 action이라는 @Qualifier 값을 가진 MovieCatalog 빈들을 Set으로 주입받고 있습니다.

 

@Qualifier 어노테이션을 활용한 커스텀 어노테이션

Spring에서는 기본 @Qualifier 외에도 커스텀 어노테이션을 만들어 사용할 수 있습니다. 이를 통해 더욱 유연하게 빈을 구분하고 주입할 수 있습니다.

 

커스텀 어노테이션 예시

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
    String value();
}

 

이 커스텀 어노테이션은 특정 장르를 기반으로 빈을 선택할 수 있도록 합니다.

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    @Autowired
    @Genre("Comedy")
    private MovieCatalog comedyCatalog;

    // ...
}

 

이제 Genre 어노테이션을 통해 장르별로 빈을 주입받을 수 있습니다.

 

XML에서 커스텀 어노테이션 사용

XML에서는 <qualifier/> 태그와 type 및 value 속성을 사용하여 커스텀 어노테이션을 지원할 수 있습니다.

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Genre" value="Action"/> 
</bean>

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Genre" value="Comedy"/> 
</bean>

 

이 예시에서는 XML 구성을 통해 커스텀 Genre 어노테이션을 적용하고 있습니다.


Using Generics as Autowiring Qualifiers

Spring에서는 제네릭 타입을 Autowiring 시에 묵시적인 자격 형태로 사용할 수 있습니다. 이는 제네릭 타입을 통해 해당하는 타입의 빈을 자동으로 선택할 수 있게 해줍니다. 즉, @Qualifier 어노테이션 없이도 제네릭 타입에 따라 주입할 빈이 자동으로 결정됩니다.

 

예시: 제네릭을 이용한 Autowiring

다음과 같은 설정을 가정해봅시다.

@Configuration
public class MyConfiguration {

	@Bean
	public StringStore stringStore() {
		return new StringStore();
	}

	@Bean
	public IntegerStore integerStore() {
		return new IntegerStore();
	}
}

 

위 설정에서 StringStore와 IntegerStore는 각각 Store<String>과 Store<Integer> 인터페이스를 구현한다고 가정합니다. 이제 제네릭 타입을 사용하여 @Autowired를 활용할 수 있습니다. 제네릭 타입이 묵시적인 자격 조건이 되어 적합한 빈을 자동으로 선택합니다.

 

@Autowired
private Store<String> s1;  // <String> 자격 조건, stringStore 빈이 주입됨

@Autowired
private Store<Integer> s2;  // <Integer> 자격 조건, integerStore 빈이 주입됨

 

이 경우 Store<String> 타입을 구현한 빈은 stringStore가 자동으로 주입되고, Store<Integer> 타입을 구현한 빈은 integerStore가 주입됩니다. 제네릭 타입이 빈을 선택하는 기준으로 사용된 것입니다.

 

리스트, 맵, 배열과 제네릭 Autowiring

제네릭은 리스트, 맵, 배열과 같은 컬렉션 타입에서도 Autowiring 자격 조건으로 사용할 수 있습니다. 다음 예시는 제네릭을 이용한 리스트의 Autowiring을 보여줍니다:

// <Integer> 제네릭을 가지는 Store 빈들을 모두 주입받음
// Store<String> 빈들은 이 리스트에 포함되지 않음
@Autowired
private List<Store<Integer>> stores;

 

이 예시에서, List<Store<Integer>> 타입으로 주입받으면, Store<Integer>를 구현한 모든 빈들이 리스트에 포함됩니다. 반면, Store<String>를 구현한 빈은 포함되지 않습니다.

 

제네릭을 이용한 자격 조건의 장점

 

  • 명확성: 제네릭 타입을 사용하면 특정 타입의 빈을 더욱 명확하게 구분하고 주입할 수 있습니다. 이는 여러 빈이 같은 인터페이스를 구현하지만 다른 제네릭 타입을 가질 때 특히 유용합니다.
  • 간결성: @Qualifier 어노테이션을 사용하지 않아도, 제네릭 타입을 통해 자동으로 적합한 빈을 선택할 수 있으므로 코드가 간결해집니다.
  • 확장성: 여러 제네릭 타입의 빈이 있을 때, 제네릭을 기준으로 주입할 빈을 자동으로 선택할 수 있어 확장성 있는 구조를 만들 수 있습니다.

이러한 제네릭 기반의 Autowiring은 스프링 프레임워크의 유연성을 극대화하며, 특히 다양한 타입의 빈을 다루는 경우 매우 효과적입니다.


Using CustomAutowireConfigurer

CustomAutowireConfigurer는 BeanFactoryPostProcessor로, 이를 통해 Spring의 @Qualifier 어노테이션이 없어도 사용자 정의 자격 어노테이션을 등록할 수 있습니다. 이는 특수한 자격 어노테이션이 필요한 경우 유용하게 사용할 수 있습니다. 다음은 CustomAutowireConfigurer를 사용하는 예시입니다:

<bean id="customAutowireConfigurer"
      class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

 

 

위 XML 구성에서는 CustomAutowireConfigurer가 정의되어 있고, 사용자 정의 자격 어노테이션인 example.CustomQualifier가 등록되어 있습니다. 이 설정을 통해 스프링은 @Qualifier 없이도 example.CustomQualifier를 사용하여 빈을 구별하고 주입할 수 있게 됩니다.

 

AutowireCandidateResolver의 작동 방식

AutowireCandidateResolver는 특정 빈이 Autowire 대상인지 여부를 결정하는 역할을 합니다. 이 과정에서 고려되는 요소는 다음과 같습니다:

  1. autowire-candidate 값: 각 빈 정의에서 autowire-candidate 속성의 값을 확인합니다. 해당 값이 false로 설정된 빈은 자동 주입 대상에서 제외됩니다.
  2. default-autowire-candidates 패턴: <beans/> 요소에 설정된 기본 autowire-candidates 패턴이 있는 경우, 이 패턴을 사용해 주입 대상 후보를 필터링합니다.
  3. @Qualifier 및 사용자 정의 어노테이션: @Qualifier 어노테이션이 있는 경우 이를 통해 빈을 필터링합니다. 또한 CustomAutowireConfigurer를 통해 등록된 사용자 정의 어노테이션이 있는 경우, 해당 어노테이션이 적용된 빈도 주입 후보로 고려됩니다.

다수의 Autowire 후보가 있을 경우

만약 여러 개의 빈이 Autowire 후보로 선택되었다면, "Primary" 빈 결정은 다음 규칙을 따릅니다:

  • Primary 속성: 만약 후보 빈 중에서 오직 하나의 빈 정의가 primary 속성이 true로 설정되어 있으면, 그 빈이 자동으로 선택됩니다.

이를 통해 다수의 후보 빈 중에서 특정 빈을 우선적으로 선택할 수 있으며, @Primary 또는 @Qualifier와 같은 메커니즘을 사용하여 보다 세밀하게 빈을 제어할 수 있습니다.


Injection with @Resource

Spring에서 @Resource 어노테이션을 사용한 주입은 JSR-250 표준을 따르는 방법으로, 주로 Jakarta EE 환경에서 사용됩니다. @Resource는 필드나 빈 속성의 setter 메소드에 적용될 수 있으며, 주로 이름 기반으로 빈을 주입합니다.

 

@Resource 주입 방식

@Resource는 name 속성을 가지며, 이 속성의 값은 기본적으로 주입할 빈의 이름으로 해석됩니다. 즉, 해당 이름과 일치하는 빈을 찾아 주입하는 방식입니다.

 

 

예시 1: name 속성 명시

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

 

이 예시에서 @Resource는 myMovieFinder라는 이름의 빈을 찾아서 setMovieFinder 메소드로 주입합니다.

 

 

예시 2: name 속성 미명시

만약 name 속성을 명시하지 않으면, 스프링은 기본적으로 필드 이름 또는 setter 메소드의 속성 이름을 사용하여 빈을 주입하려고 시도합니다.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

 

위 예시에서 @Resource는 movieFinder라는 이름의 빈을 찾아 setMovieFinder 메소드로 주입합니다. 필드 또는 메소드의 이름과 동일한 빈을 찾을 때 이러한 동작이 발생합니다.

 

빈 이름을 통한 주입의 기본 동작

스프링의 ApplicationContext는 CommonAnnotationBeanPostProcessor에 의해 빈 이름을 해석하고 주입할 빈을 결정합니다. 또한, Spring의 JNDI 조회 기능을 사용하여 이름을 통한 주입을 할 수 있지만, 대부분의 경우 스프링의 기본 동작을 사용하는 것이 권장됩니다.

 

@Resource의 타입 매칭

이름을 명시하지 않은 경우, @Resource는 필드나 메소드의 타입과 일치하는 빈을 주입하려고 시도합니다. 이 과정은 @Autowired와 유사하게 동작하며, 아래의 예시에서처럼 기본적인 타입 매칭도 가능합니다.

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }
}

 

위 코드에서:

  • customerPreferenceDao는 먼저 이름을 기반으로 빈을 찾으려고 시도합니다. 만약 이름에 맞는 빈이 없다면, 타입을 기준으로 매칭되는 빈을 찾아 주입합니다.
  • context는 ApplicationContext 타입이기 때문에 해당 타입을 기준으로 스프링이 자동으로 주입해줍니다.

Using @Value

@Value 어노테이션은 주로 외부에서 설정된 속성 값을 스프링 빈에 주입하는 데 사용됩니다. 이는 스프링의 외부화된 구성 속성 관리 기능을 사용하여, 애플리케이션 코드에서 직접적으로 값을 설정하지 않고 외부 설정 파일에서 값을 받아올 수 있게 해줍니다.

 

@Value를 통한 속성 값 주입

@Value 어노테이션은 속성 값을 필드나 생성자에 주입하는 데 사용할 수 있습니다. 예를 들어, 외부 속성 파일 application.properties에서 값을 읽어와 스프링 컴포넌트 클래스에 주입하는 방식입니다.

 

 

예시: 외부 속성 주입

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

 

위의 코드에서 @Value("${catalog.name}") 어노테이션을 통해 catalog.name이라는 키에 해당하는 값을 MovieRecommender의 생성자에 주입합니다. 이 값을 설정하기 위해 다음과 같은 구성 파일이 필요합니다:

 

application.properties 파일

catalog.name=MovieCatalog

 

이렇게 하면 catalog 필드는 MovieCatalog 값으로 주입됩니다.

 

기본적인 설정

@Value 어노테이션을 사용하기 위해 스프링 구성 파일에 외부 속성 파일을 로드할 필요가 있습니다. 이를 위해 @PropertySource를 사용합니다.

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

 

엄격한 값 검증 설정

기본적으로 스프링은 속성 값을 느슨하게 처리합니다. 예를 들어, 속성 값을 찾을 수 없을 경우에는 ${catalog.name} 자체를 값으로 주입하려고 시도합니다. 만약 존재하지 않는 값을 엄격하게 처리하고 싶다면 PropertySourcesPlaceholderConfigurer 빈을 추가하여 처리할 수 있습니다.

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

 

이 설정을 통해, 스프링은 속성 값을 찾을 수 없는 경우 초기화 단계에서 실패를 발생시키고 애플리케이션이 실행되지 않도록 합니다.

 

기본 값 설정

@Value 어노테이션에서는 기본값을 제공할 수도 있습니다. 예를 들어, 속성 파일에서 catalog.name 값을 찾을 수 없을 경우 defaultCatalog를 기본값으로 설정하는 방식입니다.

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

 

이 코드를 통해 속성 값이 없으면 catalog 필드는 defaultCatalog로 설정됩니다.

 

변환 서비스 사용

스프링은 기본적으로 문자열을 단순한 기본 타입으로 자동 변환하는 기능을 제공합니다. 예를 들어, @Value 어노테이션으로 주입된 값을 Integer, Double, Boolean 등으로 변환할 수 있습니다. 복잡한 타입 변환이 필요한 경우에는 ConversionService 빈을 사용하여 사용자 정의 변환 로직을 추가할 수 있습니다.

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

 

SpEL 표현식 사용

@Value 어노테이션은 Spring Expression Language(SpEL) 표현식도 지원합니다. SpEL을 사용하면 복잡한 표현식을 기반으로 값을 동적으로 계산하여 주입할 수 있습니다.

 

예시: SpEL을 사용한 값 주입

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

 

위 예제에서는 시스템 속성 user.catalog의 값에 'Catalog' 문자열을 더한 값을 catalog 필드에 주입합니다.

 

복잡한 데이터 구조 주입

SpEL을 사용하면 복잡한 데이터 구조도 주입할 수 있습니다. 예를 들어, Map과 같은 자료 구조를 SpEL을 사용하여 초기화하고 주입할 수 있습니다.

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

 

위 코드에서는 SpEL 표현식을 사용하여 countOfMoviesPerCatalog 맵 필드에 특정 값을 주입하고 있습니다.


Using @PostConstruct and @PreDestroy

@PostConstruct와 @PreDestroy 어노테이션은 Spring에서 빈의 생명주기 동안 초기화와 소멸 시점을 제어하는 데 사용됩니다. 이 두 어노테이션은 JSR-250 표준의 일부로, 스프링의 CommonAnnotationBeanPostProcessor가 등록되어 있는 경우 인식됩니다.

 

@PostConstruct와 @PreDestroy 어노테이션 개요

 

  • @PostConstruct: 이 어노테이션이 붙은 메서드는 빈이 초기화된 직후에 호출됩니다. 즉, 모든 의존성이 주입된 후 실행됩니다.
  • @PreDestroy: 이 어노테이션이 붙은 메서드는 빈이 소멸되기 직전에 호출됩니다. 즉, 애플리케이션 컨텍스트가 종료되거나 빈이 제거되기 전에 실행됩니다.

이 어노테이션을 사용하면 빈의 초기화 및 소멸 과정에서 특정 작업을 수행할 수 있으며, 스프링의 InitializingBean이나 DisposableBean 인터페이스를 구현하는 것과 유사한 기능을 제공합니다. 하지만 이 방식은 코드가 Spring API에 의존하지 않도록 만들어 표준 Java 어노테이션을 사용한다는 점에서 더 유연합니다.

 

예시: 캐시 초기화 및 소멸 처리

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 초기화 시점에 캐시를 채웁니다.
        System.out.println("캐시가 초기화됩니다.");
    }

    @PreDestroy
    public void clearMovieCache() {
        // 소멸 시점에 캐시를 비웁니다.
        System.out.println("캐시가 제거됩니다.");
    }
}

 

 

위 코드에서 CachingMovieLister 클래스는 다음과 같은 두 가지 주요 작업을 수행합니다:

  • @PostConstruct: 빈이 초기화될 때 populateMovieCache() 메서드가 호출되어 캐시를 채웁니다.
  • @PreDestroy: 빈이 소멸될 때 clearMovieCache() 메서드가 호출되어 캐시를 비웁니다.

 

스프링에서의 동작 방식

 

  • 이 어노테이션들은 스프링의 CommonAnnotationBeanPostProcessor에 의해 인식됩니다. 스프링 애플리케이션 컨텍스트에 CommonAnnotationBeanPostProcessor가 등록되어 있으면, 해당 어노테이션이 붙은 메서드가 적절한 시점에 자동으로 호출됩니다.
  • 초기화 시점: @PostConstruct가 붙은 메서드는 빈의 생성 및 의존성 주입이 완료된 후 자동으로 호출됩니다. 이는 스프링의 InitializingBean.afterPropertiesSet() 메서드 또는 XML/Java 설정에서 명시한 init-method와 동일한 시점에 실행됩니다.
  • 소멸 시점: @PreDestroy가 붙은 메서드는 애플리케이션 컨텍스트가 종료되거나 빈이 소멸될 때 자동으로 호출됩니다. 이는 스프링의 DisposableBean.destroy() 메서드 또는 XML/Java 설정에서 명시한 destroy-method와 동일한 시점에 실행됩니다.

 

JDK 변화와 관련된 내용

@PostConstruct와 @PreDestroy는 JDK 6부터 8까지 Java 표준 라이브러리의 일부였으나, JDK 9 이후부터는 javax.annotation 패키지가 Java 표준 모듈에서 분리되었습니다. JDK 11부터는 완전히 제거되었고, javax.annotation 패키지는 Jakarta EE로 이동하여 현재는 jakarta.annotation 패키지로 사용됩니다. 따라서, 만약 해당 어노테이션을 사용하려면 Jakarta EE 9 이후의 버전에서는 jakarta.annotation-api 라이브러리를 추가하여 의존성을 관리해야 합니다.


Classpath Scanning and Managed Components

이 장의 대부분 예제는 Spring 컨테이너 내에서 각 BeanDefinition을 생성하기 위해 XML을 사용하여 구성 메타데이터를 지정합니다. 이전 섹션(애노테이션 기반의 컨테이너 구성)에서는 소스 레벨 애노테이션을 통해 많은 구성 메타데이터를 제공하는 방법을 설명했습니다. 그러나 그 예제들에서도 "기본" 빈 정의는 XML 파일에 명시적으로 정의되었으며, 애노테이션은 주로 의존성 주입에만 관여했습니다.

 

이 섹션에서는 클래스패스 스캐닝을 통해 후보 컴포넌트를 암시적으로 감지하는 방법을 설명합니다. 후보 컴포넌트는 필터 기준에 맞는 클래스들이며, 이 클래스들은 컨테이너에 빈 정의로 등록됩니다. 이를 통해 XML을 사용하지 않고도 빈 등록을 수행할 수 있습니다. 대신, 애노테이션(@Component 등), AspectJ 타입 표현식, 또는 커스텀 필터 기준을 사용하여 어떤 클래스들이 컨테이너에 빈 정의로 등록될지를 선택할 수 있습니다.


@Component와 기타 스테레오타입 어노테이션

@Repository 어노테이션은 리포지토리(또는 데이터 접근 객체, DAO)의 역할이나 스테레오타입을 수행하는 클래스를 위한 마커입니다. 이 마커의 용도 중 하나는 예외 번역(Exception Translation)에서 설명한 바와 같이, 예외를 자동으로 번역하는 기능을 제공합니다.

 

Spring은 @Component, @Service, @Controller와 같은 추가적인 스테레오타입 어노테이션을 제공합니다. @Component는 Spring에서 관리하는 모든 컴포넌트를 위한 일반적인 스테레오타입입니다. 반면에, @Repository, @Service, @Controller는 더 특정한 용도의 컴포넌트를 위한 스페셜라이제이션 어노테이션입니다. 각각 데이터 접근 계층, 서비스 계층, 프레젠테이션 계층에서 사용됩니다. 따라서, 클래스에 @Component를 사용할 수도 있지만, 해당 클래스가 데이터 접근, 서비스, 프레젠테이션 계층에 속한다면 각각 @Repository, @Service, @Controller로 어노테이션하는 것이 더 적합합니다. 이렇게 하면 클래스가 도구에 의해 더 잘 처리되거나 애스펙트와 관련 지어질 수 있습니다. 예를 들어, 이러한 스테레오타입 어노테이션은 포인트컷의 이상적인 대상이 됩니다. 또한, @Repository, @Service, @Controller는 향후 Spring 프레임워크 릴리스에서 추가적인 의미를 가질 수 있습니다. 서비스 계층에서 @Component와 @Service 중 선택해야 한다면, @Service가 더 나은 선택입니다. 유사하게, 데이터 접근 계층에서는 @Repository가 이미 자동 예외 번역을 지원하는 마커로 사용되고 있습니다.

 

메타 어노테이션과 합성 어노테이션 사용하기

Spring에서 제공하는 많은 어노테이션은 코드에서 메타 어노테이션으로 사용할 수 있습니다. 메타 어노테이션이란 다른 어노테이션에 적용될 수 있는 어노테이션을 의미합니다. 예를 들어, 앞서 언급한 @Service 어노테이션은 @Component로 메타 어노테이션이 적용되어 있습니다. 다음 예시를 보면 확인할 수 있습니다:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {
	// ...
}

 

 

위 코드에서 @Component가 @Service에 적용되어 있어, @Service는 @Component와 동일한 방식으로 처리됩니다.

 

또한, 메타 어노테이션을 결합하여 "합성 어노테이션"을 만들 수도 있습니다. 예를 들어, Spring MVC의 @RestController 어노테이션은 @Controller와 @ResponseBody의 조합입니다.

 

추가적으로, 합성 어노테이션은 메타 어노테이션에서 일부 속성을 재선언하여 커스터마이징할 수 있습니다. 이는 메타 어노테이션의 속성 중 일부만 노출하고 싶을 때 특히 유용합니다. 예를 들어, Spring의 @SessionScope 어노테이션은 세션 스코프를 하드코딩하면서도 proxyMode를 커스터마이징할 수 있도록 허용합니다. 아래는 SessionScope 어노테이션의 정의입니다:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

 

위 코드를 보면, @SessionScope는 기본적으로 proxyMode 속성을 선언하지 않아도 사용할 수 있습니다:

@Service
@SessionScope
public class SessionScopedService {
	// ...
}

 

위와 같이 @SessionScope를 사용할 때 proxyMode를 선언하지 않아도 기본값으로 설정된 방식으로 작동합니다.

 

또한, 필요에 따라 proxyMode 값을 오버라이드할 수 있습니다. 예를 들어, 다음과 같이 설정할 수 있습니다:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
	// ...
}

 

이 예시는 @SessionScope 어노테이션의 proxyMode 값을 인터페이스 기반으로 변경한 경우를 보여줍니다.


자동으로 클래스를 감지하고 Bean 정의 등록하기

Spring은 스테레오타입으로 지정된 클래스를 자동으로 감지하고 해당 클래스를 ApplicationContext에 Bean으로 등록할 수 있습니다. 예를 들어, 다음 두 클래스는 자동 감지를 위한 조건을 충족합니다:

@Service
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}
}

@Repository
public class JpaMovieFinder implements MovieFinder {
	// 구현 생략
}

 

위의 클래스들은 @Service, @Repository 어노테이션으로 각각 스테레오타입이 지정되어 있으며, Spring은 이러한 클래스를 자동으로 감지하여 Bean으로 등록할 수 있습니다.

 

@ComponentScan을 이용한 자동 감지 및 Bean 등록

이러한 클래스들을 자동으로 감지하고 대응하는 Bean 정의를 등록하려면, @Configuration 클래스에 @ComponentScan 어노테이션을 추가해야 합니다. basePackages 속성은 해당 클래스들이 속한 부모 패키지를 지정합니다. 예를 들어:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
	// ...
}

 

위의 예시에서는 basePackages 속성으로 org.example 패키지를 지정하여, 해당 패키지 아래에 있는 @Service, @Repository, @Component 등의 어노테이션이 부착된 클래스들을 자동으로 Bean으로 등록하도록 합니다. 간결하게 작성하려면, @ComponentScan("org.example")과 같이 value 속성을 사용할 수도 있습니다.

 

XML을 이용한 구성

Java 설정 대신 XML을 사용할 수도 있습니다. 다음은 XML 설정 예시입니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="org.example"/>

</beans>

 

위의 <context:component-scan> 태그는 지정된 패키지를 스캔하여, 해당 패키지 내에서 @Component, @Service, @Repository, @Controller 어노테이션이 붙어 있는 클래스들을 자동으로 감지하고 Bean으로 등록합니다. 주의할 점은 <context:component-scan>을 사용하면 <context:annotation-config> 기능이 자동으로 활성화되므로, 별도로 <context:annotation-config> 태그를 추가할 필요는 없습니다.

 

주의사항

클래스패스 패키지를 스캔할 때는 해당 디렉터리 항목이 클래스패스에 존재해야 합니다. 예를 들어, Ant로 JAR를 빌드할 때 files-only 옵션을 활성화하지 않도록 주의해야 하며, 일부 환경에서는 보안 정책으로 인해 클래스패스 디렉터리가 노출되지 않을 수 있습니다. 특히 JDK 1.7.0_45 이상에서 standalone 애플리케이션을 실행하는 경우 'Trusted-Library' 설정이 필요할 수 있습니다.

 

DK 9의 모듈 경로(Jigsaw)에서도 Spring의 클래스패스 스캔은 정상적으로 동작합니다. 그러나 컴포넌트 클래스가 module-info에서 exports로 공개되었는지 확인해야 합니다. Spring이 비공개 멤버를 호출할 필요가 있다면, 해당 클래스가 exports 대신 opens로 선언되었는지 확인해야 합니다.

 

또한, @ComponentScan을 사용할 경우, AutowiredAnnotationBeanPostProcessor와 CommonAnnotationBeanPostProcessor가 자동으로 포함됩니다. 이로 인해 별도의 XML 설정 없이도 컴포넌트들이 자동 감지되고 자동으로 의존성 주입이 이루어집니다.

 

만약 이러한 처리기들의 등록을 비활성화하려면, annotation-config 속성을 false로 설정할 수 있습니다.


스캐닝을 사용자 정의 필터로 조정하기

기본적으로 Spring은 @Component, @Repository, @Service, @Controller, @Configuration 또는 @Component가 붙은 사용자 정의 어노테이션이 적용된 클래스만을 감지하여 빈 후보로 등록합니다. 그러나 이 동작은 사용자 정의 필터를 적용하여 수정하고 확장할 수 있습니다. 필터는 @ComponentScan 어노테이션의 includeFilters 또는 excludeFilters 속성으로 추가할 수 있으며, XML 설정에서는 <context:component-scan> 요소 내에서 <context:include-filter /> 또는 <context:exclude-filter /> 자식 요소로 추가할 수 있습니다.

 

각 필터 요소는 type과 expression 속성을 요구하며, 아래 표에서 다양한 필터링 옵션을 설명합니다:

필터 유형 표현식 예시 설명
annotation (기본) org.example.SomeAnnotation 대상 컴포넌트의 클래스 레벨에서 존재하거나 메타 존재하는 어노테이션을 필터링합니다.
assignable org.example.SomeClass 대상 컴포넌트가 할당 가능한(상속 또는 구현한) 클래스나 인터페이스를 필터링합니다.
aspectj org.example..*Service+ 대상 컴포넌트가 일치하는 AspectJ 유형 표현식을 필터링합니다.
regex org\.example\.Default.* 대상 컴포넌트의 클래스 이름과 일치하는 정규식을 필터링합니다.
custom org.example.MyTypeFilter org.springframework.core.type.TypeFilter 인터페이스를 구현한 사용자 정의 필터를 적용합니다.

 

예시: @Repository 어노테이션을 제외하고 "stub" 리포지토리를 사용하는 구성

다음과 같은 예시에서는 모든 @Repository 어노테이션을 무시하고 "stub" 리포지토리를 대신 사용하도록 구성합니다:

@Configuration
@ComponentScan(basePackages = "org.example",
		includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
		excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
public class AppConfig {
	// ...
}

 

위와 동일한 기능을 XML로 설정하면 다음과 같습니다:

<beans>
	<context:component-scan base-package="org.example">
		<context:include-filter type="regex"
				expression=".*Stub.*Repository"/>
		<context:exclude-filter type="annotation"
				expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>
</beans>

 

기본 필터를 비활성화하려면 useDefaultFilters=false를 @ComponentScan 어노테이션에 설정하거나 <component-scan> 요소의 속성으로 use-default-filters="false"를 지정하면 됩니다. 이렇게 하면 @Component, @Repository, @Service, @Controller, @RestController, @Configuration 어노테이션이 붙은 클래스의 자동 감지가 비활성화됩니다.


컴포넌트 내에서 빈 메타데이터 정의

Spring 컴포넌트는 @Configuration 어노테이션이 적용된 클래스 내에서 사용하는 것과 동일한 @Bean 어노테이션을 사용하여 빈 정의 메타데이터를 컨테이너에 기여할 수 있습니다. 다음 예시는 @Bean 어노테이션을 사용하여 빈을 정의하는 방법을 보여줍니다:

@Component
public class FactoryMethodComponent {

	@Bean
	@Qualifier("public")
	public TestBean publicInstance() {
		return new TestBean("publicInstance");
	}

	public void doWork() {
		// Component 메소드 구현 생략
	}
}

 

이 클래스는 doWork() 메소드에서 애플리케이션 전용 코드를 수행하는 Spring 컴포넌트이며, 동시에 @Bean 어노테이션을 통해 빈 정의를 컨테이너에 추가합니다. @Bean 어노테이션은 publicInstance() 메소드를 팩토리 메소드로 식별하고, @Qualifier 어노테이션을 사용하여 빈에 대한 추가 메타데이터를 제공합니다. 다른 메소드 수준 어노테이션으로는 @Scope, @Lazy 등이 있습니다.

 

@Bean 메소드와 관련된 주의 사항

 

  • @Bean 메소드가 일반 Spring 컴포넌트 클래스 내에 선언된 경우, 이는 @Configuration 클래스와 달리 CGLIB을 사용하여 메소드 호출을 가로채지 않습니다.
  • @Bean 메소드를 정적으로 선언하면 해당 클래스의 인스턴스 없이 호출될 수 있습니다.
  • @Bean 메소드는 상위 클래스나 인터페이스에 정의된 메소드로도 인식되며, Java 8의 기본 메소드도 지원됩니다.

 

자동 감지된 컴포넌트의 명명 (Naming Autodetected Components)

스프링은 클래스 경로를 스캔하는 과정에서 컴포넌트를 자동 감지하고, 해당 클래스에 대한 BeanDefinition 인스턴스를 ApplicationContext에 등록합니다. 이때 컴포넌트의 빈 이름은 해당 스캐너에서 사용 중인 BeanNameGenerator 전략에 따라 생성됩니다.

 

기본적으로 AnnotationBeanNameGenerator가 사용됩니다. 스프링의 스테레오타입 어노테이션(@Component, @Repository, @Service 등)을 사용하는 경우, 어노테이션의 value 속성을 통해 제공한 이름이 빈 이름으로 사용됩니다. 이는 JSR-250 및 JSR-330 어노테이션(@jakarta.annotation.ManagedBean, @javax.annotation.ManagedBean, @jakarta.inject.Named, @javax.inject.Named)을 사용하는 경우에도 적용됩니다.

 

Spring Framework 6.1부터는 어노테이션 속성의 이름이 반드시 value일 필요가 없습니다. 커스텀 스테레오타입 어노테이션은 @Component의 value 속성과 @AliasFor를 사용하여 다른 이름의 속성을 정의할 수 있습니다. 예를 들어, ControllerAdvice#name()는 이를 사용한 구체적인 예시입니다.

 

스프링 프레임워크 6.1에서는 관례 기반의 스테레오타입 명명 지원이 더 이상 사용되지 않으며, 미래의 버전에서는 제거될 예정입니다. 따라서 커스텀 스테레오타입 어노테이션은 반드시 @AliasFor를 사용하여 @Component의 value 속성에 대한 명시적인 별칭을 선언해야 합니다.

 

명시적인 빈 이름이 어노테이션에서 파생되지 않거나, 사용자 정의 필터에 의해 발견된 컴포넌트의 경우, 기본 빈 이름 생성기는 소문자화된 클래스 이름을 반환합니다. 예를 들어, SimpleMovieLister 클래스는 simpleMovieLister, MovieFinderImpl 클래스는 movieFinderImpl로 빈 이름이 설정됩니다.

 

빈 이름 전략을 사용자 정의하고 싶다면, BeanNameGenerator 인터페이스를 구현하고 기본 생성자를 포함시켜야 합니다. 그런 다음 스캐너를 설정할 때 이 클래스의 전체 경로를 제공하면 됩니다. 다음은 그 예시입니다:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
	// ...
}
<beans>
	<context:component-scan base-package="org.example"
		name-generator="org.example.MyNameGenerator" />
</beans>

 

만약 클래스 이름이 같은 컴포넌트들이 자동 감지되어 명명 충돌이 발생할 경우, FullyQualifiedAnnotationBeanNameGenerator를 사용하여 클래스의 전체 경로 이름을 빈 이름으로 설정할 수 있습니다.

 

일반적으로 다른 컴포넌트들이 특정 빈을 참조해야 할 경우에는 어노테이션에서 명시적인 이름을 지정하는 것이 좋습니다. 반면, 컨테이너가 빈을 자동으로 연결할 경우 자동 생성된 이름으로 충분합니다.


자동 감지된 컴포넌트의 범위 지정 (Providing a Scope for Autodetected Components)

기본적으로 스프링이 관리하는 컴포넌트의 범위(scope)는 singleton입니다. 그러나 경우에 따라 @Scope 어노테이션을 사용해 다른 범위를 지정할 수 있습니다. 예를 들어, 다음과 같이 prototype 범위를 지정할 수 있습니다:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

 

@Scope 어노테이션은 해당 컴포넌트의 구체적인 빈 클래스 또는 팩토리 메소드에서만 적용됩니다. XML 기반 빈 정의와 달리, 클래스 수준 상속 계층은 메타데이터 목적에 있어서는 중요하지 않습니다.

 

범위 해석을 위한 사용자 정의 전략을 제공하려면 ScopeMetadataResolver 인터페이스를 구현할 수 있습니다. 이 경우에도 기본 생성자를 포함해야 하며, 스캐너를 설정할 때 이 클래스의 전체 경로를 제공하면 됩니다.

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
	// ...
}
<beans>
	<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

 

특정 비-싱글톤 범위를 사용할 때, 범위 객체에 대한 프록시를 생성해야 할 수도 있습니다. 이를 위해 scoped-proxy 속성을 사용할 수 있으며, 다음과 같은 세 가지 값이 있습니다: no, interfaces, targetClass. 예를 들어, 다음 구성은 표준 JDK 동적 프록시를 생성합니다:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
	// ...
}
<beans>
	<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

어노테이션으로 한정자 메타데이터 제공 (Providing Qualifier Metadata with Annotations)

@Qualifier 어노테이션을 사용하여 자동 연결(autowiring) 후보를 세밀하게 제어할 수 있습니다. 컴포넌트 스캔을 사용할 때, 어노테이션을 사용하여 클래스 수준에서 한정자 메타데이터를 제공할 수 있습니다. 예를 들어, 다음과 같이 할 수 있습니다:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}

 

또는 사용자 정의 어노테이션을 사용할 수 있습니다.

@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}

 

위와 같은 방식으로 한정자 메타데이터를 제공하면 클래스 정의에 메타데이터가 고정됩니다. 반면, XML 설정을 사용하면 동일한 타입의 여러 빈이 각기 다른 한정자 메타데이터를 가질 수 있기 때문에, 인스턴스마다 다른 한정자를 지정할 수 있습니다.


JSR 330 표준 어노테이션 사용

스프링은 JSR-330 표준 어노테이션(의존성 주입)을 지원합니다. 이러한 어노테이션들은 스프링 어노테이션과 동일한 방식으로 스캔됩니다. 이를 사용하려면 관련된 jar 파일이 클래스 경로에 포함되어 있어야 합니다.

 

Maven 의존성 추가

Maven을 사용하는 경우 jakarta.inject 아티팩트는 표준 Maven 저장소에서 사용할 수 있습니다. 이를 pom.xml 파일에 다음과 같이 추가할 수 있습니다.

<dependency>
	<groupId>jakarta.inject</groupId>
	<artifactId>jakarta.inject-api</artifactId>
	<version>2.0.0</version>
</dependency>

 

@Inject 및 @Named를 사용한 의존성 주입

@Autowired 대신 @jakarta.inject.Inject를 사용할 수 있습니다.

import jakarta.inject.Inject;

public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	public void listMovies() {
		this.movieFinder.findMovies(...);
		// ...
	}
}

 

@Autowired와 마찬가지로 @Inject는 필드, 메소드, 생성자-인자 수준에서 사용할 수 있습니다. 또한 Provider를 사용하여 짧은 범위의 빈에 대한 온디맨드 액세스 또는 다른 빈에 대한 지연 액세스를 구현할 수 있습니다. 다음 예제는 Provider를 사용하는 변형입니다.

import jakarta.inject.Inject;
import jakarta.inject.Provider;

public class SimpleMovieLister {

	private Provider<MovieFinder> movieFinder;

	@Inject
	public void setMovieFinder(Provider<MovieFinder> movieFinder) {
		this.movieFinder = movieFinder;
	}

	public void listMovies() {
		this.movieFinder.get().findMovies(...);
		// ...
	}
}

 

@Named를 사용한 의존성 명명

특정 의존성에 대해 명명된 빈을 주입하려면 @Named 어노테이션을 사용할 수 있습니다.

import jakarta.inject.Inject;
import jakarta.inject.Named;

public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

 

@Autowired와 마찬가지로 @Inject도 java.util.Optional 또는 @Nullable과 함께 사용할 수 있습니다. @Inject에는 required 속성이 없으므로, 이 기능이 더욱 유용할 수 있습니다. 다음 예제는 이를 보여줍니다.

import java.util.Optional;
import jakarta.inject.Inject;

public class SimpleMovieLister {

	@Inject
	public void setMovieFinder(Optional<MovieFinder> movieFinder) {
		// ...
	}
}
import jakarta.inject.Inject;
import jakarta.annotation.Nullable;

public class SimpleMovieLister {

	@Inject
	public void setMovieFinder(@Nullable MovieFinder movieFinder) {
		// ...
	}
}

 

@Named 및 @ManagedBean: @Component의 표준 대응

@Component 대신 @jakarta.inject.Named 또는 @jakarta.annotation.ManagedBean을 사용할 수 있습니다.

import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener")도 사용할 수 있음
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

 

@Component와 마찬가지로 이름을 지정하지 않고도 @Named를 사용할 수 있습니다.

import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Inject
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

 

@Named 또는 @ManagedBean을 사용할 경우, 스프링 어노테이션을 사용할 때와 동일한 방식으로 컴포넌트 스캔을 사용할 수 있습니다.

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
	// ...
}

 

JSR-330의 @Named와 JSR-250의 @ManagedBean은 컴포저블하지 않으므로, 커스텀 컴포넌트 어노테이션을 구축하려면 스프링의 스테레오타입 모델을 사용해야 합니다.

 

JSR-330 표준 어노테이션의 제한 사항

JSR-330 표준 어노테이션을 사용할 때는 몇 가지 중요한 기능이 제공되지 않는다는 점을 염두에 두어야 합니다. 아래 표는 스프링 컴포넌트 모델과 JSR-330 간의 차이를 보여줍니다.

스프링 jakarta.inject.* 제한 사항
@Autowired @Inject required 속성이 없으며, Optional과 함께 사용 가능
@Component @Named / @ManagedBean 컴포저블 모델 미제공, 이름으로 컴포넌트 식별만 가능
@Scope("singleton") @Singleton JSR-330의 기본 범위는 Spring의 prototype과 유사하지만 Spring에서는 singleton이 기본값
@Qualifier @Qualifier / @Named 커스텀 한정자를 구축하는 데 사용
@Value 없음 해당 없음
@Lazy 없음 해당 없음
ObjectFactory Provider Provider.get()을 사용하여 지연 액세스 가능

Java-based Container Configuration


기본 개념: @Bean 및 @Configuraiton

Spring의 Java 기반 구성에서 중심적인 역할을 하는 두 가지 주요 아티팩트는 @Configuration으로 주석이 달린 클래스와 @Bean으로 주석이 달린 메서드입니다.

 

@Bean 어노테이션

@Bean 어노테이션은 해당 메서드가 새로운 객체를 인스턴스화하고, 구성하며, 초기화하여 Spring IoC 컨테이너가 관리할 수 있도록 한다는 것을 나타냅니다. Spring의 <beans/> XML 구성에 익숙한 사람들에게 @Bean 어노테이션은 <bean/> 요소와 동일한 역할을 합니다. @Bean으로 주석이 달린 메서드는 모든 Spring @Component와 함께 사용할 수 있지만, 가장 일반적으로는 @Configuration 클래스와 함께 사용됩니다.

 

@Configuration 어노테이션

클래스에 @Configuration을 주석으로 달면 해당 클래스의 주요 목적이 빈 정의의 소스 역할을 한다는 것을 나타냅니다. 또한, @Configuration 클래스는 동일한 클래스 내에서 다른 @Bean 메서드를 호출하여 빈 간의 의존성을 정의할 수 있도록 해줍니다.

 

가장 간단한 @Configuration 클래스는 다음과 같이 작성할 수 있습니다.

@Configuration
public class AppConfig {

	@Bean
	public MyServiceImpl myService() {
		return new MyServiceImpl();
	}
}

 

이 AppConfig 클래스는 다음과 같은 Spring <beans/> XML 구성과 동일합니다:

<beans>
	<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

 

@Configuration 클래스 내에서의 @Bean 메서드 호출

일반적인 시나리오에서 @Bean 메서드는 @Configuration 클래스 내에서 선언되며, 이는 전체 구성 클래스 처리가 적용되고 메서드 간의 참조가 컨테이너의 생명주기 관리로 리디렉션된다는 것을 보장합니다. 이를 통해 @Bean 메서드가 일반적인 Java 메서드 호출을 통해 우연히 호출되는 것을 방지하여 추적하기 어려운 미묘한 버그를 줄일 수 있습니다.

 

반면, @Bean 메서드가 @Configuration으로 주석이 달리지 않은 클래스 내에 선언되었거나 @Configuration(proxyBeanMethods=false)로 선언된 경우, 이들은 "lite" 모드에서 처리됩니다. 이 경우, @Bean 메서드는 특별한 런타임 처리가 없는 일반적인 팩토리 메서드로 동작하며, CGLIB 하위 클래스를 생성하지 않습니다. 이 경우 메서드를 호출하면 컨테이너에 의해 가로채지 않고 새로운 인스턴스가 매번 생성되며, 기존의 싱글톤(또는 범위가 있는) 인스턴스가 재사용되지 않습니다.

 

따라서, 런타임 프록시 없이 선언된 클래스의 @Bean 메서드는 빈 간 의존성을 선언하는 데 사용되지 않습니다. 대신, 해당 메서드는 주로 해당 컴포넌트의 필드나 팩토리 메서드의 인자로 동작하여 자동 주입된 협력자를 수신하도록 기대됩니다. 이러한 @Bean 메서드는 다른 @Bean 메서드를 호출할 필요가 없으며, 모든 호출은 팩토리 메서드의 인자를 통해 표현될 수 있습니다. 이 접근 방식의 긍정적인 부작용은 런타임 시 CGLIB 하위 클래스를 적용할 필요가 없으므로 오버헤드와 메모리 사용량이 감소한다는 점입니다.

 

이제 @Bean 및 @Configuration 어노테이션에 대해 더 깊이 다뤄보겠습니다. 먼저 Java 기반 구성을 사용하여 Spring 컨테이너를 생성하는 다양한 방법에 대해 알아보겠습니다.


Spring Container 생성: AnnotationConfigApplicationContext 사용

Spring 3.0에 도입된 AnnotationConfigApplicationContext는 다양한 애플리케이션 컨텍스트를 제공하는 구현체로, @Configuration 클래스뿐만 아니라 @Component 클래스나 JSR-330 메타데이터가 포함된 클래스를 입력으로 받을 수 있습니다.

 

@Configuration 클래스

@Configuration 클래스를 입력으로 제공할 경우, 해당 클래스 자체는 빈 정의로 등록되고 클래스 내에 선언된 모든 @Bean 메서드도 빈 정의로 등록됩니다.

 

@Component 및 JSR-330 클래스

@Component 또는 JSR-330 애노테이션이 적용된 클래스가 제공되면 해당 클래스들은 빈 정의로 등록되고, 필요할 경우 @Autowired나 @Inject 같은 의존성 주입 메타데이터가 사용됩니다.

 

간단한 컨테이너 생성

XML 파일을 사용해 ClassPathXmlApplicationContext를 생성하는 방식과 마찬가지로, @Configuration 클래스를 입력으로 사용하여 AnnotationConfigApplicationContext를 인스턴스화할 수 있습니다. 이로써 XML을 완전히 사용하지 않고 Spring 컨테이너를 사용할 수 있습니다. 아래는 그 예입니다:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

 

위 예제는 AppConfig 클래스를 사용해 애플리케이션 컨텍스트를 구성하고, MyService 빈을 가져와 메서드를 실행합니다.

 

또한, AnnotationConfigApplicationContext는 @Configuration 클래스 외에도 @Component나 JSR-330 애노테이션이 있는 클래스를 입력으로 받을 수 있습니다.

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

 

위 예제에서는 MyServiceImpl, Dependency1, Dependency2 클래스가 @Autowired 같은 의존성 주입 애노테이션을 사용한다고 가정합니다.

 

register(Class<?>...​) 메서드를 사용해 컨테이너 프로그래밍 방식으로 구성하기

AnnotationConfigApplicationContext는 기본 생성자를 통해 인스턴스화할 수 있으며, 그 후 register() 메서드를 사용해 클래스들을 등록할 수 있습니다. 이 방식은 프로그래밍 방식으로 컨텍스트를 구성할 때 유용합니다.

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

 

이 코드는 AppConfig, OtherConfig, AdditionalConfig 클래스들을 프로그램 방식으로 등록하고 컨텍스트를 새로고침한 후 MyService 빈을 가져옵니다.

 

scan(String...​) 메서드를 사용해 컴포넌트 스캔 활성화하기

컴포넌트 스캔을 활성화하려면 @Configuration 클래스에 @ComponentScan 애노테이션을 사용하면 됩니다.

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    // ...
}

 

위 애노테이션은 com.acme 패키지에서 @Component 애노테이션이 달린 클래스를 스캔하고 이를 Spring 빈 정의로 등록합니다.

 

AnnotationConfigApplicationContext는 scan(String...​) 메서드를 제공해 동일한 컴포넌트 스캔 기능을 수행할 수 있습니다.

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

 

위 예제는 com.acme 패키지를 스캔하여 MyService 빈을 가져오는 예제입니다.

 

웹 애플리케이션 지원: AnnotationConfigWebApplicationContext

Spring은 웹 애플리케이션 컨텍스트를 지원하는 AnnotationConfigWebApplicationContext라는 변형을 제공합니다. 이 구현은 Spring ContextLoaderListener, Spring MVC DispatcherServlet 등을 구성할 때 사용할 수 있습니다. 다음은 Spring MVC 웹 애플리케이션을 구성하는 예입니다.

<web-app>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

 

위 예제는 web.xml에서 AnnotationConfigWebApplicationContext를 사용해 Spring MVC 애플리케이션을 구성하는 방법을 보여줍니다.

 

AnnotationConfigApplicationContext를 사용하여 XML이 아닌 Java 기반으로 Spring 컨테이너를 유연하게 구성할 수 있으며, 컴포넌트 스캔 및 웹 애플리케이션 지원도 함께 가능합니다.


Spring의 @Bean 애노테이션 사용

@Bean 애노테이션은 메서드 레벨의 애노테이션으로, Spring XML의 <bean/> 요소와 동일한 역할을 수행합니다. 이 애노테이션은 다음과 같은 속성들을 지원합니다:

 

  • init-method
  • destroy-method
  • autowiring
  • name

@Bean 애노테이션은 @Configuration 또는 @Component로 애노테이션된 클래스에서 사용할 수 있습니다.

 

Bean 선언

Bean을 선언하려면 메서드에 @Bean 애노테이션을 추가합니다. 이 메서드는 반환 타입에 해당하는 객체를 Spring IoC 컨테이너에 등록합니다. 기본적으로 Bean 이름은 메서드 이름과 동일합니다. 아래는 @Bean 메서드 선언 예제입니다:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

 

 

위 설정은 다음 XML 구성과 동일한 역할을 합니다:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

 

이 두 선언 모두 transferService라는 이름의 Bean을 TransferServiceImpl 타입의 객체와 함께 ApplicationContext에 등록합니다.

 

 

인터페이스 타입으로 Bean 선언

 

Bean 메서드는 인터페이스 또는 기본 클래스 반환 타입을 사용할 수도 있습니다:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

 

하지만, 이렇게 하면 Spring 컨테이너가 전체 타입 정보를 빈 인스턴스화 시점까지 알지 못하게 되어 타입 예측이 제한될 수 있습니다. 만약 구체적인 구현체를 사용하려면 가장 구체적인 반환 타입을 선언하는 것이 안전합니다.

 

Bean 의존성

@Bean 애노테이션이 있는 메서드는 필요한 의존성을 메서드 파라미터로 받을 수 있습니다. 예를 들어 TransferService가 AccountRepository를 필요로 한다면, 이를 메서드 파라미터로 정의할 수 있습니다:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

 

이 메커니즘은 생성자 기반 의존성 주입과 동일한 방식으로 작동합니다.

 

라이프사이클 콜백 지원

@Bean 애노테이션으로 정의된 클래스는 @PostConstruct 및 @PreDestroy와 같은 JSR-250 라이프사이클 애노테이션을 사용할 수 있으며, InitializingBean, DisposableBean, Lifecycle 인터페이스를 구현한 경우에도 해당 메서드들이 컨테이너에 의해 호출됩니다.

 

또한, @Bean 애노테이션을 사용해 임의의 초기화 및 소멸 콜백 메서드를 지정할 수 있습니다:

public class BeanOne {

    public void init() {
        // 초기화 로직
    }
}

public class BeanTwo {

    public void cleanup() {
        // 소멸 로직
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

 

위 예제에서는 BeanOne 클래스의 초기화 메서드 init()과 BeanTwo 클래스의 소멸 메서드 cleanup()이 지정되었습니다.

 

 

자동 소멸 콜백 방지

 

기본적으로, Java 설정에서 정의된 Bean이 public close() 또는 shutdown() 메서드를 가지고 있다면 Spring은 이를 자동으로 소멸 콜백으로 사용합니다. 이를 방지하려면 @Bean(destroyMethod = "")를 사용해 자동 소멸 모드를 비활성화할 수 있습니다.

 

예를 들어, JNDI로 얻은 리소스의 경우 애플리케이션 외부에서 라이프사이클이 관리되므로 자동 소멸을 방지해야 합니다:

@Bean(destroyMethod = "")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

 

직접 초기화 메서드 호출

Java에서 직접 작업할 경우 컨테이너 라이프사이클에 의존하지 않고 객체의 초기화 메서드를 직접 호출할 수도 있습니다:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }
}

 

이 경우, BeanOne의 init() 메서드를 Bean 생성 시 직접 호출합니다.

 

@Bean 애노테이션을 사용하면 Java 코드에서 객체를 구성하고 초기화하는 과정을 유연하게 제어할 수 있으며, 필요에 따라 Spring 컨테이너 라이프사이클과 결합하지 않고도 사용할 수 있습니다.


Spring의 @Configuration 애노테이션 사용

@Configuration 애노테이션은 클래스 레벨에서 사용되며, 해당 클래스가 빈 정의를 제공하는 클래스임을 나타냅니다. @Configuration 클래스는 @Bean 애노테이션이 붙은 메서드를 통해 빈을 선언하고 관리합니다. 또한, 이 클래스 내에서 메서드 간의 호출을 통해 빈 간의 의존성을 설정할 수 있습니다.

 

빈 간의 의존성 주입

빈들이 서로 의존성을 가질 때, 한 빈의 메서드에서 다른 빈의 메서드를 호출하는 방식으로 의존성을 설정할 수 있습니다. 다음 예제는 빈 간의 의존성을 선언하는 방법을 보여줍니다:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

 

위의 예제에서 beanOne은 생성자 주입을 통해 beanTwo를 의존성으로 받고 있습니다.

 

이 방식은 오직 @Configuration으로 애노테이션된 클래스 내에서만 작동합니다. 일반 @Component 클래스에서는 이러한 방식으로 빈 간의 의존성을 선언할 수 없습니다.

 

Lookup Method Injection

Lookup Method Injection은 고급 기능으로, 주로 싱글톤 스코프의 빈이 프로토타입 스코프의 빈을 필요로 할 때 사용됩니다. 자바를 사용한 구성에서는 이 패턴을 자연스럽게 구현할 수 있습니다. 다음 예제는 Lookup Method Injection을 사용하는 방법을 보여줍니다:

public abstract class CommandManager {
    public Object process(Object commandState) {
        Command command = createCommand(); // 새로운 Command 인스턴스 생성
        command.setState(commandState);
        return command.execute();
    }

    protected abstract Command createCommand();
}

 

위 코드에서 createCommand()는 실제 구현체가 제공되지 않았습니다. Java 구성에서는 이를 해결하기 위해 CommandManager의 하위 클래스를 만들어 createCommand() 메서드를 오버라이드하여 새로운 프로토타입 객체를 반환할 수 있습니다:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    return new AsyncCommand();
}

@Bean
public CommandManager commandManager() {
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand(); // 새로운 프로토타입 Command 인스턴스 반환
        }
    };
}

 

이 방식으로 CommandManager는 매번 새로운 AsyncCommand 객체를 생성하여 사용할 수 있습니다.

 

Java 기반 설정의 내부 동작 방식

아래 예제는 @Bean 애노테이션이 붙은 메서드가 두 번 호출될 때의 동작 방식을 보여줍니다:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

 

이 코드에서는 clientDao() 메서드가 clientService1()과 clientService2()에서 각각 한 번씩 호출되었습니다. 일반적으로는 ClientDaoImpl의 새로운 인스턴스가 두 번 생성될 것이라고 예상할 수 있지만, Spring에서는 기본적으로 모든 빈이 싱글톤으로 관리되므로 동일한 인스턴스가 반환됩니다.

 

이러한 동작은 @Configuration 클래스가 CGLIB에 의해 서브클래싱되어 구현됩니다. CGLIB 서브클래스에서는 메서드 호출 시 컨테이너에서 해당 빈이 이미 생성되어 있는지를 먼저 확인한 후, 없으면 새로 생성합니다. 이 덕분에 빈의 스코프가 싱글톤일 때 중복 인스턴스 생성이 방지됩니다.

 

 

CGLIB의 제한사항

  • @Configuration 클래스는 final로 선언될 수 없습니다.
  • 생성자 주입을 허용하지만, CGLIB 서브클래싱을 피하고 싶다면 @Configuration(proxyBeanMethods = false)를 선언하거나, @Bean 메서드를 일반 @Component 클래스에 선언하는 것이 좋습니다.

이와 같은 방법으로 CGLIB의 동적 서브클래싱 기능을 우회하면서 @Bean 메서드를 사용할 수 있습니다.


자바 기반 설정의 구성 (Composing Java-based Configurations)

스프링의 자바 기반 설정 기능을 사용하면 애노테이션을 조합하여 설정을 구성할 수 있으며, 이를 통해 설정의 복잡성을 줄일 수 있습니다.

 

@Import 애노테이션 사용하기

XML 구성에서 <import/> 요소를 사용해 설정을 모듈화하는 것처럼, @Import 애노테이션을 사용하면 다른 구성 클래스에서 @Bean 정의를 로드할 수 있습니다. 다음 예제를 통해 이를 설명하겠습니다:

@Configuration
public class ConfigA {

	@Bean
	public A a() {
		return new A();
	}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

	@Bean
	public B b() {
		return new B();
	}
}

 

위 설정에서는 ConfigA와 ConfigB가 두 가지 구성을 정의합니다. 이제 컨텍스트를 인스턴스화할 때 ConfigB만 명시적으로 제공하면 됩니다.

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

	// 이제 A와 B 두 개의 빈이 모두 사용 가능합니다.
	A a = ctx.getBean(A.class);
	B b = ctx.getBean(B.class);
}

 

이 방식은 하나의 클래스만 다루면 되므로, 다수의 @Configuration 클래스를 기억할 필요가 없어져 컨테이너 초기화가 더 간단해집니다.

 

 

의존성 주입을 통한 @Bean 정의 가져오기

 

위의 예제는 간단한 예이지만, 실제 시나리오에서는 빈들이 서로 다른 구성 클래스에 정의된 빈에 의존할 가능성이 큽니다. XML 구성에서는 컴파일러 제약이 없으므로 ref="someBean"을 사용해 스프링이 초기화 중에 이를 해결할 수 있습니다. 하지만 자바 구성에서는 컴파일러가 올바른 자바 구문을 요구하므로, @Bean 메서드의 파라미터로 다른 빈의 참조를 전달하는 방식으로 해결할 수 있습니다. 예를 들어 다음과 같은 구성을 살펴보겠습니다:

@Configuration
public class ServiceConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

	@Bean
	public AccountRepository accountRepository(DataSource dataSource) {
		return new JdbcAccountRepository(dataSource);
	}
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// 새로운 DataSource 반환
	}
}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	// 모든 것이 구성 클래스 간에 연결됩니다.
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}

 

 

@Autowired를 사용한 의존성 주입

 

@Configuration 클래스는 일반적인 빈이므로, @Autowired를 사용해 다른 빈을 주입받을 수 있습니다. 다음은 그 예입니다:

@Configuration
public class ServiceConfig {

	@Autowired
	private AccountRepository accountRepository;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

	private final DataSource dataSource;

	public RepositoryConfig(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}
}

 

이 방식은 @Autowired를 사용하여 의존성을 주입하고 모듈화된 구성을 지원합니다. 그러나 @Autowired로 의존성을 주입받을 때 어느 클래스에서 해당 빈이 정의되었는지 명확하지 않을 수 있습니다. 이를 보완하려면 구성 클래스 자체를 주입받아 사용하는 방법이 있습니다.

@Configuration
public class ServiceConfig {

	@Autowired
	private RepositoryConfig repositoryConfig;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(repositoryConfig.accountRepository());
	}
}

 

이 방식으로 빈이 정의된 위치가 명확해지지만, 구성 클래스 간의 강한 결합이 발생할 수 있습니다.

 

 

@Configuration 클래스 또는 @Bean 메서드를 조건부로 포함하기

 

특정 시스템 상태에 따라 @Configuration 클래스 전체나 개별 @Bean 메서드를 조건부로 활성화 또는 비활성화하는 것이 유용할 수 있습니다. 예를 들어, @Profile 애노테이션을 사용해 특정 프로필이 활성화되었을 때만 빈을 활성화하는 방법을 사용할 수 있습니다.

 

@Profile은 보다 유연한 @Conditional 애노테이션으로 구현되었습니다. @Conditional은 특정 Condition 구현체를 지정하며, @Bean이 등록되기 전에 호출됩니다.

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	// @Profile 애노테이션 속성을 읽음
	MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
	if (attrs != null) {
		for (Object value : attrs.get("value")) {
			if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
				return true;
			}
		}
		return false;
	}
	return true;
}

 

이 방식으로 유연한 조건부 빈 등록을 구현할 수 있습니다.

 

조건부로 @Configuration 클래스 또는 @Bean 메서드 포함하기

스프링의 자바 기반 설정은 스프링 XML 설정을 완전히 대체하려는 것이 아닙니다. XML이 더 편리하거나 필요한 경우, ClassPathXmlApplicationContext 같은 방법으로 컨테이너를 XML 방식으로 인스턴스화하거나 AnnotationConfigApplicationContext와 @ImportResource 애노테이션을 사용하여 필요한 만큼의 XML을 가져올 수 있습니다.

 

 

XML 기반 설정에서 @Configuration 클래스 사용하기

 

대규모 기존 코드베이스에서 스프링 XML을 사용 중인 경우, 필요에 따라 @Configuration 클래스를 작성하고 기존 XML 파일에서 포함하는 방식이 더 쉽습니다.

 

 

@Configuration 클래스를 일반적인 <bean/> 요소로 선언하기

 

@Configuration 클래스는 결국 컨테이너 내의 빈 정의입니다. 예를 들어, AppConfig라는 @Configuration 클래스를 생성하고 이를 system-test-config.xml에 <bean/> 정의로 포함할 수 있습니다. 이때 <context:annotation-config/>가 활성화되어 있으면, 컨테이너는 @Configuration 애노테이션을 인식하고 AppConfig 내에 선언된 @Bean 메서드를 적절히 처리합니다.

@Configuration
public class AppConfig {

	@Autowired
	private DataSource dataSource;

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}

	@Bean
	public TransferService transferService() {
		return new TransferService(accountRepository());
	}
}

 

다음은 system-test-config.xml 파일의 예시입니다.

<beans>
	<context:annotation-config/>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

	<bean class="com.acme.AppConfig"/>

	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

 

 

@ImportResource를 사용한 자바 설정에서 XML 포함하기

 

자바 기반 설정이 컨테이너를 구성하는 주요 메커니즘일 때에도 일부 XML을 사용할 필요가 있을 수 있습니다. 이 경우 @ImportResource 애노테이션을 사용하여 필요한 만큼의 XML을 정의할 수 있습니다. 이를 통해 XML 사용을 최소화하면서 "자바 중심"의 설정 접근 방식을 유지할 수 있습니다. 다음 예제는 이를 보여줍니다:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

	@Value("${jdbc.url}")
	private String url;

	@Value("${jdbc.username}")
	private String username;

	@Value("${jdbc.password}")
	private String password;

	@Bean
	public DataSource dataSource() {
		return new DriverManagerDataSource(url, username, password);
	}
}

 

다음은 properties-config.xml 파일의 예시입니다:

<beans>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

 

jdbc.properties 파일의 내용은 다음과 같습니다:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

 

마지막으로, main 메서드를 통해 스프링 애플리케이션을 시작할 수 있습니다:

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	TransferService transferService = ctx.getBean(TransferService.class);
	// ...
}

스프링의 환경 추상화 (Environment Abstraction)

스프링의 Environment 인터페이스는 두 가지 중요한 측면을 모델링하는 추상화입니다: 프로파일프로퍼티입니다.

 

  • 프로파일은 특정 프로파일이 활성화된 경우에만 등록될 빈 정의의 논리적 그룹입니다. XML 또는 어노테이션을 사용하여 빈을 특정 프로파일에 할당할 수 있습니다. Environment 객체는 현재 활성화된 프로파일이 무엇인지, 기본적으로 활성화해야 할 프로파일이 무엇인지를 결정하는 역할을 합니다.
  • 프로퍼티는 애플리케이션에서 중요한 역할을 하며, 다양한 소스에서 제공될 수 있습니다. 예를 들어 프로퍼티 파일, JVM 시스템 프로퍼티, 시스템 환경 변수, JNDI, 서블릿 컨텍스트 파라미터 등이 있습니다. Environment 객체는 이러한 프로퍼티 소스들을 구성하고, 프로퍼티를 쉽게 해결할 수 있는 서비스 인터페이스를 제공합니다.

빈 정의 프로파일 (Bean Definition Profiles)

빈 정의 프로파일은 다양한 환경에서 서로 다른 빈을 등록할 수 있는 메커니즘을 제공합니다. 예를 들어 개발 환경에서는 임베디드 데이터소스를 사용할 수 있지만, QA나 운영 환경에서는 JNDI를 통해 데이터소스를 가져올 수 있습니다.

 

@Profile을 사용한 프로파일 지정

@Profile 어노테이션을 사용하면 특정 프로파일이 활성화될 때만 컴포넌트가 등록되도록 할 수 있습니다. 예를 들어, 개발 환경과 운영 환경에서 서로 다른 데이터소스를 사용할 수 있도록 설정할 수 있습니다.

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}

@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod = "")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

 

 

위 코드에서, StandaloneDataConfig는 개발 환경에서만 데이터소스를 등록하고, JndiDataConfig는 운영 환경에서 JNDI를 통해 데이터소스를 가져옵니다.

 

XML에서의 프로파일 정의

XML 설정에서도 프로파일을 사용할 수 있으며, beans 요소에 profile 속성을 추가하면 됩니다.

<beans profile="development">
    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>

<beans profile="production">
    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

 

프로파일 활성화

프로파일을 활성화하는 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 ApplicationContext에서 Environment API를 사용하는 것입니다.

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

 

또는 spring.profiles.active 속성을 통해 선언적으로 활성화할 수 있습니다. 예를 들어 시스템 환경 변수나 JVM 시스템 프로퍼티를 통해 설정할 수 있습니다.

-Dspring.profiles.active="profile1,profile2"

 

기본 프로파일 (Default Profile)

기본 프로파일은 활성화된 프로파일이 없을 때 사용되는 프로파일입니다. 예를 들어 다음과 같은 설정에서, 다른 프로파일이 활성화되지 않은 경우 기본 데이터소스가 생성됩니다.

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

 

기본 프로파일의 이름은 default입니다. 필요하면 Environment의 setDefaultProfiles() 메서드를 사용해 기본 프로파일 이름을 변경할 수 있습니다.


Spring의 PropertySource 추상화

Spring의 Environment 추상화는 구성 가능한 프로퍼티 소스 계층에서 검색 작업을 수행하는 기능을 제공합니다. 이는 애플리케이션에서 다양한 프로퍼티 소스(예: 시스템 환경 변수, 시스템 속성, 프로퍼티 파일 등)에서 프로퍼티를 가져올 수 있도록 도와줍니다.

 

Environment와 PropertySource

Environment 객체는 프로퍼티의 존재 여부를 확인하기 위해 다양한 PropertySource 객체를 검색합니다. PropertySource는 키-값 쌍을 가진 모든 소스에 대한 추상화입니다. 기본적으로 Spring의 StandardEnvironment는 두 가지 기본 PropertySource를 제공합니다: 하나는 JVM 시스템 속성 (System.getProperties()), 또 다른 하나는 시스템 환경 변수 (System.getenv())를 나타냅니다.

 

다음 코드는 환경에 "my-property" 프로퍼티가 정의되어 있는지를 확인하는 예입니다:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

 

이 코드는 my-property라는 시스템 속성이나 환경 변수가 런타임에 설정되어 있는지 확인하고, 그 값이 있으면 true를 반환합니다. 기본적으로, 시스템 속성이 환경 변수보다 우선합니다.

 

PropertySource 계층

Spring의 StandardServletEnvironment에서는 서블릿 구성 파라미터, 서블릿 컨텍스트 파라미터, JNDI 환경 변수, JVM 시스템 속성 및 시스템 환경 변수 등 다양한 소스에서 프로퍼티를 가져옵니다. 이러한 계층에서 검색이 이루어지며, 시스템 속성이 환경 변수보다 우선합니다.

 

사용자 정의 PropertySource

Spring에서는 기본 PropertySource 외에도 사용자 정의 PropertySource를 등록하여 사용할 수 있습니다. 예를 들어, 사용자 정의 프로퍼티 소스를 추가하여 검색 우선순위를 변경할 수 있습니다:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

 

위 코드는 MyPropertySource를 가장 높은 우선순위로 추가하여 my-property 프로퍼티가 해당 소스에 있을 경우 이를 먼저 반환하도록 설정합니다.


@PropertySource 어노테이션 사용

@PropertySource 어노테이션은 선언적으로 프로퍼티 파일을 환경에 추가하는 편리한 방법을 제공합니다. 다음 예시에서 app.properties 파일에 정의된 프로퍼티를 사용하여 빈을 설정합니다:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

 

@PropertySource는 환경 내에 프로퍼티 소스를 추가하며, 이를 통해 설정된 파일에서 프로퍼티를 읽을 수 있습니다.

 

@PropertySource에서의 플레이스홀더 사용

@PropertySource 어노테이션 내에서 프로퍼티 플레이스홀더를 사용할 수도 있습니다. 플레이스홀더는 이미 등록된 프로퍼티 소스에서 해결됩니다:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

 

위 코드에서 my.placeholder는 이미 등록된 프로퍼티 소스에서 해결됩니다. 값이 없을 경우 default/path가 기본 경로로 사용됩니다.


플레이스홀더 해석

이제 Spring의 Environment 추상화가 통합되었기 때문에, 이전에는 JVM 시스템 속성이나 환경 변수에서만 해결되던 플레이스홀더를 더 유연하게 처리할 수 있습니다. 예를 들어, 다음 구문은 customer 프로퍼티가 어느 소스에 정의되어 있든 상관없이 작동합니다:

<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
</beans>

Registering a LoadTimeWeaver

Spring의 LoadTimeWeaver는 Java 가상 머신(JVM)에 클래스를 로드할 때 동적으로 변환할 수 있도록 지원하는 도구입니다. 이는 클래스가 JVM에 로드될 때 변환할 수 있는 기능을 제공하며, 특히 AspectJ와 JPA 지원에서 유용하게 사용됩니다.

 

LoadTimeWeaver 등록 방법

Java 기반 설정

Java 기반 설정에서는 @EnableLoadTimeWeaving 어노테이션을 사용하여 로드 타임 위빙을 활성화할 수 있습니다. 이 어노테이션을 @Configuration 클래스에 추가하면 해당 설정 내에서 로드 타임 위빙이 활성화됩니다.

 

예를 들어, 다음과 같이 구성할 수 있습니다:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
    // Configuration code here
}

 

이 설정이 적용되면 ApplicationContext 내의 모든 빈이 로드 타임 위버(LoadTimeWeaver)를 사용할 수 있으며, LoadTimeWeaverAware 인터페이스를 구현하는 빈은 로드 타임 위버 인스턴스를 참조할 수 있습니다.

 

XML 기반 설정

XML 구성에서도 동일한 기능을 제공할 수 있습니다. context:load-time-weaver 요소를 사용하여 XML에서 로드 타임 위빙을 활성화할 수 있습니다:

<beans>
	<context:load-time-weaver/>
</beans>

 

LoadTimeWeaverAware 사용

LoadTimeWeaverAware 인터페이스를 구현하는 빈은 ApplicationContext 내에서 로드 타임 위버 인스턴스를 받을 수 있습니다. 이는 특히 Spring의 JPA 지원에서 유용하며, JPA 클래스 변환이 필요한 경우 로드 타임 위빙을 통해 이를 처리할 수 있습니다.

 

예를 들어, LocalContainerEntityManagerFactoryBean과 같은 빈에서 JPA 클래스 변환이 필요하다면, LoadTimeWeaverAware를 통해 로드 타임 위버 인스턴스를 주입받아 사용할 수 있습니다.


ApplicationContext의 추가 기능

Spring의 org.springframework.beans.factory 패키지는 기본적으로 빈을 관리하고 조작하는 기능을 제공합니다. org.springframework.context 패키지는 이러한 BeanFactory 기능을 확장하여 ApplicationContext 인터페이스를 제공합니다. 이 인터페이스는 BeanFactory를 확장할 뿐만 아니라, 애플리케이션 프레임워크 스타일의 추가 기능도 제공합니다.

 

대부분의 사용자는 ApplicationContext를 완전히 선언적 방식으로 사용하며, 이를 프로그래밍적으로 생성하지 않고 ContextLoader와 같은 지원 클래스를 사용하여 Jakarta EE 웹 애플리케이션의 정상적인 시작 과정에서 자동으로 인스턴스화되도록 합니다.

 

BeanFactory의 기능을 더 프레임워크 중심으로 향상시키기 위해 context 패키지는 다음과 같은 기능을 제공합니다:

  1. 국제화(i18n) 스타일로 메시지에 접근: MessageSource 인터페이스를 통해 지원됩니다.
  2. 리소스 접근: ResourceLoader 인터페이스를 통해 URL 및 파일과 같은 리소스에 접근할 수 있습니다.
  3. 이벤트 게시: ApplicationEventPublisher 인터페이스를 사용하여 ApplicationListener 인터페이스를 구현한 빈에 이벤트를 게시할 수 있습니다.
  4. 계층적 컨텍스트 로딩: 각 컨텍스트가 애플리케이션의 특정 계층에 집중할 수 있도록 HierarchicalBeanFactory 인터페이스를 통해 다중(계층적) 컨텍스트를 로드할 수 있습니다.

국제화 사용: MessageSource

ApplicationContext 인터페이스는 MessageSource를 확장하므로 국제화 기능을 제공합니다. 또한, Spring은 메시지 계층적으로 해결할 수 있는 HierarchicalMessageSource 인터페이스도 제공합니다. 이를 통해 메시지 해석의 기반을 마련합니다.

 

주요 메서드

 

  • String getMessage(String code, Object[] args, String default, Locale loc)
    • 메시지를 검색하는 기본 메서드입니다. 지정된 로케일에서 메시지를 찾을 수 없을 경우 기본 메시지를 사용합니다.
  • String getMessage(String code, Object[] args, Locale loc)
    • 기본 메시지를 지정하지 않고 메시지를 검색하는 메서드입니다. 메시지를 찾을 수 없으면 NoSuchMessageException이 발생합니다.
  • String getMessage(MessageSourceResolvable resolvable, Locale locale)
    • 모든 속성을 MessageSourceResolvable로 래핑하여 사용할 수 있는 메서드입니다.

ApplicationContext가 로드될 때 messageSource라는 이름의 빈이 정의되어 있으면 이 빈을 통해 모든 메시지 조회가 이루어집니다. 만약 messageSource가 없다면, DelegatingMessageSource가 빈 메시지 소스로 초기화됩니다.

 

MessageSource 구현체

 

  • ResourceBundleMessageSource
  • ReloadableResourceBundleMessageSource
  • StaticMessageSource

이 중 StaticMessageSource는 드물게 사용되며 프로그래밍 방식으로 메시지를 추가하는 기능을 제공합니다.

 

다음 예시는 ResourceBundleMessageSource를 설정하는 방법을 보여줍니다:

<beans>
	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>format</value>
				<value>exceptions</value>
				<value>windows</value>
			</list>
		</property>
	</bean>
</beans>

 

 

 

이 설정은 format.properties, exceptions.properties, windows.properties와 같은 리소스 번들을 클래스패스에 정의해야 합니다. 그런 다음 다음과 같이 MessageSource를 사용하여 메시지를 가져올 수 있습니다:

public static void main(String[] args) {
	MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
	String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
	System.out.println(message);
}

// 출력 결과
Alligators rock!

 

또한, 인수들을 전달하여 메시지를 형식화할 수도 있습니다:

public class Example {

	private MessageSource messages;

	public void setMessages(MessageSource messages) {
		this.messages = messages;
	}

	public void execute() {
		String message = this.messages.getMessage("argument.required",
			new Object [] {"userDao"}, "Required", Locale.ENGLISH);
		System.out.println(message);
	}
}

// 출력 결과
The userDao argument is required.

 

Spring의 MessageSource는 표준 JDK ResourceBundle과 동일한 로케일 해결 및 폴백 규칙을 따릅니다.

 

ReloadableResourceBundleMessageSource

ResourceBundleMessageSource의 대안으로 Spring은 ReloadableResourceBundleMessageSource를 제공합니다. 이 구현체는 클래스패스 이외의 위치에서 파일을 읽어오거나 핫 리로딩(재로딩)을 지원합니다. 이 클래스는 표준 ResourceBundleMessageSource보다 유연합니다.


표준 및 사용자 정의 이벤트

Spring의 ApplicationContext에서 이벤트 처리는 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 통해 제공됩니다. 만약 ApplicationListener 인터페이스를 구현한 빈이 컨텍스트에 배포되면, ApplicationContext에 게시되는 모든 ApplicationEvent가 해당 빈에 통지됩니다. 이는 표준 옵저버 디자인 패턴과 유사합니다.

 

Spring 4.2 이후, 이벤트 인프라가 크게 개선되었으며, 주석 기반 모델과 ApplicationEvent를 확장하지 않는 임의의 객체를 게시할 수 있는 기능이 추가되었습니다. 이러한 객체가 게시될 때, Spring이 자동으로 이를 이벤트로 래핑합니다.

 

Spring이 제공하는 표준 이벤트

 

주요 표준 이벤트

이벤트 설명
ContextRefreshedEvent ApplicationContext가 초기화 또는 갱신될 때 게시됩니다. 이때 모든 빈이 로드되고, 후처리 빈들이 활성화되며, 싱글톤 빈이 인스턴스화된 후 ApplicationContext가 사용 준비 상태에 도달합니다.
ContextStartedEvent ApplicationContext가 시작될 때 게시됩니다. 이는 주로 명시적으로 중지된 빈들을 다시 시작하거나, 초기화 시 자동 시작되지 않은 구성 요소들을 시작하는 데 사용됩니다.
ContextStoppedEvent ApplicationContext가 중지될 때 게시됩니다. 이때 모든 라이프사이클 빈들은 명시적으로 중지 신호를 받습니다. 중지된 컨텍스트는 나중에 start() 호출을 통해 다시 시작될 수 있습니다.
ContextClosedEvent ApplicationContext가 종료될 때 게시됩니다. 모든 싱글톤 빈이 파괴되며, 컨텍스트가 종료되면 더 이상 갱신 또는 재시작될 수 없습니다.
RequestHandledEvent HTTP 요청이 서비스된 후 게시되는 웹 전용 이벤트입니다. 이 이벤트는 Spring의 DispatcherServlet을 사용하는 웹 애플리케이션에서만 적용됩니다.
ServletRequestHandledEvent RequestHandledEvent의 하위 클래스이며, 서블릿 관련 컨텍스트 정보를 추가합니다.

 

사용자 정의 이벤트

사용자 정의 이벤트는 ApplicationEvent를 확장하여 쉽게 생성할 수 있습니다. 다음은 간단한 예입니다:

public class BlockedListEvent extends ApplicationEvent {
	private final String address;
	private final String content;

	public BlockedListEvent(Object source, String address, String content) {
		super(source);
		this.address = address;
		this.content = content;
	}

	// 접근자 및 기타 메서드...
}

 

이벤트를 게시하려면 ApplicationEventPublisher의 publishEvent() 메서드를 호출하면 됩니다. 일반적으로 ApplicationEventPublisherAware를 구현하는 클래스를 만들어 이를 Spring 빈으로 등록합니다.

public class EmailService implements ApplicationEventPublisherAware {
	private List<String> blockedList;
	private ApplicationEventPublisher publisher;

	public void setBlockedList(List<String> blockedList) {
		this.blockedList = blockedList;
	}

	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	public void sendEmail(String address, String content) {
		if (blockedList.contains(address)) {
			publisher.publishEvent(new BlockedListEvent(this, address, content));
			return;
		}
		// 이메일 전송 로직...
	}
}

 

이벤트를 수신하려면 ApplicationListener를 구현하는 클래스를 생성하고 이를 빈으로 등록하면 됩니다.

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
	private String notificationAddress;

	public void setNotificationAddress(String notificationAddress) {
		this.notificationAddress = notificationAddress;
	}

	@Override
	public void onApplicationEvent(BlockedListEvent event) {
		// 적절한 알림 처리...
	}
}

 

이벤트가 동기적으로 처리되므로, publishEvent() 메서드는 모든 리스너가 이벤트 처리를 완료할 때까지 블록됩니다. 비동기 이벤트 처리가 필요한 경우 ApplicationEventMulticaster를 사용하여 이벤트를 비동기적으로 처리할 수 있습니다.

 

어노테이션 기반 이벤트 리스너

Spring 4.2 이후로, @EventListener 어노테이션을 사용하여 메서드를 이벤트 리스너로 등록할 수 있습니다. 다음은 BlockedListNotifier를 어노테이션 기반으로 재작성한 예입니다:

public class BlockedListNotifier {
	private String notificationAddress;

	public void setNotificationAddress(String notificationAddress) {
		this.notificationAddress = notificationAddress;
	}

	@EventListener
	public void processBlockedListEvent(BlockedListEvent event) {
		// 알림 처리...
	}
}

 

추가적으로 @EventListener에 condition 속성을 사용해 특정 조건을 만족할 때만 이벤트를 처리하도록 할 수 있습니다.

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
	// 조건이 맞을 때 처리...
}

 

비동기 리스너

이벤트 리스너를 비동기적으로 처리하려면 @Async 주석을 추가하면 됩니다.

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
	// 별도의 스레드에서 처리...
}

 

비동기 리스너는 예외를 호출자에게 전파하지 않으며, 처리 중 발생한 이벤트를 반환할 수 없습니다.

 

리스너 순서 지정

리스너의 호출 순서를 제어하려면 @Order 주석을 사용할 수 있습니다:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
	// 순서대로 이벤트 처리...
}

 

제네릭 이벤트 처리

EntityCreatedEvent<T>와 같은 제네릭 이벤트를 사용하여 특정 타입의 엔터티가 생성되었을 때만 이벤트 리스너가 호출되도록 할 수 있습니다. 아래는 Person 타입에 대한 EntityCreatedEvent를 수신하는 리스너 예시입니다.

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // Person 타입의 엔터티가 생성되었을 때 처리할 로직
}

 

하지만 자바의 타입 소거(type erasure) 때문에 이 방식은 이벤트가 제네릭 파라미터를 명시적으로 해석하는 경우에만 작동합니다. 즉, PersonCreatedEvent와 같이 구체적인 클래스가 제네릭 타입을 명시적으로 해석해야 합니다.

public class PersonCreatedEvent extends EntityCreatedEvent<Person> {
    // Person 엔터티와 관련된 구체적인 이벤트 처리
}

 

 

ResolvableTypeProvider 사용

모든 이벤트가 동일한 구조를 따르는 경우 이 작업이 번거로울 수 있습니다. 이럴 때는 ResolvableTypeProvider 인터페이스를 구현하여 프레임워크에 제네릭 타입 정보를 제공할 수 있습니다. 아래 예시는 EntityCreatedEvent 클래스에 ResolvableTypeProvider를 구현한 예입니다.

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

 

이 방식은 ApplicationEvent뿐만 아니라 전송하는 임의의 객체에도 사용할 수 있습니다. ResolvableTypeProvider를 사용하면 런타임 환경이 제공하는 정보를 넘어 프레임워크가 제네릭 타입을 명확히 인식할 수 있습니다.

 

이벤트 멀티캐스터 커스터마이징

기본적으로, Spring의 이벤트 멀티캐스팅은 ApplicationEventMulticaster를 통해 처리됩니다. 기본 구현체는 SimpleApplicationEventMulticaster이며, 동기식으로 이벤트를 처리합니다. 그러나 이를 비동기식으로 처리하거나 리스너 예외를 처리하는 등의 커스터마이징이 필요할 경우, 아래와 같이 applicationEventMulticaster 빈을 정의할 수 있습니다.

@Bean
public ApplicationEventMulticaster applicationEventMulticaster() {
    SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
    multicaster.setTaskExecutor(...);  // 비동기 작업을 처리할 TaskExecutor 설정
    multicaster.setErrorHandler(...);  // 예외 처리 핸들러 설정
    return multicaster;
}

 

이를 통해 이벤트를 비동기적으로 처리하거나, 처리 중 발생하는 예외를 커스터마이징된 방식으로 처리할 수 있습니다.


로우 레벨 리소스에 대한 편리한 접근

Spring의 Resource 추상화는 파일, URL, 클래스패스 리소스 등 다양한 유형의 리소스에 대한 통합 인터페이스를 제공합니다. 이를 통해 java.net.URL 클래스를 사용하는 것보다 리소스를 다루는 것이 훨씬 간단하고 강력해집니다.

 

ApplicationContext는 ResourceLoader로서, Resource 객체를 직접 로드할 수 있습니다. Resource는 클래스패스의 파일, 파일 시스템의 파일, URL 등 다양한 위치에서 리소스를 투명하게 처리할 수 있는 인터페이스를 제공합니다. 이 리소스는 단순한 경로 문자열로 제공될 수 있으며, 컨텍스트 유형에 따라 적절하게 해석됩니다.

 

빈에서 리소스를 프로그래밍 방식으로 접근하려면 ResourceLoaderAware를 구현하여 초기화 시 ResourceLoader를 받을 수 있습니다. 또한 리소스 속성을 Resource 타입으로 선언하면, 문자열 경로를 리소스 객체로 자동 변환하여 빈에 주입할 수 있습니다.

 

예를 들어, ClassPathXmlApplicationContext와 같은 클래스패스 기반 컨텍스트에서는 단순한 경로가 클래스패스 리소스로 처리됩니다. 특정 경로를 명시하려면 classpath: 또는 file: 같은 접두사를 사용하여 리소스 위치를 강제할 수 있습니다.


애플리케이션 시작 추적

ApplicationContext는 Spring 애플리케이션의 생명 주기를 관리하며, 복잡한 시작 단계가 포함될 수 있습니다. 이러한 시작 단계를 추적하기 위해 Spring은 시작 단계 데이터를 수집하는 ApplicationStartup 인터페이스를 제공합니다.

 

이 데이터는 다음과 같습니다:

  • 애플리케이션 컨텍스트 생명 주기 단계 (예: 패키지 스캔, 구성 클래스 관리)
  • 빈 생명 주기 단계 (예: 인스턴스화, 초기화, 후처리)
  • 애플리케이션 이벤트 처리

개발자는 자신만의 구성 요소를 계측하여 맞춤형 StartupStep 데이터를 수집할 수 있습니다. 기본적으로 Spring은 아무 작업도 수행하지 않는 no-op ApplicationStartup 구현을 사용하지만, FlightRecorderApplicationStartup과 같은 구현으로 교체하여 성능을 추적할 수 있습니다. 개발자는 컨텍스트를 통해 ApplicationStartup에 접근하거나 ApplicationStartupAware를 구현할 수 있습니다.


웹 애플리케이션에서 ApplicationContext의 편리한 인스턴스화

웹 애플리케이션에서 ApplicationContext는 ContextLoaderListener를 사용하여 인스턴스화할 수 있습니다. web.xml에서 <context-param> 요소를 사용하여 컨텍스트 구성 위치를 정의합니다. 이 구성은 Ant 스타일의 경로 패턴을 지원합니다. 예를 들어:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 

contextConfigLocation 파라미터가 정의되지 않은 경우, Spring은 기본적으로 /WEB-INF/applicationContext.xml을 사용합니다.


Jakarta EE RAR 파일로 Spring ApplicationContext 배포

Spring은 Jakarta EE RAR(Resource Adapter Archive) 파일로 ApplicationContext를 배포하는 기능을 제공합니다. 이 방법은 HTTP 진입점이 필요 없지만, JTA 트랜잭션 관리자나 JMS 연결 팩토리와 같은 Jakarta EE 리소스에 접근해야 하는 헤드리스 애플리케이션 컨텍스트에 유용합니다.

 

RAR 배포를 위한 단계:

  1. Spring 애플리케이션 클래스를 RAR 파일로 패키징합니다.
  2. 필요한 라이브러리 JAR 파일을 RAR 아카이브의 루트에 추가합니다.
  3. META-INF/ra.xml 배포 설명자와 Spring XML 구성 파일을 추가합니다 (예: META-INF/applicationContext.xml).
  4. RAR 파일을 애플리케이션 서버의 배포 디렉터리에 배포합니다.

RAR 배포 단위는 일반적으로 자급자족 형식으로, 외부 세계에 컴포넌트를 노출하지 않습니다. RAR 기반 ApplicationContext는 JMS 목적지를 통해 다른 모듈과 상호작용하거나 예약된 작업을 실행할 수 있습니다.


The BeanFactory API

Spring의 BeanFactory API는 Spring IoC(Inversion of Control) 기능의 핵심을 제공합니다. 이 API는 Spring 내부 및 관련 타사 프레임워크와의 통합에서 중요한 역할을 하며, DefaultListableBeanFactory 구현체는 상위 레벨 컨테이너인 GenericApplicationContext의 중요한 위임자(delegate)로 작동합니다.

 

BeanFactory 및 관련 인터페이스(BeanFactoryAware, InitializingBean, DisposableBean 등)는 다른 프레임워크 구성 요소와의 효율적인 상호작용을 가능하게 하는 통합 지점입니다. 특히, 애플리케이션 레벨의 빈들은 이러한 콜백 인터페이스를 사용할 수 있지만, 일반적으로 주입(dependency injection)은 애너테이션이나 프로그래밍 방식의 설정을 통해 선언적으로 구현하는 것을 선호합니다.

 

BeanFactory와 ApplicationContext의 차이점

BeanFactory와 ApplicationContext는 Spring의 두 가지 주요 컨테이너 레벨입니다. 대부분의 경우 ApplicationContext를 사용하는 것이 권장되며, GenericApplicationContext 및 그 하위 클래스인 AnnotationConfigApplicationContext는 커스텀 부트스트래핑을 위한 일반적인 구현체입니다.

 

ApplicationContext는 BeanFactory의 모든 기능을 포함하므로, 빈의 초기화, 의존성 주입, 생명주기 관리, 메시지 국제화(i18n), 이벤트 발행 등의 기능을 제공합니다. 따라서 특별한 이유가 없는 한 ApplicationContext를 사용하는 것이 좋습니다. 반면, BeanFactory는 완전한 제어가 필요한 경우에만 사용됩니다.

 

다음 표는 BeanFactoryApplicationContext 간의 기능 차이를 보여줍니다.

기능 BeanFactory ApplicationContext
빈 인스턴스화 및 의존성 주입 Yes Yes
통합 생명주기 관리 No Yes
자동 BeanPostProcessor 등록 No Yes
자동 BeanFactoryPostProcessor 등록 No Yes
메시지 소스 접근 편의성 (국제화) No Yes
내장된 ApplicationEvent 발행 메커니즘 No Yes

 

BeanFactory와 BeanPostProcessor

BeanPostProcessor와 같은 확장 포인트는 BeanFactory와 ApplicationContext에서 중요한 역할을 합니다. BeanFactory에서는 이러한 프로세서를 수동으로 등록해야 하는 반면, ApplicationContext에서는 자동으로 등록됩니다. 예를 들어 DefaultListableBeanFactory에 BeanPostProcessor를 등록하려면 다음과 같은 방식으로 직접 등록해야 합니다.

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 빈 정의를 추가
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// 이후 factory를 사용

 

또한, BeanFactoryPostProcessor를 적용하려면 postProcessBeanFactory() 메서드를 명시적으로 호출해야 합니다.

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// Properties 파일에서 속성 값을 가져옴
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// 속성 값을 실제로 대체
cfg.postProcessBeanFactory(factory);

 

이와 같이 명시적인 등록은 불편하기 때문에, 일반적으로 ApplicationContext를 사용하는 것이 선호됩니다. 특히 대규모 엔터프라이즈 애플리케이션에서 BeanFactoryPostProcessor와 BeanPostProcessor 인스턴스에 의존하는 경우 ApplicationContext를 사용하는 것이 훨씬 간편합니다.


여기까지 Spring Ioc 에 대해서 알아보았습니다. 다음 시간에는 Spring Security 에 대해서 자세히 알아보는 시간을 가져보도록 하겠습니다.

반응형

'Spring' 카테고리의 다른 글

[Spring] Spring Security - 1  (0) 2024.09.03
[Spring] Spring Framework Overview - 1  (0) 2024.08.21