配色: 字号:
spring3
2017-03-31 | 阅:  转:  |  分享 
  


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

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

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

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

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



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

@Controller

publicclassAccountsController{

privateAccountRepositoryaccountRepository;



@Autowired

publicAccountsController(AccountRepositoryaccountRepository){

this.accountRepository=accountRepository;

}



@RequestMapping(method=RequestMethod.GET)

publicvoidshow(@RequestParam("number")Stringnumber,

Mapmodel){

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

}

...

对应的XML配置文件如下:






ControllerClassNameHandlerMapping"/>




InternalResourceViewResolver">





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

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



11.@RequestMapping简介

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

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

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

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

@RequestMapping("/accounts/show")

publicStringshow(HttpServletRequestrequest,Mapmodel)

throwsException{

Stringnumber=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")

publicvoidshow(HttpServletRequestrequest,Mapmodel)

throwsException{

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

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

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







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



11.3继续@RequestMapping的讨论

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/")

publicclassAccountsController{



@RequestMapping(method=RequestMethod.GET)

publicvoidshow(@RequestParam("number")Stringnumber,

Mapmodel){

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

}

...

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



13.@PathVariable

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

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

method?=?{RequestMethod.PUT,?RequestMethod.POST})?? {id}在这个请求的URL里就是个变量,可以使用@PathVariable来获取@PathVariable和@RequestParam的区别就在于:@RequestParam用来获得静态的URL请求入参channelManage.jsp

'',null,''dialogleft=200px;dialogtop=260px;dialogwidth=800px;dialogheight=400px'');location.reload();">

2.ChannelManageController.java

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

publicStringstart(@PathVariableIntegerchannelId)

throwsIOException,MalformedObjectNameException,NullPointerException,

InstanceNotFoundException,MBeanException,ReflectionException,

BusinessException{

//前面要加上权限控制





















3.xxxController.java

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

@Controller

@RequestMapping("/procAdmin/subSysProc")

publicclassSubSysProController{ privateValidatorbeanValidator; @RequestMapping(params="method=modifyProcUI",method=RequestMethod.GET)

publicStringmodifyProcUI(Processprocess,BindingResultresult,Modelmodel){ beanValidator.validate(process,result);

if(result.hasErrors()){

returnaddProcUI();

}

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

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

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



//获得子系统列表

ListsubSysList=subSysService.getAllSubSysList();



model.addAttribute("process",process);

model.addAttribute("subSysList",subSysList);

return"processManage/processModify";

} }

4.zfpt-servlet.xml


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/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd

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

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

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

















/WEB-INF/validator-rules.xml

/WEB-INF/validator.xml







5.modifyProcess.jsp



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










action=""method="PUT">




 进 程 名











 描      述









 工作目录









 子 系 统







0.提示

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



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

这个示例集中在无状态的multi-action控制器,包括多段文件上传的处理。可以在“samples/imagedb”目录找到imagedb应用程序。



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



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




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




DefaultAnnotationHandlerMapping"/>


AnnotationMethodHandlerAdapter"/>

...(controllerbeandefinitions)...





例1雁联zfpt-servlet.xml配置DefaultAnnotationHandlerMapping和/或AnnotationMethodHandlerAdapter


DefaultAnnotationHandlerMapping">



1
















AnnotationMethodHandlerAdapter">












class="com.ylink.zfpt.web.intercepor.SuperUserAccessInterceptor">

例2:web.xml




xmlns=http://java.sun.com/xml/ns/j2eexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> SpringPetClinic

SpringPetClinicsampleapplication

2.1webAppRootKey





webAppRootKey

petclinic.root



2.3log4jConfigLocation





log4jConfigLocation

/WEB-INF/classes/log4j.properties



2.4contextConfigLocation





contextConfigLocation



/WEB-INF/spring/applicationContext-jdbc.xml

/WEB-INF/applicationContext-security.xml













2.5springSecurityFilterChain



springSecurityFilterChain



org.springframework.web.filter.DelegatingFilterProxy





springSecurityFilterChain

/



2.6Log4jConfigListener









org.springframework.web.util.Log4jConfigListener



2.7ContextLoaderListener







org.springframework.web.context.ContextLoaderListener





2.8DispatcherServlet





petclinic



org.springframework.web.servlet.DispatcherServlet



2









petclinic

/



2.9exception.java



java.lang.Exception



/WEB-INF/jsp/uncaughtException.jsp







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

一个简单的基于注解的Controller

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

清单1.BbtForumController.javapackagecom.baobaotao.web;



importcom.baobaotao.service.BbtForumService;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.ModelAttribute;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RequestMethod;

importjava.util.Collection; @Controller//<——①

@RequestMapping("/forum.do")

publicclassBbtForumController{



@Autowired

privateBbtForumServicebbtForumService;



@RequestMapping//<——②

publicStringlistAllBoard(){

bbtForumService.getAllBoard();

System.out.println("calllistAllBoardmethod.");

return"listBoard";

}

}

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

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



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

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




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



SpringAnnotationMVCSample







contextConfigLocation

classpath:applicationContext.xml











org.springframework.web.context.ContextLoaderListener











annomvc

org.springframework.web.servlet.DispatcherServlet



2







annomvc

.do





清单3.annomvc-servlet.xml

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




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












AnnotationMethodHandlerAdapter"/>






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



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

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

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



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

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

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

packagecom.baobaotao.web;



importcom.baobaotao.service.BbtForumService;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.RequestMapping;



@Controller

publicclassBbtForumController{



@Autowired

privateBbtForumServicebbtForumService;



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

publicStringlistAllBoard(){

bbtForumService.getAllBoard();

System.out.println("calllistAllBoardmethod.");

return"listBoard";

}



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

publicStringlistBoardTopic(inttopicId){

bbtForumService.getBoardTopics(topicId);

System.out.println("calllistBoardTopicmethod.");

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注解很容易实现这个常用的需求。来看下面的代码

packagecom.baobaotao.web;



importcom.baobaotao.service.BbtForumService;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.RequestMapping;



@Controller

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

publicclassBbtForumController{



@Autowired

privateBbtForumServicebbtForumService;



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

@RequestMapping(params="method=listAllBoard")

publicStringlistAllBoard(){

bbtForumService.getAllBoard();

System.out.println("calllistAllBoardmethod.");

return"listBoard";

}



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

@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(inttopicId){

bbtForumService.getBoardTopics(topicId);

System.out.println("calllistBoardTopicmethod.");

return"listTopic";

}

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



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

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

packagecom.baobaotao.web;



importcom.baobaotao.service.BbtForumService;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RequestMethod;



@Controller

@RequestMapping("/bbtForum.do")

publicclassBbtForumController{



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

publicStringcreateTopic(){

System.out.println("callcreateTopicmethod.");

return"createTopic";

}

}

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



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

3.1按契约绑定

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

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



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

@RequestMapping(params="method=listBoardTopic")

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

publicStringlistBoardTopic(inttopicId){

bbtForumService.getBoardTopics(topicId);

System.out.println("calllistBoardTopicmethod.");

return"listTopic";

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

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

listBoardTopic(inttopicId):和topicIdURL请求参数绑定;

listBoardTopic(inttopicId,StringboardName):分别和topicId、boardNameURL请求参数绑定;

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



清单6.User.java:一个JavaBean

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

packagecom.baobaotao.web;



publicclassUser{

privateintuserId;

privateStringuserName;

//省略get/setter方法

publicStringtoString(){

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

}

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

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

@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(inttopicId,Useruser){

bbtForumService.getBoardTopics(topicId);

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

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

System.out.println("calllistBoardTopicmethod.");

return"listTopic";

}

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

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

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

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

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

packagecom.baobaotao.web;



importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RequestParam;





@Controller

@RequestMapping("/bbtForum.do")

publicclassBbtForumController{



@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,

Useruser){

bbtForumService.getBoardTopics(topicId);

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

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

System.out.println("calllistBoardTopicmethod.");

return"listTopic";

}



}

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

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

@RequestParam("id")Longid

@RequestParam("balance")doublebalance

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





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

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

@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,

Useruser,

ModelMapmodel){

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对象。packagecom.baobaotao.web;



importorg.springframework.ui.ModelMap;

importorg.springframework.web.bind.annotation.SessionAttributes;



@Controller

@RequestMapping("/bbtForum.do")

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

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

publicclassBbtForumController{



@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,Useruser,

ModelMapmodel){

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级作用域

publicclassBbtForumController{

@Autowired

privateBbtForumServicebbtForumService;



@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,

Useruser,

ModelMapmodel){

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

publicStringlistAllBoard(@ModelAttribute("currUser")Useruser){

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属性上了

请求处理方法入参的可选类型 说明 Java基本数据类型和String 默认情况下将按名称匹配的方式绑定到URL参数上,可以通过@RequestParam注解改变默认的绑定规则 request/response/session 既可以是ServletAPI的也可以是PortletAPI对应的对象,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 它绑定SpringMVC框架中每个请求所创建的潜在的模型对象,它们可以被Web视图对象访问(如JSP) 命令/表单对象(注:一般称绑定使用HTTPGET发送的URL参数的对象为命令对象,而称绑定使用HTTPPOST发送的URL参数的对象为表单对象) 它们的属性将以名称匹配的规则绑定到URL参数上,同时完成类型的转换。而类型转换的规则可以通过@InitBinder注解或通过HandlerAdapter的配置进行调整 org.springframework.validation.Errors/org.springframework.validation.BindingResult 为属性列表中的命令/表单对象的校验结果,注意检验结果参数必须紧跟在命令/表单对象的后面 org.springframework.web.bind.support.SessionStatus 可以通过该类型status对象显式结束表单的处理,这相当于触发session清除其中的通过@SessionAttributes定义的属性 SpringMVC框架的易用之处在于,你可以按任意顺序定义请求处理方法的入参(除了Errors和BindingResult必须紧跟在命令对象/表单参数后面以外),SpringMVC会根据反射机制自动将对应的对象通过入参传递给请求处理方法。这种机制让开发者完全可以不依赖ServletAPI开发控制层的程序,当请求处理方法需要特定的对象时,仅仅需要在参数列表中声明入参即可,不需要考虑如何获取这些对象,SpringMVC框架就象一个大管家一样“不辞辛苦”地为我们准备好了所需的一切。下面演示一下使用SessionStatus的例子:

@RequestMapping(method=RequestMethod.POST)

publicStringprocessSubmit(@ModelAttributeOwnerowner,

BindingResultresult,

SessionStatusstatus){//<——①

newOwnerValidator().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中清空。

packagecom.baobaotao.web;



importorg.springframework.ui.ModelMap;

importorg.springframework.web.bind.annotation.SessionAttributes;



@Controller

@RequestMapping("/bbtForum.do")

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

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

publicclassBbtForumController{



@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,Useruser,

ModelMapmodel){

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级作用域

publicclassBbtForumController{

@Autowired

privateBbtForumServicebbtForumService;



@RequestMapping(params="method=listBoardTopic")

publicStringlistBoardTopic(@RequestParam("id")inttopicId,

Useruser,ModelMapmodel){

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

publicStringlistAllBoard(@ModelAttribute("currUser")Useruser){



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属性上了

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

@RequestMapping("/welcome.do")

publicvoidwelcomeHandler(){

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

@RequestMapping(method=RequestMethod.GET)

publicStringsetupForm(@RequestParam("ownerId")intownerId,

ModelMapmodel){

Ownerowner=this.clinic.loadOwner(ownerId);

model.addAttribute(owner);

return"ownerForm";

}

对应的逻辑视图名为“ownerForm” org.springframework.ui.ModelMap 和返回类型为void一样,逻辑视图名取决于对应请求的URL,如下面的例子:

@RequestMapping("/vets.do")

publicModelMapvetsHandler(){

returnnewModelMap(this.clinic.getVets());

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



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

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

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

importorg.springframework.web.bind.annotation.SessionAttributes;

importjava.util.ArrayList;

importjava.util.List;

importjava.util.Set;



@Controller

@RequestMapping("/bbtForum.do")

publicclassBbtForumController{



@Autowired

privateBbtForumServicebbtForumService;



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

publicListpopulateItems(){

Listlists=newArrayList();

lists.add("item1");

lists.add("item2");

returnlists;

}



@RequestMapping(params="method=listAllBoard")

publicStringlistAllBoard(

@ModelAttribute("currUser")Useruser,ModelMapmodel){

bbtForumService.getAllBoard();

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

System.out.println("model.items:"+((List)model.get("items")).size());

return"listBoard";

}

}

说明:

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

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

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

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

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

支持错误处理和验证;

JSP表单标记库;

基类控制器。

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

@RequestMapping(method=RequestMethod.GET)

publicAccountsetupForm(){

...

} @RequestMapping(method=RequestMethod.POST)

publicvoidonSubmit(Accountaccount){

...

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

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

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

1.xxx.jsp

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

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



AccountNumber:

...



2.xxxController.java

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

@RequestMapping(method=RequestMethod.GET)

public@ModelAttribute("account")SpecialAccountsetupForm(){

...

} @RequestMapping(method=RequestMethod.POST)

publicvoidupdate(@ModelAttribute("account")SpecialAccountaccount){

...

}

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

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

@Controller

@SessionAttributes("account")

publicclassAccountFormController{

...

} @Controller

@SessionAttributes(types=Account.class)

publicclassAccountFormController{

...

}

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

@RequestMapping(method=RequestMethod.POST)

publicvoidonSubmit(Accountaccount,SessionStatussessionStatus){

...

sessionStatus.setComplete();//Clears@SessionAttributes

}

7.3雁联例子

1.效果图:

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

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

(modifyProcess.jsp)”修改了信息后,

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

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






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


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

rel="stylesheet"

type="text/css"/>

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




'''');"class="button">










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












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











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

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

@InitBinder

publicvoidinitDataBinder(WebDataBinderbinder){

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

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

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

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

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

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

下面先看向整个SpringMVC框架注册的自定义编辑器:

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











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

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



MyBindingInitializer实现了WebBindingInitializer接口,在接口方法中通过binder注册多个自定义的属性编辑器其代码如下所示:

packageorg.springframework.samples.petclinic.web;



importjava.text.SimpleDateFormat;

importjava.util.Date;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.beans.propertyeditors.CustomDateEditor;

importorg.springframework.beans.propertyeditors.StringTrimmerEditor;

importorg.springframework.samples.petclinic.Clinic;

importorg.springframework.samples.petclinic.PetType;

importorg.springframework.web.bind.WebDataBinder;

importorg.springframework.web.bind.support.WebBindingInitializer;

importorg.springframework.web.context.request.WebRequest; publicclassMyBindingInitializerimplementsWebBindingInitializer{ publicvoidinitBinder(WebDataBinderbinder,WebRequestrequest){

SimpleDateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd");

dateFormat.setLenient(false);



binder.registerCustomEditor(

Date.class,newCustomDateEditor(dateFormat,false));

binder.registerCustomEditor(

String.class,newStringTrimmerEditor(false));

}

}

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

使用@InitBinder注解控制器方法,可以在控制器类内部直接配置Web数据绑定。@InitBinder指定初始化WebDataBinder的方法,后者被用于填充注解的句柄方法的命令和表单对象参数。



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

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

@Controller

publicclassMyFormController{@InitBinder

publicvoidinitBinder(WebDataBinderbinder){

SimpleDateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd");

dateFormat.setLenient(false);



binder.registerCustomEditor(

Date.class,newCustomDateEditor(dateFormat,false));

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

}



}



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

数据绑定也许会导致类似于类型转换或域缺失的错误。不管发生什么错误,我们都希望能返回到编辑的表单,让用户自行更正。要想实现这个目的,我们可直接在方法签名的表单模型对象后面追加一个BindingResult对象注意检验结果参数必须紧跟在命令/表单对象的后面,:@RequestMapping(method=RequestMethod.POST)

publicModelAndViewonSubmit(Accountaccount,BindingResultbindingResult){

if(bindingResult.hasErrors()){

ModelAndViewmav=newModelAndView();

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

returnmav;

}

//Savethechangesandredirecttothenextview...

}

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

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



0)xxxController.java

@RequestMapping(method=RequestMethod.POST)

publicModelAndViewonSubmit(Accountaccount,BindingResultbindingResult){

newAccountValidator().validate(account,bindingResult);

if(bindingResult.hasErrors()){

ModelAndViewmav=newModelAndView();

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

returnmav;

}

//Savethechangesandredirecttothenextview...

}

1)OwnerValidator.java

publicclassAccountValidator{

publicvoidvalidate(Ownerowner,Errorserrors){

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");

}



Stringtelephone=owner.getTelephone();

if(!StringUtils.hasLength(telephone)){

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

}else{

for(inti=0;i
if((Character.isDigit(telephone.charAt(i)))==false){

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

break;

}

}

}

}

}

2)xxx.jsp







献花(0)
+1
(本文系关平藏书首藏)
类似文章 更多
发表评论: