본문 바로가기

Error/Spring Boot

com.samskivert.mustache.MustacheException$Context: No method or field with name '******' on line 11

1. Whitelabel Error Page

최근 개발자들의 사이드 프로젝트를 위한 팀빌딩 플랫폼을 개발 중에 있다. 그리고 개발 과정 중에 만난 Whitelabel Error Page.

가장 마지막 줄을 보면 HTTP Status Code가 status=500 이라고 표시되어 있다.

이것만 봐서는 서버단의 문제인 것은 알겠는데 명확하게 무슨 문제인지 확인하기 어려웠고 그래서 콘솔 창을 확인해보았다.

2. No method or field with name

2022-11-15 21:27:39.303 ERROR 7684 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.samskivert.mustache.MustacheException$Context: No method or field with name 'mentorIntro' on line 11] with root cause

com.samskivert.mustache.MustacheException$Context: No method or field with name 'mentorIntro' on line 11

아마 이동욱님께서 집필하신 "스프링부트와 AWS로 혼자 구현하는 웹 서비스"라는 책을 기반으로 스프링부트를 학습하고 계신 분들 중에 이 문제를 겪고 이 블로그로 유입된 분들이 있을 것이라 생각된다.

위의 문제를 해결하기 위해 구글링을 해보았지만 동일한 에러를 겪은 분들을 찾기 어려웠고 그마저도 만족스러운 해결책이 되지 못했다. 대부분의 블로그 및 다른 자료들의 경우 위의 에러코드에 대해서 내놓은 해결책들이 거의 유사했고 따라서 나의 경우에는 다른 접근을 해보려고 한다.

3. 생각보다 오류는 별 것 아닌 이유로 발생한 것들이 많다...

위의 에러는 UPDATE 기능을 개발하는 과정 중에 발생했다. 구체적으로는 멘토가 작성한 자신의 멘토링을 홍보하는 게시글을 수정할 수 있게 해주는 기능이다.

자, 그럼 이제 다시 콘솔창에서 띄워 준 정보를 다시 확인해보자. 다른 것은 다 생략하고 마지막 줄만 확인해본다.

com.samskivert.mustache.MustacheException$Context: No method or field with name 'mentorIntro' on line 11

11행에서 mentorIntro라는 이름의 메서드 또는 필드가 없다고 한다. 그런데, 11행이 어떤 파일에서 11행인지 정확하게 알려주지는 않는다.

멘토링 페이지의 멘토 자신의 멘토링 소개 페이지 수정 기능을 추가하는 과정 중에 발생한 것이고, 나머지 기능들은 정상적으로 작동하고 있었다.

그리고 콘솔창에서도 MustacheException 이라고 말을 해주고 있으니 먼저 Mustache 파일을 확인해본다.

11번째 줄에 mentorIntro 가 있다!

3.1. Spring MVC 패턴을 항상 기억하자.

아마 나처럼 spring boot를 처음 접하는데, 바로 나만의 프로젝트에 적용하면서 학습하는 경우에는 책에 있는 코드에 많이 의존하는 경우가 있을 것이라고 생각된다. 그러다 보면 큰 고민 없이 "당연히 이렇게 쓰는 거지~"라고 생각하며 넘어가는 경우들이 종종 발생하게 되는데, 이러한 사고 때문에 오류가 발생한다고 생각한다.

다시 위에서 다루었던 콘솔창 출력을 살펴보자.

2022-11-15 21:27:39.303 ERROR 7684 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.samskivert.mustache.MustacheException$Context: No method or field with name 'mentorIntro' on line 11] with root cause

com.samskivert.mustache.MustacheException$Context: No method or field with name 'mentorIntro' on line 11

이번에 여기서 주목해야 할 부분은 dispatcherServlet이다.

DispatcherServlet은 스프링 MVC에서 Front Controller로, 클라이언트의 요청을 처리할 컨트롤러를 찾아 위임하고, 그 결과를 받아오는 역할을 한다.

이 쯤에서 한 번 스프링 MVC의 처리과정을 살펴보려 한다.

Spring MVC

사진 출처:Overview of Spring MVC Architecture

  1. DispatcherServlet이 요청을 수신한다.
  2. DispatcherServlet은 적절한 컨트롤러를 선택하는 작업을 HandlerMapping에 전달한다. HandlerMapping은 들어오는 요청 URL에 매핑된 컨트롤러를 선택하고 선택된 Handler와 Controller를 DispatcherServlet에 반환한다.
  3. DispatcherServlet은 Controller의 비즈니스 로직을 실행하는 작업을 HandlerAdapter에 전달한다.
  4. HandlerAdapter는 Controller의 비즈니스 로직 프로세스를 호출한다.
  5. 컨트롤러는 비즈니스 로직을 실행하고 처리 결과를 Model에 설정하고 View의 logical name을 HandlerAdapter에 반환한다.
  6. DispatcherServlet은 View 이름을 ViewResolver에게 전달한다. ViewResolver는 View 이름에 매핑된 View를 반환한다.
  7. DispatcherServlet은 View에게 Model을 전달하고 화면 출력을 요청한다. 만약 Model이 null일 경우 View를 그대로 사용하고, 값이 있을 경우에는 Model의 데이터를 View에 렌더링한다.
  8. 렌더링이 완료된 view 결과를 클라이언트에게 반환한다.

3.2. DispatcherServlet이 왜 콘솔창에 출력되었을까?

앞서 문제가 처음으로 발생한 파일은 mentoring-update.mustache 파일이었고 이것은 View에 해당된다고 할 수 있다.

클라이언트(멘토)는 자신이 쓴 소개글을 수정하기 위해 자신이 쓴 글에 들어가려고 했으나(제목 클릭), Whitelabel Error Page를 만난 것이다.

클라이언트가 보낸 UPDATE 요청을 수신한 DispatcherServlet은 해당 요청을 처리해 줄 수 있는 컨트롤러를 선택하기 위해 HandlerMapping에게 업무를 전달할 것이고 결과적으로 적절한 컨트롤러를 찾게 될 것이다.

이런 과정을 생각해 보았을 때 다음으로 의심해 볼 것은 mentoring-update.mustache 에 연결되는 컨트롤러이다.

현재 멘토링 서비스를 제공하는 페이지에 관련된 작업을 담당하는 컨트롤러는 MentoringController 에 해당하고 해당 파일 안에서 멘토의 소개글 수정은 mentoringUpdate 메소드가 담당한다.

/**
 * 멘토링 페이지에 관련된 컨트롤러
 */
@RequiredArgsConstructor
@Controller
public class MentoringController {

    private final MentorIntroService mentorIntroService;

    ...

    //멘토 소개글 수정
    @GetMapping("/mentoring/update/{id}")
    public String mentoringUpdate(@PathVariable Long id, Model model) {
        MentorIntroResponseDto dto = mentorIntroService.findById(id);
        model.addAttribute("mentoringUpdate", dto);

        return "mentoring-update";
    }
}

4. addAttribute가 무슨 기능인지 알고 썼니?

그렇다. addAttribute 에 대해서 제대로 이해하지 못한 채 사용해서 발생한 문제였다. addAttribute() 의 파라미터에서 어떤 값을 받는 것인지 제대로 이해하지 못해 발생한 문제였다.

model.addAttribute(String attributeName, Object attributeValue)

Spring 공식문서를 살펴보면 addAttribute에 대해서 다음과 같이 설명하고 있다.

즉, model.addAttribute("mentoringUpdate",dto) 와 같이 작성하면 모델에 dto객체를 mentoringUpdate라는 이름으로 담을 수 있는 것이다. 결과적으로 view에 해당하는 mentoring-update.mustache에서는 dto객체의 필드에 접근할 때 mentoringUpdate라는 이름으로 객체를 명시해야 하며, 다음과 같이 수정되어야 한다.

as-is

<div class="form-group">
    <label for="id">글 번호</label>
    <input type="text" class="form-control"
           id="id" value="{{mentorIntro.id}}" readonly>
</div>

to-be

<div class="form-group">
    <label for="id">글 번호</label>
    <input type="text" class="form-control"
           id="id" value="{{mentoringUpdate.id}}" readonly>
</div>

엔티티에 직접 접근하기 보다는 DTO를 통해서 접근하기

나는 이 부분을 간과해서 문제가 생겼던 것 같다. 사실, 알고는 있었지만 아무 생각 없이 코드를 짠 것이 가장 큰 문제였다.
코드를 작성하는데 책을 참고하고 있었고, 문제가 되었던 mustache 파일을 작성할 때 참고한 코드의 주석에는 다음과 같은 내용이 담겨 있었다.

{{post.id}}
• 머스테치는 객체의 필드 접근 시 점으로 구분합니다.
• 즉, Post 클래스의 id에 대한 접근은 post.id로 사용할 수 있습니다.

여기서 post는 Post클래스의 필드에 직접 접근하는 것을 의미하는 것이 아닌데, Spring MVC에 대한 낮은 이해도와 고민 없이 작성하는 태도가 결합하여 이러한 에러가 발생했던 것이다.

결과적으로 mentoring-update.mustache 파일에서 모든 value에 대해 수정을 했고 UPDATE가 가능해졌다.

5. 마무리

SpringBoot를 활용해서 웹서비스를 개발하고 있다면 반드시 Spring MVC 패턴이 어떻게 동작하는지 제대로 이해하고 이것이 코드 상에서 어떻게 흐름을 만들어가는지 꼭 생각하며 개발해야 한다는 큰 교훈을 얻었다...

어찌 보면 당연한 이야기이지만 일정에 쫓기듯 개발하다 보면 단순히 기능 구현만 생각하다 놓치는 경우가 생기는 것 같다.

P.S.

현재 글의 주제를 벗어나는 이야기이기도 하고 자잘한 문제였기 때문에 다루지는 않았지만, 위의 에러를 해결한 뒤에 또 다른 에러가 나왔었다. UPDATE를 할 수 있게 되었지만, UPDATE를 적용할 필드들에 대해 수정을 한 뒤 저장을 하면 일부 필드를 제외한 나머지 필드들은 UPDATE가 되지 않았다.

만약 동일한 문제를 겪고 있다면 앞서 살펴본 Controller에서 model에 담는 DTO(필자의 경우 MentorIntroResponseDto)에 수정하려는 모든 필드가 담겨 있는지 확인해보면 좋을 것 같다.

필자의 경우 몇 개의 필드들을 누락해서 UPDATE가 제대로 되지 않는 문제를 겪었다.

Ref.