先谈SpringMVC的访问过程
- 一个SpringMVC的请求到来时,会先由DispatcherServlet进行拦截.
- DispatcherServlet调用Handler Mapping进行Controller选择,如post转发给Controller的method,静态资源直接返回等等.
- 将请求参数传给Controller,Controller对数据进行处理.
- Controller返回一个模式数据和逻辑上的视图名称到request中,并交给DispatcherServlet进一步处理.
- DispatcherServlet调用ViewResolver对返回的视图名称进行处理,处理结果为一个View(类似Struts2ResultType),可能为JSP或其他类型的视图.
- Dispatcher将模型数据传递给View,如果View为JSP类型,则是利用request,将模型数据交由tomcat进行数据渲染,其他类型可能直接由Spring渲染.
- 将渲染结果返回即可.
使用注解进行SpringMVC配置.
- 配置两个容器,分别是由ContextLoaderListener加载的父容器(RootConfig.class)以及由DispatcherServlet加载的子容器(WebConfig.class).
- RootConfig.java用于加载非web组件,无需使用@EnableWebMvc.WebConfig.java用于加载web组件,它是RootApplication的子容器,一般加载Controller,ViewResolver以及HandlerMapping.
- 最后要通过注解自动配置Servlet以及Listener等,必须实现WebApplicationInitializer接口,最便捷的方法是继承AbstractAnnotationConfigDispatcherServletInitializer.(tips:servlet3.0 会自动寻找当前环境下的ServletContainerInitializer接口的具体类,Spring提供了SpringServletContainerInitializer作为实现类,并在该类中转而寻找用户代码中实现了WebApplicationInitializer接口的具体类)
//初始化配置类
package com.conf;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* @author doggy
* Created on 2016-07-05.
*/
public class MVCInilizer extends AbstractAnnotationConfigDispatcherServletInitializer {
//可以在这里加载其他的Listener/Servlet/Filter.
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//一定要调用父类方法进行ContextLoaderListener与DispatcherServlet的加载
super.onStartup(servletContext);
//add my servlet here.
}
//在这里配置父容器,给ContextListener使用
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
//在这里配置子容器,给DispatchServlet使用
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
//这里配置的是DispatcherServlet的捕获路径,所有匹配该路径的访问
//都将被HandlerMapping进行映射处理到具体Controller的方法上.
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
//根容器的配置文件
@Configuration
@ComponentScan(basePackages = {"com"}, excludeFilters = {@ComponentScan.Filter(classes = {Controller.class})})
@EnableAspectJAutoProxy
public class RootConfig {
}
//子容器的配置
@Configuration
@EnableWebMvc
Enable Spring MVC
@ComponentScan("com.web")
//让它继承自WebMvcConfigurerAdapter可以处理映射配置
public class WebConfig extends WebMvcConfigurerAdapter {
//配置一个视图解析器,用于解析得到一个View(相当与Struts2的Result)
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
//实现父类接口,自动处理静态资源的映射
@Override
public void configureDefaultServletHandling( DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Controller的编写
- 使用@RequestMapping注解对应的Controller/方法
@Controller
@RequestMapping(path = {"base1","base2"})
public class AController {
//可以设置多个path,路径为${pageContext.request.contextPath}/base1[base2]/home1[home2]共四种组合
//method表示支持的请求方式,最常用为GET/POST
@RequestMapping(value = {"/home1","home2"},method = RequestMethod.GET)
public String goHome(){
System.out.println("invoked");
//返回视图名,由于只配置了一个ViewResolver,所以实际对应到/WEB-INF/AR.jsp
return "AR";
}
}
- 给Controller写单元测试,利用MockMvc使得断点测试更为方便
import com.web.AController;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* @author doggy
* Created on 2016-07-09.
*/
public class AControllerTest {
@Test
public void testGoHome() throws Exception{
AController aController = new AController();
//使用get进行测试
mockMvc.perform(MockMvcRequestBuilders.get("/base1/home1")).
andExpect(MockMvcResultMatchers.view().name("AR"));
}
}
- 接收请求数据,共三种模式
- 查询参数(Query Params):@RequestParam
- 路径变量(Path variables):@PathVariable
- 表单参数(Form params):使用POJO进行大量数据的传递.
//Query Param,用于简单参数传递
@RequestMapping("/getUser",method=RequestMethod.GET)
//uid必须与请求的参数名一致,@RequestParam属性有defaultValue,用于设置默认数据
//required设置参数是否必须,默认为true.
//如果required=true,defaultValue未设置且没有传递该参数,则会出错(400).
public String getUser(@RequestParam("uid") String id){
...
}
//@PathVariable
//使用${x}表示x是一个路径变量,使用@PathVariable进行数据映射
@RequestMapping("/user/{uid}")
public String getUser(@PathVariable("uid) String id){
...
}
//Form params,如果参数为一个POJO,则尝试对POJO设置参数,默认POJO的属性均可为空.
//请求链接为/user?name=ko&age=9,与Struts2最大区别是没有pojo前缀
//如果参数中有多个POJO具有同一个属性,则都将被设置.
@RequestMapping("/addUser")
public String getUser(User user){
...
}
class User{
private String name;
private int age;
//getter and setter.
}
//后台可以对Form params进行数据校验,校验写在POJO中,通过@Valid对参数校验,所有校验注解使用javax.validation.*标准注解.
//需要导入javax.validation包,以及一个validation实现,比如hibernate-validator
@RequestMapping("/addUser")
//errors:当请求数据出现校验错误时,spring将错误信息记录在errors中
//注意Errors必须紧跟@Valid出现,否则会有错误!!!!!!!!!!!!!
public String getUser(@Valid User user,Errors errors){
...
}
class User{
@NotNull
private String name;
@Max(100)
@Min(10)
private int age;
//getter and setter.
}
javax.validation的注解如下:
@AssertFalse,@AssertTrue,@DecimalMax,@DecimalMin,@Digits,@Future(表示一个未来时间)
@Max,@Min,@NotNull,@Null,@Past(表示过去的时间点),@Pattern(匹配给定的正则表达式)
@Size(字符串长度/集合大小)
- Controller方法中那些会被特殊处理的参数类型
1. Map/Model,如果参数为这两个类型,那么实际调用时会传入一个implicateModel,最终这些数据可以被用来解析页面数据。
- 返回数据模型与视图名
- 通过传递一个Model类型参数,将Controller的计算结果保存到该参数中,实际是保存到了一个Model.(实际会传进一个ExtendedModelMap对象引用,该对象的所有key-value,都将被保存到一个ModelAndView中)
- 通过传递一个Map类型参数,将Controller的计算结果保存到该参数中,实际是保存到了ServletRequest对象的Attribute属性中.
- 直接返回对象.
- 返回ModelAndView对象.
//1.Model
@RequestMapping("/")
public String getAllUser(Model model){
List<User> users = ...;
//保存模型数据到request中
model.addAttribute("userList",users);
//返回值为string,直接作为视图名进行解析.
return "viewName";
}
//2.Map,与Model并没有什么不同,不再重复.
//3.直接返回对象,个人认为该方法能力有限,且无法自定义属性名与视图名,不建议使用.
@RequestMapping("/")
//默认属性名由返回类型决定为userList,视图名与方法名一致,为getAllUser
public List<User> getAllUser(){
List<User> users = ...;
return users;
}
//4.使用ModelAndView作为返回值
public ModelAndView getAllUser(){
List<User> users = ...;
ModelAndView mav = new ModelAndView("viewName");
return mav.addObject("users",users);
}
//tips:这里对于参数可以使用@ModelAttribute,将参数设置到数据模型中,相当于将参数从param移动到attribute.
//user是一个Form param,spring将其属性利用请求参数进行设置,再调用request.setAttribute("user",user)添加到数据模型中.
public String addUser(@ModelAttribute("user") User user){
...
return "userMag";
}
上传列表/映射/集合数据以及自定义类型转换
- 上传列表List
@Controller
public class AController {
@RequestMapping(value = {"/home"},method = RequestMethod.POST)
public String goHome(UserList userList){
System.out.println(userList);
return "AR";
}
}
//List必须依附于一个Form对象,不能在Controller的参数中使用List
//该类必须为公有
public class UserList{
private List<User> users = new ArrayList<>();
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@Override
public String toString() {
return "UserList{" +
"users=" + users +
'}';
}
}
//必须为一个公有类
public class User{
private String name;
private int age;
public User(){}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//再看form表单
<form method="post" action="${pageContext.request.contextPath}/home" >
<input type="text" name="users[0].name">
<input type="text" name="users[0].age">
<input type="text" name="users[1].name">
<input type="text" name="users[1].age">
<input type="submit">
</form>
- Set相较于List需要提供一个构造器,对Set中的每一个元素进行初始化,不建议使用.
- 上传映射Map
//Controller并没有什么变化,同样不能直接给方法传递一个Map类型参数,而是需要一个Form Param来传递.
@Controller
public class AController {
@RequestMapping(value = {"/home"},method = RequestMethod.POST)
public String goHome(UserMap userList){
System.out.println(userList);
return "AR";
}
}
//UserMap必须公有
public class UserMap{
private Map<String,User> users;
public Map<String, User> getUsers() {
return users;
}
public void setUsers(Map<String, User> users) {
this.users = users;
}
@Override
public String toString() {
return "UserList{" +
"users=" + users +
'}';
}
}
//User 必须公有,不再重复
//form表单如下
<form method="post" action="${pageContext.request.contextPath}/home" >
<input type="text" name="users['x'].name">
<input type="text" name="users['x'].age">
<input type="text" name="users['y'].name">
<input type="text" name="users['y'].age">
<input type="submit">
</form>
- 自定义类型转换,有三种方式可以自定义类型转换,查找顺序由上到下.
- Controller范围的属性编辑器
- 转换器Converter/Formater
- 全局范围的属性编辑器
1. Controller范围的属性编辑器
@Controller
public class AController {
@RequestMapping(value = {"/home"},method = RequestMethod.POST)
public String goHome(@RequestParam("time") Date time){
System.out.println(time);
return "AR";
}
//使用@InitBinder进行注册
@InitBinder
public void initBinder(WebDataBinder binder){
//注册自定义的编辑器
binder.registerCustomEditor(java.util.Date.class, new DatePropertyEditor());
}
}
//编写一个类实现PropertyEditor接口即可,最方便的是继承PropertyEditorSupport,并重写setAsText即可.
class DatePropertyEditor extends PropertyEditorSupport{
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date time = null;
try {
time = sdf.parse(text);
}catch (Exception e){
e.printStackTrace();
}
this.setValue(time);
}
}
2.通过实现Converter接口,并在WebConfig中重写addFormatters方法.
public class DateConverter<S,T> implements Converter<String,Date> {
@Override
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date time = null;
try {
time = sdf.parse(source);
}catch (Exception e){
e.printStackTrace();
}
return time;
}
}
//配置文件
@Configuration
@ComponentScan(basePackages = {"com.web"}, includeFilters = {@ComponentScan.Filter(classes = {Controller.class})})
@EnableAspectJAutoProxy
@EnableWebMvc
public class DispatcherServletApplicationConfigure extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//在这里添加转换器
@Override
public void addFormatters(FormatterRegistry registry) {
super.addFormatters(registry);
registry.addConverter(new DateConverter<String,Date>());
}
}
页面渲染
- ViewResolver与View
Spring的页面渲染主要与两个接口有关,分别时ViewResolver与View.
ViewResolver的作用类似于Struts2中根据method的返回值查找ResultType的过程,而View则是和ResultType一样一样的东西.
所以ViewResolver主要是解析视图名得到一个视图View,View则定义了怎么通过模型数据去渲染一个结果(可能为JSP/Excel/…)
以下是二者的接口:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
public interface View {
...
//渲染功能的实现,最重要!!!
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
- InternalResourceViewResolver是最常用的JSP视图解析器.
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".jsp");
//以下两行启用JSTL.
resolver.setExposeContextBeansAsAttributes(true);
resolver.setViewClass(JstlView.class);
return resolver;
}
- Spring的标签库
Spring的标签库分为核心标签库与表单标签库,其中form标签库最为常用.
1. form标签库(uri="http://www.springframework.org/tags/form" prefix="sf")
//其中commandName必须指定,若无则默认为command.
//在request中必须存在一个名为dc的属性,否则出错;也就是模型数据中必须存在dc属性,否则报错.
<sf:form method="post" commandName="dc">
//在存在dc属性的前提下,dc对象还必须有time属性,否则也报错.所以对于一些非form param数据只能通过el表达式获取.
//渲染之后id/name均为time,value为模型数据.
<sf:input path="time"/>
</sf:form>
//再看一个checkboxes标签
//其中的items必须是一个el表达式,里面的值可以是一个List,Set/Map
//通过判断List/Set/Map.keys的值来生成checkbox标签的value,显示的内容可能为Map.values
//如果value在hobbies中,则选中.
//items不可为空,所以必须在controller方法中,往模型中放入一个allHobbies.
<sf:checkboxes path="hobbies" items="${allHobbies}"/>
2. 略
- Tiles页面渲染,Apache Tiles提供了模板替换的功能,用于减少重复代码.
1. 需要配置两个bean
@Bean
public ViewResolver tilesViewResolver(){
TilesViewResolver tilesViewResolver = new TilesViewResolver();
return tilesViewResolver;
}
//用于加载Tiles的定义文件
@Bean
public TilesConfigurer tilesConfigurer(){
TilesConfigurer tilesConfigurer = new TilesConfigurer();
tilesConfigurer.setDefinitions(new String[]{"/WEB-INF/learn/tiles.xml"});
tilesConfigurer.setCheckRefresh(true);
return tilesConfigurer;
}
- Thymeleaf配置
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
@Bean
public TemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
@Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
文件上传
- 通过配置MultipartResolver接口的实现bean来实现,可选实现有CommonsMultipartResolver和StandardServletMultipartResolver,3.1及以上版本建议使用StandardServletMultipartResolver,本文只介绍StandardServletMultipartResolver的配置
- 具体配置如下
- 使用@Beans配置一个StandardServletMultipartResolver实例.
- 对dispatchServlet进行配置,通过AbstractAnnotationConfigDispatcherServletInitializer#customizeRegistration,对dispatch servlet设置一个multipart config即可.
- 在controller方法中,使用MultipartFile进行接收.
- 以下是代码
1.
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
2.在Initilizer中重写AbstractAnnotationConfigDispatcherServletInitializer#customizeRegistration
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
super.customizeRegistration(registration);
//配置文件路径,最大单个文件为2M,整个请求的大小最大为4M,可以使用内存进行缓存的空间为0byte.
MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/tmp/file",2*1024*1024,4*1024*1024,0);
registration.setMultipartConfig(multipartConfigElement);
}
3.接收,记得要有@RequestParam指明具体文件.
@RequestMapping("/upload")
public String upload(@RequestParam("f") MultipartFile multipartFile) throws IOException{
//写到/tmp/file/filename.type
multipartFile.transferTo(new File(multipartFile.getOriginalFilename()));
return "jsp/success";
}
一些有趣的内容 :-)
- Spring是如何处理Controller方法参数的呢?为什么参数中传递Model/Map参数就可以传递模型数据呢?为什么使用Errors参数必须在@Valid之后呢?
1.DispatcherServlet通过HandlerMapping进行url<->method的映射,然后再通过HandlerAdapter实际调用method. HandlerAdapter的调用,源码如下
//入口函数
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
//在这里调用HandlerMethodInvoker,解析传递参数,获得方法调用结果.
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
//对不同的结果类型进行解析,获得ModelAndView
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
//getModelAndView源码
@SuppressWarnings("unchecked")
public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,
ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatusAnn != null) {
HttpStatus responseStatus = responseStatusAnn.value();
String reason = responseStatusAnn.reason();
if (!StringUtils.hasText(reason)) {
webRequest.getResponse().setStatus(responseStatus.value());
}
else {
webRequest.getResponse().sendError(responseStatus.value(), reason);
}
// to be picked up by the RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus);
responseArgumentUsed = true;
}
// Invoke custom resolvers if present...
if (customModelAndViewResolvers != null) {
for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
ModelAndView mav = mavResolver.resolveModelAndView(
handlerMethod, handlerType, returnValue, implicitModel, webRequest);
if (mav != ModelAndViewResolver.UNRESOLVED) {
return mav;
}
}
}
if (returnValue instanceof HttpEntity) {
handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
return null;
}
else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
handleResponseBody(returnValue, webRequest);
return null;
}
else if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
else if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
}
else if (returnValue instanceof View) {
return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
}
else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {
addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
return new ModelAndView().addAllObjects(implicitModel);
}
else if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);
}
else if (returnValue instanceof String) {
return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
}
else if (returnValue == null) {
// Either returned null or was 'void' return.
if (this.responseArgumentUsed || webRequest.isNotModified()) {
return null;
}
else {
// Assuming view name translation...
return new ModelAndView().addAllObjects(implicitModel);
}
}
else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
// Assume a single model attribute...
addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
return new ModelAndView().addAllObjects(implicitModel);
}
else {
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
}
}
2.HandlerMethodInvoker是调用方法的处理类,可以看到对方法的调用以及实际参数的一些处理,以下为HandlerMethodInvoker的部分源码,可以看到对于Map/Model类型是特殊处理的.
//调用实际方法
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
boolean debug = logger.isDebugEnabled();
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
if (attrValue != null) {
implicitModel.addAttribute(attrName, attrValue);
}
}
for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
continue;
}
ReflectionUtils.makeAccessible(attributeMethodToInvoke);
Object attrValue = attributeMethodToInvoke.invoke(handler, args);
if ("".equals(attrName)) {
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
}
if (!implicitModel.containsAttribute(attrName)) {
implicitModel.addAttribute(attrName, attrValue);
}
}
//根据参数类型解析处理,并返回实际参数.
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
}
ReflectionUtils.makeAccessible(handlerMethodToInvoke);
return handlerMethodToInvoke.invoke(handler, args);
}
catch (IllegalStateException ex) {
// Internal assertion failed (e.g. invalid signature):
// throw exception with full handler method context...
throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
}
catch (InvocationTargetException ex) {
// User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
ReflectionUtils.rethrowException(ex.getTargetException());
return null;
}
}
//resolveHandlerArguments方法,根据参数类型,解析获取实际参数.
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Class<?>[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName = null;
String headerName = null;
boolean requestBodyFound = false;
String cookieName = null;
String pathVarName = null;
String attrName = null;
boolean required = false;
String defaultValue = null;
boolean validate = false;
Object[] validationHints = null;
int annotationsFound = 0;
Annotation[] paramAnns = methodParam.getParameterAnnotations();
for (Annotation paramAnn : paramAnns) {
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.name();
required = requestParam.required();
defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
annotationsFound++;
}
else if (RequestHeader.class.isInstance(paramAnn)) {
RequestHeader requestHeader = (RequestHeader) paramAnn;
headerName = requestHeader.name();
required = requestHeader.required();
defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
annotationsFound++;
}
else if (RequestBody.class.isInstance(paramAnn)) {
requestBodyFound = true;
annotationsFound++;
}
else if (CookieValue.class.isInstance(paramAnn)) {
CookieValue cookieValue = (CookieValue) paramAnn;
cookieName = cookieValue.name();
required = cookieValue.required();
defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
annotationsFound++;
}
else if (PathVariable.class.isInstance(paramAnn)) {
PathVariable pathVar = (PathVariable) paramAnn;
pathVarName = pathVar.value();
annotationsFound++;
}
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
annotationsFound++;
}
else if (Value.class.isInstance(paramAnn)) {
defaultValue = ((Value) paramAnn).value();
}
else {
Validated validatedAnn = AnnotationUtils.getAnnotation(paramAnn, Validated.class);
if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
validate = true;
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn));
validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
}
}
}
if (annotationsFound > 1) {
throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
"do not specify more than one such annotation on the same parameter: " + handlerMethod);
}
if (annotationsFound == 0) {
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
else {
Class<?> paramType = methodParam.getParameterType();
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
if (!paramType.isAssignableFrom(implicitModel.getClass())) {
throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
"Model or Map but is not assignable from the actual model. You may need to switch " +
"newer MVC infrastructure classes to use this argument.");
}
args[i] = implicitModel;
}
else if (SessionStatus.class.isAssignableFrom(paramType)) {
args[i] = this.sessionStatus;
}
else if (HttpEntity.class.isAssignableFrom(paramType)) {
args[i] = resolveHttpEntityRequest(methodParam, webRequest);
}
else if (Errors.class.isAssignableFrom(paramType)) {
throw new IllegalStateException("Errors/BindingResult argument declared " +
"without preceding model attribute. Check your handler method signature!");
}
else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
else {
attrName = "";
}
}
}
if (paramName != null) {
args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
}
else if (headerName != null) {
args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
}
else if (requestBodyFound) {
args[i] = resolveRequestBody(methodParam, webRequest, handler);
}
else if (cookieName != null) {
args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
}
else if (pathVarName != null) {
args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}
else if (attrName != null) {
WebDataBinder binder =
resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
}
return args;
}