Controller에서 만든 값이 HTML에 그대로 보이지 않을 때는 브라우저가 Java 객체를 모르는 것이 정상이라는 점부터 기억하면 된다. Controller는 데이터를 Model에 넣고, 템플릿 엔진은 그 모델을 사용해 최종 HTML을 만든다.
Model은 Controller와 템플릿 사이의 전달 상자다
다음 코드는 이름 하나를 Model에 넣고 hello라는 뷰를 반환한다.
@Controller
class HelloController {
@GetMapping("/hello")
String hello(@RequestParam(name = "name", defaultValue = "guest") String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
}hello는 src/main/resources/templates/hello.html을 가리키는 뷰 이름이다. name은 URL에 붙은 입력일 수 있으므로, 화면에 보여 줄 값이라고 해서 신뢰할 수 있는 값은 아니다. 데이터 조회와 권한 확인이 필요한 화면이라면 요청 파라미터를 그대로 사용하지 않고 서버에서 확인한 값을 모델에 넣는다.
Thymeleaf는 th:text로 HTML을 만든다
템플릿 파일에서는 th:text가 모델의 name을 읽는다.
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="|안녕하세요, ${name}님|">안녕하세요</h1>
</body>
</html>서버가 렌더링한 뒤 브라우저가 받는 것은 템플릿 문법이 처리된 HTML이다. 그래서 개발자 도구에서 Java의 Model 객체가 보이지 않아도 이상하지 않다. 확인해야 할 것은 응답 HTML에 기대한 문구가 들어갔는지다.
/hello?name=Jin을 열면 응답 HTML에는 안녕하세요, Jin님이 들어간다. /hello에서는 기본값 guest가 들어간다. 두 결과를 나눠 봐야 요청 파라미터가 모델에 들어오지 않은 문제와 템플릿 표현식이 틀린 문제를 구분할 수 있다. 입력에 <script> 같은 HTML 문자열이 들어오더라도 th:text는 이를 HTML 코드로 실행하지 않고 텍스트로 이스케이프한다. 다만 화면 이스케이프만으로 권한 확인이나 입력 검증을 대신할 수는 없다.
표시용 모델과 도메인 객체를 구분한다
간단한 화면에서는 문자열 하나로 충분하지만, 목록이나 회원 정보처럼 값이 늘어나면 템플릿에 엔티티를 그대로 넘기는 습관은 위험해진다. 비밀번호 해시, 내부 상태, 권한 판단용 필드가 화면으로 새어 나올 수 있기 때문이다.
화면이 필요한 값만 담은 DTO를 만들면 표시 책임이 분명해진다.
record ProfileView(String displayName, String email) {}
model.addAttribute("profile", new ProfileView("Kim", "[email protected]"));이 예시 주소는 형식 설명용이다. 실제 계정 정보는 모델이나 템플릿 예시에 넣지 않는다.
빈 값은 템플릿에서 조용히 숨기지 말자
name이 비어 있을 수 있다면 Controller에서 기본값을 정하거나, 템플릿에 명시적인 대체 문구를 둔다. 데이터가 없다는 사실이 오류인지 정상 상태인지도 서비스 계층에서 결정하는 편이 낫다. 템플릿이 복잡한 조건문으로 업무 규칙을 품기 시작하면 테스트하기도 어려워진다.
MockMvc 테스트는 어떤 뷰와 모델을 넘겼는지 확인하는 데 유용하다.
mockMvc.perform(get("/hello").param("name", "Mina"))
.andExpect(status().isOk())
.andExpect(view().name("hello"))
.andExpect(model().attribute("name", "Mina"))
.andExpect(content().string(containsString("안녕하세요, Mina님")));모델 속성은 있는데 마지막 응답 본문 검사가 실패하면 템플릿 파일과 th:text 표현식을 확인한다. 반대로 모델 속성 검사부터 실패하면 Controller가 요청값을 모델에 넣는 부분을 먼저 본다.
핵심 요약
Model은 Controller가 준비한 값을 템플릿으로 전달하는 통로이고, Thymeleaf의 th:text는 그 값을 HTML로 렌더링한다. 입력값을 곧바로 신뢰하지 말고, 화면에는 필요한 필드만 담은 데이터를 넘기자. 테스트에서는 상태 코드뿐 아니라 뷰 이름과 모델 속성도 확인하면 화면 오류를 더 빨리 찾을 수 있다.

