[Sprinbg] @PostConstruct, @PreDestory, @Lazy, @BeanSc
@PostConstruct
빈이 생성된 직후 의존성 주입까지 끝난 뒤 바로 실행되는 메서드에 붙힌다.
데이터 로딩 , 연결 초기화 등에 사용된다.
실행 시점:
- 생성자 이후
- 의존성 주입(DI)이 모두 완료된 후
- Spring이 해당 객체를 사용할 준비가 끝난 시점
@Component
public class MyService {
@PostConstruct
public void init() {
System.out.println("빈 초기화 후 실행됨");
}
}
@PreDestory
빈이 소멸되기 직전에 호출되는 메서드에 사용한다.
자원 정리 작업 (소켓 정리, DB 연결 해제 등)에 사용한다.
이 두 어노테이션들은 반드시 매개변수 없는 메서드에만 사용 가능하고, 반환 타입은 void 여야 한다.
메서드 하나에만 사용가능 같은 어노테이션 여러번 x
@Lazy
해당 빈을 지연 초기화 하도록 지정한다.
일반적으로 스프링은 애플리케이션 실행시점에 빈을 미리 다 생성해서 컨텍스트에 넣는다.
하지만 지연 초기화를 사용하면 해당 빈은 실제로 사용될 때까지 생성되지 않는다.
보통 초기 구동 속도 개선을 위해 사용하거나
순환 참조 문제를 우회할때 사용되곤 한다.
실제로는 거의 사용하지 않는 이유
1. 대부분의 서비스 빈은 시작 시 한 번 생성되며, 즉시 사용할 수 있도록 준비되는데 스프링의 철학에서 좀 벗어남
2. 요청 시마다 처음 한번이 느려진다.
3. 초기화 중 에러가 런타임 중간에 터짐 → 테스트나 서버 구동 시점에 안 드러남 차라리 컴파일 오류가 낫지 런타임 오류는 좀....
4. 구조 자체가 잘못됐는데 지연 초기화로 땜질하는 식의 설계는 유지보수에 악영향
지연 초기화로 순환 참조를 해결하는 원리
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
서로가 서로를 의존하는 무한 루프 상황
@Component
public class A {
@Autowired
@Lazy // 지연 주입
private B b;
}
이렇게 지연 초기화를 해놓으면
1. A 생성 -> B 주입 -> 일단 프록시 객체 b만 주입
2. B 생성하고 A에 주입
3. 이후 A에서 b 사용시 B 인스턴스를 늦게 생성
하지만 예측 불가능한 동작, 성능 이슈, 오류 지연 발견 등으로 유지보수 리스크가 커지기 때문에 대체로 사용하지 않는걸 권장
@Scope
어노테이션을 사용해서 빈의 생명 주기(범위, Scope) 를 지정
| singleton (기본값) | 스프링 컨테이너에 하나만 등록됨. 모든 요청이 같은 인스턴스를 공유 |
| prototype | 요청할 때마다 새 객체 생성. 상태가 있는 객체에 적합 |
| request | HTTP 요청 하나당 하나의 빈 생성 (웹 애플리케이션에서만 사용) |
| session | HTTP 세션 하나당 하나의 빈 생성 (로그인 사용자 단위 등) |
| application | ServletContext 전체에서 공유되는 하나의 빈 (웹 전역) |
prototype 은 주의할게 있는데 스프링이 자동으로 destroy()를 호출하지 않아서 자원해제는 직접 해야 한다.
(근데 GC가 알아서 회수해 갈것. 근데 @PreDestroy 같은건 사용못함----> 왜냐면 스프링이 컨테이너에서 관리하는 대상이 아니기 때문에)
그럼 프로토타입은 언제 쓰는거냐?
1. 사용자 입력 폼 같은 매번 새로운 폼이 필요할때
2. 상태를 갖는 객체를 활용할때 (내부에 값이 쌓이거나 동작하는 객체는 싱글톤이 쓰레드간 충돌 위험이 있다.)
멀티쓰레드 환경에서 사용자마다 분리된 범위가 필요할때는 request, session 스코프를 사용하면 된다.
- request 스코프를 통하여 HTTP 요청별로 다른 UUID 생성하는 예제
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
private final String uuid = UUID.randomUUID().toString();
public String getUuid() {
return uuid;
}
}
@RestController
@RequestMapping("/request")
public class RequestScopeController {
private final RequestScopedBean requestScopedBean;
public RequestScopeController(RequestScopedBean requestScopedBean) {
this.requestScopedBean = requestScopedBean;
}
@GetMapping
public String getRequestScopedUuid() {
return "Request UUID: " + requestScopedBean.getUuid();
}
}
- session 스코프 (로그인 사용자 상태 저장 등 세션마다 하나의 빈 인스턴스 유지)
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedBean {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
@RestController
@RequestMapping("/session")
public class SessionScopeController {
private final SessionScopedBean sessionScopedBean;
public SessionScopeController(SessionScopedBean sessionScopedBean) {
this.sessionScopedBean = sessionScopedBean;
}
@PostMapping("/set")
public String setUsername(@RequestParam String username) {
sessionScopedBean.setUsername(username);
return "Username set to: " + username;
}
@GetMapping("/get")
public String getUsername() {
return "Stored username: " + sessionScopedBean.getUsername();
}
}
proxyMode = TARGET_CLASS 를 붙혀주는 이유는 request와 session 스코프 빈은 실제로는 싱글톤 빈에 주입되면
안되기 때문이다.
그래서 스프링이 내부적으로 프록시 객체를 만들어서 주입 시점은 동일하게 유지,
실제 실행 시점에 현재 request/session에 맞는 진짜 객체를 만들어준다.