SPRING WEB MVC 注解

0.提示

1) Spring发行版本附带了PetClinic 示例,它是一个在简单的表单处理的上下文中, 利用了本节中说明的注解支持的Web应用程序。可以在“samples/petclinic ”目录中找到PetClinic 应用程序。

 

2) 另外一个建立在基于注解的Web MVC上的示例应用程序,请见imagedb 。

    这个示例集中在无状态的multi-action控制器,包括多段文件上传的处理。

    可以在“samples/imagedb ”目录找到imagedb 应用程序。

 

1.建立dispatcher实现注解支持

只有对应的HandlerMapping(为了实现类型级别的注解)和/ 或HandlerAdapter (为了实现方法级别的注解)出现在 dispatcher中时, @RequestMapping 才会被处理。 这在DispatcherServlet 和DispatcherPortlet 中都是缺省的行为。

 

然而,如果是在定义自己的HandlerMappings或HandlerAdapters ,就需要确保一个对应的自定义的DefaultAnnotationHandlerMapping 和 /或AnnotationMethodHandlerAdapter 同样被定义——假设想要使用@RequestMapping 。

 
 

<?xml  version="1.0" encoding="UTF-8"?>

 

<beans   xmlns="http://www.springframework.org/schema/beans"

 

          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 

          xsi:schemaLocation="http://www.springframework.org/schema/beans

 

          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 

          <!—启用spring注解-->

 

           <context:annotation-config />

 

           <context:component-scan base-package="com" />

 

           <!—启用mvc注解,可以在此配置拦截器-->

 

<bean  class="org.springframework.web.servlet.mvc.DefaultAnnotationHandlerMapping"/> 

<bean  class="org.springframework.web.servlet.mvc.AnnotationMethodHandlerAdapter"/> 

          

 

        <!—读取属性文件-->

 

<bean id="cfgProperty"  class="org.springframework.beans.factory.config. PropertyPlaceholderConfigurer">

 

       <property name="location"  value="classpath:jdbc.properties" />

 

        </bean> 

 

 

 

</beans>

 

 

 

 

1.一个简单的基于注解的 Controller

使用过低版本 SpringMVC 的读者都知道:

1.当创建一个 Controller 时,我们需要直接或间接地实现org.springframework.web.servlet.mvc.Controller 接口。一般情况下,我们是通过继承 SimpleFormController 或 MultiActionController 来定义自己的 Controller 的。

2.在定义 Controller 后,一个重要的事件是在 Spring MVC 的配置文件中通过 HandlerMapping 定义请求和控制器的映射关系,以便将两者关联起来。

3.来看一下基于注解的 Controller 是如何定义做到这一点的,下面是使用注解的BbtForumController:

实现效果:

启动 Tomcat,发送 http://localhost/forum.do URL 请求,BbtForumController 的 listAllBoard() 方法将响应这个请求,并转向 WEB-INF/jsp/listBoard.jsp 的视图页面。

 

清单 1. BbtForumController.java

 
 

package  com.baobaotao.web;

 

 

 

import  com.baobaotao.service.BbtForumService;

 

import  org.springframework.beans.factory.annotation.Autowired;

 

import  org.springframework.stereotype.Controller;

 

import  org.springframework.web.bind.annotation.ModelAttribute;

 

import  org.springframework.web.bind.annotation.RequestMapping;

 

import  org.springframework.web.bind.annotation.RequestMethod;

 

import  java.util.Collection;

 
 
 

@Controller                   //<——①

 

@RequestMapping("/forum.do")

 

public class BbtForumController  {

 

 

 

    @Autowired

 

    private BbtForumService bbtForumService;

 

 

 

    @RequestMapping  //<——②

 

    public String listAllBoard() {

 

        bbtForumService.getAllBoard();

 

        System.out.println("call  listAllBoard method.");

 

        return "listBoard";

 

    }

 

}

 

 

在 ①处使用了两个注解,分别是 @Controller 和 @RequestMapping。在“使用 Spring 2.5 基于注解驱动的 IoC”这篇文章里,笔者曾经指出过 @Controller、@Service 以及 @Repository 和 @Component 注解的作用是等价的:将一个类成为 Spring 容器的 Bean。由于 Spring MVC 的 Controller 必须事先是一个 Bean,所以 @Controller 注解是不可缺少的。

真正让 BbtForumController 具备 Spring MVC Controller 功能的是 @RequestMapping 这个注解。@RequestMapping 可以标注在类定义处,将 Controller 和特定请求关联起来;还可以标注在方法签名处,以便进一步对请求进行分流。在 ① 处,我们让 BbtForumController 关联“/forum.do”的请求,而 ② 处,我们具体地指定 listAllBoard() 方法来处理请求。所以在类声明处标注的 @RequestMapping 相当于让 POJO 实现了 Controller接口,而在方法定义处的@RequestMapping 相当于让 POJO 扩展 Spring 预定义的 Controller(如 SimpleFormController 等)。

 

清单 2. web.xml:启用 Spring 容器和 Spring MVC框架

为了让基于注解的Spring MVC 真正工作起来,需要在Spring MVC 对应的xxx-servlet.xml 配置文件中做一些手脚。在此之前,还是先来看一下 web.xml 的配置吧

 
 

<?xml  version="1.0" encoding="UTF-8"?>

 

<web-app  xmlns="http://java.sun.com/xml/ns/javaee"

 

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 

     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

 

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  version="2.5">

 

 

 

<display-name>Spring  Annotation MVC Sample</display-name>

 

 

 

    <!--  Spring 服务层的配置文件  -->

 

    <context-param>

 

        <param-name>contextConfigLocation</param-name>

 

         <param-value>classpath:applicationContext.xml</param-value>

 

    </context-param>

 

    

 

    <!--  Spring 容器启动监听器  -->

 

    <listener>

 

        <listener-class>

 

                org.springframework.web.context.ContextLoaderListener

 

        </listener-class>

 

    </listener>

 

 

 

<!--  Spring MVC 的Servlet,它将加载WEB-INF/annomvc-servlet.xml  的 配置文件,

 

       以启动Spring MVC模块-->

 

    <servlet>

 

        <servlet-name>annomvc</servlet-name>

 

        <servlet-class>

 

              org.springframework.web.servlet.DispatcherServlet

 

        </servlet-class>

 

        <load-on-startup>2</load-on-startup>

 

    </servlet>

 

 

 

    <servlet-mapping>

 

         <servlet-name>annomvc</servlet-name>

 

         <url-pattern>*.do</url-pattern>

 

    </servlet-mapping>

 

</web-app>

 

 

清单 3.annomvc-servlet.xml

web.xml 中定义了一个名为 annomvc 的 Spring MVC 模块,按照 Spring MVC 的契约,需要在 WEB-INF/annomvc-servlet.xml 配置文件中定义 Spring MVC 模块的具体配置。annomvc-servlet.xml 的配置内容如下所示:

 
 

<?xml  version="1.0" encoding="UTF-8"?>

 

<beans

 

     xmlns="http://www.springframework.org/schema/beans"

 

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 

    xmlns:p="http://www.springframework.org/schema/p" 

 

     xmlns:context="http://www.springframework.org/schema/context"

 

     xsi:schemaLocation="http://www.springframework.org/schema/beans

 

     http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

 

     http://www.springframework.org/schema/context

 

     http://www.springframework.org/schema/context/spring-context-2.5.xsd">

 

    

 

    <!-- ①:对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能  -->

 

    <context:component-scan  base-package="com.baobaotao.web"/>

 

 

 

    <!-- ②:启动Spring MVC的注解功能,完成请求和注解POJO的映射 -->

 

    <bean  class="org.springframework.web.servlet.mvc.annotation.

 

                                                       AnnotationMethodHandlerAdapter"/>

 

 

 

    <!--  ③:对模型视图名称的解析,即在模型视图名称添加前后缀 -->

 

<bean

 

    class="org.springframework.web.servlet.view.InternalResourceViewResolver"

 

        p:prefix="/WEB-INF/jsp/"  p:suffix=".jsp"/>

 

</beans>

 

 

因为 Spring 所有功能都在 Bean 的基础上演化而来,所以必须事先将 Controller 变成 Bean,这是通过在类中标注@Controller 并在annomvc-servlet.xml 中启用组件扫描机制来完成的,如 ① 所示。

在 ②处,配置了一个 AnnotationMethodHandlerAdapter,它负责根据 Bean 中的 Spring MVC 注解对 Bean 进行加工处理,使这些 Bean 变成控制器并映射特定的 URL 请求。

而 ③处的工作是定义模型视图名称的解析规则,这里我们使用了 Spring2.5 的特殊命名空间,即 p 命名空间,它将原先需要通过 <property> 元素配置的内容转化为 <bean> 属性配置,在一定程度上简化了 <bean> 的配置。

 

2.让一个 Controller 处理多个 URL 请求

在低版本的 SpringMVC 中,我们可以通过继承MultiActionController 让一个 Controller 处理多个 URL 请求。使用@RequestMapping 注解后,这个功能更加容易实现了。请看下面的代码:

清单 3. 每个请求处理参数对应一个 URL

 
 

package  com.baobaotao.web;

 

 

 

import  com.baobaotao.service.BbtForumService;

 

import  org.springframework.beans.factory.annotation.Autowired;

 

import  org.springframework.stereotype.Controller;

 

import  org.springframework.web.bind.annotation.RequestMapping;

 

 

 

@Controller

 

public class BbtForumController  {

 

 

 

    @Autowired

 

    private BbtForumService bbtForumService;

 

 

 

    @RequestMapping("/listAllBoard.do")  // <—— ①

 

    public String listAllBoard() {

 

        bbtForumService.getAllBoard();

 

        System.out.println("call  listAllBoard method.");

 

        return "listBoard";

 

    }

 

 

 

    @RequestMapping("/listBoardTopic.do")  // <—— ②

 

    public String listBoardTopic(int topicId)  {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("call listBoardTopic  method.");

 

        return "listTopic";

 

    }

 

}

 

 

在这里,我们分别在 ① 和 ② 处为 listAllBoard() 和 listBoardTopic() 方法标注了 @RequestMapping 注解,分别指定这两个方法处理的 URL 请求,这相当于将BbtForumController 改造为 MultiActionController。这样 /listAllBoard.do 的 URL 请求将由listAllBoard() 负责处理,而/listBoardTopic.do?topicId=1 的 URL 请求则由listBoardTopic() 方法处理。

 

清单 4. 一个 Controller 对应一个 URL,由请求参数决定请求处理方法

对于处理多个 URL 请求的 Controller 来说,我们倾向于通过一个 URL 参数指定 Controller 处理方法的名称(如 method=listAllBoard),而非直接通过不同的 URL 指定 Controller 的处理方法。使用 @RequestMapping 注解很容易实现这个常用的需求。来看下面的代码

 
 

package  com.baobaotao.web;

 

 

 

import  com.baobaotao.service.BbtForumService;

 

import  org.springframework.beans.factory.annotation.Autowired;

 

import  org.springframework.stereotype.Controller;

 

import  org.springframework.web.bind.annotation.RequestMapping;

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do")   // <—— ① 指定控制器对应URL请求

 

public class BbtForumController  {

 

 

 

    @Autowired

 

    private BbtForumService bbtForumService;

 

 

 

    // <—— ② 如果URL请求中包括"method=listAllBoard"的参数,由本方法进行处理

 

    @RequestMapping(params = "method=listAllBoard")

 

    public String listAllBoard() {

 

        bbtForumService.getAllBoard();

 

        System.out.println("call  listAllBoard method.");

 

        return "listBoard";

 

    }

 

 

 

    // <—— ③ 如果URL请求中包括"method=listBoardTopic"的参数,由本方法进行处理

 

    @RequestMapping(params = "method=listBoardTopic")

 

    public String listBoardTopic(int topicId)  {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("call  listBoardTopic method.");

 

        return "listTopic";

 

    }

 

}

 

在类定义处标注的 @RequestMapping 让 BbtForumController 处理所有包含 /bbtForum.do 的 URL 请求,而 BbtForumController 中的请求处理方法对 URL 请求的分流规则在 ② 和③ 处定义分流规则按照 URL 的 method 请求参数确定。所以分别在类定义处和方法定义处使用 @RequestMapping 注解,就可以很容易通过 URL 参数指定 Controller 的处理方法了。

 

清单5. 让请求处理方法处理特定的 HTTP 请求方法

@RequestMapping 注解中除了 params 属性外,还有一个常用的属性是 method,它可以让 Controller 方法处理特定 HTTP 请求方式的请求,如让一个方法处理 HTTP GET 请求,而另一个方法处理 HTTP POST 请求,如下所示:

 
 

package  com.baobaotao.web;

 

 

 

import  com.baobaotao.service.BbtForumService;

 

import  org.springframework.beans.factory.annotation.Autowired;

 

import  org.springframework.stereotype.Controller;

 

import  org.springframework.web.bind.annotation.RequestMapping;

 

import  org.springframework.web.bind.annotation.RequestMethod;

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do") 

 

public class BbtForumController  {

 

 

 

    @RequestMapping(params =  "method=createTopic", method = RequestMethod.POST)

 

    public String createTopic(){

 

        System.out.println("call  createTopic method.");

 

        return "createTopic";

 

    }

 

}

 

 

 

这样只有当 /bbtForum.do?method=createTopic 请求以 HTTP POST 方式提交时,createTopic()才会进行处理。

 

3.处理 方法入参 如何 绑定 URL参数

3.1 按契约绑定

Controller 的方法标注了 @RequestMapping 注解后,它就能处理特定的 URL 请求。

我们不禁要问:请求处理方法入参是如何绑定 URL 参数的呢?在回答这个问题之前先来看下面的代码

 

清单 5. 按参数名匹配进行绑定

 
 

@RequestMapping(params =  "method=listBoardTopic")

 

    //<—— ① topicId入参是如何绑定URL请求参数的?

 

    public String listBoardTopic(int topicId) {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("call  listBoardTopic method.");

 

        return "listTopic";

 

    }

 

当我们发送 http://localhost//bbtForum.do?method=listBoardTopic&topicId=10 的 URL 请求时,Spring 不但让 listBoardTopic() 方法处理这个请求,而且还将topicId 请求参数在类型转换后绑定到 listBoardTopic() 方法的 topicId 入参上。而 listBoardTopic() 方法的返回类型是 String,它将被解析为逻辑视图的名称。也就是说 Spring 在如何给处理方法入参自动赋值以及如何将处理方法返回值转化为ModelAndView 中的过程中存在一套潜在的规则,不熟悉这个规则就不可能很好地开发基于注解的请求处理方法,因此了解这个潜在规则无疑成为理解 Spring MVC 框架基于注解功能的核心问题。

我们不妨从最常见的开始说起:请求处理方法入参的类型可以是 Java 基本数据类型或 String类型,这时方法入参按参数名匹配的原则绑定到 URL 请求参数,同时还自动完成String 类型的 URL 请求参数到请求处理方法参数类型的转换。下面给出几个例子:

listBoardTopic(int topicId):和 topicId URL 请求参数绑定;

listBoardTopic(inttopicId,String boardName):分别和 topicId、boardName URL 请求参数绑定;

特别的,如果入参是基本数据类型(如 int、long、float 等),URL 请求参数中一定要有对应的参数,否则将抛出TypeMismatchException 异常,提示无法将 null 转换为基本数据类型。

 

清单 6. User.java:一个 JavaBean

另外,请求处理方法的入参也可以一个 JavaBean,如下面的 User 对象就可以作为一个入参

 
 

package  com.baobaotao.web;

 

 

 

public class User {

 

    private int userId;

 

    private String userName;

 

    //省略get/setter方法

 

    public String toString(){

 

        return this.userName  +","+this.userId;

 

    }

 

}

 

 

清单 7. 使用 JavaBean 作为请求处理方法的入参

下面是将 User 作为listBoardTopic() 请求处理方法的入参:

 
 

@RequestMapping(params =  "method=listBoardTopic")

 

    public String listBoardTopic(int topicId,User user)  {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("topicId:"+topicId);

 

         System.out.println("user:"+user);

 

        System.out.println("call  listBoardTopic method.");

 

        return "listTopic";

 

    }

 

 

这时,如果我们使用以下的 URL 请求:

http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom

topicId URL 参数将绑定到 topicId 入参上,

而 userId 和 userName URL 参数将绑定到 user 对象的 userId 和 userName 属性中。

和 URL 请求中不允许没有 topicId 参数不同,虽然 User 的 userId 属性的类型是基本数据类型,但如果 URL 中不存在 userId 参数,Spring 也不会报错,此时 user.userId 值为 0。如果 User 对象拥有一个 dept.deptId 的级联属性,那么它将和 dept.deptId URL 参数绑定。

 

3.2通过注解指定绑定的 URL 参数

如果我们想改变这种默认的按名称匹配的策略,比如让 listBoardTopic(int topicId,Useruser) 中的 topicId 绑定到 id 这个 URL 参数,那么可以通过对入参使用@RequestParam 注解来达到目的:

清单 8. 通过 @RequestParam 注解指定

这里,对listBoardTopic() 请求处理方法的topicId 入参标注了@RequestParam("id") 注解,所以它将和 id 的 URL 参数绑定。

 
 

package  com.baobaotao.web;

 

 

 

import  org.springframework.web.bind.annotation.RequestMapping;

 

import  org.springframework.web.bind.annotation.RequestParam;

 

 …

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do")

 

public class BbtForumController  {

 

 

 

    @RequestMapping(params =  "method=listBoardTopic")

 

public  String listBoardTopic(@RequestParam("id")  int topicId,

 

                                                            User user) {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("topicId:"+topicId);

 

         System.out.println("user:"+user);

 

        System.out.println("call  listBoardTopic method.");

 

        return "listTopic";

 

    }

 

 

}

 

 

这里@RequestParam注解可以用来提取名为“topicId”的int类型的参数,并将之作为输入参数传入。 @RequestParam支持类型转换,还有必需和可选参数。类型转换目前支持所有的基本Java类型,你可通过定制的PropertyEditors来扩展它的范围。下面是一些例子,其中包括了必需和可选参数:

 
 

@RequestParam(value="number",  required=false) String number

 

@RequestParam("id")  Long id

 

@RequestParam("balance")  double balance

 

@RequestParam double  amount

 

注意,最后一个例子没有提供清晰的参数名。当且仅当代码带调试符号编译时,结果会提取名为“amount ”的参数,否则,将抛出IllegalStateException异常,因为当前的信息不足以从请求中提取参数。由于这个原因,在编码时最好显式的指定参数名。

 

 

3.3 绑定模型对象中某个属性—ModelMap

Spring 2.0 定义了一个 org.springframework.ui.ModelMap 类,它作为通用的模型数据承载对象,传递数据供视图所用。我们可以在请求处理方法中声明一个 ModelMap 类型的入参,Spring 会将本次请求模型对象引用通过该入参传递进来,这样就可以在请求处理方法内部访问模型对象了。来看下面的例子:

清单 9. 使用 ModelMap 访问请示对应的隐含模型对象

 
 

@RequestMapping(params  = "method=listBoardTopic")

 

 public String listBoardTopic(@RequestParam("id")int  topicId,

 

 User user,

 

 ModelMap model) {

 

     bbtForumService.getBoardTopics(topicId);

 

     System.out.println("topicId:"  + topicId);

 

     System.out.println("user:" +  user);

 

     //① 将user对象以currUser为键放入到model中

 

     model.addAttribute("currUser",user);

 

     return "listTopic";

 

 }

 

 

对于当次请求所对应的模型对象来说,其所有属性都将存放到 request 的属性列表中。

象上面的例子,ModelMap 中的 currUser 属性将放到 request 的属性列表中,所以可以在 JSP 视图页面中通过 request.getAttribute(“currUser”) 或者通过${currUser} EL 表达式访问模型对象中的 user 对象。从这个角度上看, ModelMap 相当于是一个向 request 属性列表中添加对象的一条管道,借由 ModelMap 对象的支持,我们可以在一个不依赖 Servlet API 的 Controller 中向 request 中添加属性。

 

清单 10. 使模型对象的特定属性具有 Session 范围的作用域

在默认情况下,ModelMap 中的属性作用域是 request 级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。

Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注@SessionAttributes 注解来实现的。请看下面的代码:

 
 

package  com.baobaotao.web;

 

 

import  org.springframework.ui.ModelMap;

 

import  org.springframework.web.bind.annotation.SessionAttributes;

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do")

 

@SessionAttributes("currUser") //①将ModelMap中属性名为currUser的属性

 

//放到Session属性列表中,以便这个属性可以跨请求访问

 

public class BbtForumController  {

 

 

    @RequestMapping(params =  "method=listBoardTopic")

 

public  String listBoardTopic(@RequestParam("id")int  topicId,

 

User user,

 

ModelMap model) {

 

         bbtForumService.getBoardTopics(topicId);

 

         System.out.println("topicId:" + topicId);

 

        System.out.println("user:"  + user);

 

        model.addAttribute("currUser",user); //②向ModelMap中添加一个属性

 

        return "listTopic";

 

    }

 

}

 

 

我们在 ② 处添加了一个 ModelMap 属性,其属性名为 currUser,而 ① 处通过 @SessionAttributes 注解将 ModelMap 中名为 currUser 的属性放置到 Session 中,所以我们不但可以在 listBoardTopic() 请求所对应的 JSP 视图页面中通过request.getAttribute(“currUser”) 和 session.getAttribute(“currUser”) 获取 user 对象,还可以在下一个请求所对应的 JSP 视图页面中通过session.getAttribute(“currUser”) 或ModelMap#get(“currUser”) 访问到这个属性。

1)这里我们仅将一个 ModelMap 的属性放入 Session 中,其实 @SessionAttributes 允许指定多个属性:

     你可以通过字符串数组的方式指定多个属性,如 @SessionAttributes({“attr1”,”attr2”})。

 

2)此外,@SessionAttributes还可以通过属性类型指定要session 化的 ModelMap 属性:

     如 @SessionAttributes(types = User.class),

   当然也可以指定多个类,如 @SessionAttributes(types = {User.class,Dept.class}),

 

3)还可以联合使用属性名和属性类型指定:

    @SessionAttributes(types ={User.class,Dept.class},value={“attr1”,”attr2”})。

 

清单 11. 使模型对象的特定属性具有 Session 范围的作用域

上面讲述了如何往ModelMap中放置属性以及如何使ModelMap中的属性拥有Session域的作用范围。除了在JSP视图页面中通过传统的方法访问ModelMap中的属性外,读者朋友可能会问:是否可以将ModelMap中的属性绑定到请求处理方法的入参中呢?答案是肯定的。Spring为此提供了一个@ModelAttribute的注解,下面是使用@ModelAttribute注解的例子

 
 

@Controller

 

@RequestMapping("/bbtForum.do")

 

@SessionAttributes("currUser") //①让ModelMap的currUser属性拥有session级作用域

 

public class BbtForumController  {

 

     @Autowired

 

private BbtForumService  bbtForumService;

 

 

 

    @RequestMapping(params = "method=listBoardTopic")

 

public  String listBoardTopic(@RequestParam("id")int  topicId,

 

User user,

 

ModelMap model) {

 

         bbtForumService.getBoardTopics(topicId);

 

         System.out.println("topicId:" + topicId);

 

        System.out.println("user:"  + user);

 

        model.addAttribute("currUser",user); //②向ModelMap中添加一个属性

 

        return "listTopic";

 

    }

 

 

 

//③将ModelMap中的currUser属性绑定到user入参中。因为currUser是session级别的

 

@RequestMapping(params  = "method=listAllBoard")

 

public String listAllBoard(@ModelAttribute("currUser")  User user) {

 

        bbtForumService.getAllBoard();

 

         System.out.println("user:"+user);

 

        return "listBoard";

 

    }

 

}

 

 

在 ② 处,我们向 ModelMap 中添加一个名为 currUser 的属性,

而 ① 外的注解使这个 currUser 属性拥有了session级的作用域

所以,我们可以在 ③ 处通过 @ModelAttribute 注解将 ModelMap 中的 currUser 属性绑定以请求处理方法的 user 入参中。

所以当我们先调用以下URL 请求:

http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12以执行listBoardTopic()请求处理方法,

然后再访问以下URL:http://localhost/sample/bbtForum.do?method=listAllBoard

你将可以看到 listAllBoard() 的 user 入参已经成功绑定到 listBoardTopic() 中注册的 session 级的 currUser 属性上了

 

4.请求处理方法的签名规约

4.1 方法入参

我们知道标注了@RequestMapping 注解的 Controller方法就成为了请求处理方法,SpringMVC 允许极其灵活的请求处理方法签名方式。对于方法入参来说,它允许多种类型的入参,通过下表进行说明

 
 

请求处理方法入参的可选类型

 
 
 

说明

 
 
 

Java 基本数据类型和 String

 
 
 

默认情况下将按名称匹配的方式绑定到 URL 参数上,可以通过 @RequestParam 注解改变默认的绑定规则

 
 
 

request/response/session

 
 
 

既可以是 Servlet API 的也可以是 Portlet API 对应的对象,Spring 会将它们绑定到 Servlet 和 Portlet 容器的相应对象上

 
 
 

org.springframework.web.context.request.WebRequest

 
 
 

内部包含了 request 对象

 
 
 

java.util.Locale

 
 
 

绑定到 request 对应的 Locale 对象上

 
 
 

java.io.InputStream/java.io.Reader

 
 
 

可以借此访问 request 的内容

 
 
 

java.io.OutputStream  / java.io.Writer

 
 
 

可以借此操作 response 的内容

 
 
 

任何标注了 @RequestParam 注解的入参

 
 
 

被标注 @RequestParam 注解的入参将绑定到特定的 request 参数上。

 
 
 

java.util.Map /  org.springframework.ui.ModelMap

 
 
 

它绑定 Spring MVC 框架中每个请求所创建的潜在的模型对象,它们可以被 Web 视图对象访问(如 JSP)

 
 
 

命令/表单对象(注:一般称绑定使用 HTTP GET 发送的 URL 参数的对象为命令对象,而称绑定使用 HTTP POST 发送的 URL 参数的对象为表单对象)

 
 
 

它们的属性将以名称匹配的规则绑定到 URL 参数上,同时完成类型的转换。而类型转换的规则可以通过 @InitBinder 注解或通过 HandlerAdapter 的配置进行调整

 
 
 

org.springframework.validation.Errors/  org.springframework.validation.BindingResult

 
 
 

为属性列表中的命令/表单对象的校验结果,注意检验结果参数必须紧跟在命令/表单对象的后面

 
 
 

org.springframework.web.bind.support.SessionStatus

 
 
 

可以通过该类型 status 对象显式结束表单的处理,这相当于触发 session 清除其中的通过 @SessionAttributes 定义的属性

 

 

Spring MVC 框架的易用之处在于,你可以按任意顺序定义请求处理方法的入参(除了 Errors 和 BindingResult 必须紧跟在命令对象/表单参数后面以外),SpringMVC 会根据反射机制自动将对应的对象通过入参传递给请求处理方法。这种机制让开发者完全可以不依赖 Servlet API 开发控制层的程序,当请求处理方法需要特定的对象时,仅仅需要在参数列表中声明入参即可,不需要考虑如何获取这些对象,Spring MVC 框架就象一个大管家一样“不辞辛苦”地为我们准备好了所需的一切。下面演示一下使用 SessionStatus 的例子:

清单 12. 使用 SessionStatus 控制 Session 级别的模型属性

 
 

@RequestMapping(method  = RequestMethod.POST)

 

public String processSubmit(@ModelAttribute Owner owner,

 

                                   BindingResult result,

 

                                   SessionStatus status) {//<——①

 

       new  OwnerValidator().validate(owner, result);

 

       if  (result.hasErrors()) {

 

            return "ownerForm";

 

       }else  {

 

            this.clinic.storeOwner(owner);

 

            status.setComplete();//<——②

 

            return  "redirect:owner.do?ownerId=" + owner.getId();

 

       }

 

}

 

 

processSubmit() 方法中的 owner 表单对象将绑定到 ModelMap 的“owner”属性中,

result 参数用于存放检验 owner 结果的对象,

而 status 用于控制表单处理的状态。

在 ② 处,我们通过调用 status.setComplete() 方法,该 Controller 所有放在 session 级别的模型属性数据将从 session 中清空。

 

在默认情况下,ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。

Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注@SessionAttributes 注解来实现的。请看下面的代码:

 

清单 10. 使模型对象的特定属性具有 Session 范围的作用域

 
 

package  com.baobaotao.web;

 

 

import  org.springframework.ui.ModelMap;

 

import  org.springframework.web.bind.annotation.SessionAttributes;

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do")

 

@SessionAttributes("currUser") //①将ModelMap中属性名为currUser的属性

 

//放到Session属性列表中,以便这个属性可以跨请求访问

 

public class BbtForumController  {

 

 

    @RequestMapping(params =  "method=listBoardTopic")

 

public  String listBoardTopic(@RequestParam("id")int  topicId,

 

User user,

 

ModelMap model) {

 

         bbtForumService.getBoardTopics(topicId);

 

        System.out.println("topicId:"  + topicId);

 

        System.out.println("user:"  + user);

 

         model.addAttribute("currUser",user); //②向ModelMap中添加一个属性

 

        return "listTopic";

 

    }

 

}

 

 

我们在 ② 处添加了一个 ModelMap 属性,其属性名为 currUser,而 ① 处通过 @SessionAttributes 注解将 ModelMap 中名为 currUser 的属性放置到 Session 中,所以我们不但可以在 listBoardTopic() 请求所对应的 JSP 视图页面中通过request.getAttribute(“currUser”) 和session.getAttribute(“currUser”) 获取 user 对象,还可以在下一个请求所对应的 JSP 视图页面中通过 session.getAttribute(“currUser”) 或 ModelMap#get(“currUser”) 访问到这个属性。

1)这里我们仅将一个 ModelMap 的属性放入 Session 中,其实 @SessionAttributes 允许指定多个属性:

     你可以通过字符串数组的方式指定多个属性,如 @SessionAttributes({“attr1”,”attr2”})。

 

2)此外,@SessionAttributes 还可以通过属性类型指定要 session 化的 ModelMap 属性:

     如 @SessionAttributes(types = User.class),

   当然也可以指定多个类,如 @SessionAttributes(types = {User.class,Dept.class}),

 

3)还可以联合使用属性名和属性类型指定:

    @SessionAttributes(types ={User.class,Dept.class},value={“attr1”,”attr2”})。

 

清单 11. 使模型对象的特定属性具有 Session 范围的作用域

上面讲述了如何往ModelMap中放置属性以及如何使ModelMap中的属性拥有Session域的作用范围。除了在JSP视图页面中通过传统的方法访问ModelMap中的属性外,读者朋友可能会问:是否可以将ModelMap中的属性绑定到请求处理方法的入参中呢?答案是肯定的。Spring为此提供了一个@ModelAttribute的注解,下面是使用@ModelAttribute注解的例子

 
 

@Controller

 

@RequestMapping("/bbtForum.do")

 

@SessionAttributes("currUser") //①让ModelMap的currUser属性拥有session级作用域

 

public class BbtForumController  {

 

     @Autowired

 

private BbtForumService  bbtForumService;

 

 

 

    @RequestMapping(params  = "method=listBoardTopic")

 

public  String listBoardTopic(@RequestParam("id")int  topicId,

 

User user, ModelMap model) {

 

        bbtForumService.getBoardTopics(topicId);

 

         System.out.println("topicId:" + topicId);

 

        System.out.println("user:"  + user);

 

        model.addAttribute("currUser",user); //②向ModelMap中添加一个属性

 

        return "listTopic";

 

    }

 

 

 

//③将ModelMap中的currUser属性绑定到user入参中。

 

@RequestMapping(params =  "method=listAllBoard")

 

public String listAllBoard(@ModelAttribute("currUser")  User user) {

 

 

 

        bbtForumService.getAllBoard();

 

        System.out.println("user:"+user);

 

        return "listBoard";

 

    }

 

}

 

 

在 ② 处,我们向 ModelMap 中添加一个名为 currUser 的属性,

而 ① 外的注解使这个 currUser 属性拥有了 session 级的作用域。

所以,我们可以在 ③ 处通过 @ModelAttribute 注解将 ModelMap 中的 currUser 属性绑定以请求处理方法的 user 入参中。所以当我们先调用以下 URL 请求:

http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12以执行listBoardTopic()请求处理方法,

然后再访问以下URL: http://localhost/sample/bbtForum.do?method=listAllBoard

你将可以看到 listAllBoard() 的 user 入参已经成功绑定到 listBoardTopic() 中注册的 session 级的 currUser 属性上了

 

4.2 方法返回参数

在低版本的 SpringMVC 中,请求处理方法的返回值类型都必须是 ModelAndView。

而在 Spring 2.5 中,你拥有多种灵活的选择。通过下表进行说明:

 
 

请求处理方法入参的可选类型

 
 
 

说明

 
 
 

void

 
 
 

此时逻辑视图名由请求处理方法对应的 URL 确定,如以下的方法:

 

@RequestMapping("/welcome.do")

 

    public void welcomeHandler() {

 

}

 

对应的逻辑视图名为”welcome”

 
 
 

String

 
 
 

此时逻辑视图名为返回的字符,如以下的方法:

 

@RequestMapping(method  = RequestMethod.GET)

 

public String setupForm(

 

     @RequestParam("ownerId") int  ownerId,

 

     ModelMap model) {

 

         Owner owner =  this.clinic.loadOwner(ownerId);

 

         model.addAttribute(owner);

 

         return "ownerForm";

 

}

 

对应的逻辑视图名为“ownerForm”

 
 
 

org.springframework.ui.ModelMap

 
 
 

和返回类型为 void 一样,逻辑视图名取决于对应请求的 URL,如下面的例子:

 

@RequestMapping("/vets.do")

 

public ModelMap vetsHandler()  {

 

         return new  ModelMap(this.clinic.getVets());

 

}

 

对应的逻辑视图名为“vets”,返回的 ModelMap 将被作为请求对应的模型对象,可以在 JSP 视图页面中访问到。

 
 
 

ModelAndView

 
 
 

当然还可以是传统的  ModelAndView。

 

应该说使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很大的灵活性,而模型数据又可以通过 ModelMap 控制。当然直接使用传统的 ModelAndView 也不失为一个好的选择。

 

6.如何准备数据--@ModelAttribute

在编写 Controller 时,常常需要在真正进入请求处理方法前准备一些数据,以便请求处理视图渲染时使用。在传统的 SimpleFormController 里,是通过复写其 referenceData() 方法来准备引用数据的。

在 Spring 2.5 时,可以将任何一个拥有返回值的方法标注上 @ModelAttribute,使其返回值将会进入到模型对象的属性列表中。来看下面的例子:

6.1清单 16.定义为处理请求准备数据的方法

 
 

import  org.springframework.web.bind.annotation.SessionAttributes;

 

import  java.util.ArrayList;

 

import  java.util.List;

 

import  java.util.Set;

 

 

 

@Controller

 

@RequestMapping("/bbtForum.do")

 

public class BbtForumController  {

 

 

 

    @Autowired

 

    private BbtForumService bbtForumService

 

 

 

    @ModelAttribute("items")//<——①向模型对象中添加一个名为items的属性

 

    public List<String> populateItems()  {

 

        List<String> lists = new  ArrayList<String>();

 

        lists.add("item1");

 

        lists.add("item2");

 

        return lists;

 

    }

 

 

 

    @RequestMapping(params =  "method=listAllBoard")

 

public  String listAllBoard(

 

@ModelAttribute("currUser")User user, ModelMap model)  {

 

        bbtForumService.getAllBoard();

 

        //<——②在此访问模型中的items属性

 

         System.out.println("model.items:" +

 

                      ((List<String>)model.get("items")).size());

 

        return "listBoard";

 

    }

 

}

 

 

说明:

在 ① 处,通过使用 @ModelAttribute 注解,populateItem() 方法将在任何请求处理方法执行前调用,SpringMVC 会将该方法返回值以“items”为名放入到隐含的模型对象属性列表中。

所以在 ② 处,我们就可以通过 ModelMap 入参 访问到 items 属性,当执行 listAllBoard() 请求处理方法时,② 处将在控制台打印出“model.items:2”的信息。

 

当然我们也可以在请求的视图中访问到模型对象中的 items 属性。

7.@MVC表单处理

一个典型的表单处理场景包括:获得可编辑对象,在编辑模式下显示它持有的数据、允许用户提交并最终进行验证和保存变化数据。

Spring MVC提供下列几个特性辅助进行上述所有活动:

1)  数据绑定机制,完全用从请求参数中获得的数据填充一个对象;

2)  支持错误处理和验证;

3)  JSP表单标记库;

4)  基类控制器。

使用@MVC,除了由于@ModelAttribute、@InitBinder和@SessionAttributes这些注解的存在而不再需要基类控制器外,其它一切都不需要改变。

7.1 @ModelAttribute注解

看一下这些请求处理方法签名:

 
 

@RequestMapping(method=RequestMethod.GET)

 

 public Account setupForm() {

 

    ...

 

 }

 
 
 

 @RequestMapping(method=RequestMethod.POST)

 

 public void onSubmit(Account account) {

 

    ...

 

 }

 

它们是非常有效的请求处理方法签名。

第一个方法处理初始的HTTP GET请求,准备被编辑的数据,返回一个Account对象供Spring MVC表单标签使用。

第二个方法在用户提交更改时处理随后的HTTP POST请求,并接收一个Account对象作为输入参数,它是Spring MVC的数据绑定机制用请求中的参数自动填充的。这是一个非常简单的程序模型。

1.xxx.jsp

Account对象 中 含有 要被编辑的数据。

在Spring MVC的术语当中,Account被称作是 表单模型对象。这个对象必须通过某个名称让表单标签(还有数据绑定机制)知道它的存在。下面是从JSP页面中截取的部分代码,引用了一个名为“account”的表单模型对象:

 
 

<form:form  modelAttribute="account"   method="post">

 

    Account Number: <form:input  path="number"/><form:errors path="number"/>

 

    ...

 

 </form>

 

 

2.xxxController.java

即使我们没有在任何地方指定“account”的名称,这段JSP程序也会和上面所讲的方法签名协作的很好。这是因为@MVC用返回对象类型名称作为默认值,因此一个Account类型的对象 默认的 就对应一个名为“account”的表单模型对象。如果默认的不合适,我们就可以用@ModelAttribute来改变它的名称,如下所示:

 
 

@RequestMapping(method=RequestMethod.GET)

 

 public @ModelAttribute("account")  SpecialAccount setupForm() {

 

    ...

 

 }

 
 
 

@RequestMapping(method=RequestMethod.POST)

 

 public void update(@ModelAttribute("account")  SpecialAccount account) {

 

    ...

 

 }

 

 

此处setupForm ()不是一个请求处理方法,而是任何请求处理方法被调用之前,用来准备表单项模型对象的一个方法。对那些熟悉 Spring MVC的老用户来说,这和SimpleFormController的formBackingObject()方法是非常相似的。

最初的GET方法中我们得到一次表单模型对象,在随后的POST方法中当我们依靠数据绑定机制用用户所做的改变覆盖已有的Account对象时,我们会第二次得到它,在这种表单处理场景中把@ModelAttribute放在方法上是很有用的。当然,作为一种两次获得对象的替换方案,我们也可以在两次请求过程中将它保存进HTTP的会话(session),这就是我们下面将要分析的情况。

 

7.2 用@SessionAttributes存储属性

@SessionAttributes注解可以用来指定请求过程中要放进session中的表单模型对象的名称或类型,例子:

 
 

@Controller

 

@SessionAttributes("account") 

 

 public class AccountFormController {

 

    ...

 

 }

 
 
 

 @Controller

 

 @SessionAttributes(types = Account.class)

 

 public class AccountFormController {

 

    ...

 

 }

 

 

根据上面的注解,AccountFormController会在初始的GET方法和随后的POST方法之间,把名为 “account”的表单模型对象(或者象第二个例子中的那样,把所有Account类型的表单模型对象)存入HTTP会话(session)中。不过,当有改变连续发生的时候,就应当把属性对象从会话中移除了。我们可以借助SessionStatus实例来做这件事,如果把它添加进onSubmit的方法签名中,@MVC会完成这个任务:

 
 

@RequestMapping(method=RequestMethod.POST)

 

 public void onSubmit(Account account,  SessionStatus sessionStatus) {

 

    ...

 

    sessionStatus.setComplete(); //Clears  @SessionAttributes

 

 }

 

 

7.3雁联例子

1.效果图:

在“subProcess.jsp”页面,点击“修改”按钮,进入“修改页面(modifyProcess.jsp)”,

“修改页面(modifyProcess.jsp)”上会显示 该条记录的信息(根据ID 从数据库读取记录,然后显示在页面上)

 

2.遇到的问题:

用户在“修改页面(modifyProcess.jsp)”修改了信息后,

用户再次点击“修改按钮”进入到“修改页面(modifyProcess.jsp)”时,“修改页面(modifyProcess.jsp)”上显示的不是“修改后”的信息,而还是“修改前”的 信息。

解决方案:此问题是由于 浏览器 的缓存 引起的,在“修改页面(modifyProcess.jsp)”的head处加入如下语句:

 
 

<meta http-equiv="pragma" content="no-cache">

 

<meta http-equiv="cache-control" content="no-cache">

 

<meta http-equiv="expires" content="0">

 
 
 

 

 
 
 

<base target="_self">

 
 
 

<script type="text/javascript" 

 

          src="<spring:url

 

          value="/static/js/public.js"/>">

 

</script>

 
 
 

<link href="<spring:url

 

       value="/static/css/cssCn.css"/>"

 

       rel="stylesheet" 

 

       type="text/css"/>

 

 

“subProcess.jsp页面”中 ,关于“修改”按钮 的代码 如下:

 
 

<td align="center">

 

<input type="button"  name="Submit"  value="修改"

 

        onClick="modifyProecss(

 

  '<c:url value="/procAdmin/subSysProc?method=modifyProcUI&id=${process.id}"/>');" class="button">

 

</td>

 

 

3.xxxController.java

在“subProcess.jsp页面” 中 点击 “修改”按钮 ,程序运行到 xxxController.java.

 
 

@Controller

 

@RequestMapping("/procAdmin/subSysProc")

 

public class SubSysProController {

 
 
 

    private  Validator beanValidator;

 
 
 

    @RequestMapping(params = "method=modifyProcUI", method = RequestMethod.GET)

 

    public String modifyProcUI(Process process, BindingResult result, Model model) {

 
 
 

        beanValidator.validate(process,  result);

 

       if (result.hasErrors()) {

 

           return addProcUI();

 

       }

 
 
 

   

 

         //根据要修改记录ID,得到要修改的 记录的信息

 

       process  = processService.getProcessById(process.getId());

 

       System.out.println("1.prcsDescription="+process.getPrcsDescription());

 

      

 

       // 获得子系统列表

 

       List<SubSys>  subSysList = subSysService.getAllSubSysList();

 

 

 

       model.addAttribute("process", process);

 

       model.addAttribute("subSysList", subSysList);

 

       return "processManage/processModify";

 

    }

 
 
 

}

 

 

4.zfpt-servlet.xml

 
 

<beans xmlns="http://www.springframework.org/schema/beans"

 

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 

       xmlns:webflow="http://www.springframework.org/schema/webflow-config"

 

       xmlns:p="http://www.springframework.org/schema/p"

 

       xmlns:context="http://www.springframework.org/schema/context"

 

       xmlns:mvc="http://www.springframework.org/schema/mvc"      

 

       xsi:schemaLocation="

 

        http://www.springframework.org/schema/mvc  http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd

 

        http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

 

        http://www.springframework.org/schema/webflow-config  http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd

 

       http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd">

 
 
 

    <context:component-scan base-package="com.ylink.zfpt.web.spring"/>

 
 
 

<bean class="org.springframework.web.servlet.mvc.annotation.

 

AnnotationMethodHandlerAdapter">

 

       <property name="webBindingInitializer">

 

           <bean class="com.ylink.zfpt.web.spring.ZfptBindingInitializer"/>

 

       </property>

 

    </bean>

 
 
 

 

 
 
 

<bean id="beanValidator"  class="org.springmodules.validation.commons.DefaultBeanValidator">

 

          <property name="validatorFactory" ref="validatorFactory"/>

 

</bean>

 
 
 

<bean id="validatorFactory" class="org.springmodules.validation.commons.DefaultValidatorFactory">

 

       <property name="validationConfigLocations">

 

           <list>

 

              <value>/WEB-INF/validator-rules.xml</value>

 

              <value>/WEB-INF/validator.xml</value>

 

           </list>

 

       </property>

 

</bean>

 

 

5.modifyProcess.jsp

 
 

<head>

 

    //禁止浏览器缓存,本例子中有用

 

    <meta http-equiv="pragma"  content="no-cache">

 

    <meta http-equiv="cache-control"  content="no-cache">

 

    <meta http-equiv="expires" content="0">

 
 
 

    

 

    <base target="_self">

 
 
 

</head>

 
 
 

 

 
 
 

<body>

 

    <form:form modelAttribute="process"

 

                 action="<c:url value='/procAdmin/subSysProc'/>" method="PUT">

 
 
 

       <table class=tb_input  cellspacing=1 width="100%">

 
 
 

           <tr>

 

            <td width="15%"  nowrap class="td_title">

 

                  &nbsp;进&nbsp;程&nbsp;名

 

                  <span class="fond_b STYLE2">*</span>

 

              </td>

 

              <td>

 

                 <form:input path="imageName" cssStyle="WIDTH: 180px" maxlength="15"/>

 

                 <form:errors path="imageName" cssClass="errors"/>

 

              </td>

 

          </tr>

 
 
 

          <tr>

 

            <td nowrap  class="td_title">

 

                &nbsp;描&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;述

 

              </td>

 

              <td>

 

                 <form:input path="prcsDescription"

 

                                 cssStyle="WIDTH: 180px"  maxlength="200"/>

 

           </td>                          

 

           </tr>

 
 
 

          <tr>

 

            <td nowrap  class="td_title">

 

                 &nbsp;工作目录

 

            </td>

 

            <td>

 

              <form:input path="workDirectory" 

 

                               cssStyle="WIDTH: 180px" maxlength="100"/>

 

              </td>                        

 

       </tr>

 
 
 

         <tr>

 

              <td nowrap class="td_title">

 

                  &nbsp;子&nbsp;系&nbsp;统

 

                  <span class="fond_b STYLE2">*</span>

 

               </td>

 

               <td>

 

                  <form:select path="subSys.id" cssStyle="WIDTH: 180px">

 

                     <form:option value=""></form:option>

 

                      <form:options items="${subSysList}"

 

                                          itemLabel="code" itemValue="id"/>

 

                     </form:select>

 

                     <form:errors path="subSys.id" cssClass="errors"/>

 

              </td>

 

       </tr>

 
 
 

      </table>

 
 
 

      <input type="button" class="button"  οnclick="save();" value="修改进程"/>

 

      <input type="button"  value="取消" class="button"  οnclick="window.close();">

 
 
 

</form:form>

 

 

 
 

<script>

 

    function save() {       

 

           document.forms[0].action="<c:url  value='/procAdmin/subSysProc'/>";

 

           document.forms[0].submit();     

 

    }

 

</script>

 

 

8.定制数据绑定--@InitBinder注解

有时数据绑定需要定制,例如我们也许需要指定必需填写的域,或者需要为日期、货币金额等类似事情注册定制的PropertyEditors。用@MVC实现这些功能是非常容易的:

 
 

@InitBinder

 

 public void initDataBinder(WebDataBinder  binder)  {

 

    binder.setRequiredFields(new String[]  {"number", "name"});

 

 }

 

@InitBinder注解方法可以访问@MVC用来 绑定请求参数DataBinder实例,它允许我们为每个控制器定制必须项

 

例1.注册自己的属性编辑器

SpringMVC 有一套常用的属性编辑器,这包括基本数据类型及其包裹类的属性编辑器、String 属性编辑器JavaBean 的属性编辑器等。但有时我们还需要向 Spring MVC 框架注册一些自定义的属性编辑器,如特定时间格式的属性编辑器就是其中一例。

1)Spring MVC  允许向整个 Spring 框架 注册属性编辑器,它们对所有 Controller 都有影响。 

2)当然 Spring MVC 也允许仅向某个 Controller 注册属性编辑器,对其它的 Controller 没有影响。

前者可以通过 AnnotationMethodHandlerAdapter 的配置做到,而后者则可以通过 @InitBinder 注解实现。

 

例1:下面先看向整个 Spring MVC 框架注册的自定义编辑器:

清单 13. 注册框架级的自定义属性编辑器
 
 

<bean    class="org.springframework.web.servlet.mvc.annotation.

 

AnnotationMethodHandlerAdapter">

 

       <property  name="webBindingInitializer">

 

           <bean class="com.baobaotao.web.MyBindingInitializer"/>

 

       </property>

 

</bean>

 

 

清单 14.自定义属性编辑器--配置一个定制的WebBindingInitializer

为了外化数据绑定初始化的过程,可以提供一个WebBindingInitializer 接口的自定义实现。 通过为一个AnnotationMethodHandlerAdapter 提供一个定制的bean配置可以使它启用,这样就覆盖了默认配置。

 

例子:MyBindingInitializer实现了WebBindingInitializer接口,在接口方法中通过binder 注册多个自定义的属性编辑器.为所有的java.util.Date 表单属性配置一个CustomDateEditor。其代码如下所示:

 
 

package  org.springframework.samples.petclinic.web;

 

 

 

import  java.text.SimpleDateFormat;

 

import  java.util.Date;

 

import  org.springframework.beans.factory.annotation.Autowired;

 

import  org.springframework.beans.propertyeditors.CustomDateEditor;

 

import  org.springframework.beans.propertyeditors.StringTrimmerEditor;

 

import  org.springframework.samples.petclinic.Clinic;

 

import  org.springframework.samples.petclinic.PetType;

 

import  org.springframework.web.bind.WebDataBinder;

 

import org.springframework.web.bind.support.WebBindingInitializer;

 

import  org.springframework.web.context.request.WebRequest;

 
 
 

public class MyBindingInitializer  implements WebBindingInitializer  {

 
 
 

    public void initBinder(WebDataBinder binderWebRequest  request)  {

 

        SimpleDateFormat dateFormat = new  SimpleDateFormat("yyyy-MM-dd");

 

        dateFormat.setLenient(false);

 

 

 

        binder.registerCustomEditor(

 

Date.class, new CustomDateEditor(dateFormat, false));

 

        binder.registerCustomEditor(

 

String.class, new StringTrimmerEditor(false));

 

    }

 

}

 

 

例2:看特定的Controller注册的自定义编辑器:

如果希望 某个属性编辑器仅作用于特定的 Controller,可以在 Controller 中定义一个标注 @InitBinder 注解的方法,可以在该方法中向 Controller 了注册若干属性编辑器.

 

使用@InitBinder  注解 控制器方法,可以在控制器类内部直接配置 Web数据绑定

@InitBinder指定初始化WebDataBinder 的方法, 后者被用于填充注解句柄方法命令表单对象参数

 

这个init-binder方法支持@RequestMapping 支持的全部参数,除了命令/表单对象和对应的验证结果对象。 Init-binder方法必须没有返回值。因此,它们常被声明为void 。 典型的参数,包括 WebDataBinder 以及WebRequest 或者java.util.Locale ,允许代码注册上下文特定的编辑器。

 

下面的例子说明了@InitBinder 的用法,来看下面的代码:

清单 15. 注册 Controller 级的自定义属性编辑器

注意:被标注@InitBinder 注解的方法必须拥有一个 WebDataBinder 类型入参,以便SpringMVC 框架将 注册 属性编辑器 WebDataBinder 对象传递进来。

 
 

@Controller

 

public class MyFormController  {

 

 

 

    @InitBinder

 

    public void initBinder(WebDataBinder binder) {

 

        SimpleDateFormat dateFormat = new  SimpleDateFormat("yyyy-MM-dd");

 

        dateFormat.setLenient(false);

 

 

 

        binder.registerCustomEditor(

 

Date.class, new  CustomDateEditor(dateFormat, false));

 

        // binder.setRequiredFields(new String[]  {"number", "name"});

 

    }

 

    …

 

}

 

 

 

9.数据绑定结果和验证--BindingResult

数据绑定 也许会导致类似于类型转换域缺失的错误。

不管发生什么错误,我们都希望能返回到编辑的表单,让用户自行更正。

要想实现这个目的,我们可直接在方法签名表单模型对象后面追加一个BindingResult对象(注意检验结果参数必须紧跟在命令/表单对象的后面),例如:

 

0)xxxController.java

 
 

@RequestMapping(method=RequestMethod.POST)

 

 public ModelAndView onSubmit(Account account, BindingResult  bindingResult)  {

 

    if (bindingResult.hasErrors()) {

 

       ModelAndView mav = new ModelAndView();

 

       mav.getModel().putAll(bindingResult.getModel());

 

       return mav;

 

    }

 

    // Save the  changes and redirect to the next view...

 

 }

 

 

发生错误时我们返回到出现问题的视图,并把从BindingResult得到的属性增加到模型上,这样特定域的错误就能够反馈给用户。要注意的是,我们并没有指定一个显式的视图名,而是允许DispatcherServlet依靠与入口URI路径信息匹配的默认视图名。

调用Validator对象并把BindingResult传给它,仅这一行代码就可实现验证操作。这允许我们在一个地方收集绑定和验证错误:

 

0)xxxController.java

 
 

@RequestMapping(method=RequestMethod.POST)

 

 public ModelAndView onSubmit(Account account, BindingResult  bindingResult)  {

 

      new  AccountValidator().validate(account, bindingResult);

 

      if (bindingResult.hasErrors()) {

 

           ModelAndView mav = new ModelAndView();

 

           mav.getModel().putAll(bindingResult.getModel());

 

           return mav;

 

      }

 

      // Save the changes and redirect to the next  view...

 

 }

 

 

1)OwnerValidator.java

 
 

public class AccountValidator {

 

    public void validate(Owner owner, Errors errors) {

 

       if (!StringUtils.hasLength(owner.getFirstName()))  {

 

           errors.rejectValue("firstName", "required",  "required");

 

       }

 

       if (!StringUtils.hasLength(owner.getLastName()))  {

 

           errors.rejectValue("lastName", "required",  "required");

 

       }

 

       if (!StringUtils.hasLength(owner.getAddress()))  {

 

           errors.rejectValue("address", "required",  "required");

 

       }

 

       if (!StringUtils.hasLength(owner.getCity()))  {

 

           errors.rejectValue("city", "required",  "required");

 

       }

 

 

 

       String  telephone = owner.getTelephone();

 

       if (!StringUtils.hasLength(telephone))  {

 

           errors.rejectValue("telephone", "required",  "required");

 

       }else {

 

           for (int i = 0; i < telephone.length(); ++i) {

 

              if ((Character.isDigit(telephone.charAt(i)))  == false) {

 

                  errors.rejectValue("telephone", "nonNumeric",  "non-numeric");

 

                  break;

 

              }

 

           }

 

       }

 

    }

 

}

 

 

2)xxx.jsp

 
 

<td>

 

     <form:input path=" firstName  " cssStyle="WIDTH:  180px" maxlength="15"/>

 

     <form:errors path=" firstName " cssClass="errors"/>

 

</td>

 

 

现在是时候结束我们的Spring 2.5 Web层注解(非正式称法为@MVC)之旅了。

 

10.消除类级别的请求映射

Web层注解频遭诟病是有事实依据的,那就是嵌入源代码的URI路径。

这个问题很好矫正,URI路径和控制器类之间的匹配关系用XML配置文件去管理,只在方法级映射中使用@RequestMapping注解

我们将配置一个ControllerClassNameHandlerMapping,它使用 依赖控制器类名字的惯例,将URI映射到控制器:

 
 

<bean  class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

 

现在“/accounts/*”这样的请求都被匹配到AccountsController上,它与方法级别上的@RequestMapping注解协作的很好,只要添加上方法名就能够完成上述映射。此外,既然我们的方法并不会返回视图名称,我们现在就可以依据惯例匹配类名、方法名、URI路径和视图名。

 

当@Controller被完全转换为@MVC后,程序的写法如下:

 
 

@Controller

 

 public class AccountsController {

 

      private  AccountRepository accountRepository;

 

 

 

     @Autowired

 

      public  AccountsController(AccountRepository  accountRepository) {

 

           this.accountRepository  = accountRepository;

 

      }

 

 

 

     @RequestMapping(method=RequestMethod.GET)

 

public void show(@RequestParam("number") String  number,  

 

Map<String, Object> model)  {

 

          model.put("account",  accountRepository.findAccount(number));

 

     }

 

    ...

 

 

对应的XML配置文件如下:

 
 

<context:component-scan  base-package="com.abc.accounts"/>

 

 

 

       <bean  class="org.springframework.web.servlet.mvc.support.

 

ControllerClassNameHandlerMapping"/>

 

 

 

       <bean  class="org.springframework.web.servlet.view.

 

                                           InternalResourceViewResolver">

 

            <property name="prefix"  value="/WEB-INF/views/" />

 

            <property name="suffix"  value=".jsp" />

 

       </bean>

 

你可以看出这是一个最精减的XML。程序里注解中没有嵌入URI路径,也没有显式指定视图名,请求处理方法也只有很简单的一行,方法签名与我们的需求精准匹配,其它的请求处理方法也很容易添加。不需要基类,也不需要XML(至少也是没有直接配置控制器),我们就能获得上述所有优势。

也许接下来你就可以看到,这种程序设计模型是多么有效了。

 

11.@RequestMapping简介

11.1 灵活的请求处理方法签名

我们曾经承诺过要提供灵活的方法签名,现在来看一下成果。

输入的参数中移除了响应对象,增加了一个代表模型的Map;

返回的不再是ModelAndView,而是一个字符串,指明呈现响应时要用的视图名字:

 
 

@RequestMapping("/accounts/show")

 

 public String show(HttpServletRequest  request, Map<String, Object> model)

 

 throws Exception {

 

      String  number = ServletRequestUtils.getStringParameter(request, "number");

 

      model.put("account",  accountRepository.findAccount(number));

 

      return  "/WEB-INF/views/accounts/show.jsp";

 

 }

 

 

Map输入参数是一个“隐式的”模型,对于我们来说在调用方法前创建它很方便,其中添加的键—值对数据便于在视图中解析应用。本例视图为show.jsp页面。

 

11.2 当方法没有指定视图时

有种令人感兴趣的情形是当方法没有指定视图时(例如返回类型为void)会有什么事情发生,按照惯例DispatcherServlet要再使用请求URI的路径信息,不过要移去前面的斜杠和扩展名。让我们把返回类型改为void:

 
 

@RequestMapping("/accounts/show")

 

 public void show(HttpServletRequest  request, Map<String, Object> model)

 

throws Exception {

 

       String  number = ServletRequestUtils.getStringParameter(request, "number");

 

       model.put("account",  accountRepository.findAccount(number));

 

 }

 

对于给定的请求处理方法和“/accounts/show”的请求映射,我们可以期望DispatcherServlet能够获得“accounts/show”的默认视图名称,当它与如下适当的视图解析器结合共同作用时,会产生与前面指明返回视图名同样的结果:

 
 

<bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">

 

       <property  name="prefix" value="/WEB-INF/views/" />

 

       <property  name="suffix" value=".jsp" />

 

 </bean>

 

强烈推荐视图名称依赖惯例的方式,因为这样可以从控制器代码中消除硬编码的视图名称。如果你想定制DispatcherServlet获取默认视图名的方式,就在servlet上下文环境中配置一个你自己的 RequestToViewNameTranslator实现,并为其bean id赋名为“viewNameTranslator”。

 

11.3 继续@RequestMapping的讨论

把@RequestMapping放在类级别上是合法的,这可令它与方法级别上的@RequestMapping注解协同工作,取得缩小选择范围的效果,下面是一些例子:

11.1 类级别:

 
 

RequestMapping("/accounts/*")

 

 

11.2 方法级别:

 
 

@RequestMapping(value="delete",  method=RequestMethod.POST)

 

@RequestMapping(value="index",  method=RequestMethod.GET, params="type=checking")

 

@RequestMapping

 

 

第一个方法级的请求映射和类级别的映射结合,当HTTP方法是POST时与路径“/accounts/delete”匹配;

第二个添加了一个要求,就是名为“type”的请求参数和其值“checking”都需要在请求中出现;

第三个根本就没有指定路径,这个方法匹配所有的 HTTP方法,如果有必要的话可以用它的方法名。

下面改写我们的方法,使它可以依靠方法名进行匹配,程序如下:

 
 

@Controller

 

 @RequestMapping("/accounts/*")

 

 public class AccountsController {

 

 

 

    @RequestMapping(method=RequestMethod.GET)

 

public  void show(@RequestParam("number")  String number,

 

Map<String, Object>  model){

 

           model.put("account",  accountRepository.findAccount(number));

 

    }

 

    ...

 

 

方法匹配的请求是“/accounts/show”,依据的是类级别的@RequestMapping指定的匹配路径“/accounts/*”和方法名“show”。

 

13.@PathVariable

@PathVariable是用来对指定请求的URL路径里面的变量

例1:

 
 

@RequestMapping( value = "form/{id}/apply", 

 

                     method = {RequestMethod.PUT, RequestMethod.POST})  

 

{id}在这个请求的URL里就是个变量,可以使用@PathVariable来获取 .

@PathVariable和@RequestParam的区别就在于:@RequestParam用来获得静态的URL请求入参 .

 

例2:雁联

1.channelManage.jsp

 
 

<input   type="button"   name="Submit"    class="button"    value="启动渠道"

 

          οnclick="window.showModalDialog('<spring:url value="/channel/${channel.channelID}/start"/>',null,'dialogleft=200px;dialogtop=260px;dialogwidth=800px;dialogheight=400px');location.reload();">

 

 

2.ChannelManageController.java

 
 

@RequestMapping(value = "/channel/{channelId}/start", method = RequestMethod.GET)

 

public String start(@PathVariable Integer channelId)

 

throws  IOException, MalformedObjectNameException, NullPointerException,

 

InstanceNotFoundException,  MBeanException, ReflectionException,

 

BusinessException  {

 

       //前面要加上权限控制

 

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值