[KOSTA 프로젝트] 04. properties파일에서 api key를 가져올 때 발생하는 java.lang.IllegalArgumentException
프로젝트에서 날씨 api가 필요했기 때문에 해당 홈페이지에서 api key를 받아왔다.
어쨋든 api key를 그대로 노출시켜서 사용할 수는 없으니 properties에 넣어두고 @Value로 받아오려 했다.
@Value("${api-key}")
private String apiKey;
이 때 얌전히 application.properties에만 api-key를 넣어놓았으면 쉽게 끝날 문제였다.
그러니까 나는 굳~~이 src/main/resources 내부에 또다시 messages 디렉토리를 생성하고 그 안에
commons, validation, errors 이렇게 세개의 파일로 나누어 메세지를 저장해두려고 했던 것이다.
application.properties에 spring.messages.basename를 설정해두기도 했고,
WebMvcConfigurer를 구현한 클래스에도 MessageSource를 Bean으로 등록해두었기 때문에
그냥 알맞은 변수명을 호출하면 스프링이 알아서 properties파일들 속에서 메세지를 찾아오는 줄로만 알았다.
두 가지 설정이 충돌해서 제기능을 못하나 싶어 하나씩 주석처리 해보는 등 계속 시도했지만,,,
결국 스프링은 메세지를 찾아오지 못했고,,,
java.lang.IllegalArgumentException: Could not resolve placeholder 'api-key' in value "${api-key}"
이런 에러가 발생하였다,,,왜,,,
아무리 구글링을 해보아도 보통은 나처럼 app-key를 굳이굳이 따로 생성한 properties에 저장하지 않았기 때문에 좀처럼 이유를 찾기 힘들었다.
결론적으로 Chat GPT에게 질문만 수십번을 해서 얻어낸 해결책은 @PropertySource 어노테이션을 사용하여 스프링에 해당 프로퍼티 파일의 위치를 명시적으로 지정해주어야 한다는 것이었다.
우선 spring.messages.basename을 지정하는 것은 메시지 소스를 설정하는데 사용되는 것이지,
@Value("${app-key}")를 사용하여 값을 주입받는 데 직접적으로 영향을 주지는 않는다고 한다.
그리고 일반적으로 스프링 부트에서는 클래스패스의 루트 디렉토리에 위치하는
application.properties (또는 application.yml) 파일에 정의된 프로퍼티들을 @PropertySource 없이도
자동으로 인식하고 주입해 준다고 한다.
따라서 application.properties 내부에 "app-key"를 정의해 두었다면 별도의 @PropertySource 어노테이션 없이도
@Value("${app-key}")를 사용하여 프로퍼티 값을 주입받을 수 있지만 다른 프로퍼티 파일을 사용할 경우 어노테이션을 통해 위치를 지정해주어야 한다는 것이다.
이와 비슷한 문제가 한번 더 발생했었다.
이번에는 @Valid로 유효성을 검사할 때 오류메세지를 properties에서 받아오는 부분이었다.
@PostMapping("/save")
public String save(@Valid EmployeeDTO employee, BindingResult result, Model model,
@RequestParam("profile") MultipartFile profile,
@RequestParam("stamp") MultipartFile stamp) {
if (result.hasErrors()) { // 에러가 생길 경우
// log.error("result: {}", result.getAllErrors().toString());
if(employee.getEmpId() != null) { // 수정 페이지로 돌아가기
model.addAttribute("name", "edit");
model.addAttribute("positions", Position.values());
model.addAttribute("depts", deptRepository.findAll());
} else { // 생성 페이지로 돌아가기
Long id = service.getLastEmpId();
model.addAttribute("id", id + 1);
model.addAttribute("positions", Position.values());
model.addAttribute("depts", deptRepository.findAll());
model.addAttribute("name", "register");
}
return "hr/emp/empWriter";
}
Employee savedEmp = service.saveEmp(employee);
Long id = savedEmp.getEmpId();
return "redirect:/hr/emp/" + id;
}
따로 Validator 구현체를 만들어서 디테일하게 유효성 검사를 할 생각이 없었기 때문에 DTO에 붙여두었던
Validation 애너테이션으로 공백, 이메일, 사이즈 정도만 검사하도록 하고
BindingResult로 검증 오류 정보의 유무를 확인했다.
commons.properties에 모든 단계의 오류코드를 적어보아도 적용되지 않았는데
결국 근본적인 문제는 messages 디렉토리를 내부에 파일을 세개나 둔 것이었다.
commons, validation, errors 파일 중 validation을 지웠더니 그제서야 메세지를 잘 찾아서 출력하는 것을 볼 수 있었다.
#commons.properties
# 공통 유효성 검증
NotBlank=필수 입력 항목입니다.
NotNull=필수 입력 항목입니다.
Email=이메일 형식이 아닙니다.
특별한 이유는 아직까지 찾지 못했다.
파일 제목도 commons라서 무언가 특별한 의미도 없고 개발자 마음대로 이름을 지어도 되는 걸로 아는데,,
참 허무했지만 그래도 해결되어서 다행이었다....
<dl>
<dt>이메일</dt>
<dd>
<input type="text" th:field="*{email}">
<div class="error" th:each="err : ${#fields.errors('email')}" th:text="${err}"></div>
</dd>
</dl>
잘 출력되었다!