@PostConstruct는 무엇일까?
1. 스프링 빈 생명주기(Spring Bean LifeCyle)
@PostConstruct를 이해하려면 먼저 스프링 빈 생명주기를 알아야합니다.
스프링 컨테이너 생성 -> Bean 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸콜백 -> 스프링 종료
여기서 PostConstruct는 초기화 콜백에 사용됩니다.
2. 어따가 사용하죠?
만약에 @Value를 사용해서 주입한 인스턴스 필드의 값을 static 필드의 값으로 초기화 해주고 싶을 때 사용한다.
@Value("${openai.api.key}") // 가능
private String apiKey;
Value 무조건 인스턴스 필드에 값을 주입해야한다.
@Value("${openai.api.key}") // 불가능
private static String staticApiKey;
왜 @Value는 static 필드에 값을 주입할 수 없을까?
static 필드와 메서드는 java 파일을 컴파일하는 시점에 클래스 레벨에서 참조할 수 있도록 초기화 된다.
컴파일 시점에서부터 이미 사용할 수 있도록 초기화 되는 것이다.
하지만 인스턴스의 경우에 인스턴스 생성을 해주지 않으면 초기화 되지 않아 사용할 수 없다.
// 인스턴스 생성
restUtils utils = new RestUtils
따라서 static 필드는 이미 생성됐고, 클래스 레벨에서 접근이 가능하다. @Value는 객체레벨의 인스턴스 필드에 값을 주입할 수 있는데 static 필드는 클래스 레벨에서 사용되고 있어 값을 주입할 수 없는 것이다.
스프링 컨테이너가 생성되기 전부터 이미 static 메서드, 필드는 클래스 레벨에서 초기화가 완료된 상태라는 것이다.
1. 컴파일 시점에 static 필드 클래스 레벨에서 초기화 완료
2. Spring IOC 컨테이너에서 Bean 등록 및 의존성 연결 시작
3. 인스턴스 필드에 @Value로 데이터 주입
컴파일 시점부터 static 클래스 레벨에서 초기화 완료 -> 초기화 되는 시점에 인스턴스 필드를 참조하고 싶음 -> 에러
1번이 초기화될 때 아직 생성되지 않은 3번의 @Value 값을 사용하고 싶다 말하는 것..
그래서 static 메서드에서는 인스턴스 필드를 참조할 수 없다.
package creativeprj.creative.Utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
public class RestUtils {
// 객체레벨이라 3번째로 초기화
@Value("${openai.api.key}")
private String apiKey;
/**
* @regdate 24.05.22
* @Test complete 24.05.23
* @param String ResponseBody
* @return Assistants threadId
* @throws JsonProcessingException
* @expain String json 데이터의 id 값반환
*/
public static String jsonNodeGetId(String responseBody) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
return jsonNode.path("id").asText();
}
// static은 클래스 레벨이라 스프링 컨테이너 올라오기도 전에 가장 먼저 초기화
// 중복되는 헤더 생성
public static HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey); // 참조 불가능, 아직 스프링 컨테이너도 안 올라옴
headers.set("OpenAI-Beta", "assistants=v1");
return headers;
}
}
그러면 static으로 사용하지 않고 HTTP Header를 생성하고 싶을 때마다 인스턴스를 생성 해야하는걸까?
헤더가 많이 사용되고 그만큼 코드 중복도 많기 때문에 위와 같은 RestUtils 클래스를 만든 것인데 인스턴스 생성을 계속 반복하면
의미가 없지 않을까 했다.
이를 해결하기 위해선 다음과 같이 코드를 수정해주면 된다.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
//추가
@Component
public class RestUtils {
@Value("${openai.api.key}")
private String apiKey;
//추가
private static String staticApiKey;
//추가
@PostConstruct
private void init(){
staticApiKey = apiKey;
}
/**
* @regdate 24.05.22
* @param String responseBody
* @return Assistants threadId
* @throws JsonProcessingException
* @expain String json 데이터의 id 값반환
*/
public static String jsonNodeGetId(String responseBody) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
return jsonNode.path("id").asText();
}
// 중복되는 헤더 생성
public static HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(staticApiKey); // 변수명 변경
headers.set("OpenAI-Beta", "assistants=v1");
return headers;
}
}
이렇게 수정하면 헤더를 사용하고 싶을 때마다 인스턴스 생성 안 해도 된다.
간단하게 바로 다음과 같이 사용 가능하다.
HttpHeaders header = RestUtils.createHeader();
3. 정리
@PostConsturct 덕분에 static 메서드에서도 인스턴스 필드를 참조할 수 있다. 왜냐면 초기화 콜백을 통해 객체레벨의 인스턴스 필드의 값을 한번 더 클래스 레벨의 static 필드의 값으로 초기화 해줄 수 있기 때문이다.
@Component
public class RestUtils {
@Value("${openai.api.key}")
private String apiKey;
private static String staticApiKey;
@PostConstruct
private void init(){
staticApiKey = apiKey;
}
인스턴스 필드에 등록된 값을 PostConstruct가 bean 등록이 끝나는 시점에 초기화 콜백을 통해서 static 필드에 한번 더 초기화 해준다.
덕분에 인스턴스 생성이 아닌 클래스 접근 메서드로 간결하고 편하게 헤더 생성이 가능하다.
'Spring' 카테고리의 다른 글
[Spring Boot] Spring 대용량 데이터 페이징 처리하기 2탄(Spring Data JPA + PostgreSQL) (0) | 2025.02.20 |
---|---|
[Spring Boot] Spring 대용량 데이터 페이징 처리하기 1탄(Spring Data JPA + PostgreSQL) (0) | 2025.02.19 |
[Spring Boot]Gradle 의존성 implementation vs runtimeOnly vs api 차이 (0) | 2025.02.17 |
[Spring] WebClient를 사용 해야하는 이유 [RestTemplate vs WebClient] 성능비교 (0) | 2024.06.08 |
[Spring Boot] Response Entity를 사용하는 이유와 잘 사용하는 법 (0) | 2024.06.01 |