본문 바로가기

spring

@ModelAttribute와 커맨드 객체(2)

커맨드 객체(Command Object) 

커맨드 객체(Command Object)란 HttpServletRequest를 통해 들어온 요청 파라미터들을 setter메서드를 이용하여 객체에 정의되어있는 속성에 바인딩이 되는 객체를 의미합니다.

 

커맨드 객체는 보통 VO 나 DTO를 의미하며, HttpServletRequest로 받아오는 요청 파라미터의 key 값과 동일한 이름의 속성들과 setter 메서드를 가지고 있어야 합니다.

 

어떻게 자동으로 바인딩을 시켜주냐 하면, 스프링이 내부적으로 HttpServletRequest와 커맨드 객체의 setter 메서드를 이용하여 알아서 바인딩 시켜줍니다. 마치 객체를 JSON 형식으로 변환하기 위해 Jackon2ObjectMapperBuilder가 autoDetectGetterSetters() 메서드를 이용하는 것과 비슷하다고 생각하시면 됩니다.

@Getter @Setter
public class User {
    private String userName;
    private String phone;
    private int age;
}
@PostMapping("/ins")
public String ins(User user, Model model) {
    String name = user.getUserName();
    String phone = user.getPhone();
    int age = user.getAge();
    
    //user 파라미터를 model에 담는다.
    model.addAttribute("user", user);
    return REDIRECT_LIST;
}

 

HttpServletRequest 나 @RequestParam을 사용하는 것보다 훨씬 코드 양도 줄고, 가독성도 좋아지고 간편해졌습니다.

 

위에서 user파라미터를 model에 담는 것을 볼 수 있습니다. 이 코드 또한 @ModelAttribute 어노테이션을 사용하여 제거할 수 있습니다.

 

@ModelAttribte

@ModelAttribute의 사용 위치에 따라 기능이 달라집니다. 크게 메서드명위에 사용되는 경우와 파라미터 옆에 사용되는 경우로 나뉩니다.

 

@ModelAttribute의 기능 중 하나를 먼저말하자면, 커맨드 객체와 같이 요청 파라미터들을 객체 프로퍼티에 바인딩 시켜준다는 것입니다. 하지만 @ModelAttribute를 생략해도 커맨드 객체를 이용해서 바인딩이 되는데, @RequestParam 또한 생략해도 사실상 바인딩이 가능합니다.

 

그 이유는 스프링이 내부적으로 String 이나 int 등은 @RequestParam으로 보고, 그 외의 복잡한 객체들은 @ModelAttribute가 생략되었다고 간주하기 때문입니다. 하지만 그렇다고 무조건 생략하는 것은 위헙합니다. 스프링은 간단한 숫자나 문자로 전달된 요청 파라미터를 제법 복잡한 객체로 변환할수도 있기 때문입니다.

 

이제 나머지 기능에 대해서 알아보겠습니다.

 

파라미터 객체 옆에 @ModelAttribute 사용하는 경우

@PostMapping("/ins")
public String ins(@ModelAttribute User user, Model model) {
    String name = user.getuserName();
    String phone = user.getPhone();
    int age = user.getAge();
    
    // user 객체를 모델에 담는 코드를 작성하지 않아도, 담겨져 있다.
    // 내부적으로 model.addAttribute("user", user); 로 담는다.
    // 만약 객체명과 변수명이 @ModelAttribute UserVo user로 되어있는 경우에는 어떻게 담길까?
    // 클래스명을 기준으로 카멜케이스를 적용하여 model.addAttribute("userVo", user); 로 담는다.
    
    return REDIRECT_LIST;
}

 

@ModelAttribute의 역할 중 하나는 model에 객체를 담아준다는 것입니다. 파라미터 객체 옆에 @ModelAttribute를 사용했을 때 얻는 또 다른 이점은 @ModelAttribute가 붙은 파라미터를 처리할 때는 @RequestParam과 달리 검증(Validation) 작업을 내부적으로 진행합니다.

 

@RequestParam의 경우 스프링의 기본 타입 변환 기능을 이용해서 요청 파라미터 값을 메서드 파라미터 타입으로 변환하는데, 만약 숫자 타입의 파라미터라면 문자열 타입으로 들어온 요청 파라미터의 타입 변환을 시도하고 실패하면 Http 400 Bad Requeset 응답이 클라이언트로 가게 됩니다.

 

이 경우, 친절하게 메시지를 보여주고 싶으면

org.springframework.beans.TypeMismatchException 예외를 처리하는 예외 리졸버를 추가해주면 됩니다.

 

하지만 @ModelAttribute 의 경우 내부적으로 검증(Validation) 작업을 진행하기 때문에 setter 메서드를 이용하여 값을 바인딩하려고 시도하닥 예외를 만나지만 작업이 중단되면서 Http 400 Bad Request가 발생하지 않습니다. 타입 변환에 실패해도 작업은 계속되며 BindingException 타입의 객체에 담겨서 컨트롤러로 전달됩니다.

 

보통 등록이나, 수정을 처리하는 핸들러 메서드의 경우 다양한 검증을 실시해야 하고, 사용자의 입력 값에 오류가 있을 때에는 이에 대한 처리를 컨트롤러에게 맡겨야 합니다. 따라서 @ModelAttribute를 통해서 폼의 정보를 전달 받는 경우 Errors 객체나 BindingResult 객체를 @ModelAttribute가 붙은 파라미터 바로 뒤에 선언해서 검증 처리를 실시합니다.

 

메서드 위에 @ModelAttribute가 사용되는 경우

컨트롤러에서 메서드 위에 @ModelAttribute가 사용되는 경우는, 해당 컨트롤러 내의 어떠한 핸들러 메서드들보다 먼저 동작하게 됩니다.

/**
 * @ModelAttribute 메서드가 먼저 동작하기 때문에
 * 다른 핸들러 메서드에서 model 에 담겨져 있는 user 키 값을 이용하여
 * user 객체를 꺼내서 쓸 수 있다.
 */
@ModlAttribute("user")
public String initUser() {
    // 내부적으로 model.addAttribute("user", userService.findUser(FIRST_USER_SEQ)); 
    // 형태로 담는다.
    return userService.findUser(FIRST_USER_SEQ);
}

따라서 여러 핸들러 메서드에서 공통으로 쓰이며, View 단에서도 꺼내 쓸일이 있는 것들은 이런식으로 처리해서 사용하기도 합니다.

 

결론

HttpServletRequest 부터 @ModelAttribute 까지 알아봤습니다. 해당 기능들을 잘 숙지하여 적절한 상황에 알맞게 사용하는 것이 좋습니다.

 

'spring' 카테고리의 다른 글

Spring MVC 구조 - (하)  (0) 2020.10.20
IOC(Inversion of Control)컨테이너 - 서블릿 컨테이너  (0) 2020.10.03
MVC 프레임워크 구현 - (중)  (0) 2020.10.03
@ModelAttribute와 커맨드 객체(1)  (0) 2020.09.06
web.xml - Element  (0) 2020.03.15