读书人

Spring3.X 引文精华

发布时间: 2012-08-21 13:00:22 作者: rapoo

Spring3.X 注解精华
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">

?

<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>

?

例1:雁联zfpt-servlet.xml

配置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>

?

例2:web.xml

<?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>

?

2.1?webAppRootKey

<!--

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>

?

2.3?log4jConfigLocation

???<!--

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>

?

2.4?contextConfigLocation

<!--

-?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>

?

?

2.5?springSecurityFilterChain

<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>

?

2.6?Log4jConfigListener

<!--

-?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>

?

2.7?ContextLoaderListener

<!--

-?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>

?

2.8?DispatcherServlet

<!--

-?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>

?

2.9?exception.java

<error-page>

<exception-type>java.lang.Exception</exception-type>

<!--?Displays?a?stack?trace?-->

<location>/WEB-INF/jsp/uncaughtException.jsp</location>

</error-page>

?

?

?

?

例3:雁联web.xml

如果你想要自定义映射策略,显式的定义一个DefaultAnnotationHandlerMapping?和?/或AnnotationMethodHandlerAdapter?也有实际意义。?例如,指定一个自定义的PathMatcher?或者WebBindingInitializer

?

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

使用过低版本?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?的视图页面。

?

清单?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?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>?的配置。

?

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

在低版本的?Spring?MVC?中,我们可以通过继承?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(int?topicId,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,User?user)?中的?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?方法就成为了请求处理方法,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?的例子:

清单?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?方法返回参数

在低版本的?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?也不失为一个好的选择。

?

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()?方法将在任何请求处理方法执行前调用,Spring?MVC?会将该方法返回值以“items”为名放入到隐含的模型对象属性列表中。

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

?

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

7.@MVC表单处理

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

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

使用@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?从数据库读取记录,然后显示在页面上)

Spring3.X 引文精华

?

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>

?

??<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>

?

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">?

?进?程?名

<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>

?

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

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

@InitBinder

?public?void?initDataBinder(WebDataBinder?binder)?{

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

?}

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

?

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

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

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

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

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

?

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

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

<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>

?

清单?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?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));

????}

}

?

例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?类型的入参,以便?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"});

????}

????…

}

?

?

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?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(至少也是没有直接配置控制器),我们就能获得上述所有优势。

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

?

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?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”。

?

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="启动渠道"?

??????????onclick="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?{

//前面要加上权限控制

}

?

<!--EndFragment-->

读书人网 >编程

热点推荐