@ModelAttribute는 어떻게 Formatter 없이 작동할까?
@RequestParam
이나 @PathVariable
로 들어온 문자열 값을 객체로 받기 위해서는 Formatter 가 필요하다. 아래 예제로 살펴보자.
- title이라는 String형 변수와 length라는 int형 변수를 가진 DTO이다.
@Getter
@Setter
public class Music {
String title;
int length;
}
만약 @RequestParam이나 @PathVariable로 넘어온 문자열 값을 Music객체로 받고 싶다면 아래와 같이 코드를 작성하면 된다.
@ResponseBody
@GetMapping("/hello/{title}"){
public String hello(@Pathvariable("title") Music music)
}
테스트 코드를 아래와 같이 실행해보면,
@Test
public void hello() throws Exception {
mockMvc.perform(get("/hello/kh")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(content().string("kh"));
}
Resolved Exception이 발생한다.
핸들러의 인자로 Music 타입을 받겠다고 했는데 실제로 주어진 것은 String 타입이라는 것이다. 즉, String이 Music으로 conversion되지 않아서 발생한 예외이다.
이제 Formatter를 작성해보자.
@Component
public class MusicFormatter implements Formatter<Music> {
@Override
public Music parse(String s, Locale locale) throws ParseException {
Music music = new Music();
music.setTitle(s);
return music;
}
@Override
public String print(Music music, Locale locale) {
return null;
}
}
Formatter를 Bean에 등록하면 테스트가 정상적으로 통과하는것을 볼 수 있다.
@RequestParam
의 경우도 마찬가지다.
@GetMapping("/hello")
public String hello2(@RequestParam("title") Music music) {
return music.getTitle();
}
@Test
@DisplayName("@RequestParam으로 받기")
public void hello2() throws Exception {
mockMvc.perform(get("/hello")
.param("title","kh"))
.andDo(print())
.andExpect(content().string("kh"));
}
Formatter를 Bean으로 등록했다면 정상적으로 통과한다.
의아한 점이 있다. @ModelAttribute
도 primitive type의 객체를 래퍼런스 타입의 객체로 바인딩해주는 로직을 가지고 있다고 알고있는데, 왜 @ModelAttribute
은 Formatter 없이도 작동되는것일까?
먼저 @ModelAttribute
가 Formatter 없이도 정상 작동 되는지 확인해보자.
기존에 있던 Formatter는 삭제하거나 Bean에서 빼도록 하자.
@PostMapping("/hello")
public Music hello3(@ModelAttribute Music music) {
return music;
}
@Test
@DisplayName("@ModelAttribute로 받기")
public void hello3() throws Exception {
mockMvc.perform(post("/hello")
.param("title", "kh")
.param("length", "10"))
.andDo(print())
.andExpect(jsonPath("title").value("kh"))
.andExpect(jsonPath("length").value(10));
}
}
열심히 구글링 해본 결과 @ModelAttribute
는 다음과 같은 일이 일어난다고 한다.
-
파라미터로 넘겨준 타입의 오브젝트를 자동으로 생성한다.
- 넘겨준 타입은 setter/getter가 구현되어 있어야 한다.
-
생성된 오브젝트에서 HTTP로 넘어온 값을 setter 메소드를 통해서 자동으로 바인딩 한다.
/hello?title=kh&length=10
실제로 아래 코드처럼 setter와 getter를 제거하고 테스트를 실행해보면,
public class Music {
String title;
int length;
}
java.lang.AssertionError: No value at JSON path "title" 오류가 발생하면서 테스트가 실패한다.
결론
- @PathVariable이나 @ReqeustParam으로 받은 값은 요청 파라미터를 메소드에서 1:1로 받기 때문에 무엇으로 변환하는지 Formatter를 통해 명시적으로 알려줘야 한다.
- @ModelAttribute는 클라이언트가 전송하는 여러 파라미터들을 1대1로 객체에 바인딩하기 때문에 setter메소드만 구현했다면 자동으로 바인딩이 된다. 따라서 Formatter를 구현할 필요가 없다.
'Spring & JPA' 카테고리의 다른 글
[JPA] @OneToOne에서 Fetch 전략을 Lazy로 설정했을때 발생하는 이슈 (2) | 2021.03.24 |
---|---|
트랜잭션(Transactional) (0) | 2021.02.18 |
자동 의존성 주입 어노테이션에 대하여(@Autowired vs @Resource vs @Inject) (0) | 2020.12.19 |
[JPA] 영속성 컨텍스트에 대한 정리 (1) | 2020.10.24 |
@Valid 와 validation을 이용한 중복체크 및 유효성 검사 (0) | 2020.10.05 |