스프링 빈의 이벤트 라이프사이클은 다음과 같이 이루어진다.
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링종료
애플리케이션 시작 전, 데이터베이스 커넥션 풀이나, 네트워크 소켓 연결이 완료되어야 한다. 하지만 객체 생성자에 conntect()와 같은 메서드를 사용할 경우 초기화 되지 않은 필드로 인해 null이 할당되는 오류가 발생한다.
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
connect();
call("초기화 연결 메시지");
}
public void setUrl(String url) {
this.url = url;
}
public void connect() {
System.out.println("connect: " + url);
}
초기화 메서드(setUrl)을 호출하지 않아 garbage값이 할당된 url이 출력된다. 해당 문제를 해결하기 위해 이벤트 라이프 사이클에서, 의존관계 주입이 끝난 시점에 초기화 콜백 사용이 필요하다. 하지만 개발자가 그 시점을 어떻게 알까? -> 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다.
public class NetworkClient implements InitializingBean, DisposableBean {
//InitializingBean, DisposableBean 인터페이스를 상속 받고,
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disConnect();
}
afterPropertiesSet()와 destroy() 메서드를 통해 자동으로 초기화 콜백,소멸 콜백을 지원한다.(거의 사용하지 않음)
public class NetworkClient {
//생략(초기화 부분만)
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
//설정 정보
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
설정 정보에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지정할 수 있다.
public class NetworkClient {
//생략(초기화 부분만)
@PostConstruct
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disConnect();
}
}
스프링은 다음과 같은 다양한 스코프를 지원한다.
싱글톤 빈은 여러 고객에게 항상 같은 인스턴스만을 반환한다. 하지만 프로토타입 빈은 요청과 함께 새로운 빈을 생성하고 의존관계 주입과 반환으로 해당 객체에 대한 관리는 하지 않는다. -> **관리 책임은 클라이언트에게 있다
싱글톤으로 설정한 객체안에 프로토타입 빈을 주입받는 의존관계가 있다면, 프로토타입 빈 주입 시점에 새로 생성되고 이후로 싱글톤 생명주기와 함께 지속된다. 따라서 항상 새로운 프로토타입 빈을 주입받고 싶다면 Provider를 통해 해결이 가능하다.
의존관계를 외부에서 주입(DI) 받는게 아니라 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존관계 조회(탐색) 이라한다. 지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 딱! DL 정도의 기능만 제공하는 무언가가 있으면 된다.
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
public class SingletonService {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
// ObjectProvider를 사용하여 매번 새로운 PrototypeBean 인스턴스를 가져옴
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount(); // 프로토타입 빈에 상태 변경
int count = prototypeBean.getCount(); // 변경된 카운트 값 반환
return count;
}
}
ObjectProvider를 통해 프로토타입 빈이 필요할 때마다 새로운 객체를 얻을 수 있어, 싱글톤 빈의 생명 주기와 독립적인 새로운 프로토타입 빈이 사용된다.
웹 환경에서만 동작하며 스프링이 해당 스코프 종료 시점까지 관리한다.(종료 메서드 자동 호출)
@Component
@Scope(value = "request")
public class MyLogger {
// 생략
@PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
System.out.println("[" + uuid + "] request scope bean create:" + this);
}
@PreDestroy
public void close() {
System.out.println("[" + uuid + "] request scope bean close:" + this);
}
}
많은 클라이언트의 요청을 받는 스프링에서 해당 스코프를 적용하면 HTTP 요청 당 하나씩 생성되고 UUID를 통해 클라이언트를 구분한다.
주의!) 만약 애플리케이션 시작 시점에 @Component와 @RequiredArgsConstructor를 가지는 싱글톤 빈의 필드에 request 웹 스코프를 가지는 클래스를 주입 받는다면 Request 스코프 빈은 HTTP 요청이 없으면 생성될 수 없기 때문에 활성화 되지 않는다는 오류가 발생한다. -> ObjectProvider 클래스를 통해 지연 초기화로 해결한다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
//private final MyLogger myLogger; (url 설정이 없어 오류)
private final ObjectProvider<MyLogger> myLoggerProvider;
}
ObjectProvider 덕분에 ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.
또는 프록시 방법이 있다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
프록시의 동작 방식은 CGLIB라는 라이브러리로 가짜 프록시 객체를 만들어 싱글톤 빈의 생성 시점에 같이 주입한다. 가짜 객체에는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.