생성자 바인딩 오류(Cannot resolve parameter names for constructor)

worldclasscitizen·2024년 9월 24일

Spring Boot

목록 보기
3/5
post-thumbnail

본 포스팅은 Spring Boot 3.3.4 를 다루고 있습니다.


문제 상황

제목과 같이 생성자 바인딩 오류가 발생했다.

시간이 없는 사람들을 위해 미리 스포하자면, 이 포스팅에서 h2 크기로 작성된 두 줄만 읽으면 된다.

어디서 막힌 건지 코드로 살펴보겠다.


다음은 뷰 페이지 edit.html 의 코드다.
HTML 에서는 GET, POST 방식밖에 지원하지 않기 때문에, 아래처럼 input 태그를 작성해야 컨트롤러의 PatchMapping 과 매핑시킬 수 있다.

<form class="container"
	th:action="@{/articles/{id}(id=${article.id})}" method="post">
<input type="hidden" name="_method" value="patch"/>

다음은 수정한 게시물을 DB 에 저장하기 위한 컨트롤러의 PATCH 메서드다.
클라이언트에게 받은 폼 데이터를 @ModelAttribute 를 통해 DTO 로 받아오고 있다.

@PatchMapping("/{id}")
public String updateArticle(@PathVariable("id") Long id,
    						@ModelAttribute ArticleDto dto) {
Article updated = articleService.updateArticle(id, dto);
return "redirect:/articles/" + updated.getId();
    }

그런데 문제가 바로 여기서 발생한다.

로그를 찍어보니 DTO 에 아무 값도 담겨 있지 않았다.


시도

처음 난관에 부딪혔을 땐 HTML 을 잘 몰라서 억지로 PATCH 방식을 사용하게 input 태그를 사용하고 있는 것이 문제인가 싶었다.

조금 웹 서핑을 해 보니 아니란 건 금방 알 수 있었다.

그래도 얻은 수확이라면, Spring Security 를 이용할 때 CSRF(Cross-Site Request Forgery) 공격에 대한 보호 기능이 활성화되어 있기 때문에 POST, PUT, DELETE 등의 메서드를 이용하지 못할 수도 있다고 한다.

이는 CSRF 토큰("우리 안전해요!"라는 의미)을 폼에 포함시키는 것으로 해결이 가능하다고 한다.

<input type="hidden"
	name="${_csrf.parameterName}" value="${_csrf.token}"/>

Service 코드는 다음과 같은데,

여기서 온갖 로그를 다 찍어봤다.

dto 가 문제인지, toEntity() 메서드로 만든 article 객체가 문제인지,
아니면 리파지터리에 저장하고 반환받은 target 객체가 문제인지 ..

  1. 일단 여기서 dto 에 아무 값도 담겨 있지 않았고,
  2. target == null 을 잘못 작성해서 target != null 이라고 쓴 부분이 문제였다.

2번은 바로 해결했는데, dto 에 id/title/content 어느 값도 들어 있지 않았다.


이때 발생했던 에러가 [ Cannot resolve parameter names for constructor ] 였다.

일단 parameter names 를 보고 "아 이거 3.2부터 달라졌다던 바이트코드 파싱 문제구나" 싶긴 했는데, 내 코드에 명시해놓지 않은 파라미터 이름이 뭐가 있는지 파악하기 어려웠다.

그리고 생성자라는 단어가 나와서 감이 잘 안 잡혔기에, 우선 파라미터 이름을 가지고 뭐라도 해보자 싶었다.

그때 생각난 것이, 지난 번에 바이트코드 파싱 기능이 없어지면서 해결 방법으로 1) 파라미터 이름을 명시하거나, 2) Config 에서 -parameters 를 설정하는 방법이 있었다.

당시에는 1번으로 문제를 해결했지만 이번엔 2번을 적용해봤다.


-parameters 설정하기

IntelliJ 설정 > 빌드, 실행, 배포 > 컴파일러 > Java 컴파일러 > 추가 명령줄 매개변수

여기에 -parameters 를 입력하고 저장하면 끝이다.

물론, 이걸로 해결이 안 됐다.

ChatGPT 형님께서는 이 설정만 하면 된다고 하셨는데.. 역시 아직까지는 구글링과 공식 문서 참고가 최고다.


문제 해결

열심히 찾아보다가 알게 된 것이, 내가 헤매고 있는 오류에서 생성자라는 단어가 중요했던 것이 맞았고, 이를 세터 메서드 바인딩을 통해 해결할 수 있다는 것이다.

다음과 같은 DTO 클래스가 있을 때,

public class ArticleDto {
    private Long id;
    private String title;
    private String content;

    public ArticleDto(Long id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }
}

HTTP 요청에서 전송된 폼 데이터를 DTO 객체로 변환할 때, DTO 의 생성자를 사용한다.

이 과정에서 매개변수의 이름 id / title / content 가 필요한 것인데, 앞서 말했듯 이 매개변수의 이름을 추론하는 기능이 사라졌다.

그리고 세간에서는 -parameters 설정 말고도 또 다른 해결책을 말하고 있는데,

NoArgsConstructor & Setter 사용하기

바로 기본 생성자와 세터를 사용하는 것이다.

기본 생성자를 사용하면 Spring 이 객체화(인스턴스화)해준다.
따라서 파라미터의 이름 정보가 필요하지 않다.

그리고 Setter 또한 파라미터의 이름이 필요하지 않다.
HTML 코드에 적혀 있는 input, textarea 태그의 name 속성의 값을 Spring 이 객체의 필드와 매핑하는데, 이 이름과 동일한 이름의 Setter 가 있으면 바로 연결시켜준다.

예를 들어,
setTitle(String title) 메서드가 존재하면, 요청 파라미터에서 name="title" 에 해당하는 값을 설정한다.


이렇게 해서 해결할 수 있었는데, 약간 의아했던 대목은 문제를 해결하고 나서 설정이나 CSRF 토큰을 빼도 잘 작동한다는 점이었다.

Spring Security 를 이번 게시판 만들기 프로젝트에서 처음 다뤄보려고 하는데, 차근차근 공부해야겠다.

0개의 댓글