Spring3.X 注解精华
1)?Spring发行版本附带了PetClinic?示例,它是一个在简单的表单处理的上下文中,?利用了本节中说明的注解支持的Web应用程序。?可以在“samples/petclinic?”目录中找到PetClinic?应用程序。
?
2)?另外一个建立在基于注解的Web?MVC上的示例应用程序,请见imagedb?。
????这个示例集中在无状态的multi-action控制器,包括多段文件上传的处理。?
????可以在“samples/imagedb?”目录找到imagedb?应用程序。
?
只有对应的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">
?
<bean?style="line-height: 150%; margin-top: 0pt; text-indent: 243pt; margin-bottom: 0pt;">DefaultAnnotationHandlerMapping"/>
<bean?style="line-height: 150%; margin-top: 0pt; text-indent: 252pt; margin-bottom: 0pt;">AnnotationMethodHandlerAdapter"/>
???????????...?(controller?bean?definitions)?...
?
</beans>
?
配置DefaultAnnotationHandlerMapping?和?/或AnnotationMethodHandlerAdapter
<context:component-scan?base-package="com.ylink.zfpt.web.spring"/>
DefaultAnnotationHandlerMapping">
???? <property?name="order">
<value>1</value>
</property>
???? <property?name="interceptors">
???? <list>
???? <ref?bean="sessionInterceptor"/>
???? <ref?bean="superUserInterceptor"/>
???? </list>
???? </property>
</bean>
<bean?class="org.springframework.web.servlet.mvc.annotation.
AnnotationMethodHandlerAdapter">
<property?name="webBindingInitializer">
<bean?class="com.ylink.zfpt.web.spring.ZfptBindingInitializer"/>
</property>
</bean>
</bean>
<bean?id="superUserInterceptor"
class="com.ylink.zfpt.web.intercepor.SuperUserAccessInterceptor"></bean>
?
<?xml??version="1.0"???encoding="ISO-8859-1"?>
<web-app????version="2.4"?
xmlns=http://java.sun.com/xml/ns/j2ee?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Spring?PetClinic</display-name>
???<description>Spring?PetClinic?sample?application</description>
?
<!--
Key?of?the?system?property?that?should?specify?the?root?directory?of?this
web?app.?Applied?by?WebAppRootListener?or?Log4jConfigListener.
??-->
<context-param>
??<param-name>webAppRootKey</param-name>
??<param-value>petclinic.root</param-value>
</context-param>
?
???<!--
Location?of?the?Log4J?config?file,?for?initialization?and?refresh?checks.
Applied?by?Log4jConfigListener.
-->
<context-param>
??<param-name>log4jConfigLocation</param-name>
??<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
?
<!--
-?Location?of?the?XML?file?that?defines?the?root?application?context.
-?Applied?by?ContextLoaderServlet.
-
-?Can?be?set?to:
-?"/WEB-INF/applicationContext-hibernate.xml"?for?the?Hibernate?implementation,
-?"/WEB-INF/applicationContext-jpa.xml"?for?the?JPA?one,?or
-?"/WEB-INF/applicationContext-jdbc.xml"?for?the?JDBC?one.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/applicationContext-jdbc.xml
/WEB-INF/applicationContext-security.xml
</param-value>
????????<!--
<param-value>/WEB-INF/spring/applicationContext-hibernate.xml</param-value>
<param-value>/WEB-INF/spring/applicationContext-jpa.xml</param-value>
-->
?
<!--
To?use?the?JPA?variant?above,?you?will?need?to?enable?Spring?load-time
weaving?in?your?server?environment.?Out?of?the?box,?Spring?will?try?to
detect?the?running?environment?and?use?the?appropriate?weaver?but?if?that
fails,?one?must?enable?one?by?hand?or?use?the?VM-wide?weaver.
See?PetClinic's?readme?and/or?Spring's?JPA?documentation?for?more?information.
-->
</context-param>
?
?
<filter>
????<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
????<filter-name>springSecurityFilterChain</filter-name>
????<url-pattern>/*</url-pattern>
</filter-mapping>
?
<!--
-?Configures?Log4J?for?this?web?app.
-?As?this?context?specifies?a?context-param?"log4jConfigLocation",?its?file?path
-?is?used?to?load?the?Log4J?configuration,?including?periodic?refresh?checks.
-
-?Would?fall?back?to?default?Log4J?initialization?(non-refreshing)?if?no?special
-?context-params?are?given.
-
-?Exports?a?"web?app?root?key",?i.e.?a?system?property?that?specifies?the?root
-?directory?of?this?web?app,?for?usage?in?log?file?paths.
-?This?web?app?specifies?"petclinic.root"?(see?log4j.properties?file).
-->
<!--?Leave?the?listener?commented-out?if?using?JBoss?-->
?
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
?
<!--
-?Loads?the?root?application?context?of?this?web?app?at?startup,
-?by?default?from?"/WEB-INF/applicationContext.xml".
-?Note?that?you?need?to?fall?back?to?Spring's?ContextLoaderServlet?for
-?J2EE?servers?that?do?not?follow?the?Servlet?2.4?initialization?order.
-
-?Use?WebApplicationContextUtils.getWebApplicationContext(servletContext)
-?to?access?it?anywhere?in?the?web?application,?outside?of?the?framework.
-
-?The?root?context?is?the?parent?of?all?servlet-specific?contexts.
-?This?means?that?its?beans?are?automatically?available?in?these?child?contexts,
-?both?for?getBean(name)?calls?and?(external)?bean?references.
-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
?
<!--
-?Servlet?that?dispatches?request?to?registered?handlers?(Controller?implementations).
-?Has?its?own?application?context,?by?default?defined?in?"{servlet-name}-servlet.xml",
-?i.e.?"petclinic-servlet.xml".
-
-?A?web?app?can?contain?any?number?of?such?servlets.
-?Note?that?this?web?app?has?a?shared?root?application?context,?serving?as?parent
-?of?all?DispatcherServlet?contexts.
-->
<servlet>
<servlet-name>petclinic</servlet-name>
????<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
?
<!--
-?Maps?the?petclinic?dispatcher?to?"*.do".?All?handler?mappings?in
-?petclinic-servlet.xml?will?by?default?be?applied?to?this?subpath.
-?If?a?mapping?isn't?a?/*?subpath,?the?handler?mappings?are?considered
-?relative?to?the?web?app?root.
-
-?NOTE:?A?single?dispatcher?can?be?mapped?to?multiple?paths,?like?any?servlet.
-->
<servlet-mapping>
<servlet-name>petclinic</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
?
<error-page>
<exception-type>java.lang.Exception</exception-type>
<!--?Displays?a?stack?trace?-->
<location>/WEB-INF/jsp/uncaughtException.jsp</location>
</error-page>
?
?
?
?
如果你想要自定义映射策略,显式的定义一个DefaultAnnotationHandlerMapping?和?/或AnnotationMethodHandlerAdapter?也有实际意义。?例如,指定一个自定义的PathMatcher?或者WebBindingInitializer
?
使用过低版本?Spring?MVC?的读者都知道:
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?的视图页面。
?
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?等)。
?
为了让基于注解的?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>
?
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?style="line-height: 150%; margin-top: 0pt; margin-bottom: 0pt;">???????????????????????????????????????????????????????AnnotationMethodHandlerAdapter"/>
?
????<!--??③:对模型视图名称的解析,即在模型视图名称添加前后缀?-->
<bean
????style="line-height: 150%; margin-top: 0pt; margin-bottom: 0pt;">????????p:prefix="/WEB-INF/jsp/"?p:suffix=".jsp"/>
</beans>
?
因为?Spring?所有功能都在?Bean?的基础上演化而来,所以必须事先将?Controller?变成?Bean,这是通过在类中标注?@Controller?并在?annomvc-servlet.xml?中启用组件扫描机制来完成的,如?①?所示。
在?②?处,配置了一个?AnnotationMethodHandlerAdapter,它负责根据?Bean?中的?Spring?MVC?注解对?Bean?进行加工处理,使这些?Bean?变成控制器并映射特定的?URL?请求。
而?③?处的工作是定义模型视图名称的解析规则,这里我们使用了?Spring?2.5?的特殊命名空间,即?p?命名空间,它将原先需要通过?<property>?元素配置的内容转化为?<bean>?属性配置,在一定程度上简化了?<bean>?的配置。
?
在低版本的?Spring?MVC?中,我们可以通过继承?MultiActionController?让一个?Controller?处理多个?URL?请求。使用?@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
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()?方法处理。
?
对于处理多个?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?的处理方法了。
?
@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()才会进行处理。
?
Controller?的方法标注了?@RequestMapping?注解后,它就能处理特定的?URL?请求。
我们不禁要问:请求处理方法入参是如何绑定?URL?参数的呢?在回答这个问题之前先来看下面的代码
?
@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(int?topicId,String?boardName):分别和?topicId、boardName?URL?请求参数绑定;?
特别的,如果入参是基本数据类型(如?int、long、float?等),URL?请求参数中一定要有对应的参数,否则将抛出?TypeMismatchException?异常,提示无法将?null?转换为基本数据类型。
?
另外,请求处理方法的入参也可以一个?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;
????}
}
?
下面是将?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?参数绑定。
?
如果我们想改变这种默认的按名称匹配的策略,比如让?listBoardTopic(int?topicId,User?user)?中的?topicId?绑定到?id?这个?URL?参数,那么可以通过对入参使用?@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异常,因为当前的信息不足以从请求中提取参数。由于这个原因,在编码时最好显式的指定参数名。
?
?
Spring?2.0?定义了一个?org.springframework.ui.ModelMap?类,它作为通用的模型数据承载对象,传递数据供视图所用。我们可以在请求处理方法中声明一个?ModelMap?类型的入参,Spring?会将本次请求模型对象引用通过该入参传递进来,这样就可以在请求处理方法内部访问模型对象了。来看下面的例子:
@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?中添加属性。
?
在默认情况下,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”})。
?
上面讲述了如何往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?属性上了
?
我们知道标注了?@RequestMapping?注解的?Controller?方法就成为了请求处理方法,Spring?MVC?允许极其灵活的请求处理方法签名方式。对于方法入参来说,它允许多种类型的入参,通过下表进行说明
请求处理方法入参的可选类型
说明
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?必须紧跟在命令对象/表单参数后面以外),Spring?MVC?会根据反射机制自动将对应的对象通过入参传递给请求处理方法。这种机制让开发者完全可以不依赖?Servlet?API?开发控制层的程序,当请求处理方法需要特定的对象时,仅仅需要在参数列表中声明入参即可,不需要考虑如何获取这些对象,Spring?MVC?框架就象一个大管家一样“不辞辛苦”地为我们准备好了所需的一切。下面演示一下使用?SessionStatus?的例子:
@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?注解来实现的。请看下面的代码:
?
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”})。
?
上面讲述了如何往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?属性上了
?
在低版本的?Spring?MVC?中,请求处理方法的返回值类型都必须是?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?也不失为一个好的选择。
?
在编写?Controller?时,常常需要在真正进入请求处理方法前准备一些数据,以便请求处理或视图渲染时使用。在传统的?SimpleFormController?里,是通过复写其?referenceData()?方法来准备引用数据的。
在?Spring?2.5?时,可以将任何一个拥有返回值的方法标注上?@ModelAttribute,使其返回值将会进入到模型对象的属性列表中。来看下面的例子:
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()?方法将在任何请求处理方法执行前调用,Spring?MVC?会将该方法返回值以“items”为名放入到隐含的模型对象属性列表中。
所以在?②?处,我们就可以通过?ModelMap?入参?访问到?items?属性,当执行?listAllBoard()?请求处理方法时,②?处将在控制台打印出“model.items:2”的信息。
?
当然我们也可以在请求的视图中访问到模型对象中的?items?属性。
一个典型的表单处理场景包括:获得可编辑对象,在编辑模式下显示它持有的数据、允许用户提交并最终进行验证和保存变化数据。
Spring?MVC提供下列几个特性辅助进行上述所有活动:
使用@MVC,除了由于@ModelAttribute、@InitBinder和@SessionAttributes这些注解的存在而不再需要基类控制器外,其它一切都不需要改变。
看一下这些请求处理方法签名:
@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的数据绑定机制用请求中的参数自动填充的。这是一个非常简单的程序模型。
Account对象?中?含有?要被编辑的数据。
在Spring?MVC的术语当中,Account被称作是?表单模型对象。这个对象必须通过某个名称让表单标签(还有数据绑定机制)知道它的存在。下面是从JSP页面中截取的部分代码,引用了一个名为“account”的表单模型对象:
<form:form??modelAttribute="account"??method="post">
????Account?Number:?<form:input?path="number"/><form:errors?path="number"/>
????...
?</form>
?
即使我们没有在任何地方指定“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),这就是我们下面将要分析的情况。
?
@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
?}
?
在“subProcess.jsp”页面,点击“修改”按钮,进入“修改页面(modifyProcess.jsp)”,
“修改页面(modifyProcess.jsp)”上会显示?该条记录的信息(根据ID?从数据库读取记录,然后显示在页面上)
?
用户在“修改页面(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>
?
在“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";
}
}
?
<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>
?
??<property?name="validatorFactory"?ref="validatorFactory"/>
</bean>
<property?name="validationConfigLocations">
<list>
<value>/WEB-INF/validator-rules.xml</value>
<value>/WEB-INF/validator.xml</value>
</list>
</property>
?
<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">?
?进?程?名
<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">
?????描??????述
??</td>
??<td>
??<form:input?path="prcsDescription"?
?????????????????????????????????cssStyle="WIDTH:?180px"??maxlength="200"/>
</td>
??????</tr>
??????????<tr>
?<td?nowrap?class="td_title">
???工作目录
?</td>
?<td>
<form:input?path="workDirectory"?
???????????????????????????????cssStyle="WIDTH:?180px"?maxlength="100"/>
??</td>
</tr>
????????<tr>
??<td?nowrap?class="td_title">
?子?系?统
<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"?onclick="save();"?value="修改进程"/>?
??<input?type="button"??value="取消"?class="button"?onclick="window.close();">
</form:form>
?
<script>
????function?save()?{??? ??
???????????document.forms[0].action="<c:url?value='/procAdmin/subSysProc'/>";
???????????document.forms[0].submit();??????
????}
</script>
?
有时数据绑定需要定制,例如我们也许需要指定必需填写的域,或者需要为日期、货币金额等类似事情注册定制的PropertyEditors。用@MVC实现这些功能是非常容易的:
@InitBinder
?public?void?initDataBinder(WebDataBinder?binder)?{
????binder.setRequiredFields(new?String[]?{"number",?"name"});
?}
@InitBinder注解的方法可以访问@MVC用来?绑定请求参数?的DataBinder实例,它允许我们为每个控制器定制必须项。
?
Spring?MVC?有一套常用的属性编辑器,这包括基本数据类型及其包裹类的属性编辑器、String?属性编辑器、JavaBean?的属性编辑器等。但有时我们还需要向?Spring?MVC?框架注册一些自定义的属性编辑器,如特定时间格式的属性编辑器就是其中一例。
1)Spring?MVC??允许向?整个?Spring?框架?注册?属性编辑器,它们对所有?Controller?都有影响。??
2)当然?Spring?MVC?也允许仅向某个?Controller?注册属性编辑器,对其它的?Controller?没有影响。
前者可以通过?AnnotationMethodHandlerAdapter?的配置做到,而后者则可以通过?@InitBinder?注解实现。
?
<bean???style="line-height: 150%; margin-top: 0pt; text-indent: 78.39pt; margin-bottom: 0pt;">AnnotationMethodHandlerAdapter">
???????<property?name="webBindingInitializer">
???????????<bean?style="line-height: 150%; margin-top: 0pt; margin-bottom: 0pt;">???????</property>
</bean>
?
为了外化数据绑定初始化的过程,可以提供一个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?binder,?WebRequest?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));
????}
}
?
如果希望?某个属性编辑器?仅作用于特定的?Controller,可以在?Controller?中定义一个标注?@InitBinder?注解的方法,可以在该方法中向?Controller?了注册若干个属性编辑器.
?
使用@InitBinder??注解?控制器方法,可以在控制器类内部直接配置?Web数据绑定。?
@InitBinder?指定初始化WebDataBinder?的方法,?后者被用于填充注解的句柄方法的命令和表单对象参数。
?
这个init-binder方法支持@RequestMapping?支持的全部参数,除了命令/表单对象和对应的验证结果对象。?Init-binder方法必须没有返回值。因此,它们常被声明为void?。?典型的参数,包括?WebDataBinder?以及WebRequest?或者java.util.Locale?,允许代码注册上下文特定的编辑器。
?
下面的例子说明了@InitBinder?的用法,来看下面的代码:
注意:被标注?@InitBinder?注解的方法必须拥有一个?WebDataBinder?类型的入参,以便?Spring?MVC?框架将?注册?属性编辑器的?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"});
????}
????…
}
?
?
数据绑定?也许会导致类似于类型转换或域缺失的错误。
不管发生什么错误,我们都希望能返回到编辑的表单,让用户自行更正。
要想实现这个目的,我们可直接在方法签名的表单模型对象后面追加一个BindingResult对象(注意检验结果参数必须紧跟在命令/表单对象的后面),例如:
?
@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传给它,仅这一行代码就可实现验证操作。这允许我们在一个地方收集绑定和验证错误:
?
@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...
?}
?
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;
}
}
}
}
}
?
<td>
?<form:input?path="?firstName?"?cssStyle="WIDTH:?180px"?maxlength="15"/>
?<form:errors?path="?firstName?"?cssClass="errors"/>
</td>
?
现在是时候结束我们的Spring?2.5?Web层注解(非正式称法为@MVC)之旅了。
?
Web层注解频遭诟病是有事实依据的,那就是嵌入源代码的URI路径。
这个问题很好矫正,URI路径和控制器类之间的匹配关系用XML配置文件去管理,只在方法级的映射中使用@RequestMapping注解。
我们将配置一个ControllerClassNameHandlerMapping,它使用?依赖控制器?类名字的惯例,将URI映射到控制器:
<bean?style="line-height: 150%; margin-top: 0pt; text-indent: 21pt; margin-bottom: 0pt;">现在“/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?style="line-height: 150%; margin-top: 0pt; text-indent: 231.75pt; margin-bottom: 0pt;">ControllerClassNameHandlerMapping"/>
?
???????<bean?style="line-height: 150%; margin-top: 0pt; text-indent: 76.5pt; margin-bottom: 0pt;">???????????????????????????????????????????InternalResourceViewResolver">
????????????<property?name="prefix"?value="/WEB-INF/views/"?/>
????????????<property?name="suffix"?value=".jsp"?/>
???????</bean>
你可以看出这是一个最精减的XML。程序里注解中没有嵌入URI路径,也没有显式指定视图名,请求处理方法也只有很简单的一行,方法签名与我们的需求精准匹配,其它的请求处理方法也很容易添加。不需要基类,也不需要XML(至少也是没有直接配置控制器),我们就能获得上述所有优势。
也许接下来你就可以看到,这种程序设计模型是多么有效了。
?
我们曾经承诺过要提供灵活的方法签名,现在来看一下成果。
输入的参数中移除了响应对象,增加了一个代表模型的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页面。
?
有种令人感兴趣的情形是当方法没有指定视图时(例如返回类型为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?style="line-height: 150%; margin-top: 0pt; margin-bottom: 0pt;">???????<property?name="prefix"?value="/WEB-INF/views/"?/>
???????<property?name="suffix"?value=".jsp"?/>
?</bean>
强烈推荐视图名称依赖惯例的方式,因为这样可以从控制器代码中消除硬编码的视图名称。如果你想定制?DispatcherServlet获取默认视图名的方式,就在servlet上下文环境中配置一个你自己的?RequestToViewNameTranslator实现,并为其bean?id赋名为“viewNameTranslator”。
?
把@RequestMapping放在类级别上是合法的,这可令它与方法级别上的@RequestMapping注解协同工作,取得缩小选择范围的效果,下面是一些例子:
RequestMapping("/accounts/*")
?
@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”。
?
@PathVariable是用来对指定请求的URL路径里面的变量
@RequestMapping(?value?=?"form/{id}/apply",?
?????????????????????method?=?{RequestMethod.PUT,?RequestMethod.POST})??
{id}在这个请求的URL里就是个变量,可以使用@PathVariable来获取?.
@PathVariable和@RequestParam的区别就在于:@RequestParam用来获得静态的URL请求入参?.
?
<input???type="button"???name="Submit"???class="button"???value="启动渠道"?
??????????onclick="window.showModalDialog('<spring:url?value="/channel/${channel.channelID}/start"/>',null,'dialogleft=200px;dialogtop=260px;dialogwidth=800px;dialogheight=400px');location.reload();">
?
@RequestMapping(value?=?"/channel/{channelId}/start",?method?=?RequestMethod.GET)
public?String?start(@PathVariable?Integer?channelId)?
throws?IOException,?MalformedObjectNameException,?NullPointerException,?
InstanceNotFoundException,?MBeanException,?ReflectionException,?
BusinessException?{
//前面要加上权限控制
}
?
<!--EndFragment-->