Spring框架从创建伊始就致力于为复杂问题提供强大的、非侵入性的解决方案。Spring 2.0当中为缩减XML配置文件数量引入定制命名空间功能,从此它便深深植根于核心Spring框架(aop、context、jee、jms、 lang、tx和util命名空间)、Spring Portfolio项目(例如Spring Security)和非Spring项目中(例如CXF)。
Spring 2.5推出了一整套注解,作为基于XML的配置的替换方案。注解可用于Spring管理对象的自动发现、依赖注入、生命周期方法、Web层配置和单元/集成测试。
探索Spring 2.5中引入的注解技术系列文章由三部分组成,本文是其中的第二篇,它主要讲述了Web层中的注解支持。最后一篇文章将着重介绍可用于集成和测试的其它特性。
这个系列文章的第一部分论述了Java注解(annotation)是如何代替XML来配置Spring管理对象和依赖注入的。我们再用一个例子回顾一下:
@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
      this.clinic = clinic;
    }
    ...mvc实例
@Controller表明ClinicController是Web层组件,@Autowired请求一个被依赖注入的Clinic实例。这个例子只需要少量的XML语句就能使容器识别两个注解,并限定组件的扫描范围:
<context:component-scan base-package="org.springframework.samples.petclinic"/>
这对Web层可谓是个福音,因为在这层Spring的XML配置文件已日益臃肿,甚至可能还不如层下的配置来得有用。控制器掌握着许多属性,例如视 图名称、表单对象名称和验证器类型,这些多是关乎配置的,甚少关于依赖注入的。通过bean定义继承,或者避免配置变化不是很频繁的属性,也可以有效的管 理类似的配置。不过以我的经验,很多开发人员都不会这
样做,结果就是XML文件总比实际需要的要庞大。不过 @Controller和@Autowired对Web层的配置会产生积极的作用。
在系列文章的第二部分我们将继续讨论这个问题,并浏览Spring 2.5在Web层的注解技术。这些注解被非正式的称为@MVC,它涉及到了Spring MVC和Spring Porlet MVC,实际上本文讨论的大部分功能都可以应用在这两个框架上。
从Controller到@Controller
与第一部分讨论的注解相比,@MVC已不只是作为配置的一种替换方案这样简单了,考虑下面这个著名的Spring MVC控制器签名:
public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
}
所有的Spring MVC控制器要么直接实现Controller接口,要么就得扩展类似AbstractControlle
r、 SimpleFormController、 MultiActionController或AbstractWizardFormController这样的基类实现。正是Controller接口 允许Spring MVC的DispatcherServlet把所有上述对象都看作是“处理器(handlers)”,并在一个名为 SimpleControllerHandlerAdapter的适配器的帮助下调用它们。
@MVC从三个重要的方面改变了这个程序设计模型:
1. 不需要任何接口或者基类。
2. 允许有任意数量的请求处理方法。
3. 在方法签名上具有高度的灵活性。
考虑到以上三个要点,就可以说很公平的说@MVC不仅仅是个替换方案了,它将会是Spring MVC的控制器技术演变过程中下一个重要步骤。
DispatcherServlet在名为AnnotationMethodHandlerAdapter的适配器帮助下调用被注解的控制器。正是这个适配器做了大量工作支持我们此后将会讨论的注解,同时也是它有效的取代了对于控制器基类的需求。
@RequestMapping简介
我们还是从一个类似于传统的Spring MVC Controller控制器开始:
@Controller
public class AccountsController {

    private AccountRepository accountRepository;

    @Autowired
    public AccountsController(AccountRepository accountRepository) {
      this.accountRepository = accountRepository;
    }

    @RequestMapping("/accounts/show")
    public ModelAndView show(HttpServletRequest request,
                            HttpServletResponse response) throws Exception {
      String number = StringParameter(request, "number");
      ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
      mav.addObject("account", accountRepository.findAccount(number));
      return mav;
    }
}
此处与以往的不同在于,这个控制器并没有扩展Controller接口,并且它用@RequestMapping注解指明show()是映射到 URI路径 “/accounts/show”的请求处理方法。除此以外,其余代码都是一个典型的Spring MVC控制器应有的内容。
在将上述的方法完全转化到@MVC后,我们会再回过头来看@RequestMapping,但是在此之前还有一点需要提请注意,上面的请求映射URI也可匹配带有任意扩展名的URI路径,例如:
/accounts/show.htm
/
accounts/show.xls
/accounts/show.pdf
...
灵活的请求处理方法签名
我们曾经承诺过要提供灵活的方法签名,现在来看一下成果。输入的参数中移除了响应对象,增加了一个代表模型的Map;返回的不再是ModelAndView,而是一个字符串,指明呈现响应时要用的视图名字:
@RequestMapping("/accounts/show")
public String show(HttpServletRequest request, Map<String, Object> model)
throws Exception {
    String number = StringParameter(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:
@RequestMapping("/accounts/show")
public void show(HttpServletRequest request, Map<String, Object> model) throws Exception {
    String number = StringParameter(request, "number");
    model.put("account", accountRepository.findAccount(number));
}
对于给定的请求处理方法和“/accounts/show”的请求映射,我们可以期望DispatcherServlet能够获得“accounts/show”的默认视图名称,当它与如下适当的视图解析器结合共同作用时,会产生与前面指明返回视图名同样的结果:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>
强烈推荐视图名称依赖惯例的方式,因为这样可以从控制器代码中消除硬编码的视图名称。如果你想定制 DispatcherServlet获取默认视图名的方式,就在servlet上下文环境中配置一个你自己的 RequestToViewNameTranslator实现,并为其bean id赋名为“viewNameTranslator”。