SpringMVC注解

先谈SpringMVC的访问过程


SpringMVC访问流程

  1. 一个SpringMVC的请求到来时,会先由DispatcherServlet进行拦截.
  2. DispatcherServlet调用Handler Mapping进行Controller选择,如post转发给Controller的method,静态资源直接返回等等.
  3. 将请求参数传给Controller,Controller对数据进行处理.
  4. Controller返回一个模式数据和逻辑上的视图名称到request中,并交给DispatcherServlet进一步处理.
  5. DispatcherServlet调用ViewResolver对返回的视图名称进行处理,处理结果为一个View(类似Struts2ResultType),可能为JSP或其他类型的视图.
  6. Dispatcher将模型数据传递给View,如果View为JSP类型,则是利用request,将模型数据交由tomcat进行数据渲染,其他类型可能直接由Spring渲染.
  7. 将渲染结果返回即可.

使用注解进行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,最终这些数据可以被用来解析页面数据。
  • 返回数据模型与视图名
    1. 通过传递一个Model类型参数,将Controller的计算结果保存到该参数中,实际是保存到了一个Model.(实际会传进一个ExtendedModelMap对象引用,该对象的所有key-value,都将被保存到一个ModelAndView中)
    2. 通过传递一个Map类型参数,将Controller的计算结果保存到该参数中,实际是保存到了ServletRequest对象的Attribute属性中.
    3. 直接返回对象.
    4. 返回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>
  • 自定义类型转换,有三种方式可以自定义类型转换,查找顺序由上到下.
    1. Controller范围的属性编辑器
    2. 转换器Converter/Formater
    3. 全局范围的属性编辑器
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的配置
  • 具体配置如下
    1. 使用@Beans配置一个StandardServletMultipartResolver实例.
    2. 对dispatchServlet进行配置,通过AbstractAnnotationConfigDispatcherServletInitializer#customizeRegistration,对dispatch servlet设置一个multipart config即可.
    3. 在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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值