`
kingxss
  • 浏览: 973841 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Spring3 MVC的最佳实践和理解(7)

阅读更多

 

个人学习参考所用,勿喷! 

 

7.表单处理和多页表单向导

7.1)表单处理。

表单处理很常见。比如我们有下面的表单:

<form:form method="post" modelAttribute="reservation">
<form:errors path="*" cssClass="error" />
<table>
  <tr>
    <td>Court Name</td>
    <td><form:input path="courtName" /></td>
    <td><form:errors path="courtName" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Date</td>
    <td><form:input path="date" /></td>
    <td><form:errors path="date" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Hour</td>
    <td><form:input path="hour" /></td>
    <td><form:errors path="hour" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Player Name</td>
    <td><form:input path="player.name" /></td>
    <td><form:errors path="player.name" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Player Phone</td>
    <td><form:input path="player.phone" /></td>
    <td><form:errors path="player.phone" cssClass="error" /></td>
  </tr>
  <tr>
    <td colspan="3"><input type="submit" /></td>
  </tr>
</table>
</form:form>

 

这里使用的是spring的标签,HTML中需要<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>。 很多属性一看就知道含义。需要注意的是,modelAttribute属,这里联想下Struts2中的值栈就能够明白这里代表的是处理的返回结果。

编写后台的控制器类:

@Controller
// Bind controller to URL /reservationForm
// initial view will be resolved to the name returned in the default GET method
@RequestMapping("/reservationForm")
// Add Reservation object to session, since its created on setup and used after submission
@SessionAttributes("reservation") // Command name class was used in earlier Spring versions
public class ReservationFormController {
    private ReservationService reservationService;

    // Wire service in constructor, available in application context 
    @Autowired
    public ReservationFormController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }

    // Create attribute for model 
    // Will be represented as drop box Sport Types in reservationForm
    @ModelAttribute("sportTypes")
    public List<SportType> populateSportTypes() {
        return reservationService.getAllSportTypes();
    }

    // Controller will always look for a default GET method to call first, irrespective of name
    // In this case, named setupForm to ease identification
    @RequestMapping(method = RequestMethod.GET)
    public String setupForm( @RequestParam(required = false, value = "username") String username,
						Model model) {
        Reservation reservation = new Reservation();
        reservation.setPlayer(new Player(username, null));
        model.addAttribute("reservation", reservation);
        return "reservationForm";
    }

    // Controller will always look for a default POST method irrespective of name
    // when a submission ocurrs on the URL (i.e.@RequestMapping(/reservationForm)) 
    // In this case, named submitForm to ease identification
    @RequestMapping(method = RequestMethod.POST)
    // Model reservation object, BindingResult and SessionStatus as parameters 
    public String submitForm(@ModelAttribute("reservation") Reservation reservation,
            BindingResult result, SessionStatus status) { 
        reservationService.make(reservation);
		return "redirect:reservationSuccess";
    }
}

 需要注意的是@SessionAttributes("reservation")注解和submitForm(...)方法的返回值方式return "redirect:reservationSuccess"。

@SessionAttributes是的reservation对象唯一的存在于用户会话中,目的是为了保持用户每次提交的数据(用户提交可能会出错,所以不是一次提交就一定能够成功)。

"redirect:reservationSuccess"逻辑视图表明重定向到reservationSuccess逻辑视图,应用中设定是返回reservationSuccess.jsp,这样做的目的是为了防止提交成功后的刷新造成再次提交。这里应用了post/redirect/get设计模式来处理这个问题。

 

提供表单参考数据。例如这里的体育类型查考选择框:

<tr>
<td>Sport Type</td>
<td>
  <form:select path="sportType" items="${sportTypes}"
	itemValue="id" itemLabel="name" />
</td>
<td><form:errors path="sportType" cssClass="error" /></td>
</tr>

 

这里需要在控制器中添加一个@ModelAttribute("sportTypes") 标识的模式属性sportTypes(@ModelAttribute用于定义全局模式属性),在前面的submitForm(...)中也有@ModelAttribute("reservation") 用于应用session中的reservation对象。

@ModelAttribute("sportTypes")
public List<SportType> populateSportTypes() {
	return reservationService.getAllSportTypes();
}

 

绑定自定义类型的属性。上述的SportType属性提交到处理程序是一个String id = "1" 的字符串类型,我们需要的是String  sportType= "棒球" 这样字段。那么这里需要如同Struts2中的转换器相同功能的实现方式:

public class SportTypeEditor extends PropertyEditorSupport {

    private ReservationService reservationService;

    public SportTypeEditor(ReservationService reservationService) {
        this.reservationService = reservationService;
    }

    public void setAsText(String text) throws IllegalArgumentException {
        int sportTypeId = Integer.parseInt(text);
        SportType sportType = reservationService.getSportType(sportTypeId);
        setValue(sportType);
    }
}

 

有了这个转换实现类,我们现在需要将其对应的控制器类联系起来,这里需要编写自己的WebBindingInitializer实现:

public class ReservationBindingInitializer implements WebBindingInitializer {

    private ReservationService reservationService;

    @Autowired
    public ReservationBindingInitializer(ReservationService reservationService) {
        this.reservationService = reservationService;
    }

    public void initBinder(WebDataBinder binder, WebRequest request) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(
                dateFormat, true));
        binder.registerCustomEditor(SportType.class, new SportTypeEditor(
                reservationService));
    }
}

 

还需要在应用上下文中进行配置注册:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
	<property name="webBindingInitializer">
		<bean class="com.apress.springrecipes.court.web.ReservationBindingInitializer" />
	</property>
</bean>

 

 

表单的校验。表单成功提交前的校验在服务器端也需要做不仅是标准的做法,尤其在请求来自不支持JavaScript的wap终端时。这里可以编写如下的检验类:

@Component
public class ReservationValidator implements Validator {

    public boolean supports(Class<?> clazz) {
        return Reservation.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "courtName",
                "required.courtName", "Court name is required.");
        ValidationUtils.rejectIfEmpty(errors, "date",
                "required.date", "Date is required.");
        ValidationUtils.rejectIfEmpty(errors, "hour",
                "required.hour", "Hour is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "player.name",
                "required.playerName", "Player name is required.");
        ValidationUtils.rejectIfEmpty(errors, "sportType",
                "required.sportType", "Sport type is required.");

        Reservation reservation = (Reservation) target;
        Date date = reservation.getDate();
        int hour = reservation.getHour();
        if (date != null) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                if (hour < 8 || hour > 22) {
                    errors.reject("invalid.holidayHour", "Invalid holiday hour.");
                }
            } else {
                if (hour < 9 || hour > 21) {
                    errors.reject("invalid.weekdayHour", "Invalid weekday hour.");
                }
            }
        }
    }
}

 @Component注解告诉Spring实例化该类为与类名一样的Bean,这里为reservationValidator。这里需要像激活@Controller一样的激活改类所在的包。

然后在ReservationFormController控制器中添加添加使用刚刚定义的校验器:

@Controller
@RequestMapping("/reservationForm")
@SessionAttributes("reservation")
public class ReservationFormController {
    private ReservationService reservationService;
    private ReservationValidator validator;

    @Autowired
    public ReservationFormController(ReservationService reservationService, 
	    ReservationValidator validator) {
        this.reservationService = reservationService;
        this.validator = validator;
    }

    @ModelAttribute("sportTypes")
    public List<SportType> populateSportTypes() {
        return reservationService.getAllSportTypes();
    }

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(Model model) {
        Reservation reservation = new Reservation();
        reservation.setPlayer(new Player(username, null));
        model.addAttribute("reservation", reservation);
        return "reservationForm";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String submitForm(@ModelAttribute("reservation") Reservation reservation,
            BindingResult result, SessionStatus status) { 
        validator.validate(reservation, result);	
        if (result.hasErrors()) {
            return "reservationForm";
        } else {
            reservationService.make(reservation);
			status.setComplete();
            return "redirect:/reservationSuccess";
        }
    }
}

 

 这里通过ReservationValidator的校验,如果有错误则返回错提示。一旦校验成功,完成提交后,需要通过status.setComplete()来释放@SessionAttributes("reservation")中的reservation对象,以此来处理到期的控制器的回话数据。

 

7.2)向导表单。

处理跨越多页的向导表单(Wizard forms),需要为向导控制器定义多个视图,一个控制器管理所有这些表单数据的状态。现在需要使用向导表单来完成预订球场功能,请求domain代码如下:

public class PeriodicReservation {
    private String courtName;
    private Date fromDate;
    private Date toDate;
    private int period;
    private int hour;
    private Player player;
    //getter an setter
	...
}

 

 表单向导页依次分为如下三页:

reservationPlayerForm.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!-- 添加页面编码设置来解决编码问题 -->
<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>  
<html>
<head>
<title>Reservation Court Form</title>
<style>
.error {
  color: #ff0000;
  font-weight: bold;
}
</style>
</head>

<body>
<form:form method="post" modelAttribute="reservation">
<table>
  <tr>
    <td>Court Name</td>
    <td><form:input path="courtName" /></td>
    <td><form:errors path="courtName" cssClass="error" /></td>
  </tr>
  <tr>
    <td colspan="3">
      <input type="hidden" value="0" name="_page"/>
      <input type="submit" value="Next" name="_target1" />
      <input type="submit" value="Cancel" name="_cancel" />
    </td>
  </tr>
</table>
</form:form>
</body>
</html>

reservationTimeForm.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!-- 添加页面编码设置来解决编码问题 -->
<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>  
<html>
<head>
<title>Reservation Time Form</title>
<style>
.error {
  color: #ff0000;
  font-weight: bold;
}
</style>
</head>

<body>
<form:form method="post" modelAttribute="reservation">
<table>
  <tr>
    <td>From Date</td>
    <td><form:input path="fromDate" /></td>
    <td><form:errors path="fromDate" cssClass="error" /></td>
  </tr>
  <tr>
    <td>To Date</td>
    <td><form:input path="toDate" /></td>
    <td><form:errors path="toDate" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Period</td>
    <td><form:select path="period" items="${periods}" /></td>
    <td><form:errors path="period" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Hour</td>
    <td><form:input path="hour" /></td>
    <td><form:errors path="hour" cssClass="error" /></td>
  </tr>
  <tr>
    <td colspan="3">
      <input type="hidden" value="1" name="_page"/>
      <input type="submit" value="Previous" name="_target0" />
      <input type="submit" value="Next" name="_target2" />
      <input type="submit" value="Cancel" name="_cancel" />
    </td>
  </tr>
</table>
</form:form>
</body>
</html>

 

reservationPlayerForm.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!-- 添加页面编码设置来解决编码问题 -->
<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>  
<html>
<head>
<title>Reservation Player Form</title>
<style>
.error {
  color: #ff0000;
  font-weight: bold;
}
</style>
</head>

<body>
<form:form method="post" modelAttribute="reservation">
<table>
  <tr>
    <td>Player Name</td>
    <td><form:input path="player.name" /></td>
    <td><form:errors path="player.name" cssClass="error" /></td>
  </tr>
  <tr>
    <td>Player Phone</td>
    <td><form:input path="player.phone" /></td>
    <td><form:errors path="player.phone" cssClass="error" /></td>
  </tr>
  <tr>
    <td colspan="3">
      <input type="hidden" value="2" name="_page"/>
      <input type="submit" value="Previous" name="_target1" />
      <input type="submit" value="Finish" name="_finish" />
      <input type="submit" value="Cancel" name="_cancel" />
    </td>
  </tr>
</table>
</form:form>
</body>
</html>

上面三个视图中包含了pre、next、submit和cancel按钮。控制器代码如下: 

@Controller
@RequestMapping("/periodicReservationForm")
@SessionAttributes("reservation")
public class PeriodicReservationController {

    private ReservationService reservationService;
    @SuppressWarnings("unused")
	private PeriodicReservationValidator validator;

    @Autowired
    public PeriodicReservationController(ReservationService reservationService,
             PeriodicReservationValidator periodicReservationValidator) {
        this.reservationService = reservationService;
	this.validator = periodicReservationValidator;
    }

 
    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(Model model) {	
        PeriodicReservation reservation = new PeriodicReservation();
        reservation.setPlayer(new Player());
        model.addAttribute("reservation", reservation);
        return "reservationCourtForm";
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @RequestMapping(method = RequestMethod.POST)
    public String submitForm(
	    HttpServletRequest request, HttpServletResponse response,
            @ModelAttribute("reservation") PeriodicReservation reservation,
            BindingResult result, SessionStatus status,
	    @RequestParam("_page") int currentPage, Model model) {	
	Map pageForms = new HashMap();
	pageForms.put(0,"reservationCourtForm");// Mapped to /WEB-INF/jsp/reservationCourtForm.jsp
	pageForms.put(1,"reservationTimeForm");// Mapped to /WEB-INF/jsp/reservationTimeForm.jsp
	pageForms.put(2,"reservationPlayerForm");// Mapped to /WEB-INF/jsp/reservationPlayerForm.jsp
	if (request.getParameter("_cancel") != null) {
	    return (String)pageForms.get(currentPage);	    
	} else if (request.getParameter("_finish") != null) {
	    new PeriodicReservationValidator().validate(reservation, result);
	    if (!result.hasErrors()) { 
		reservationService.makePeriodic(reservation);
		status.setComplete();
		return "redirect:reservationSuccess";
	    } else {
		return (String)pageForms.get(currentPage);	    
	    }
	} else {
	    int targetPage = WebUtils.getTargetPage(request, "_target", currentPage);
	    if (targetPage < currentPage) {
		return (String)pageForms.get(targetPage);
	    } 
	    switch (currentPage) {
   	        case 0: 
		    new PeriodicReservationValidator().validateCourt(reservation, result); break;
	        case 1: 
		    new PeriodicReservationValidator().validateTime(reservation, result); break;
    	        case 2: 
		    new PeriodicReservationValidator().validatePlayer(reservation, result); break;
	    }
	    if (!result.hasErrors()) {
		return (String)pageForms.get(targetPage);
	    } else { 
		return (String)pageForms.get(currentPage);
	    }
	}
    }
    
    @ModelAttribute("periods")
    public Map<Integer, String> periods() {	
	Map<Integer, String> periods = new HashMap<Integer, String>();
	periods.put(1, "Daily");
	periods.put(7, "Weekly");
        return periods;
    }
}

 

 这里还是用HTTP GET方法来初始化空表单,用一个HTTP POST处理方法来处理前进、后退、提交和退出等事件。不同页面中的数据都用@SessionAttributes("reservation")的唯一的会话对象中。在这里还添加对数据的校验功能,而且需要特别注意上面对每次next步骤中的数据校验,可以看到校验器代码如下:

@Component
public class PeriodicReservationValidator implements Validator {

    public boolean supports(Class<?> clazz) {
        return PeriodicReservation.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        validateCourt(target, errors);
        validateTime(target, errors);
        validatePlayer(target, errors);
    }

    public void validateCourt(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "courtName",
                "required.courtName", "Court name is required.");
    }

    public void validateTime(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "fromDate",
                "required.fromDate", "From date is required.");
        ValidationUtils.rejectIfEmpty(errors, "toDate", "required.toDate",
                "To date is required.");
        ValidationUtils.rejectIfEmpty(errors, "period",
                "required.period", "Period is required.");
        ValidationUtils.rejectIfEmpty(errors, "hour", "required.hour",
                "Hour is required.");
    }

    public void validatePlayer(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "player.name",
                "required.playerName", "Player name is required.");
    }
}

 

 这种更加细粒度的校验实现对分页表单相当来说是最好的检验方式。

 

 

参考:

juyon的blog:spring3 MVC国际化支持之中文乱码

Gary Mark等的书籍:《Spring Recipes》2ed

分享到:
评论

相关推荐

    Spring MVC jar包

    而`spring-framework-2.5.6-with-docs.zip`可能包含了Spring 2.5.6的源码和文档,帮助开发者了解Spring MVC的内部实现和最佳实践。 总之,这个压缩包提供了开发基于Spring MVC和Hibernate的Java Web应用所需要的...

    spring-mvc-showcase

    首先,"spring-mvc-showcase" 是 SpringSource 提供的一个示例应用,用于展示 Spring MVC 的各种特性和最佳实践。这个项目包含了一系列精心设计的示例,涵盖了从基本请求处理到复杂业务逻辑的多种场景,是学习 ...

    Spring3 MVC 深入研究

    **Spring3 MVC 深入研究*...通过深入理解其核心机制和最佳实践,开发者可以构建高效、可维护的Web应用。随着Spring框架的不断演进,如Spring Boot和Spring Cloud等,Spring3 MVC的理念和设计模式仍具有很高的参考价值。

    网上书城 spring-mvc

    《网上书城 Spring-MVC》项目是基于Spring-MVC框架构建的一个典型电子商务平台,它展示了如何使用Spring-MVC来实现高效、...开发者可以通过学习和实践这个项目,深入理解Spring-MVC的机制和最佳实践,提升Web开发能力。

    Maven构建Spring3mvc和Hibernate

    ** Maven构建Spring3 MVC与Hibernate整合详解 ** 在软件开发中,Maven是一个强大的...在实际开发中,还需要结合最佳实践和具体需求进行调整和优化。通过阅读博文链接中的内容,可以获取更具体的实现细节和示例代码。

    Spring4Spring-MVc.rar_mvc4_spring4_spring4 mvc_www.mvn3

    同时,模板中的最佳实践和常见配置示例可以帮助开发者理解和学习这些技术的使用方式。 在实际使用过程中,开发者可以按照以下步骤操作: 1. 解压"Spring4Spring-MVc.rar",得到项目目录结构。 2. 使用IDE导入Maven...

    廖雪峰 Spring 2.0 核心技术与最佳实践 高清扫描版

    《Spring 2.0 核心技术与最佳实践》是由知名IT教育家廖雪峰编写的教程,旨在为从初学者到高级工程师提供全面而深入的Spring 2.0框架理解与应用指导。Spring框架是Java开发中的核心工具,尤其在企业级应用中广泛使用...

    Spring.MVC学习指南.pdf

    以上是Spring MVC的基本知识点,这份学习指南可能会详细讲解这些内容,并提供实际案例和最佳实践,帮助读者从基础到高级全面掌握Spring MVC。通过深入学习,开发者能够更好地构建高效、可扩展的Web应用。

    Spring3MVC+ajax

    在Web应用开发中,Spring3 MVC框架和Ajax技术的结合使用能够极大地提升用户体验,实现页面的无刷新更新。本文将深入探讨如何在Spring3 MVC项目中集成Ajax,以便更好地理解这两者的协同工作原理。 **一、Spring3 MVC...

    Java EE企业级应用开发教程(Spring Spring MVC MyBatis)(黑马程序员编著)

    通过学习本书,开发者不仅可以掌握Java EE开发的基本技能,还能深入理解Spring、Spring MVC和MyBatis这三个框架的原理和最佳实践。这将有助于他们在实际工作中构建出更加高效、可扩展的后端应用,同时也能提升团队...

    Spring核心技术与最佳实践

    通过深入理解Spring的核心技术和最佳实践,开发者可以创建出高效、可维护的Web应用程序。在实际开发中,掌握HTTP协议原理、Servlet组件的使用以及Spring MVC的配置和编程模型,对于优化代码结构和提升开发效率至关...

    【面试资料】-(机构内训资料)看透Spring MVC源代码分析与实践.zip

    在面试中,对Spring MVC的深入理解和源代码分析能力通常被视为高级Java开发者的重要技能。这份【面试资料】-(机构内训资料)看透Spring MVC源代码分析与实践.zip文件很可能是为了帮助求职者准备相关面试问题而设计...

    Mastering Spring Webmvc

    《精通Spring Web MVC》 Spring Web MVC是Spring框架的核心组件之一,它为构建基于Java的Web应用程序提供了模型-...通过阅读《Mastering Spring Webmvc》及参考博文,可以深入理解Spring MVC的内部机制和最佳实践。

    Spring mvc整合mybatis例子

    7. **最佳实践** - 使用@Autowired注解进行依赖注入,减少XML配置。 - 利用Spring的AOP实现事务管理,确保数据一致性。 - 使用MyBatis的动态SQL功能,使SQL更易于维护和调整。 综上所述,整合Spring MVC和...

    spring mvc step by step,例子

    在实际应用中,我们还会涉及到一些高级特性和最佳实践,例如: - **AOP(面向切面编程)**:可以用来实现事务管理、日志记录等功能,通过定义切面和通知,将这些通用逻辑与业务代码解耦。 - **Spring Data JPA**:...

    最新Spring3 MVC 示例 demo程序

    这个"最新Spring3 MVC 示例 demo程序"旨在帮助开发者理解并掌握Spring 3的最新特性和最佳实践。 1. **Spring MVC基本架构** Spring MVC通过DispatcherServlet作为前端控制器,它负责接收HTTP请求,并根据请求信息...

    spring4-mvc-gradle

    《Spring4-MVC-Gradle:构建现代Java Web应用程序的基石》 在现代Web开发领域,Spring框架以其强大的功能和灵活性...通过深入研究这个项目,你可以掌握构建现代Web应用的关键技术和最佳实践,从而提升你的开发技能。

    spring-MVC搭建所需包(spring3.0)附带搭建源码

    - 开源项目:通过阅读和分析开源项目中的Spring MVC代码,可以学习到实际应用场景下的最佳实践。 总之,Spring MVC提供了一种强大且灵活的方式来构建Web应用程序,其注解驱动的特性极大地简化了开发流程。通过理解...

Global site tag (gtag.js) - Google Analytics