现时对于一些类型的配置数据有一个趋势,就是偏爱注解方式而不是XML文件。为了方便实现,Spring现在(从2.5开始)提供了使用注解配置MVC框架下的组件的支持。
Spring 2.5为MVC控制器引入了一种基于注解的编程模型,在其中使用诸如@RequestMapping、@RequestParam、@ModelAttribute,等等。
这种注解支持在Servlet MVC和Portlet MVC中均可使用。通过这种方式实现的控制器不必由特定的基类继承而来,或者实现特定的接口。
更进一步的,它们通常并不直接依赖于Servlet或Portlet API,虽然如果需要,它们可以方便的访问Servlet或Portlet的功能。
Spring发行版本附带了PetClinic示例,它是一个在简单的表单处理的上下文中,
利用了本节中说明的注解支持的Web应用程序。
可以在“samples/petclinic”目录中找到PetClinic应用程序。
另外一个建立在基于注解的Web MVC上的示例应用程序,请见imagedb。
这个示例集中在无状态的multi-action控制器,包括多段文件上传的处理。
可以在“samples/imagedb”目录找到imagedb应用程序。
下面的章节记录了这些注解以及通常如何使用它们。
只有对应的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 class="org.springframework.web.servlet.mvc.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.AnnotationMethodHandlerAdapter"/>
... (controller bean definitions) ...
</beans>
如果你想要自定义映射策略,显式的定义一个DefaultAnnotationHandlerMapping和/或AnnotationMethodHandlerAdapter也有实际意义。
例如,指定一个自定义的PathMatcher或者WebBindingInitializer(见下面)。
注解@Controller指明一个特定的类承担控制器的职责,
而没有扩展任何控制器基类或者引用Servlet API的必要。当然,如果需要还是可以引用特定Servlet功能。
注解@Controller的基本目标是担任所注解的类的原型的角色,指明它的职责。
Dispatcher将会在这样被注解的类中扫描映射的方法,探测注解@RequestMapping(见下一节)。
所注解的控制器bean可以被显式定义,这个过程是在dispatcher的上下文中使用一个标准的Spring bean定义完成的。
然而,@Controller原型也允许自动探测,就像Spring 2.5对探测组件的类以及为它们自动注册bean定义的普遍支持一样。
要实现对这样的所注解的控制器的自动探测,必须要向配置中加入组件扫描的部分。 通过使用在下面的XML片段中所展示出的spring-context schema,这很容易实现:
<?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">
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>
...
</beans>
注解@RequestMapping被用于映射如“/editPet.do”这样的URL到一个完整的类或者一个特定的处理方法。
典型的,顶层的注解映射一个特定的请求路径(或者路径模式)到一个表单控制器,另外的方法一级的注解可以缩小这个主要映射的范围,包括对于一个特定的HTTP请求方法(“GET/POST”)或者特定的HTTP请求参数。
@RequestMapping在类型一级也可以被用于Controller接口的普通实现。
在这种情况下,请求处理的代码会遵循传统的handleRequest模样,而控制器的映射将会通过一个@RequestMapping注解体现。
这对于预先构建的Controller基类,诸如SimpleFormController,也一样有效。
在下面的讨论中,我们将会关注基于通过注解实现的处理方法的控制器。
下面是一个使用了这种注解的表单控制器的例子,它选自PetClinic:
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { private final Clinic clinic; @Autowired public EditPetForm(Clinic clinic) { this.clinic = clinic; } @ModelAttribute("types") public Collection<PetType> populatePetTypes() { return this.clinic.getPetTypes(); } @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } @RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } else { this.clinic.storePet(pet); status.setComplete(); return "redirect:owner.do?ownerId=" + pet.getOwner().getId(); } } }
对于一个传统的multi-action控制器,由于控制器会响应多个URL,URL就通常被直接映射到方法上。
下面是一个使用了@RequestMapping的multi-action控制器的例子,它选自PetClinic:
@Controller
public class ClinicController {
private final Clinic clinic;
@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
/**
* Custom handler for the welcome view.
* Note that this handler relies on the RequestToViewNameTranslator to
* determine the logical view name based on the request URL: "/welcome.do"
* -> "welcome".
*/
@RequestMapping("/welcome.do")
public void welcomeHandler() {
}
/**
* Custom handler for displaying vets.
* Note that this handler returns a plain {@link ModelMap} object instead of
* a ModelAndView, thus leveraging convention-based model attribute names.
* It relies on the RequestToViewNameTranslator to determine the logical
* view name based on the request URL: "/vets.do" -> "vets".
*
* @return a ModelMap with the model attributes for the view
*/
@RequestMapping("/vets.do")
public ModelMap vetsHandler() {
return new ModelMap(this.clinic.getVets());
}
/**
* Custom handler for displaying an owner.
* Note that this handler returns a plain {@link ModelMap} object instead of
* a ModelAndView, thus leveraging convention-based model attribute names.
* It relies on the RequestToViewNameTranslator to determine the logical
* view name based on the request URL: "/owner.do" -> "owner".
*
* @param ownerId the ID of the owner to display
* @return a ModelMap with the model attributes for the view
*/
@RequestMapping("/owner.do")
public ModelMap ownerHandler(@RequestParam("ownerId") int ownerId) {
return new ModelMap(this.clinic.loadOwner(ownerId));
}
}
使用@RequestMapping注解的处理器方法允许具有非常灵活的外观。
它们可以拥有下面类型的参数,在任意的顺序下(除非是对于验证结果,它需要紧跟在对应的命令对象后面,如果需要):
请求和/或响应对象(Servlet API或者Portlet API)。 可以选择任何特定的请求/响应类型,例如,ServletRequest/HttpServletRequest或者PortletRequest/ActionRequest/RenderRequest。 注意那个Portlet的例子里,一个被显式声明了的action/render参数被用于映射特定的请求类型到一个处理方法(在没有提供其他信息来区分action和render requests的情况下)。
会话对象(Servlet API或者Portlet API):不管是HttpSession还是PortletSession。
一个此种类型的参数将会保证出现一个对应的会话。这样就造成,这样一个参数永远也不可以是null。
注意会话访问可以并不是线程安全的,特别是在Servlet环境中:如果允许多个请求同时访问一个会话,就考虑把AnnotationMethodHandlerAdapter的“synchronizeOnSession”旗标置为“true”
org.springframework.web.context.request.WebRequest或org.springframework.web.context.request.NativeWebRequest。
允许像访问请求/会话属性一样的访问一般的请求参数,而不是锁定在原生的Servlet/Portlet API上。
java.util.Locale用于当前请求区域属性(由可用的最接近的区域属性解析器决定,也就是,
在Servlet环境中配置好的LocaleResolver以及在Portlet环境中的portal locale)。
java.io.InputStream/java.io.Reader用于访问请求的内容。
这将是Servlet/Portlet API暴露出的天然的InputStream/Reader。
java.io.OutputStream/java.io.Writer用于生成响应的内容。
这将是Servlet/Portlet API暴露出的天然的OutputStream/Writer。
以@RequestParam注解的参数用于访问特定的Servlet/Portlet请求参数。
参数的值将被转换为声明的方法参数类型。
java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap用于充实将被暴露到Web视图的隐含模型。
绑定参数到的命令/表单对象:带有自定义的类型转换的bean属性或者域,依赖于@InitBinder方法和/或HandlerAdapter配置——参见AnnotationMethodHandlerAdapter的“webBindingInitializer”属性。
这样的命令对象,包括它们的验证结果,将会暴露为模型属性,默认的会在属性注解中使用非限定的命令类名(例如,对于类型“mypackage.OrderAddress”使用“orderAddress”)。
为声明一个特定的模型属性名称指定一个参数级别的ModelAttribute注解。
org.springframework.validation.Errors/org.springframework.validation.BindingResult验证结果用于前面的一个命令/表单对象(前面紧接的参数)。
org.springframework.web.bind.support.SessionStatus状态处理用于把表单处理过程标记为已完成(触发会话属性的清理,这些会话属性是在句柄类型级别由@SessionAttributes注解指示出的)。
@RequestParam注解是用于在控制器中绑定请求参数到方法参数。
下面取自PetClinic实例程序的代码片段说明了这种用法:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
使用这个注解的参数默认是必需的,但是可以把@RequestParam的required属性置为false从而让这个参数可选(例如,@RequestParam(value="id", required="false"))。
@ModelAttribute在控制器中有两种使用场景。
当作为一个方法参数时,@ModelAttribute用于映射一个模型属性到特定的注解的方法参数(见下面的processSubmit()方法)。
这是控制器获得持有表单数据的对象引用的方法。另外,这个参数也可以被声明为特定类型的表单支持对象,而不是一般的java.lang.Object,这就增加了类型安全性。
@ModelAttribute也用于在方法级别为模型提供引用数据(见下面的populatePetTypes()方法)。
在这种用法中,方法编写可以包含与上面描述的@RequestMapping注解相同的类型。
注意:使用@ModelAttribute注解的方法将会在选定的使用@RequestMapping注解的方法之前执行。
它们有效的使用特定的属性预先填充隐含的模型,这些属性常常来自一个数据库。
这样一个属性也就可以通过在选定的方法中使用@ModelAttribute注解的句柄方法参数来访问了,潜在的可以应用绑定和验证。
下面的代码片段展示了此注解的这两种用法:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@ModelAttribute("types")
public Collection<PetType> populatePetTypes() {
return this.clinic.getPetTypes();
}
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result,
SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "petForm";
}
else {
this.clinic.storePet(pet);
status.setComplete();
return "redirect:owner.do?ownerId=" + pet.getOwner().getId();
}
}
}
类型级别的@SessionAttributes注解使用一个特定的句柄声明会话属性。
这通常会列出模型属性的名称,这些属性应被透明的保存在会话或者对话存储中,用于在后续的请求之间作为表单支持beans。
下面的代码片段展示了此注解的这种用法:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
为了通过Spring的WebDataBinder使用PropertyEditors等自定义请求参数绑定,可以或者使用@InitBinder——在控制器之内的注解的方法,
或者通过提供一个定制的WebBindingInitializer把配置具体化。
使用@InitBinder注解控制器方法,可以在控制器类内部直接配置Web数据绑定。
@InitBinder指定初始化WebDataBinder的方法,
后者被用于填充注解的句柄方法的命令和表单对象参数。
这个init-binder方法支持@RequestMapping支持的全部参数,除了命令/表单对象和对应的验证结果对象。
Init-binder方法必须没有返回值。因此,它们常被声明为void。
典型的参数,包括 WebDataBinder以及WebRequest或者java.util.Locale,允许代码注册上下文特定的编辑器。
下面的例子说明了@InitBinder的用法,为所有的java.util.Date表单属性配置一个CustomDateEditor。
@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));
}
// ...
}
为了外化数据绑定初始化的过程,可以提供一个WebBindingInitializer接口的自定义实现。
通过为一个AnnotationMethodHandlerAdapter提供一个定制的bean配置可以使它启用,这样就覆盖了默认配置。
下面取自PetClinic应用的例子展示了一个使用WebBindingInitializer接口的自定义实现的配置——org.springframework.samples.petclinic.web.ClinicBindingInitializer,
完成多个PetClinic控制器都需要的PropertyEditors的配置。
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="cacheSeconds" value="0" />
<property name="webBindingInitializer">
<bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
</property>
</bean>