👨🏻🏫 MemberForm과 같은 객체를 커맨드 객체라고 하는데, xxForm과 같은 이름을 짓는 이유는 화면단에서 서버로 넘어온 Form객체임을 명확히 알 수 있다는 특징이 있습니다.
📌 출처 : 커맨드 객체
커맨드 객체 (Command Object)
커맨드 객체란? 쉽게 말해서 VO(DTO) 와 같다고 생각하셔도 됩니다. 즉, 커맨드 객체가 되기 위한 조건은 Getter와 Setter가 필수적으로 있어야 합니다.커맨드 객체의 역할에는 크게 3 가지로 나뉩니다.
- 컨트롤러에서 View 로 바인딩 : View 단에서 form:form 태그를 사용하는 경우
- View 에서 컨트롤러로 바인딩 : View 단에서 input type="text" 혹은 input type="hidden" 으로 값을 컨트롤러로 전송하는 경우
- 컨트롤러에서 Mapper.xml 로 바인딩 : Mapper.xml 에서 title = #{title}, contents = #{contents}처럼 사용하는 경우, 커맨드 객체를 통해 #{변수명}과 커맨드 객체의 필드명을 통해 바인딩 해주는 경우
👉🏻 View 에서 컨트롤러로 바인딩
@RequestMapping(value="/boardWrite", method=RequestMethod.POST) public String boardWrite(@Valid BoardDTO boardDTO, BindingResult bindingResult) { if(bindingResult.hasErrors()) { return "boardWrite"; // ViewResolver로 보냄 } else { return "redirect:/boardSearchList"; } }
등록 버튼을 누르게 되면 POST 방식으로 지정된 boardWrite 컨트롤러로 오게 됩니다.
boarsWrite 의 파라미터를 보면 BoardDTO boardDTO라고 커맨드 객체가 명시 되어있습니다.
그 이유는 View 의 폼에서 입력한 값을, 파라미터 (boardDTO) 를 통해 알아서 Setter 를 불러와 바인딩을 시켜줍니다.
즉, 파라미터 boardDTO 의 title 필드와, contents 필드에는 폼에서 입력한 값이 담겨 있습니다.
👦🏻 "회원 등록 기능 구현"에서는 두번째 역할인
'View 에서 컨트롤러로 바인딩'로 쓰인것 같다
@PostMapping("/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
👨🏻🏫
이건 스프링 MVC가 제공하는 기본 기능입니다^^
스프링 MVC는 HTTP 파라미터로 넘어오는 모든 이름들을 인식해서 (name, age 등등)
MemberForm 객체(직접 만든 아무 객체든 상관 없음)에 동일한 프로퍼티 이름이 있으면 찾아서 넣어줍니다.
해당 부분은 스프링 프레임워크에서 개발의 편의를 위해 자동으로 처리해주는 부분입니다.
우리가 HTML 폼에 어떤 자료를 작성하고 전송하면 HTTP 헤더 혹은 바디에 폼의 이름과 사용자가 입력한 값들을 짝을 지어서 전달하게 됩니다. (폼 전송방식이 GET일 경우 헤더에, POST일 경우 바디에 전달합니다). 이 때 전달하는 양식은 name=spring&age=13 처럼 키와 값으로 이루어져 있고 &로 구분되어 있습니다..
웹 개발에서는 대부분의 코드가 이 사용자가 입력한 폼값에 대한 처리라고 보셔도 됩니다. (나머지 반은 데이터베이스에 이 값을 넣거나 빼오거나 하는거죠 :)). 거기다가 사용자가 정확한 값을 입력하였는지 일일히 검토를 해야하기 때문에 매우 반복적이고 지루한 작업입니다.
그래서 스프링 프레임워크에서 이를 자동으로 처리해주는 기능을 제공하는것입니다. 폼에 있는 input 태그의 '이름' (name) 과 컨트롤러가 받아야 하는 객체 (여기서는 MemberForm이 됩니다)가 가지고 있는 필드중 이름이 일치하는 것이 있다면 자동으로 값을 할당한 후 컨트롤러의 매개변수로 전달하게 됩니다.
👨🏻🏫 커뮤니티 질문 답변
input이 하나가 아니라 여러개 일 수록 form 객체에 받아서 처리하는게 편리합니다.
Form 객체로 값을 받아서 오기 때문에 파라미터가 간소화 되어 있습니다.
또한 SpringMVC로 개발하다보면 @Valid 등 폼 객체를 검증을 할 수 있으며, 그에 대한 에러 결과도 파라미터로 넘어옵니다.
MemberForm 객체가 필요한 이유 : 파라미터 간소화 + 폼객체 검증 용이
🤷♀️ Q. MemberForm을 Controller에 선언한 이유와 실무에서도 저렇게 선언해도 되는지, 선언해도 되고 많이 선언해 사용한다면 vo? dto? domain? 이곳에 선언해야 할 것과 Controller에 선언해 사용해도 될 것의 차이가 무엇인지 궁금합니다.
🙆🏻♂️ A. MemberForm이 컨트롤러와 같은 web 패키지에 있는 이유는 Form을 웹 계층까지만 사용하기 때문입니다. Form을 서비스 계층에 넘기면 UI에 대한 의존도가 서비스까지 넘어가서 유지보수에 좋지 않은 결과를 가져올 수 있습니다.
MemberForm은 단순히 HTML의 Form 데이터를 전달하는 목적으로 사용합니다. 그래서 MemberForm의 역할을 단순히 컨트롤러가 있는 web 패키지로 제한하기 위해 이 위치에 두었습니다.
이것을 domain에 두어도 되지만, MemberForm은 단순히 HTML Form의 데이터만 전달하는 역할이기 때문에 domain의 역할을 하는 것은 아닙니다.
MemberForm의 목적은 화면에서 넘어온 Form 데이터를 받기 위한 목적입니다. 따라서 컨트롤러에서만 사용하는 것이 좋습니다.
도메인 패키지는 가급적 HTML Form이나 UI에서 직접 넘어오는 데이터를 모르도록 설계를 해야 좋습니다.
이렇게 분리해두면 향후 UI나 넘어오는 정보가 변경되어도 해당 도메인을 변경하지 않아도 됩니다.
(물론 도메인을 변경해야 할 만큼 큰 변화가 있으면 모두 변경이 필요합니다.)
🤷♀️ Q. 굳이 컨트롤러에 MemberForm을 만들 이유가 있나요? 그냥 MemberController에서 아래처럼 멤버를 바로 받아올 수는 없는건가요? 만약에 멤버폼을 반드시 이용해야하는 것이라면 MemberForm이라는 클래스는 어떠한 어노테이션도 없이 순수 자바코드인데 어떻게 폼에서 name값을 받아와 setName을 해주는지 궁금합니다.
혹시 input의 name속성과 MemberForm의 private 변수의 이름을 똑같이 지정해줘야 값을 받아올 수 있는 건지 궁금합니다
🙆🏻♂️ A.
-> 사실 컨트롤러에 MemberForm 대신에 Member를 그대로 받아도 기술적인 문제는 없습니다. 다만 지금은 예제이지만, 실무에서는 컨트롤러에 넘어오는 정보가 Member가 필요한 데이터 이상으로 많은 데이터들이 들어옵니다. 예를 들어서 회원 정보 뿐만 아니라 약관 정보도 들어오고, 화면을 처리하기 위한 추가 정보들도 들어옵니다. 또는 Member에 필요한 정보들이 화면이 아니라 데이터베이스에 더 있을 수도 있습니다. 더 여러가지가 있지만, 주로 이런 이유 때문에 MemberForm과 Member를 분리하는 것이 좋습니다^^
-> 스프링 MVC가 기본으로 지원하는 기능입니다^^ 웹의 파라미터에 name이라는 이름이 있으면 이 이름을 보고 스프링 MVC가 setName을 호출합니다.