Preventing duplicate form submission
Spring MVC에서는 중복된 폼 서브밋을 방지하기 위해 AbstractFormController를 제공하고, 폼기능 구현할때 사용하는 SimpleFormController 또한
AbstractFormController를 상속받아 위와같은 처리가 가능하다. Anyframe에서는 이같은 중복된 폼 서브밋을 방지하기위해 SimpleFormController를
확장한 SessionFormController를 제공한다. 하지만 AbstractFormController를 상속받는 모든 컨트롤러에서는 메소드 이름을 통한 MultiAction기능을 사용할 수 없으며
사용 방법 또한 복잡하다. 이에 SessionFormController 현재 deprecated되어 있는 Class 이며 Anyframe Web 3.1.0 버전에서 제외될 예정이므로 별도의 설명은 하지 않도록 하겠다.
이러한 SessionFormController의 제약에 따라 Anyframe Web에서는 SessionFormController와 MultiActionController의 기능을 합한 AnyframeFormController를 확장하여 제공한다.
이러한 AnyframeFormController를 사용함으로써 간단한 property 설정만으로 중복 폼 서브밋 방지 기능을 사용할 수 있다.
property 정의하기
중복 폼 서브밋의 방지 기능의 원리는 기능을 적용할 페이지로 가는 Action을 수행할 때 폼정보를 세션에 저장하고 페이지 출력 후 서브밋이 발생하면 session에 폼정보가
있는지를 확인, 없으면 에러메시지를 출력하고 있으면 세션의 form 정보를 없애고 정상 수행을 한다.
이러한 적용을 위해 폼 중복 서브밋을 적용할 페이지의 전 후 Action에 sessionForm 속성을 true로 지정하며, 페이지 출력을 위한 Action에는 폼 정보를 세션에 저장하기 위해
showNewForm 속성을 true로 지정해줘야 한다. 또한 페이지로 가기전 Action에서 세션에 폼정보를 저장한 후 포워딩해줄 뷰를 지정해 줘야하는데 이 뷰는 중복 서브밋 방지 기능을
사용할 뷰를 의미하며 formView라는 프로퍼티로 정의한다.
속성 정의 방법은 다음과 같다.
AnyframeFormController 사용하기
AnyframeFormController의 사용 방법은 중복 폼 서브밋 방지 기능을 적용할 페이지 전, 후의 Action에 따라 구현해야 하는 메소드가 달라진다.
메소드 구현 방법은 다음과 같다.
- 페이지 출력용 action (Ex. addUserView.do)
중복 폼 서브밋 방지 기능을 구현 할 페이지로 이동하기 위해서는 위에서 언급한 바와 같이 sessionForm과 showNewForm 속성을 true로 설정해야한다.
여기서 showNewForm을 true로 설정하게 되면 별도의 method 정의와 상관없이 AnyframeFormController에서 제공하는 formBackingObject 메소드를 호출하게 되고
이는 반드시 구현해야한다. 이 메소드 안에는 페이지 출력시 필요한 데이터를 넘겨주며 매핑될 도메인 객체를 생성하여 리턴해 준다. 이 메소드를 정의함에 따라 비로소 세션에
폼 정보를 저장할 수 있다. 구현 예는 다음과 같다.
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
//addUser.jsp에서 출력해줘야할 데이터가 있으면 정의해준다.
//ex> request.setAttribute("UserVO", userVO);
//form에서 사용할 도매인 객체를 생성한다.
return new UserVO();
}
- 페이지에서의 submit action (Ex. addUser.do)
폼 서브밋 후 수행될 Action은 위에서 본바와 같이 sessionForm 속성을 true로 정의해 주면 된다. 기타 구현방법은 MultiActionController와 같다.
public ModelAndView addUser(HttpServletRequest request,
HttpServletResponse response) throws Exception {
UserVO userVO = new UserVO();
bind(request, userVO);
userService.addUser(userVO);
return new ModelAndView(this.getSuccess_add());
}
* Tip : Spring MVC에서는 ModelAndView 타입의 객체를 리턴할 때 포워딩할 뷰 이름을 설정하게 되는데 AnyframeFormController에서는 기본 CRUD에 관한
success_add, success_update, succes_get, success_list, succeess_delete 의 프로퍼티를 상위클래스에서 setter injection으로 꺼내고 있다.
이에 개발자는 설정파일에 위와같은 프로퍼티에 매핑될 페이지를 value로 정의함으로써 쉽게 사용할 수 있다. 다른 이름의 프로퍼티로 정의하여 사용하고 싶다면 프로퍼티로 정의 후
setter injection을 통해 사용할 수 있다. 또한 위와 같이 한 컨트롤러에서 여러 action을 정의할 수 있게 ParameterMethodResolver를 통해 메소드를 정의할 수 있는데
만약, 여러 Action이 아닌 한 컨트롤러에 하나의 action만 수행할 때는 ParameterMethodResolver 없이 서브밋 후 수행할 한가지 action을 public ModelAndView process(HttpServetRequest request, HttpServletResponse response) 메소드에 구현하는 것이 가능하다.

messageSource 추가하기
AnyframeFormController를 사용하여 중복 폼 서브밋이 발생하였을 때 에러메시지를 출력해 줘야하는데 이 메시지는 anyframe.web.springmvc-3.0.1.jar 파일의
anyframe/web/springmvc/messages/springmvc.properties파일에 정의되어 있다. 이 때 사용하는 key값은 common.msg.invalidtoken.error이다.
그러므로 이 resource properties파일을 applicationContext.xml의 messageSource 정의 부분에 해당 properties 파일을 등록해 정의해줘야한다.
<bean
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
id="messageSourceWeb">
<property name="basenames">
<list>
<!-- Anyframe Web. Service message properties files -->
<value>anyframe/web/springmvc/messages/springmvc</value>
</list>
</property>
</bean>
또한 Anyframe에서 제공해주는 기본 메시지가 아닌 다른 문자열을 지정해 주고 싶으면 common.msg.invalidtoken.error 키 값으로 properties파일로 정의한 후 MessageSource설정 부분에 위와같이 파일을 추가 하면된다.
AnyframeFormController Sample
다음은 AnyframeFormController를 사용한 샘플 코드이다.
- user-servlet.xml
//ParameterMethodNameResolver정의 . 아래와 같이 method로 정의할 경우 addUser.do?method=addUser로 정의하여 사용
<bean id="paramResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
<property name="paramName"><value>method</value></property>
</bean>
<!-- User 등록 화면 이동 -->
<bean name="/**/addUserView.do" class="com.sds.emp.web.UserController">
<property name="userService" ref="com.sds.emp.services.UserController"/>
<property name="sessionForm" value="true"/>
<property name="showNewForm" value="true"/>
<property name="formView" value="/user/addUser.jsp" />
</bean>
<!-- User 등록 -->
<bean name="/**/addUser.do" class="com.sds.emp.web.UserContoller">
<property name="methodNameResolver" ref="paramResolver"></property>
<property name="userService" ref="com.sds.emp.services.UserController"/>
<property name="success_add" value="/userList.do?method=searchUserList"/>
<property name="sessionForm" value="true"/>
</bean>
- UserController.java
public class UserController extends AnyframeFormController {
// Setter Injection
private UserService userService = null;
public void setUserService(UserService userService) {
this.userService = userService;
}
// Call business methods
/**
* user 등록 (addUser.do?method=addUser)
*/
public ModelAndView addUser(HttpServletRequest request,
HttpServletResponse response) throws Exception {
UserVO userVO = new UserVO();
//폼 입력 데이터 바인딩
bind(request, userVO);
userService.addUser(userVO);
return new ModelAndView(this.getSuccess_add());
}
/**
* 중복 폼 서브밋 방지를 위해 xml 설정의 property인 sessionForm, showNewForm이 true일 경우 아래의
* formBackingObject를 수행한다. formBackingObject는 중복 폼 서브밋 방지를 수행할 페이지로 가는 액션을
* 수행할 때 session에 폼 정보를 저장하며 해당 뷰에 필요한 데이터를 전달한다.
*
* user 등록 화면 이동
*/
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
// 별도의 페이지로 전달해 줄 데이터가 없음(필요시 입력)
// 도메인객체 생성
return new UserVO();
}
}