Customizing the Nature of a Bean

2024. 11. 14. 14:25Spring Framework/Spring IoC

Customizing the Nature of a Bean

Lifecycle Callbacks

컨테이너의 빈 생명주기 관리와 상호 작용하려면, 스프링의 InitializingBean과 DisposableBean 인터페이스를 구현할 수 있습니다. 컨테이너는 전자에 대해서는 afterPropertiesSet()을, 후자에 대해서는 destroy()를 호출하여, 빈의 초기화와 소멸 시 특정 행동을 수행할 수 있게 합니다.

현재 Spring 애플리케이션에서 생명주기 콜백을 받기 위해 JSR-250의 @PostConstruct와 @PreDestroy 어노테이션을 사용하는 것이 일반적으로 최선의 방법으로 간주됩니다. 이 어노테이션들을 사용하면, 빈이 Spring 특정 인터페이스에 결합되지 않습니다. 자세한 내용은 @PostConstruct와 @PreDestroy 사용하기를 참조하세요. JSR-250 어노테이션을 사용하고 싶지 않지만 여전히 결합을 제거하고 싶다면, init-method와 destroy-method 빈 정의 메타데이터를 고려하세요.
JSR-250이란?
JSR-250은 Java Community Process(JCP)를 통해 개발된 Java 표준 기술의 일부입니다. JSR(Java Specification Request)은 Java 플랫폼에 추가하고자 하는 새로운 사양이나 기존 사양의 변경을 제안하는 공식 문서입니다. JSR-250은 "Common Annotations for the Java Platform"으로 명명되어 있으며, 자바 플랫폼 전반에 걸쳐 공통적으로 사용되는 어노테이션들을 정의합니다. JSR-250에 포함된 주요 어노테이션들 중 일부는 다음과 같습니다:  @PostConstruct: 객체가 생성되고 의존성 주입이 완료된 후 실행되어야 하는 메소드에 사용됩니다. 초기화 작업을 위해 종종 사용됩니다.
@PreDestroy: 객체가 소멸되기 전에 실행되어야 하는 메소드에 사용됩니다. 주로 정리 작업을 위해 사용됩니다.
@Resource: 의존성 주입을 위해 사용됩니다. 특정 자원에 대한 참조를 주입하는 데 사용됩니다.
JSR-250은 Java EE 표준의 일부로 채택되었으며, 이후 Spring과 같은 프레임워크에서도 널리 사용되고 있습니다. 이 어노테이션들은 플랫폼에 종속되지 않는 방식으로 어플리케이션의 구성 요소를 정의하고 관리하는 데 도움을 줍니다.

 

내부적으로, Spring 프레임워크는 BeanPostProcessor 구현을 사용하여 찾을 수 있는 모든 콜백 인터페이스를 처리하고 적절한 메소드를 호출합니다. 기본적으로 Spring이 제공하지 않는 사용자 정의 기능이나 다른 생명주기 행동이 필요한 경우, 직접 BeanPostProcessor를 구현할 수 있습니다. 자세한 정보는 Container Extension Points를 참조하세요.

초기화 및 소멸 콜백 외에도, Spring 관리 객체는 Lifecycle 인터페이스를 구현하여 컨테이너의 자체 생명주기에 의해 주도되는 시작 및 종료 과정에 참여할 수 있습니다.

이 섹션에서는 생명주기 콜백 인터페이스에 대해 설명합니다.

 

Initialization Callbacks

org.springframework.beans.factory.InitializingBean 인터페이스는 컨테이너가 빈에 필요한 모든 속성을 설정한 후 초기화 작업을 수행할 수 있도록 합니다. InitializingBean 인터페이스는 단 하나의 메소드를 명시합니다:
void afterPropertiesSet() throws Exception;
InitializingBean 인터페이스의 사용은 권장하지 않습니다. 이는 코드를 Spring에 불필요하게 결합시키기 때문입니다. 대신, @PostConstruct 어노테이션을 사용하거나 POJO 초기화 메소드를 지정하는 것이 좋습니다. XML 기반 구성 메타데이터의 경우, init-method 속성을 사용하여 void 매개변수 없는 시그니처를 가진 메소드의 이름을 지정할 수 있습니다. Java 구성에서는 @Bean의 initMethod 속성을 사용할 수 있습니다. 생명주기 콜백 수신을 참조하세요. 다음 예를 고려해 보세요:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public ExampleBean exampleInitBean() {
        return new ExampleBean();
    }
}

 

public class ExampleBean {

	public void init() {
		// do some initialization work
	}
}

 

앞서 제시된 예시는 다음 예시(두 개의 목록으로 구성됨)와 거의 동일한 효과를 가집니다:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public AnotherExampleBean exampleInitBean() {
        return new AnotherExampleBean();
    }
}

 

public class AnotherExampleBean implements InitializingBean {

	@Override
	public void afterPropertiesSet() {
		// do some initialization work
	}
}

 

그러나 앞서 언급된 두 예시 중 첫 번째 예시는 코드를 Spring에 결합시키지 않습니다.

(이 문장의 의미는 첫 번째 예시가 Spring 프레임워크에 특정한 인터페이스나 클래스에 의존하지 않는다는 것입니다. 즉, 해당 코드는 Spring에 독립적으로 작성되었으며, 다른 환경이나 프레임워크로 이전하는 데에 있어서도 더 유연하게 대처할 수 있습니다.

이는 일반적으로 좋은 소프트웨어 설계 원칙을 따르는 것으로, 특정 프레임워크나 라이브러리에 강하게 결합(coupling)되는 것을 피하고, 대신 유지보수성과 확장성을 높이기 위해 느슨한 결합(loose coupling)과 모듈성을 추구하는 것을 의미합니다. 첫 번째 예시에서는 @PostConstruct, @PreDestroy 어노테이션 또는 POJO 초기화 메소드를 사용하여 Spring에 특화된 인터페이스(InitializingBean, DisposableBean)를 사용하지 않음으로써 이를 달성하고 있습니다)

 

@PostConstruct와 일반적인 초기화 메소드는 컨테이너의 싱글톤 생성 락에서 실행된다는 점을 인지하십시오. 빈 인스턴스는 @PostConstruct 메소드에서 리턴된 후에야 완전히 초기화되었고 다른 빈에게 공개될 준비가 되었다고 간주됩니다. 이러한 개별 초기화 메소드는 구성 상태를 검증하고 주어진 구성에 기반한 일부 데이터 구조를 준비하는 것을 목적으로 하지만, 외부 빈 접근과 관련된 추가 활동은 없어야 합니다. 그렇지 않으면 초기화 교착 상태(deadlock)의 위험이 있습니다. 비싼 비용이 드는 초기화 후 활동을 실행해야 하는 시나리오에서는, 예를 들어 비동기 데이터베이스 준비 단계와 같은 경우, 빈은 SmartInitializingSingleton.afterSingletonsInstantiated()를 구현하거나 컨텍스트 새로 고침 이벤트에 의존해야 합니다: ApplicationListener<ContextRefreshedEvent>를 구현하거나 해당 어노테이션 동등물 @EventListener(ContextRefreshedEvent.class)를 선언합니다. 이러한 변형들은 모든 일반 싱글톤 초기화 이후에 발생하므로 어떠한 싱글톤 생성 잠금 바깥에서 이루어집니다. 또한, (Smart)Lifecycle 인터페이스를 구현하고 컨테이너의 전반적인 생명주기 관리, 자동 시작 메커니즘, 소멸 전 정지 단계, 잠재적인 정지/재시작 콜백과 통합할 수도 있습니다(아래 참조).

 

 

Destruction Callbacks

org.springframework.beans.factory.DisposableBean 인터페이스를 구현하면, 빈을 포함하는 컨테이너가 Destroy 될 때 콜백을 받을 수 있습니다. DisposableBean 인터페이스는 단 하나의 메소드를 명시합니다:

void destroy() throws Exception;

 

DisposableBean 콜백 인터페이스의 사용은 권장하지 않습니다. 이는 코드를 Spring에 불필요하게 결합시키기 때문입니다. 대신, @PreDestroy 어노테이션을 사용하거나, 빈 정의에서 지원되는 일반적인 메소드를 지정하는 것이 좋습니다. XML 기반 구성 메타데이터에서는 <bean/> 태그에 destroy-method 속성을 사용할 수 있습니다. Java 구성에서는 @Bean의 destroyMethod 속성을 사용할 수 있습니다. 생명주기 콜백 수신을 참조하세요. 다음 정의를 고려해 보세요:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(destroyMethod = "cleanup")
    public ExampleBean exampleDestructionBean() {
        return new ExampleBean();
    }
}

 

public class ExampleBean {

	public void cleanup() {
		// do some destruction work (like releasing pooled connections)
	}
}

 

앞서 제시된 정의는 다음 정의와 거의 동일한 효과를 가집니다:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public AnotherExampleBean exampleDestructionBean() {
        return new AnotherExampleBean();
    }
}

 

public class AnotherExampleBean implements DisposableBean {

	@Override
	public void destroy() {
		// do some destruction work (like releasing pooled connections)
	}
}

 

그러나 두 가지 이전 정의 중 첫 번째 정의는 코드를 Spring과 결합시키지 않습니다.

또한 Spring은 destroy 메서드를 추론하는 기능을 지원하며, public close 또는 shutdown 메서드를 감지합니다. 이것은 Java 구성 클래스의 @Bean 메서드에 대한 기본 동작이며, 자동으로 java.lang.AutoCloseable 또는 java.io.Closeable 구현과 일치하며, 이로 인해 destory 로직이 Spring과 결합되지 않습니다.

 

XML을 사용하여 destroy 메서드 추론을 수행하려면 <bean> 요소의 destroy-method 속성에 특별한 (추론된) 값을 할당하면 됩니다. 이 값은 Spring에게 특정 빈 정의에 대해 빈 클래스에서 public close 또는 shutdown 메서드를 자동으로 감지하도록 지시합니다. 또한 <beans> 요소의 default-destroy-method 속성에 이 특별한 (추론된) 값을 설정하여 이 동작을 여러 빈 정의 집합에 적용할 수도 있습니다.

아래는 이를 자바 기반 구성 정보로 변환한 것입니다:

1. 특정 빈 정의에 대한 destroy 메서드 추론:

@Configuration
public class AppConfig {
    
    @Bean(destroyMethod = "close")
    public MyBean myBean() {
        return new MyBean();
    }
}


위의 코드에서 @Bean 어노테이션 내에서 destroyMethod 속성을 설정하여 빈이 destroy될 때 호출할 메서드를 지정할 수 있습니다. 위 예제에서는 close라는 이름의 메서드가 빈 destory 시 자동으로 호출됩니다.

2. 여러 빈 정의에 대한 default-destroy-method 설정:

@Configuration
public class AppConfig {
    
    @Bean
    public MyBean myBean1() {
        return new MyBean();
    }
    
    @Bean
    public MyBean myBean2() {
        return new MyBean();
    }
    
    // 다른 빈들의 destroy 메서드를 설정하지 않고, 기본적으로 "close" 메서드를 사용하도록 설정
    @Bean(destroyMethod = "")
    public MyBean myBean3() {
        return new MyBean();
    }
}


위의 코드에서 @Bean(destroyMethod = "")을 사용하여 myBean3() 메서드에 대한 빈 정의에서 destory 메서드를 설정하지 않고, 대신 기본 값인 close 메서드를 사용하도록 설정합니다. 이것은 여러 빈 정의에 동일한 destroy 메서드를 적용하려는 경우에 유용합니다.

 

Default Initialization and Destroy Methods

Spring의 InitializingBean 및 DisposableBean 콜백 인터페이스를 사용하지 않고 init 및 destory 메서드 콜백을 작성할 때, 일반적으로 init(), initialize(), dispose() 등과 같은 이름의 메서드를 작성합니다. 이러한 라이프사이클 콜백 메서드의 이름은 이 프로젝트 내에서 표준화되어야 하므로, 모든 개발자가 동일한 메서드 이름을 사용하여 일관성을 유지할 수 있도록 합니다.

Spring 컨테이너를 설정하여 모든 빈에 대한 init 및 destroy 콜백 메서드 이름을 "찾도록" 구성할 수 있습니다. 이것은 응용 프로그램 개발자로서 init()라는 초기화 콜백을 작성하고 각 빈 정의에 init-method="init" 속성을 구성할 필요 없이 사용할 수 있음을 의미합니다. Spring IoC 컨테이너는 해당 메서드를 빈이 생성될 때 호출하며(이전에 설명한 표준 라이프사이클 콜백 계약에 따라), 이 기능은 init 및 destroy 메서드 콜백에 대한 일관된 명명 규칙을 강제합니다.

만약 초기화 콜백 메서드가 init()로 명명되고 destroy 콜백 메서드가 destroy()로 명명되었다면, 아래 예제와 같이 클래스가 보일 것입니다:
public class DefaultBlogService implements BlogService {

	private BlogDao blogDao;

	public void setBlogDao(BlogDao blogDao) {
		this.blogDao = blogDao;
	}

	// this is (unsurprisingly) the initialization callback method
	public void init() {
		if (this.blogDao == null) {
			throw new IllegalStateException("The [blogDao] property must be set.");
		}
	}
}

 

그런 다음 다음과 유사한 형태의 빈에서 해당 클래스를 사용할 수 있습니다:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public DefaultBlogService blogService() {
        DefaultBlogService blogService = new DefaultBlogService();
        blogService.setBlogDao(blogDao());
        return blogService;
    }

    @Bean
    public BlogDao blogDao() {
        return new BlogDao();
    }
}

 

Combining Lifecycle Mechanisms

Spring 2.5부터, 빈 라이프사이클 동작을 제어하기 위한 세 가지 옵션이 제공됩니다:

  1. InitializingBean 및 DisposableBean 콜백 인터페이스
  2. 사용자 정의 init() 및 destroy() 메서드
  3. @PostConstruct 및 @PreDestroy 어노테이션

이러한 메커니즘을 결합하여 특정 빈의 라이프사이클을 제어할 수 있습니다.

 

빈에 대해 여러 라이프사이클 메커니즘이 구성되고 각 메커니즘이 서로 다른 메서드 이름으로 설정된 경우, 아래에 나열된 순서에 따라 각 설정된 메서드가 실행됩니다. 하지만, 동일한 메서드 이름(예: 초기화 메서드로 init())이 여러 라이프사이클 메커니즘에 설정된 경우, 해당 메서드는 앞서 설명한 것처럼 한 번만 실행됩니다.

 

같은 빈에 대해 여러 라이프사이클 메커니즘이 구성되고 서로 다른 초기화 메서드가 있는 경우, 메서드는 다음 순서로 호출됩니다:

  1. @PostConstruct로 어노테이션된 메서드
  2. InitializingBean 콜백 인터페이스에서 정의된 afterPropertiesSet() 메서드
  3. 사용자 정의 초기화 메서드(init() 등)

destroy 메서드도 동일한 순서로 호출됩니다:

  1. @PreDestroy로 어노테이션된 메서드
  2. DisposableBean 콜백 인터페이스에서 정의된 destroy() 메서드
  3. 사용자 정의 소멸 메서드(destroy() 등)

 

Startup and Shutdown Callbacks

Lifecycle 인터페이스는 백그라운드 프로세스를 시작하거나 중지해야 하는 객체의 라이프사이클 요구사항을 정의하는 필수 메서드를 제공합니다:

public interface Lifecycle {

	void start();

	void stop();

	boolean isRunning();
}

 

Spring에서 관리되는 모든 객체는 Lifecycle 인터페이스를 구현할 수 있습니다. 이렇게 하면 애플리케이션 컨텍스트(ApplicationContext) 자체가 시작 및 종료 신호를 받을 때(예: 런타임 중지/재시작 시나리오), 해당 컨텍스트 내에 정의된 모든 Lifecycle 인터페이스를 구현한 모든 빈에 이러한 호출을 전파합니다. 이는 다음과 같이 정의된 LifecycleProcessor에 위임하여 수행됩니다:

 

public interface LifecycleProcessor extends Lifecycle {

	void onRefresh();

	void onClose();
}

 

LifecycleProcessor는 Lifecycle 인터페이스의 확장으로, 컨텍스트가 갱신(refresh)되거나 닫힐(close) 때 반응하는 두 가지 메서드(onRefresh, onClose)를 추가합니다.

 

주의:org.springframework.context.Lifecycle 인터페이스는 명시적인 시작 및 중지 알림을 위한 단순한 계약(인터페이스)이며, 컨텍스트 엡데이트 시 자동 시작(auto-startup)을 암시하지 않습니다. 특정 빈의 자동 시작 및 정교한 종료를 제어하려면 확장된 org.springframework.context.SmartLifecycle 인터페이스를 구현하는 것을 고려하세요.
또한, 정지(stop) 알림이 파괴(destroy) 전에 발생한다고 보장되지 않습니다. 일반적인 종료(shutdown) 시 모든 Lifecycle 빈은 일반적인 destroy 콜백이 전파되기 전에 먼저 stop 알림을 받습니다. 그러나 컨텍스트 수명 중 핫 갱신(hot refresh) 또는 정지된 상태에서의 업데이트 시도 시에는  destroy 메서드만 호출됩니다.

시작 및 종료 호출 순서

시작 및 종료 호출 순서는 중요할 수 있습니다. 두 객체 간에 "종속성(depends-on)" 관계가 존재하는 경우, 종속된 객체는 의존 대상이 시작된 후에 시작되고, 의존 대상이 종료되기 전에 종료됩니다. 하지만 때로는 직접적인 종속성이 명확하지 않을 수 있습니다. 특정 유형의 객체가 다른 유형의 객체보다 먼저 시작되어야 한다는 것만 알 수도 있습니다.

이러한 경우, SmartLifecycle 인터페이스는 Phased 슈퍼 인터페이스에 정의된 getPhase() 메서드를 통해 다른 옵션을 제공합니다. 아래는 Phased 인터페이스의 정의입니다:

public interface Phased {

	int getPhase();
}

 

다음은 SmartLifecycle 인터페이스의 정의입니다:

public interface SmartLifecycle extends Lifecycle, Phased {

	boolean isAutoStartup();

	void stop(Runnable callback);
}

 

getPhase() 및 실행 순서

  • 시작 시: getPhase() 메서드가 반환하는 값이 가장 낮은 객체가 먼저 시작됩니다.
  • 종료 시: 반대로, 가장 높은 getPhase() 값을 가진 객체가 가장 먼저 종료됩니다.

따라서, getPhase() 메서드가 Integer.MIN_VALUE를 반환하는 객체는 가장 먼저 시작되고, 가장 마지막에 종료됩니다. 반면, Integer.MAX_VALUE를 반환하는 객체는 가장 마지막에 시작되고, 가장 먼저 종료됩니다. 이는 해당 객체가 다른 프로세스가 실행 중인 상태에 의존하기 때문일 가능성이 높습니다.

디폴트 값:
일반적인 Lifecycle 객체의 디폴트 getPhase() 값은 0입니다. 따라서 음수의 phase 값은 해당 객체가 표준 구성 요소보다 먼저 시작되고 나중에 종료되어야 함을 나타냅니다. 반대로, 양수의 phase 값은 표준 구성 요소보다 나중에 시작되고 먼저 종료되어야 함을 나타냅니다.

 

SmartLifecycle의 stop() 메서드와 비동기 종료

SmartLifecycle의 stop() 메서드는 Runnable 콜백을 받습니다. 구현체는 종료 프로세스가 완료된 후 해당 콜백의 run() 메서드를 호출해야 합니다. 이는 필요한 경우 비동기 종료를 가능하게 합니다.

LifecycleProcessor 인터페이스의 디폴트 구현체인 DefaultLifecycleProcessor는 각 phase의 객체 그룹이 해당 콜백을 호출할 때까지 최대 타임아웃 값만큼 대기합니다. 디폴트 타임아웃 값은 30초입니다.

타임아웃 값을 수정하려면 다음과 같이 lifecycleProcessor 빈을 정의할 수 있습니다:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
	<!-- 타임아웃 값 (밀리초 단위) -->
	<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

 

SmartLifecycle과 컨텍스트 갱신(refresh)

LifecycleProcessor 인터페이스는 컨텍스트 갱신과 종료에 대한 콜백 메서드를 정의합니다. 종료 시에는 컨텍스트가 닫히면서 stop()이 명시적으로 호출된 것처럼 동작합니다. 반면, 갱신(refresh) 콜백은 SmartLifecycle 빈의 또 다른 기능을 활성화합니다.

컨텍스트가 갱신되면(모든 객체가 인스턴스화되고 초기화된 후), SmartLifecycle 객체의 isAutoStartup() 메서드에서 반환된 boolean 값이 확인됩니다. **true**를 반환하면, 해당 객체는 컨텍스트의 시작(start()) 메서드가 명시적으로 호출될 때까지 기다리지 않고 바로 시작됩니다.

 

 

 

 

 

Shutting Down the Spring IoC Container Gracefully in Non-Web Applications

 

Thread Safety and Visibility

 
 

ApplicationContextAware and BeanNameAware

 

 

 

 

 

Other Aware Interfaces

'Spring Framework > Spring IoC' 카테고리의 다른 글

Container Extension Points  (3) 2024.11.14
Classpath Scanning and Managed Components  (1) 2024.11.14
Composing Java-based Configurations  (1) 2024.11.14
Using the @Configuration annotation  (0) 2024.11.14
Using the @Bean Annotation  (0) 2024.11.14