component-scanbase-package="org.springframework.samples.petclinic"/>
这对Web层可谓是个福音,因为在这层Spring的XML配置文件已日益臃肿,甚至可能还不如层下的配置来得有用。
控制器掌握着许多属性,例如视图名称、表单对象名称和验证器类型,这些多是关乎配置的,甚少关于依赖注入的。
通过bean定义继承,或者避免配置变化不是很频繁的属性,也可以有效的管理类似的配置。
不过以我的经验,很多开发人员都不会这样做,结果就是XML文件总比实际需要的要庞大。
不过@Controller和@Autowired对Web层的配置会产生积极的作用。
在系列文章的第二部分我们将继续讨论这个问题,并浏览Spring2.5在Web层的注解技术。
这些注解被非正式的称为@MVC,它涉及到了SpringMVC和SpringPorletMVC,实际上本文讨论的大部分功能都可以应用在这两个框架上。
从Controller到@Controller
与第一部分讨论的注解相比,@MVC已不只是作为配置的一种替换方案这样简单了,考虑下面这个著名的SpringMVC控制器签名:
Java代码
1.public interface Controller {
2. ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
3.response) throws Exception;
4. }
publicinterfaceController{
ModelAndViewhandleRequest(HttpServletRequestrequest,HttpServletResponse
response)throwsException;
}
所有的SpringMVC控制器要么直接实现Controller接口,要么就得扩展类似AbstractController、SimpleFormController、MultiActionController或AbstractWizardFormController这样的基类实现。
正是Controller接口允许SpringMVC的DispatcherServlet把所有上述对象都看作是“处理器(handlers)”,并在一个名为SimpleControllerHandlerAdapter的适配器的帮助下调用它们。
@MVC从三个重要的方面改变了这个程序设计模型:
1.不需要任何接口或者基类。
2.允许有任意数量的请求处理方法。
3.在方法签名上具有高度的灵活性。
考虑到以上三个要点,就可以说很公平的说@MVC不仅仅是个替换方案了,它将会是SpringMVC的控制器技术演变过程中下一个重要步骤。
DispatcherServlet在名为AnnotationMethodHandlerAdapter的适配器帮助下调用被注解的控制器。
正是这个适配器做了大量工作支持我们此后将会讨论的注解,同时也是它有效的取代了对于控制器基类的需求。
@RequestMapping简介
我们还是从一个类似于传统的SpringMVCController控制器开始:
Java代码
1.@Controller
2. public class AccountsController {
3.
4. private AccountRepository accountRepository;
5.
6. @Autowired
7. public AccountsController(AccountRepository accountRepository) {
8. this.accountRepository = accountRepository;
9. }
10.
11. @RequestMapping("/accounts/show")
12. public ModelAndView show(HttpServletRequest request,
13. HttpServletResponse response) throws Exception {
14. String number = ServletRequestUtils.getStringParameter(request, "number");
15. ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
16. mav.addObject("account", accountRepository.findAccount(number));
17. return mav;
18. }
@Controller
publicclassAccountsController{
privateAccountRepositoryaccountRepository;
@Autowired
publicAccountsController(AccountRepositoryaccountRepository){
this.accountRepository=accountRepository;
}
@RequestMapping("/accounts/show")
publicModelAndViewshow(HttpServletRequestrequest,
HttpServletResponseresponse)throwsException{
Stringnumber=ServletRequestUtils.getStringParameter(request,"number");
ModelAndViewmav=newModelAndView("/WEB-INF/views/accounts/show.jsp");
mav.addObject("account",accountRepository.findAccount(number));
returnmav;
}
}
此处与以往的不同在于,这个控制器并没有扩展Controller接口,并且它用@RequestMapping注解指明show()是映射到URI路径“/accounts/show”的请求处理方法。
除此以外,其余代码都是一个典型的SpringMVC控制器应有的内容。
在将上述的方法完全转化到@MVC后,我们会再回过头来看@RequestMapping,但是在此之前还有一点需要提请注意,上面的请求映射URI也可匹配带有任意扩展名的URI路径,例如:
Java代码
1./accounts/show.htm
2. /accounts/show.xls
3. /accounts/show.pdf
4. ...
/accounts/show.htm
/accounts/show.xls
/accounts/show.pdf
...
灵活的请求处理方法签名
我们曾经承诺过要提供灵活的方法签名,现在来看一下成果。
输入的参数中移除了响应对象,增加了一个代表模型的Map;返回的不再是ModelAndView,而是一个字符串,指明呈现响应时要用的视图名字:
Java代码
1.@RequestMapping("/accounts/show")
2. public String show(HttpServletRequest request, Map model)
3. throws Exception {
4. String number = ServletRequestUtils.getStringParameter(request, "number");
5. model.put("account", accountRepository.findAccount(number));
6. return "/WEB-INF/views/accounts/show.jsp";
7. }
@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页面。
@MVC可以接受多种类型的输入参数,例如HttpServletRequest/HttpServletResponse、HttpSession、Locale、InputStream、OutputStream、File[]等等,它们的顺序不受任何限制;同样它也允许多种返回类型,例如ModelAndView、Map、String,或者什么都不返回。
你可以查看@RequestMapping的JavaDoc以了解它支持的所有输入和返回参数类型。
有种令人感兴趣的情形是当方法没有指定视图时(例如返回类型为void)会有什么事情发生,按照惯例DispatcherServlet要再使用请求URI的路径信息,不过要移去前面的斜杠和扩展名。
让我们把返回类型改为void:
Java代码
1.@RequestMapping("/accounts/show")
2. public void show(HttpServletRequest request, Map model) throws Exception {
3. String number = ServletRequestUtils.getStringParameter(request, "number");
4. model.put("account", accountRepository.findAccount(number));
5. }
@RequestMapping("/accounts/show")
publicvoidshow(HttpServletRequestrequest,Mapmodel)throwsException{
Stringnumber=ServletRequestUtils.getStringParameter(request,"number");
model.put("account",accountRepository.findAccount(number));
}
对于给定的请求处理方法和“/accounts/show”的请求映射,我们可以期望DispatcherServlet能够获得“accounts/show”的默认视图名称,当它与如下适当的视图解析器结合共同作用时,会产生与前面指明返回视图名同样的结果:
Java代码
1.
2.
3.
4.
强烈推荐视图名称依赖惯例的方式,因为这样可以从控制器代码中消除硬编码的视图名称。
如果你想定制DispatcherServlet获取默认视图名的方式,就在servlet上下文环境中配置一个你自己的RequestToViewNameTranslator实现,并为其beanid赋名为“viewNameTranslator”。
用@RequestParam提取和解析参数
@MVC另外一个特性是其提取和解析请求参数的能力。
让我们继续重构上面的方法,并在其中添加@RequestParam注解:
Java代码
1.@RequestMapping("/accounts/show")
2. public void show(@RequestParam("number") String number, Map model) {
3. model.put("account", accountRepository.findAccount(number));
4. }
@RequestMapping("/accounts/show")
publicvoidshow(@RequestParam("number")Stringnumber,Mapmodel){
model.put("account",accountRepository.findAccount(number));
}
这里@RequestParam注解可以用来提取名为“number”的String类型的参数,并将之作为输入参数传入。
@RequestParam支持类型转换,还有必需和可选参数。
类型转换目前支持所有的基本Java类型,你可通过定制的PropertyEditors来扩展它的范围。
下面是一些例子,其中包括了必需和可选参数:
Java代码
1.@RequestParam(value="number", required=false) String number
2. @RequestParam("id") Long id
3. @RequestParam("balance") double balance
4. @RequestParam double amount
@RequestParam(value="number",required=false)Stringnumber
@RequestParam("id")Longid
@RequestParam("balance")doublebalance
@RequestParamdoubleamount
注意,最后一个例子没有提供清晰的参数名。
当且仅当代码带调试符号编译时,结果会提取名为“amount”的参数,否则,将抛出IllegalStateException异常,因为当前的信息不足以从请求中提取参数。
由于这个原因,在编码时最好显式的指定参数名。
继续@RequestMapping的讨论
把@RequestMapping放在类级别上是合法的,这可令它与方法级别上的@RequestMapping注解协同工作,取得缩小选择范围的效果,下面是一些例子。
类级别:
Java代码
1.RequestMapping("/accounts/*")
2.
3.方法级别:
4.@RequestMapping(value="delete", method=RequestMethod.POST)
5.@RequestMapping(value="index", method=RequestMethod.GET, params="type=checking")
6.@RequestMapping
RequestMapping("/accounts/*")
方法级别:
@RequestMapping(value="delete",method=RequestMethod.POST)
@RequestMapping(value="index",method=RequestMethod.GET,params="type=checking")
@RequestMapping
第一个方法级的请求映射和类级别的映射结合,当HTTP方法是POST时与路径“/accounts/delete”匹配;第二个添加了一个要求,就是名为“type”的请求参数和其值“checking”都需要在请求中出现;第三个根本就没有指定路径,这个方法匹配所有的HTTP方法,如果有必要的话可以用它的方法名。
下面改写我们的方法,使它可以依靠方法名进行匹配,程序如下:
Java代码
1.@Controller
2. @RequestMapping("/accounts/*")
3. public class AccountsController {
4.
5. @RequestMapping(method=RequestMethod.GET)
6. public void show(@RequestParam("number") String number, Map model)
7. {
8. model.put("account", accountRepository.findAccount(number));
9. }
10. ...
@Controller
@RequestMapping("/accounts/*")
publicclassAccountsController{
@RequestMapping(method=RequestMethod.GET)
publicvoidshow(@RequestParam("number")Stringnumber,Mapmodel)
{
model.put("account",accountRepository.findAccount(number));
}
...
方法匹配的请求是“/accounts/show”,依据的是类级别的@RequestMapping指定的匹配路径“/accounts/*”和方法名“show”。
消除类级别的请求映射
Web层注解频遭诟病是有事实依据的,那就是嵌入源代码的URI路径。
这个问题很好矫正,URI路径和控制器类之间的匹配关系用XML配置文件去管理,只在方法级的映射中使用@RequestMapping注解。
我们将配置一个ControllerClassNameHandlerMapping,它使用依赖控制器类名字的惯例,将URI映射到控制器:
Java代码
1.