빈 생명주기 콜백 (Bean LifeCycle Callback)
스프링의 IoC 컨테이너는 Bean 객체들의 의존성을 관리한다.
LifeCycle(생명주기) : 생성부터 소멸까지
객체를 관리 == 객체의 LifeCycle을 관리한다.
-> 스프링의 IoC 컨테이너는 개발자 대신 객체(Bean)의 LifeCycle을 관리한다.
-> 객체 관리의 주체는 Spring Framework(IoC Container)가 되기 때문에 개발자는 로직에 집중할 수 있다.
빈 생명주기
- 스프링 컨테이너 생성
- 빈 객체 생성
- 의존 설정
- 초기화 (콜백)
- 빈 사용
- 빈 소멸 (콜백)
- 스프링 컨테이너 종료
스프링 컨테이너는 빈 객체를 생성하고 의존 주입을 통해 의존을 설정한다. 의존 자동 주입을 사용한다면 이 시점에 의존 주입이 수행된다.
의존주입이 모두 완료된 다음에 초기화 작업이 일어난다. 이 때 초기화 작업을 수행할 수 있도록 콜백 메서드를 등록할 수 있다. 마찬가지로 소멸 작업도 콜백 메서드를 통해 등록할 수 있다.
생성자에서도 초기화 작업을 진행할 수 있긴하다. 그러나, 생성자는 객체를 생성하고 객체의 의존관계를 설정하는 책임에 집중해야한다.
초기화 콜백에서는 이렇게 생성된 값들을 이용해 보다 복잡한 처리를 하는데 사용한다. 초기화 작업이 매우 간단한 경우에는 생성자에서 초기화 작업을 진행해도 되지만 복잡한 경우라면 메서드를 나누는 것이 유지보수 하기에 편한 경우가 많다.
초기화와 소멸 콜백을 등록하는 좋은 예는 데이터베이스 커넥션 풀이다. 초기화 과정에서 데이터베이스 커넥션들을 생성한다. 커넥션 풀이 사용되는 동안만 커넥션들을 유지하고 소멸 콜백을 통해 객체가 소멸되기 전 데이터베이스 커넥션들을 모두 끊어야 한다.
참고
빈 생명주기 콜백의 필요성
콜백(callback)이란
프로그래밍에서 콜백(callback) 또는 콜백 함수(callback function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 다른 함수의 인자로서 넘겨진 후 필요에 따라 즉시 호출할 수도 있고 나중에 호출할 수도 있다.
일반적으로 콜백수신 코드로 콜백 코드(함수)를 전달할 때는 콜백 함수의 포인터(핸들), 서브루틴 또는 람다함수의 형태로 넘겨준다.
객체의 초기화 및 종료 작업이 필요
객체 생성 → 의존관계 주입이라는 라이프사이클을 가진다.
의존성 주입 과정
1. 스프링 IoC컨테이너 생성
2. Component Scanning을 통해 Bean 등록
- @Configuration, @Controller, @Service 등등 Bean 으로 등록할 수 있는 어노테이션들과 설정파일들을 읽어 IoC 컨테이너 안에 Bean 으로 등록
3. 의존 관계 주입
- 주입 방식에 따른 객체 생성 시기
- 생성자 주입: 객체의 생성, 의존관계 주입이 동시에 일어남
- setter, Field 주입: 객체의 생성 -> 의존관계 주입으로 라이프 사이클이 나누어져 있음
-> 생성자 주입은 객체 생성 시 의존하는 객체를 주입하지 않으면 생성할 수 없음.
-> setter, Field 주입은 의존하는 객체 없이도 객체를 생성할 수 있음.
2022.12.27 - [.../Spring] - 빈 주입 방법 및 장단점
? 객체의 생성과 의존관계 주입이 동시에 일어난다..
5.4.1 Dependency injection
Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes, or the Service Locator pattern.
Code is cleaner with the DI principle and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies, and does not know the location or class of the dependencies. As such, your classes become easier to test, in particular when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.
DI exists in two major variants, Constructor-based dependency injection and Setter-based dependency injection.
Constructor-based dependency injection
Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion treats arguments to a constructor and to a static factory method similarly. The following example shows a class that can only be dependency-injected with constructor injection. Notice that there is nothing special about this class, it is a POJO that has no dependencies on container specific interfaces, base classes or annotations.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can 'inject' a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder is omitted...
}
Constructor argument resolution matching occurs using the argument's type. If no potential ambiguity exists in the constructor arguments of a bean definition, then the order in which the constructor arguments are defined in a bean definition is the order in which those arguments are supplied to the appropriate constructor when the bean is being instantiated. Consider the following class:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
No potential ambiguity exists, assuming that Bar and Baz classes are not related by inheritance. Thus the following configuration works fine, and you do not need to specify the constructor argument indexes and/or types explicitly in the <constructor-arg/> element.
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
When another bean is referenced, the type is known, and matching can occur (as was the case with the preceding example). When a simple type is used, such as <value>true<value>, Spring cannot determine the type of the value, and so cannot match by type without help. Consider the following class:
package examples;
public class ExampleBean {
// No. of years to the calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
참고
스프링 의존관계 주입이 완료된 시점을 개발자가 어떻게 알 수 있을까?
의존관계 주입이 완료된 시점 확인 방법
스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공
스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 제공
- 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
- 소멸 전 콜백: 빈이 소멸되기 직전에 호출
빈 생명주기 콜백 - 3가지 방법
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션 지원
InitializingBean, DisposableBean 인터페이스의 콜백을 이용한 코드
@Component
public class TestComponent implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception { // 의존관계 주입이 끝나면 호출해주는 콜백
System.out.println("초기화 콜백 테스트");
}
@Override
public void destroy() throws Exception {
System.out.println("소멸 전 콜백 테스트");
}
}
- InitializingBean은 afterPropertiesSet 메서드로 초기화를 지원한다.(의존관계 주입 이후 진행됨)
- DisposableBean는 destroy 메서드로 소멸을 지원한다. (마무리 작업 처리하고 소멸)
인터페이스 사용의 단점
- InitializingBean, DisposableBean 인터페이스는 스프링 전용 인터페이스이다.
- 해당 코드는 스프링 전용 인터페이스에 의존한다.
- 초기화, 소멸 메서드의 이름을 변경할 수 없다.
- 수정 권한이 없는 외부 라이브러리에 적용할 수 없다.
InitializingBean, DisposableBean 인터페이스를 사용하는 초기화 및 종료 방법은 스프링 초창기에 나온 방법들이기 때문에 지금은 거의 사용하지 않는다.
@PostConstruct, @PreDestory 어노테이션
@Component
public class TestComponent {
@PostConstruct
public void initTest() {
System.out.println("초기화");
}
@PreDestroy
public void destoryTest() {
System.out.println("종료");
}
}
- 최신 스프링에서 가장 권장하는 방법이다.
- 어노테이션 하나만 붙이면 되므로 매우 편리하다.
- 스프링이 아닌 다른 컨테이너에서도 동작한다.
- javax.annotation 패키지.
- 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.
- 컴포넌트 스캔과 잘 어울린다.
- 단점은 외부 라이브러리에는 적용하지 못한다.
- 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하면 된다.
빈 생명주기 콜백 시
- @PostConstruct, @PreDestory 애노테이션을 사용하자.
- 코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 @Bean 의 initMethod , destroyMethod 를 사용하자.
참고