l?Spring 2.0 ?Spring MVC q行重大升后,Spring 2.5 又ؓ(f) Spring MVC 引入?jin)注解驱动功能。现在你无须?Controller l承M接口Q无需?XML 配置文g中定义请求和 Controller 的映关p,仅仅使用注解可以让一?POJO h Controller 的绝大部分功?—?Spring MVC 框架的易用性得C(jin)q一步的增强.在框架灵zL、易用性和扩展性上QSpring MVC 已经全面越?jin)其它?MVC 框架Q伴随着 Spring 一路高qq,可以预见 Spring MVC ?MVC ?jng)场上的吸引力将来不可抗拒?/p>
本文介l?Spring 2.5 新增?Sping MVC 注解功能Q讲q如何用注解配|替换传l的Z XML ?Spring MVC 配置?/p>
![]() ![]() |
![]()
|
使用q低版本 Spring MVC 的读者都知道Q当创徏一?Controller Ӟ我们需要直接或间接地实?org.springframework.web.servlet.mvc.Controller 接口。一般情况下Q我们是通过l承 SimpleFormController ?MultiActionController 来定义自q Controller 的。在定义 Controller 后,一个重要的事g是在 Spring MVC 的配|文件中通过 HandlerMapping 定义h和控制器的映关p,以便两者关联v来?/p>
来看一下基于注解的 Controller 是如何定义做到这一点的Q下面是使用注解?BbtForumControllerQ?/p>
清单 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"; } } |
从上面代码中Q我们可以看?BbtForumController 和一般的cdƈ没有区别Q它没有实现MҎ(gu)的接口,因而是一个地道的 POJO。让q个 POJO 与众不同的魔就?Spring MVC 的注解!
??处用了(jin)两个注解Q分别是 @Controller ?@RequestMapping。在“使用 Spring 2.5 Z注解驱动?IoC”q篇文章里,W者曾l指?gu)?@Controller、@Service 以及(qing) @Repository ?@Component 注解的作用是{h(hun)的:(x)一个类成ؓ(f) Spring 容器?Bean。由?Spring MVC ?Controller 必须事先是一?BeanQ所?@Controller 注解是不可缺的?/p>
真正?BbtForumController 具备 Spring MVC Controller 功能的是 @RequestMapping q个注解。@RequestMapping 可以标注在类定义处,?Controller 和特定请求关联v来;q可以标注在Ҏ(gu){处,以便q一步对hq行分流。在 ?处,我们?BbtForumController 兌“/forum.do”的请求,??处,我们具体地指?listAllBoard() Ҏ(gu)来处理请求。所以在cd明处标注?@RequestMapping 相当于让 POJO 实现?Controller 接口Q而在Ҏ(gu)定义处的 @RequestMapping 相当于让 POJO 扩展 Spring 预定义的 ControllerQ如 SimpleFormController {)(j)?/p>
Z(jin)让基于注解的 Spring MVC 真正工作hQ需要在 Spring MVC 对应?xxx-servlet.xml 配置文g中做一些手脚。在此之前,q是先来看一?web.xml 的配|吧Q?/p>
清单 2. web.xmlQ启?Spring 容器?Spring MVC 框架
<?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 服务层的配置文g --> <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 的ServletQ它?yu)加载WEB-INF/annomvc-servlet.xml ? 配置文gQ以启动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 中定义了(jin)一个名?annomvc ?Spring MVC 模块Q按?Spring MVC 的契U,需要在 WEB-INF/annomvc-servlet.xml 配置文g中定?Spring MVC 模块的具体配|。annomvc-servlet.xml 的配|内容如下所C:(x)
<?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"> <!-- ①:(x)对web包中的所有类q行扫描Q以完成Bean创徏和自动依赖注入的功能 --> <context:component-scan base-package="com.baobaotao.web"/> <!-- ②:(x)启动Spring MVC的注解功能,完成h和注解POJO的映?--> <bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"/> <!-- ③:(x)Ҏ(gu)型视囑U的解析Q即在模型视囑U添加前后缀 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> </beans> |
因ؓ(f) Spring 所有功能都?Bean 的基上演化而来Q所以必M先将 Controller 变成 BeanQ这是通过在类中标?@Controller q在 annomvc-servlet.xml 中启用组件扫描机制来完成的,??所C?/p>
??处,配置?jin)一?AnnotationMethodHandlerAdapterQ它负责Ҏ(gu) Bean 中的 Spring MVC 注解?Bean q行加工处理Qɘq些 Bean 变成控制器ƈ映射特定?URL h?/p>
??处的工作是定义模型视囑U的解析规则Q这里我们用了(jin) Spring 2.5 的特D命名空_(d)?p 命名I间Q它?yu)原先需要通过 <property> 元素配置的内容{化ؓ(f) <bean> 属性配|,在一定程度上化了(jin) <bean> 的配|?/p>
启动 TomcatQ发?http://localhost/forum.do URL hQBbtForumController ?listAllBoard() Ҏ(gu)响应这个请求,q{?WEB-INF/jsp/listBoard.jsp 的视N面?/p>
![]() ![]() |
![]()
|
在低版本?Spring MVC 中,我们可以通过l承 MultiActionController 让一?Controller 处理多个 URL h。?@RequestMapping 注解后,q个功能更加Ҏ(gu)实现?jin)。请看下面的代码Q?/p>
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"; } } |
在这里,我们分别????处ؓ(f) listAllBoard() ?listBoardTopic() Ҏ(gu)标注?@RequestMapping 注解Q分别指定这两个Ҏ(gu)处理?URL hQ这相当于将 BbtForumController 攚wؓ(f) MultiActionController。这?/listAllBoard.do ?URL h由 listAllBoard() 负责处理Q?/listBoardTopic.do?topicId=1 ?URL h则由 listBoardTopic() Ҏ(gu)处理?/p>
对于处理多个 URL h?Controller 来说Q我们們于通过一?URL 参数指定 Controller 处理Ҏ(gu)的名Uͼ?method=listAllBoardQ,而非直接通过不同?URL 指定 Controller 的处理方法。?@RequestMapping 注解很容易实现这个常用的需求。来看下面的代码Q?/p>
清单 4. 一?Controller 对应一?URLQ由h参数军_h处理Ҏ(gu)
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") // <—??指定控制器对应U(ku)RLh public class BbtForumController { @Autowired private BbtForumService bbtForumService; // <—??如果URLh中包?method=listAllBoard"的参敎ͼ由本Ҏ(gu)q行处理 @RequestMapping(params = "method=listAllBoard") public String listAllBoard() { bbtForumService.getAllBoard(); System.out.println("call listAllBoard method."); return "listBoard"; } // <—??如果URLh中包?method=listBoardTopic"的参敎ͼ由本Ҏ(gu)q行处理 @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 hQ?BbtForumController 中的h处理Ҏ(gu)?URL h的分规则在 ???处定义分规则按?URL ?method h参数定。所以分别在cd义处和方法定义处使用 @RequestMapping 注解Q就可以很容易通过 URL 参数指定 Controller 的处理方法了(jin)?/p>
@RequestMapping 注解中除?params 属性外Q还有一个常用的属性是 methodQ它可以?Controller Ҏ(gu)处理特定 HTTP h方式的请求,如让一个方法处?HTTP GET hQ而另一个方法处?HTTP POST hQ如下所C:(x)
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"; } } |
q样只有?/bbtForum.do?method=createTopic h?HTTP POST 方式提交ӞcreateTopic() Ҏ(gu)才会(x)q行处理?/p>
![]() ![]() |
![]()
|
Controller 的方法标注了(jin) @RequestMapping 注解后,它就能处理特定的 URL h。我们不要问:(x)h处理Ҏ(gu)入参是如何绑?URL 参数的呢Q在回答q个问题?sh)前先来看下面的代码Q?/p>
@RequestMapping(params = "method=listBoardTopic") //<—??topicId入参是如何绑定URLh参数的? 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 hӞSpring 不但?listBoardTopic() Ҏ(gu)处理q个hQ而且q将 topicId h参数在类型{换后l定?listBoardTopic() Ҏ(gu)?topicId 入参上。?listBoardTopic() Ҏ(gu)的返回类型是 StringQ它?yu)被解析为逻辑视图的名U。也是?Spring 在如何给处理Ҏ(gu)入参自动赋g?qing)如何将处理?gu)q回D{化ؓ(f) ModelAndView 中的q程中存在一套潜在的规则Q不熟?zhn)q个规则׃可能很好地开发基于注解的h处理Ҏ(gu)Q因此了(jin)解这个潜在规则无疑成为理?Spring MVC 框架Z注解功能的核?j)问题?/p>
我们不妨从最常见的开始说P(x)h处理Ҏ(gu)入参的类型可以是 Java 基本数据cd?String cdQ这时方法入参按参数名匹配的原则l定?URL h参数Q同时还自动完成 String cd?URL h参数到请求处理方法参数类型的转换。下面给出几个例子:(x)
特别的,如果入参是基本数据类型(?int、long、float {)(j)QURL h参数中一定要有对应的参数Q否则将抛出 TypeMismatchException 异常Q提C无法将 null 转换为基本数据类型?/p>
另外Q请求处理方法的入参也可以一?JavaBeanQ如下面?User 对象可以作Z个入参:(x)
package com.baobaotao.web; public class User { private int userId; private String userName; //省略get/setterҎ(gu) public String toString(){ return this.userName +","+this.userId; } } |
下面是将 User 作ؓ(f) listBoardTopic() h处理Ҏ(gu)的入参:(x)
@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"; } |
q时Q如果我们用以下的 URL hQhttp://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom
topicId URL 参数绑定到 topicId 入参上,?userId ?userName URL 参数绑定到 user 对象?userId ?userName 属性中。和 URL h中不允许没有 topicId 参数不同Q虽?User ?userId 属性的cd是基本数据类型,但如?URL 中不存在 userId 参数QSpring 也不?x)报错,此?user.userId gؓ(f) 0。如?User 对象拥有一?dept.deptId 的联属性,那么它将?dept.deptId URL 参数l定?/p>
如果我们x(chng)变这U默认的按名U匹配的{略Q比如让 listBoardTopic(int topicId,User user) 中的 topicId l定?id q个 URL 参数Q那么可以通过对入参?@RequestParam 注解来达到目的:(x)
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"; } … } |
q里Q对 listBoardTopic() h处理Ҏ(gu)?topicId 入参标注?@RequestParam("id") 注解Q所以它?yu)?id ?URL 参数l定?/p>
Spring 2.0 定义?jin)一?org.springframework.ui.ModelMap c,它作为通用的模型数据承载对象,传递数据供视图所用。我们可以在h处理Ҏ(gu)中声明一?ModelMap cd的入参,Spring ?x)将本次h模型对象引用通过该入参传递进来,q样可以在h处理Ҏ(gu)内部讉K模型对象?jin)。来看下面的例子Q?/p>
清单 9. 使用 ModelMap 讉KL(fng)对应的隐含模型对?/strong>
@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"; } |
对于当次h所对应的模型对象来_(d)其所有属性都存攑ֈ request 的属性列表中。象上面的例子,ModelMap 中的 currUser 属性将攑ֈ request 的属性列表中Q所以可以在 JSP 视图面中通过 request.getAttribute(“currUser”) 或者通过 ${currUser} EL 表达式访问模型对象中?user 对象。从q个角度上看Q?ModelMap 相当于是一个向 request 属性列表中d对象的一条管道,借由 ModelMap 对象的支持,我们可以在一个不依赖 Servlet API ?Controller 中向 request 中添加属性?/p>
在默认情况下QModelMap 中的属性作用域?request U别是,也就是说Q当本次hl束后,ModelMap 中的属性将销毁。如果希望在多个h中共?ModelMap 中的属性,必须其属性{存到 session 中,q样 ModelMap 的属性才可以被跨h讉K?/p>
Spring 允许我们有选择地指?ModelMap 中的哪些属性需要{存到 session 中,以便下一个请求属对应?ModelMap 的属性列表中q能讉K到这些属性。这一功能是通过cd义处标注 @SessionAttributes 注解来实现的。请看下面的代码Q?/p>
清单 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属性列表中Q以便这个属性可以跨h讉K 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"; } } |
我们??处添加了(jin)一?ModelMap 属性,其属性名?currUserQ??处通过 @SessionAttributes 注解?ModelMap 中名?currUser 的属性放|到 Session 中,所以我们不但可以在 listBoardTopic() h所对应?JSP 视图面中通过 request.getAttribute(“currUser”) ?session.getAttribute(“currUser”) 获取 user 对象Q还可以在下一个请求所对应?JSP 视图面中通过 session.getAttribute(“currUser”) ?ModelMap#get(“currUser”) 讉K到这个属性?/p>
q里我们仅将一?ModelMap 的属性放?Session 中,其实 @SessionAttributes 允许指定多个属性。你可以通过字符串数l的方式指定多个属性,?@SessionAttributes({“attr1”,”attr2”})。此外,@SessionAttributes q可以通过属性类型指定要 session 化的 ModelMap 属性,?@SessionAttributes(types = User.class)Q当然也可以指定多个c,?@SessionAttributes(types = {User.class,Dept.class})Q还可以联合使用属性名和属性类型指定:(x)@SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})?/p>
上面讲述?jin)如何往ModelMap中放|属性以?qing)如何ModelMap中的属性拥有Session域的作用范围。除?jin)在JSP视图面中通过传统的方法访问ModelMap中的属性外Q读者朋友可能会(x)问:(x)是否可以ModelMap中的属性绑定到h处理Ҏ(gu)的入参中呢?{案是肯定的。Spring为此提供?jin)一个@ModelAttribute的注解,下面是用@ModelAttribute注解的例子:(x)
清单 11. 使模型对象的特定属性具?Session 范围的作用域
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.ModelAttribute; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/bbtForum.do") @SessionAttributes("currUser") //①让ModelMap的currUser属性拥有sessionU作用域 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"; } @RequestMapping(params = "method=listAllBoard") //③将ModelMap中的 public String listAllBoard(@ModelAttribute("currUser") User user) { //currUser属性绑定到user入参中? bbtForumService.getAllBoard(); System.out.println("user:"+user); return "listBoard"; } } |
??处,我们?ModelMap 中添加一个名?currUser 的属性,??外的注解使这?currUser 属性拥有了(jin) session U的作用域。所以,我们可以??处通过 @ModelAttribute 注解?ModelMap 中的 currUser 属性绑定以h处理Ҏ(gu)?user 入参中?/p>
所以当我们先调用以?URL hQ?http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12
以执行listBoardTopic()h处理Ҏ(gu)Q然后再讉K以下URLQ?http://localhost/sample/bbtForum.do?method=listAllBoard
你将可以看到 listAllBoard() ?user 入参已经成功l定?listBoardTopic() 中注册的 session U的 currUser 属性上?jin)?/p>
![]() ![]() |
![]()
|
我们知道标注?@RequestMapping 注解?Controller Ҏ(gu)成Z(jin)h处理Ҏ(gu)QSpring MVC 允许极其灉|的请求处理方法签名方式。对于方法入参来_(d)它允许多U类型的入参Q通过下表q行说明Q?/p>
h处理Ҏ(gu)入参的可选类?/th> | 说明 |
---|---|
Java 基本数据cd?String | 默认情况下将按名U匹配的方式l定?URL 参数上,可以通过 @RequestParam 注解改变默认的绑定规?/td> |
request/response/session | 既可以是 Servlet API 的也可以?Portlet API 对应的对象,Spring ?x)将它们l定?Servlet ?Portlet 容器的相应对象上 |
org.springframework.web.context.request.WebRequest | 内部包含?request 对象 |
java.util.Locale | l定?request 对应?Locale 对象?/td> |
java.io.InputStream/java.io.Reader | 可以借此讉K request 的内?/td> |
java.io.OutputStream / java.io.Writer | 可以借此操作 response 的内?/td> |
M标注?@RequestParam 注解的入?/td> | 被标?@RequestParam 注解的入参将l定到特定的 request 参数上?/td> |
java.util.Map / org.springframework.ui.ModelMap | 它绑?Spring MVC 框架中每个请求所创徏的潜在的模型对象Q它们可以被 Web 视图对象讉KQ如 JSPQ?/td> |
命o(h)/表单对象Q注Q一般称l定使用 HTTP GET 发送的 URL 参数的对象ؓ(f)命o(h)对象Q而称l定使用 HTTP POST 发送的 URL 参数的对象ؓ(f)表单对象Q?/td> | 它们的属性将以名U匹配的规则l定?URL 参数上,同时完成cd的{换。而类型{换的规则可以通过 @InitBinder 注解或通过 HandlerAdapter 的配|进行调?/td> |
org.springframework.validation.Errors / org.springframework.validation.BindingResult | 为属性列表中的命?表单对象的校验结果,注意(g)验结果参数必ȝ跟在命o(h)/表单对象的后?/td> |
rg.springframework.web.bind.support.SessionStatus | 可以通过该类?status 对象昑ּl束表单的处理,q相当于触发 session 清除其中的通过 @SessionAttributes 定义的属?/td> |
Spring MVC 框架的易用之处在于,你可以按L序定义h处理Ҏ(gu)的入参(除了(jin) Errors ?BindingResult 必须紧跟在命令对?表单参数后面以外Q,Spring MVC ?x)根据反机制自动将对应的对象通过入参传递给h处理Ҏ(gu)。这U机制让开发者完全可以不依赖 Servlet API 开发控制层的程序,当请求处理方法需要特定的对象Ӟ仅仅需要在参数列表中声明入参即可,不需要考虑如何获取q些对象QSpring MVC 框架p一个大家一?#8220;不辞辛苦”Cؓ(f)我们准备好了(jin)所需的一切。下面演CZ下?SessionStatus 的例子:(x)
@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() Ҏ(gu)中的 owner 表单对象绑定到 ModelMap ?#8220;owner”属性中Qresult 参数用于存放(g)?owner l果的对象,?status 用于控制表单处理的状态。在 ?处,我们通过调用 status.setComplete() Ҏ(gu)Q该 Controller 所有放?session U别的模型属性数据将?session 中清I?/p>
在低版本?Spring MVC 中,h处理Ҏ(gu)的返回值类型都必须?ModelAndView。而在 Spring 2.5 中,你拥有多U灵zȝ选择。通过下表q行说明Q?/p>
h处理Ҏ(gu)入参的可选类?/th> | 说明 | |
---|---|---|
void |
此时逻辑视图名由h处理Ҏ(gu)对应?URL 定Q如以下的方法:(x)
对应的逻辑视图名ؓ(f)“welcome” |
|
String |
此时逻辑视图名ؓ(f)q回的字W,如以下的Ҏ(gu)Q?/p>
对应的逻辑视图名ؓ(f)“ownerForm” |
|
org.springframework.ui.ModelMap |
和返回类型ؓ(f) void 一P逻辑视图名取决于对应h?URLQ如下面的例子:(x)
对应的逻辑视图名ؓ(f)“vets”Q返回的 ModelMap 被作ؓ(f)h对应的模型对象,可以?JSP 视图面中访问到?/p> |
|
ModelAndView | 当然q可以是传统?ModelAndView?/td> |
应该说?String 作ؓ(f)h处理Ҏ(gu)的返回值类型是比较通用的方法,q样q回的逻辑视图名不?x)和h URL l定Q具有很大的灉|性,而模型数据又可以通过 ModelMap 控制。当然直接用传l的 ModelAndView 也不׃ؓ(f)一个好的选择?/p>
![]() ![]() |
![]()
|
Spring MVC 有一套常用的属性编辑器Q这包括基本数据cd?qing)其包裹cȝ属性编辑器、String 属性编辑器、JavaBean 的属性编辑器{。但有时我们q需要向 Spring MVC 框架注册一些自定义的属性编辑器Q如特定旉格式的属性编辑器是其中一例?/p>
Spring MVC 允许向整?Spring 框架注册属性编辑器Q它们对所?Controller 都有影响。当?Spring MVC 也允总向某?Controller 注册属性编辑器Q对其它?Controller 没有影响。前者可以通过 AnnotationMethodHandlerAdapter 的配|做刎ͼ而后者则可以通过 @InitBinder 注解实现?/p>
下面先看向整?Spring MVC 框架注册的自定义~辑器:(x)
>bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"< >property name="webBindingInitializer"< >bean class="com.baobaotao.web.MyBindingInitializer"/< >/property< >/bean< |
MyBindingInitializer 实现?WebBindingInitializer 接口Q在接口Ҏ(gu)中通过 binder 注册多个自定义的属性编辑器Q其代码如下所C:(x)
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)); } } |
如果希望某个属性编辑器仅作用于特定?ControllerQ可以在 Controller 中定义一个标?@InitBinder 注解的方法,可以在该Ҏ(gu)中向 Controller ?jin)注册若q个属性编辑器Q来看下面的代码Q?/p>
清单 15. 注册 Controller U的自定义属性编辑器
@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)); } … } |
注意被标?@InitBinder 注解的方法必L有一?WebDataBinder cd的入参,以便 Spring MVC 框架注册属性编辑器?WebDataBinder 对象传递进来?/p>
![]() ![]() |
![]()
|
在编?Controller Ӟ常常需要在真正q入h处理Ҏ(gu)前准备一些数据,以便h处理或视图渲染时使用。在传统?SimpleFormController 里,是通过复写?referenceData() Ҏ(gu)来准备引用数据的。在 Spring 2.5 Ӟ可以Q何一个拥有返回值的Ҏ(gu)标注?@ModelAttributeQ其返回值将?x)进入到模型对象的属性列表中。来看下面的例子Q?/p>
清单 16. 定义为处理请求准备数据的Ҏ(gu)
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; 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")//<——①向模型对象中d一个名为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(); //<——②在此讉K模型中的items属? System.out.println("model.items:" + ((List<String>)model.get("items")).size()); return "listBoard"; } } |
??处,通过使用 @ModelAttribute 注解QpopulateItem() Ҏ(gu)在Mh处理Ҏ(gu)执行前调用,Spring MVC ?x)将该方法返回g“items”为名攑օ到隐含的模型对象属性列表中?/p>
所以在 ?处,我们可以通过 ModelMap 入参讉K?items 属性,当执?listAllBoard() h处理Ҏ(gu)Ӟ?处将在控制台打印?#8220;model.items:2”的信息。当然我们也可以在请求的视图中访问到模型对象中的 items 属性?/p>
![]() ![]() |
![]()
|
Spring 2.5 ?Spring MVC q行?jin)很大增强,现在我们几乎完全可以使用Z注解?Spring MVC 完全替换掉原来基于接?Spring MVC E序。基于注解的 Spring MVC 比之于基于接口的 Spring MVC 拥有以下几点好处Q?/p>
但是Z注解?Spring MVC q不完美Q还存在优化的空_(d)因ؓ(f)在某些配|上它比Z XML 的配|更J琐。比如对于处理多个请求的 Controller 来说Q假设我们用一?URL 参数指定调用的处理方法(?xxx.do?method=listBoardTopicQ,当用注解时Q每个请求处理方法都必须使用 @RequestMapping() 注解指定对应?URL 参数Q如 @RequestMapping(params = "method=listBoardTopic")Q,而在 XML 配置中我们仅需要配|一?ParameterMethodNameResolver 可以了(jin)?/p>
注释配置相对?XML 配置h很多的优势:(x)
因此在很多情况下Q注释配|比 XML 配置更受Ƣ迎Q注释配|有q一步流行的势。Spring 2.5 的一大增强就是引入了(jin)很多注释c,现在(zhn)已l可以用注释配|完成大部分 XML 配置的功能。在q篇文章里,我们向(zhn)讲qC用注释进?Bean 定义和依赖注入的内容?/p>
![]() ![]() |
![]()
|
在用注释配|之前,先来回顾一下传l上是如何配|?Bean q完?Bean 之间依赖关系的徏立。下面是 3 个类Q它们分别是 Office、Car ?BossQ这 3 个类需要在 Spring 容器中配|ؓ(f) BeanQ?/p>
Office 仅有一个属性:(x)
package com.baobaotao; public class Office { private String officeNo =”001”; //省略 get/setter @Override public String toString() { return "officeNo:" + officeNo; } } |
Car 拥有两个属性:(x)
package com.baobaotao; public class Car { private String brand; private double price; // 省略 get/setter @Override public String toString() { return "brand:" + brand + "," + "price:" + price; } } |
Boss 拥有 Office ?Car cd的两个属性:(x)
package com.baobaotao; public class Boss { private Car car; private Office office; // 省略 get/setter @Override public String toString() { return "car:" + car + "\n" + "office:" + office; } } |
我们?Spring 容器中将 Office ?Car 声明?BeanQƈ注入?Boss Bean 中:(x)下面是用传l?XML 完成q个工作的配|文?beans.xmlQ?/p>
清单 4. beans.xml 以上三个类配置?Bean
<?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 id="boss" class="com.baobaotao.Boss"> <property name="car" ref="car"/> <property name="office" ref="office" /> </bean> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="002"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" U旗 CA72"/> <property name="price" value="2000"/> </bean> </beans> |
当我们运行以下代码时Q控制台正打?boss 的信息:(x)
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); } } |
q说?Spring 容器已经正确完成?Bean 创徏和装配的工作?/p>
![]() ![]() |
![]()
|
Spring 2.5 引入?@Autowired
注释Q它可以对类成员变量、方法及(qing)构造函数进行标注,完成自动装配的工作。来看一下?@Autowired
q行成员变量自动注入的代码:(x)
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; public class Boss { @Autowired private Car car; @Autowired private Office office; … } |
Spring 通过一?BeanPostProcessor
?@Autowired
q行解析Q所以要?@Autowired
起作用必M先在 Spring 容器中声?AutowiredAnnotationBeanPostProcessor
Bean?/p>
清单 7. ?@Autowired 注释工作h
<?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"> <!-- ?BeanPostProcessor 自动v作用Q对标注 @Autowired ?Bean q行自动注入 --> <bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <!-- U除 boss Bean 的属性注入配|的信息 --> <bean id="boss" class="com.baobaotao.Boss"/> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" U旗 CA72"/> <property name="price" value="2000"/> </bean> </beans> |
q样Q当 Spring 容器启动ӞAutowiredAnnotationBeanPostProcessor
扫?Spring 容器中所?BeanQ当发现 Bean 中拥?@Autowired
注释时就扑ֈ和其匚wQ默认按cd匚wQ的 BeanQƈ注入到对应的地方中去?/p>
按照上面的配|,Spring 直接采?Java 反射机制?Boss 中的 car
?office
q两个私有成员变量进行自动注入。所以对成员变量使用 @Autowired
后,(zhn)大可将它们?setter Ҏ(gu)Q?code>setCar() ?setOffice()
Q从 Boss 中删除?/p>
当然Q?zhn)也可以通过 @Autowired
Ҏ(gu)法或构造函数进行标注,来看下面的代码:(x)
package com.baobaotao; public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired public void setOffice(Office office) { this.office = office; } … } |
q时Q?code>@Autowired 查找被标注的方法的入参cd?BeanQƈ调用Ҏ(gu)自动注入q些 Bean。而下面的使用Ҏ(gu)则对构造函数进行标注:(x)
package com.baobaotao; public class Boss { private Car car; private Office office; @Autowired public Boss(Car car ,Office office){ this.car = car; this.office = office ; } … } |
׃ Boss()
构造函数有两个入参Q分别是 car
?office
Q?code>@Autowired 分别寻扑֒它们cd匚w?BeanQ将它们作ؓ(f) Boss(Car car ,Office office)
的入参来创徏 Boss
Bean?/p>
![]() ![]() |
![]()
|
在默认情况下使用 @Autowired
注释q行自动注入ӞSpring 容器中匹配的候?Bean 数目必须有且仅有一个。当找不C个匹配的 Bean ӞSpring 容器抛?BeanCreationException
异常Qƈ指出必须臛_拥有一个匹配的 Bean。我们可以来做一个实验:(x)
<?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 class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <bean id="boss" class="com.baobaotao.Boss"/> <!-- ?office Bean 注释?--> <!-- <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean>--> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" U旗 CA72"/> <property name="price" value="2000"/> </bean> </beans> |
׃ office
Bean 被注释掉?jin),所?Spring 容器中将没有cd?Office
?Bean ?jin),?Boss ?office
属性标注了(jin) @Autowired
Q当启动 Spring 容器Ӟ异常׃生了(jin)?/p>
当不能确?Spring 容器中一定拥有某个类?Bean Ӟ可以在需要自动注入该c?Bean 的地方可以?@Autowired(required = false)
Q这{于告诉 SpringQ在找不到匹?Bean 时也不报错。来看一下具体的例子Q?/p>
清单 11. 使用 @Autowired(required = false)
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired(required = false) public void setOffice(Office office) { this.office = office; } … } |
当然Q一般情况下Q?@Autowired
的地斚w是需要注?Bean 的,使用?jin)自动注入而又允许不注入的情况一般仅?x)在开发期或测试期到Q如Z(jin)快速启?Spring 容器Q仅引入一些模块的 Spring 配置文gQ,所?@Autowired(required = false)
?x)很用到?/p>
和找不到一个类型匹?Bean 相反的一个错误是Q如?Spring 容器中拥有多个候?BeanQSpring 容器在启动时也会(x)抛出 BeanCreationException
异常。来看下面的例子Q?/p>
清单 12. ?beans.xml 中配|两?Office cd?Bean
… <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="office2" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> … |
我们?Spring 容器中配|了(jin)两个cd?Office
cd?BeanQ当?Boss ?office
成员变量q行自动注入ӞSpring 容器无法确定到底要用哪一?BeanQ因此异常发生了(jin)?/p>
Spring 允许我们通过 @Qualifier
注释指定注入 Bean 的名Uͼq样歧义消除了(jin)Q可以通过下面的方法解军_常:(x)
@Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; } |
@Qualifier("office")
中的 office
?Bean 的名Uͼ所?@Autowired
?@Qualifier
l合使用Ӟ自动注入的策略就?byType 转变?byName ?jin)?code>@Autowired 可以Ҏ(gu)员变量、方法以?qing)构造函数进行注释,?@Qualifier
的标注对象是成员变量、方法入参、构造函数入参。正是由于注释对象的不同Q所?Spring 不将 @Autowired
?@Qualifier
l一成一个注释类。下面是Ҏ(gu)员变量和构造函数入参进行注释的代码Q?/p>
Ҏ(gu)员变量进行注释:(x)
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; … } |
Ҏ(gu)造函数入参进行注释:(x)
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car , @Qualifier("office")Office office){ this.car = car; this.office = office ; } } |
@Qualifier
只能?@Autowired
l合使用Q是?@Autowired
有益的补充。一般来Ԍ@Qualifier
Ҏ(gu)法签名中入参q行注释?x)降低代码的可读性,而对成员变量注释则相对好一些?/p>
![]() ![]() |
![]()
|
Spring 不但支持自己定义?@Autowired
的注释,q支持几个由 JSR-250 规范定义的注释,它们分别?@Resource
?code>@PostConstruct 以及(qing) @PreDestroy
?/p>
@Resource
的作用相当于 @Autowired
Q只不过 @Autowired
?byType 自动注入Q面 @Resource
默认?byName 自动注入|了(jin)?code>@Resource 有两个属性是比较重要的,分别?name ?typeQSpring ?@Resource
注释?name 属性解析ؓ(f) Bean 的名字,?type 属性则解析?Bean 的类型。所以如果?name 属性,则?byName 的自动注入策略,而?type 属性时则?byType 自动注入{略。如果既不指?name 也不指定 type 属性,q时通过反射机制使用 byName 自动注入{略?/p>
Resource 注释cM?Spring 发布包的 lib/j2ee/common-annotations.jar cd中,因此在用之前必d其加入到目的类库中。来看一个?@Resource
的例子:(x)
package com.baobaotao; import javax.annotation.Resource; public class Boss { // 自动注入cd?Car ?Bean @Resource private Car car; // 自动注入 bean 名称?office ?Bean @Resource(name = "office") private Office office; } |
一般情况下Q我们无需使用cM?@Resource(type=Car.class)
的注释方式,因ؓ(f) Bean 的类型信息可以通过 Java 反射从代码中获取?/p>
要让 JSR-250 的注释生效,除了(jin)?Bean cM标注q些注释外,q需要在 Spring 容器中注册一个负责处理这些注释的 BeanPostProcessor
Q?/p>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> |
CommonAnnotationBeanPostProcessor
实现?BeanPostProcessor
接口Q它负责扫描使用?JSR-250 注释?BeanQƈ对它们进行相应的操作?/p>
Spring 容器中的 Bean 是有生命周期的,Spring 允许?Bean 在初始化完成后以?Bean 销毁前执行特定的操作,(zhn)既可以通过实现 InitializingBean/DisposableBean 接口来定制初始化之后 / 销毁之前的操作Ҏ(gu)Q也可以通过 <bean> 元素?init-method/destroy-method 属性指定初始化之后 / 销毁之前调用的操作Ҏ(gu)。关?Spring 的生命周期,W者在《精?Spring 2.x—企业应用开发精解》第 3 章进行了(jin)详细的描qͼ有兴的读者可以查阅?/p>
JSR-250 为初始化之后/销毁之前方法的指定定义?jin)两个注释类Q分别是 @PostConstruct ?@PreDestroyQ这两个注释只能应用于方法上。标注了(jin) @PostConstruct 注释的方法将在类实例化后调用Q而标注了(jin) @PreDestroy 的方法将在类销毁之前调用?/p>
清单 17. 使用 @PostConstruct ?@PreDestroy 注释?Boss.java
package com.baobaotao; import javax.annotation.Resource; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class Boss { @Resource private Car car; @Resource(name = "office") private Office office; @PostConstruct public void postConstruct1(){ System.out.println("postConstruct1"); } @PreDestroy public void preDestroy1(){ System.out.println("preDestroy1"); } … } |
(zhn)只需要在Ҏ(gu)前标?@PostConstruct
?@PreDestroy
Q这些方法就?x)?Bean 初始化后或销毁之前被 Spring 容器执行?jin)?/p>
我们知道Q不是通过实现 InitializingBean
/DisposableBean
接口Q还是通过 <bean> 元素?init-method/destroy-method
属性进行配|,都只能ؓ(f) Bean 指定一个初始化 / 销毁的Ҏ(gu)。但是?@PostConstruct
?@PreDestroy
注释却可以指定多个初始化 / 销毁方法,那些被标?@PostConstruct
?@PreDestroy
注释的方法都?x)在初始?/ 销毁时被执行?/p>
通过以下的测试代码,(zhn)将可以看到 Bean 的初始化 / 销毁方法是如何被执行的Q?/p>
清单 18. 试cM?/strong>
package com.baobaotao; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); ctx.destroy();// 关闭 Spring 容器Q以触发 Bean 销毁方法的执行 } } |
q时Q?zhn)看到标注?jin) @PostConstruct
?postConstruct1()
Ҏ(gu)在 Spring 容器启动Ӟ创徏 Boss
Bean 的时候被触发执行Q而标注了(jin) @PreDestroy
注释?preDestroy1()
Ҏ(gu)在 Spring 容器关闭前销?Boss
Bean 的时候被触发执行?/p>
![]() ![]() |
![]()
|
使用 <context:annotation-config/> 化配|?/span>
Spring 2.1 d?jin)一个新?context ?Schema 命名I间Q该命名I间Ҏ(gu)释驱动、属性文件引入、加载期l入{功能提供了(jin)便捷的配|。我们知道注释本w是不会(x)做Q何事情的Q它仅提供元数据信息。要使元数据信息真正起作用,必须让负责处理这些元数据的处理器工作h?
而我们前面所介绍?AutowiredAnnotationBeanPostProcessor
?CommonAnnotationBeanPostProcessor
是处理q些注释元数据的处理器。但是直接在 Spring 配置文g中定义这?Bean 昑־比较W拙。Spring 为我们提供了(jin)一U方便的注册q些 BeanPostProcessor
的方式,q就?<context:annotation-config/>。请看下面的配置Q?/p>
清单 19. 调整 beans.xml 配置文g
<?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: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"> <context:annotation-config/> <bean id="boss" class="com.baobaotao.Boss"/> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" U旗 CA72"/> <property name="price" value="2000"/> </bean> </beans> |
<context:annotationconfig/> 隐式地?Spring 容器注册 AutowiredAnnotationBeanPostProcessor
?code>CommonAnnotationBeanPostProcessor?code>PersistenceAnnotationBeanPostProcessor 以及(qing) equiredAnnotationBeanPostProcessor
q?4 ?BeanPostProcessor?/p>
在配|文件中使用 context 命名I间之前Q必d <beans> 元素中声?context 命名I间?/p>
![]() ![]() |
![]()
|
虽然我们可以通过 @Autowired
?@Resource
?Bean cM使用自动注入功能Q但?Bean q是?XML 文g中通过 <bean> q行定义 —?也就是说Q在 XML 配置文g中定?BeanQ通过 @Autowired
?@Resource
?Bean 的成员变量、方法入参或构造函数入参提供自动注入的功能。能否也通过注释定义 BeanQ从 XML 配置文g中完全移?Bean 定义的配|呢Q答案是肯定的,我们通过 Spring 2.5 提供?@Component
注释可以达到这个目标了(jin)?/p>
下面Q我们完全用注释定?Bean q完?Bean 之间装配Q?/p>
清单 20. 使用 @Component 注释?Car.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Car { … } |
仅需要在cd义处Q?@Component
注释可以将一个类定义?Spring 容器中的 Bean。下面的代码?Office
定义Z?BeanQ?/p>
清单 21. 使用 @Component 注释?Office.java
package com.baobaotao; import org.springframework.stereotype.Component; @Component public class Office { private String officeNo = "001"; … } |
q样Q我们就可以?Boss cM通过 @Autowired
注入前面定义?Car
?Office Bean
?jin)?/p>
清单 22. 使用 @Component 注释?Boss.java
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component("boss") public class Boss { @Autowired private Car car; @Autowired private Office office; … } |
@Component
有一个可选的入参Q用于指?Bean 的名Uͼ?Boss 中,我们将 Bean 名称定义?#8220;boss
”。一般情况下QBean 都是 singleton 的,需要注?Bean 的地方仅需要通过 byType {略可以自动注入了(jin)Q所以大可不必指?Bean 的名U?/p>
在?@Component
注释后,Spring 容器必须启用cL描机制以启用注释驱动 Bean 定义和注释驱?Bean 自动注入的策略。Spring 2.5 ?context 命名I间q行?jin)扩展,提供了(jin)这一功能Q请看下面的配置Q?/p>
清单 23. 化版?beans.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: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"> <context:component-scan base-package="com.baobaotao"/> </beans> |
q里Q所有通过 <bean> 元素定义 Bean 的配|内容已l被U除Q仅需要添加一?<context:component-scan/> 配置px(chng)有问题(sh)(jin)——Spring XML 配置文g得到?jin)极致的化(当然配置元数据还是需要的Q只不过以注释Ş式存在Ş?jin)?j)?lt;context:component-scan/> ?base-package 属性指定了(jin)需要扫描的cdQ类包及(qing)光归子包中所有的c都?x)被处理?/p>
<context:component-scan/> q允许定义过滤器基包下的某些类U_或排除。Spring 支持以下 4 U类型的qo(h)方式Q通过下表说明Q?/p>
?1. 扫描qo(h)方式
qo(h)器类?/th> | 说明 |
---|---|
注释 | 假如 com.baobaotao.SomeAnnotation 是一个注释类Q我们可以将使用该注释的c过滤出来?/td> |
cd指定 | 通过全限定类名进行过滤,如?zhn)可以指定?com.baobaotao.Boss U_扫描Q而将 com.baobaotao.Car 排除在外?/td> |
正则表达?/td> | 通过正则表达式定义过滤的c,如下所C:(x) com\.baobaotao\.Default.* |
AspectJ 表达?/td> | 通过 AspectJ 表达式定义过滤的c,如下所C:(x) com. baobaotao..*Service+ |
下面是一个简单的例子Q?/p>
<context:component-scan base-package="com.baobaotao"> <context:include-filter type="regex" expression="com\.baobaotao\.service\..*"/> <context:exclude-filter type="aspectj" expression="com.baobaotao.util..*"/> </context:component-scan> |
值得注意的是 <context:component-scan/> 配置不但启用了(jin)对类包进行扫描以实施注释驱动 Bean 定义的功能,同时q启用了(jin)注释驱动自动注入的功能(卌隐式地在内部注册?AutowiredAnnotationBeanPostProcessor
?CommonAnnotationBeanPostProcessor
Q,因此当?<context:component-scan/> 后,可以将 <context:annotation-config/> U除?jin)?/p>
默认情况下通过 @Component
定义?Bean 都是 singleton 的,如果需要用其它作用范围的 BeanQ可以通过 @Scope
注释来达到目标,如以下代码所C:(x)
package com.baobaotao; import org.springframework.context.annotation.Scope; … @Scope("prototype") @Component("boss") public class Boss { … } |
q样Q当?Spring 容器中获?boss
Bean Ӟ每次q回的都是新的实例了(jin)?/p>
![]() ![]() |
![]()
|
Spring 2.5 中除?jin)提?@Component
注释外,q定义了(jin)几个拥有Ҏ(gu)语义的注释,它们分别是:(x)@Repository
?code>@Service ?@Controller
。在目前?Spring 版本中,q?3 个注释和 @Component
是等效的Q但是从注释cȝ命名上,很容易看?gu)?3 个注释分别和持久层、业务层和控制层QWeb 层)(j)相对应。虽然目前这 3 个注释和 @Component
相比没有什么新意,?Spring 在以后的版本中为它们添加特D的功能。所以,如果 Web 应用E序采用?jin)经典的三层分层l构的话Q最好在持久层、业务层和控制层分别采用 @Repository
?code>@Service ?@Controller
对分层中的类q行注释Q而用 @Component
寚w些比较中立的c进行注释?/p>
![]() ![]() |
![]()
|
是否有了(jin)q些 IOC 注释Q我们就可以完全摒除原来 XML 配置的方式呢Q答案是否定的。有以下几点原因Q?/p>
JdbcTemplate
?code>SessionFactoryBean {)(j)Q注释配|将无法实施Q此?XML 配置是唯一可用的方式?
@Transaction
事务注释Q?aop/tx 命名I间的事务配|更加灵zd单?所以在实现应用中,我们往往需要同时用注释配|和 XML 配置Q对于类U别且不?x)发生变动的配置可以优先考虑注释配置Q而对于那些第三方cM?qing)容易发生调整的配置则应优先考虑使用 XML 配置。Spring ?x)在具体实?Bean 创徏?Bean 注入之前这两种配置方式的元信息融合在一赗?/p>
![]() ![]() |
![]()
|
Spring ?2.1 以后Ҏ(gu)释配|提供了(jin)强力的支持,注释配置功能成ؓ(f) Spring 2.5 的最大的亮点之一。合理地使用 Spring 2.5 的注释配|,可以有效减少配置的工作量Q提高程序的内聚性。但是这q不意味着传统 XML 配置走向消亡,在第三方c?Bean 的配|,以及(qing)那些诸如数据源、缓存池、持久层操作模板cR事务管理等内容的配|上QXML 配置依然拥有不可替代的地位?/p>