0. 에러분석
일단 제일 아래에 있는 오류부터 확인했다
Could not resolve placeholder 'jwt.secret.key' in value "${jwt.secret.key}"
jwt.secret.key 라는 자리에 해당 값을 찾을 수 없는듯 하다
-> IllegarArgumentException
해당 오류는 jwtUtil 에서 발생했기 때문에 Component annotation 이 붙은 JwtUtil class 가 정상적으로 만들어지지 않았고
-> BeanCreationException

jwtUtil 을 참조하고 있는 FilterConfig 에서는 의존성이 만족되지 못해 오류가 발생한다
-> UnsatisfiedDependencyException
해당 부분까진 트러블 슈팅으로 메모해둔 부분이라 순서를 알겠는데 WebServerException 과 ApplicationContextException 은 잘 모르겠어서 일단 처음 발생한 IllegarArgumentException 을 해결해주도록 한다.

해당 화면에서 필드를 설명하면
SecretKey 는 JWT 의 서명을 생성하고 검증할 때 쓰는 비밀키 -> 원래 application.properties 에 적혀있음
key 는 secretKey 를 변환한 실제 키 객체임
init 에서는 bytes 를 key 를 초기화해서 값을 넣어줌
application.properties 에서 가져온 문자열 secretKey 를 디코딩해서 원래 배열로 변환하는 역할
찾아보니 application 파일이 존재하지 않기 때문에 이전 프로젝트에서 썼던 파일을 새로 생성
심화주차는 application.propertise 가 아닌 application.yml 파일을 사용했기 때문에 해당 파일을 복사해왔다
파일 위치 -> src/main/resources/application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/nbcam
username: root
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
jwt:
secret:
key: happyjavawithtakoiloveyoutakolivewithmeforeverplzbabocatcutecat # 여기를 임의로 설정했다

너무 짧게 설정하면 위 같은 오류가 뜨니 어느정도의 길이를 가지게 secret key 를 설정해준다
application 파일만 추가하니까 추가로 있던 WebServerException 과 ApplicationContextException이 사라진걸 보니까 다 연결된 오류인 것 같다!
웹서버에서 문제가 생기면 Spring container 가 초기화가 안 돼서 생기는 문제
코드 돌아가는 김에 diagram 뽑아서 확인하고 넘어가기

1. ArgumentResolver
ArgumentResolver 와 관련한 자료를 찾아봤다
https://breakcoding.tistory.com/402
[Spring] HandlerMethodArgumentResolver에 대해 알아보자
안녕하세요 오늘은 Spring에서 제공하는 정말 편리한 기능인 HandlerMethodArgumentResolver에 대해 알아보겠습니다. HandlerMethodArgumentResolver는 무엇이고 어떤 경우에 사용하는 것이 좋을까요? 예제를 보며
breakcoding.tistory.com
client 가 header 에 token 을 담아서 request 를 보낼 때 원래는 @RequestHeader String token 이 있고
해당 token 을 decode (파싱) 해서 client 의 정보를 뽑고 객체를 사용함
이 때 특정 어노테이션을 하나 생성하고, HandlerMethodArgumentResolver 를 구현한 클래스를 만들고 등록하면
토큰을 파싱하고 객체를 return, 즉 사용자 정보를 얻는 과정을 쉽게 만들 수 있음
이 때 필요한게 회색 줄 친 클래스들이다
다시 코드로 돌아가서 어느 클래스가 구현이 잘 되지 않았는지 확인했다
# 1. 특정 어노테이션 class
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}
# 2. HandlerMethodArgumentResolver 를 구현한 클래스
public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {
# 후략
일단 해당 클래스에 bean 으로 등록하는 어노테이션이 빠져있으므로 추가

또 HandlerMethodArgumentResolver 를 구현한 클래스를 등록해주는 클래스가 존재하지 않는다
-> WebConfig 클래스 만들어주기
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final AuthUserArgumentResolver authUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers){
resolvers.add(authUserArgumentResolver);
}
}


Controller 에서 확인을 해보니 Signup 과 Signin 둘 다 bearerToken 을 return 하고, 그대로 잘 작동한다
2. 코드개선
(1) Early Return

@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());
UserRole userRole = UserRole.of(signupRequest.getUserRole());
# 후략
Encoding 하는 코드와 String 형태로 받아온 UserRole 을 Enum 형태로 바꿔주는 메서드 앞으로 email 중복체크 로직을 이동
확인



미리 table 에 값을 삽입하고 똑같은 값으로 요청을 보냈을 때 debug 를 찍어보니까
SignupRequest 를 확인한 후 encoding 하는 로직 없이 바로 error 가 던져지는 것을 확인할 수 있음
(2) unnecessary IF - ELSE
if else 문이 중첩되어 나타나게 되면 코드 가독성이 떨어지기 때문에 불필요하게 사용하는것을 지양해야함
기존 코드를 살펴보면 getTodayWeather method는 Todo 를 저장할 때 당일의 날씨를 return 해주는 역할
responseEntity 전체를 가져와서
요청이 성공했는지 확인 ( getStatusCode 를 통한 OK 체크)
가져와졌다면 getBody 를 통한 DTO 체크
WeatherDTO 리스트를 가져와서 Entity.getBody 를 통해서 가져와서 null 인지 확인, 비어있는지 확인
# 날씨 데이터를 가져오는 로직이 OK 로 성공했는지 확인
# 해당 if 문을 통과 했다는 것은 요청 자체가 성공했다는 의미
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
}
# Body 를 가져옴
# 이 때 responseEntity 가 Null 이면 NPE 가 뜨겠지만 위에서 확인을 했고
# getBody method 자체는 nullable 이기 때문에 아래 if 문으로 확인
WeatherDto[] weatherArray = responseEntity.getBody();
# 날씨 데이터를 조회하는 요청이 성공
# -> null 이거나 공백인지 확인 하는 로직은 따로 작성해주기
if (weatherArray == null || weatherArray.length == 0) {
throw new ServerException("날씨 데이터가 없습니다.");
}
else 문을 풀어주는것과 동시에 getBody 부분을 getStatus 를 확인한 다음으로 옮겨서 불필요한 메서드 호출을 방지했다
(3) Validation

service 계층에서 password 를 확인해주던걸 DTO 로 옮겨줬다
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserChangePasswordRequest {
@NotBlank
private String oldPassword;
@NotBlank
@Pattern(regexp = "^(?=.*[A-Z])(?=.*\\d).{8,}$",
message = "새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다.")
private String newPassword;
}
3. N+1 problem
query 문을 주석처리 하고 아래 코드를 method 위에 추가했다
@EntityGraph(attributePaths = {"user"})
강의자료에서 EntityGraph 는 Page로 return 하는 게 안 된다고 해서 더 찾아봤는데 다른 자료에서 EntityGraph 도 Page 를 사용해줄 수 있다고.. 나와있어서 일단 체크하고 패스
4. 테스트 코드
(1) 정상작동 되도록 확인
matches method 는 매개변수 순서가 raw 이후 encoded 를 넣어줘야함

boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);
| Java HotSpot(TM) 64비트 서버 VM 경고: 부트스트랩 클래스 경로가 추가되었기 때문에 부트 로더 클래스에만 공유가 지원됩니다 |
테스트는 잘 통과했는데 저 warning은 뭐지 싶어서 일단 papago 를 돌려봤는데 별건 아니고
JVM 을 실행할 때 옵션을 추가한 게 있으면 발생하는 오류라고 한다
error 가 아니라 warning 이므로 문제가 있을 수도 있다고 sharing 해주는거니까 크게 신경쓰지 않아도 됨
(IntelliJ 가 classLoader 를 건드려서 생기는 문제!)
(2) 예상대로 예외처리하도록 수정
1)

실제 코드에서 던지는 메시지가 Todo not found 이므로 Test 에서도 Manager -> Todo 로 수정

2)

현재 ServerException 으로 던지고 있는걸 InvalidRequestException 으로 수정해주기

3)
if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다.");
}
해당 if 문에서 오류가 발생
처음보는 method 가 있어서 찾아봤다
https://jh-tr.tistory.com/285#google_vignette
[Java] ObjectUtils 클래스 메서드 사용법, 예제 (isEmpty, nullSafeEquals, isArray)
ObjectUtils 클래스는 Spring Framework에서 제공하는 유틸리티 클래스로서, 객체와 배열에 관련된 다양한 작업을 수행할 수 있습니다. ObjectUtils 클래스에서 자주 사용하는 ObjectUtils.isEmpty , ObjectUtils.nullS
jh-tr.tistory.com
ObjectUtils.nullSafeEquals(obj1, obj2)
obj1 과 obj2 가 모두 null 이면 true 반환 (null == null 느낌인듯)
하나가 null 이면 그냥 equals method 를 호출, NullPointerException 을 발생 Xx
-> user.getId 가 null 이든 todo.getUser().getId() 가 null 이든 NullPointerException 이 발생하지 않는데 대체 왜 NullPointerException 이 발생하는가. .
todo.getUser() 에서 NPE 발생
애초에 todo.getUser() 가 null 이면 getId() 호출 순간 null 이 발생하게 됨
if (todo.getUser() == null){
throw new InvalidRequestException("일정을 생성한 유저만 담당자를 지정할 수 있습니다.");
}
위 코드로 한 번 더 확인해줘야함
message 가 동일하니까 하나의 if문으로 합쳐줄까 고민했는데 더 가독성이 떨어지는 기분이라 일단 따로 작성했다

'SPARTA 과제 > SPRING' 카테고리의 다른 글
| 심화) LV 6 코드 리팩토링 (0) | 2026.03.07 |
|---|---|
| 심화) LV 5 API 로깅 (0) | 2026.03.04 |
| 숙련) 일정관리앱 트러블슈팅 (4) (0) | 2026.02.13 |
| 숙련) 일정관리앱 트러블슈팅 (3) (0) | 2026.02.12 |
| 숙련) 일정관리앱 트러블슈팅 (2) (0) | 2026.02.11 |