zoukankan      html  css  js  c++  java
  • SpringMVC源码阅读:Controller中参数解析

    1.前言

    SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

    本文将通过源码(基于Spring4.3.7)分析,弄清楚Controller是如何匹配我们传入的参数,并定义简单的参数解析器

    2.源码分析

    demo源码在这里,回到DispatcherServlet的doDispatch方法,DispatchServlet分析见SpringMVC源码阅读:核心分发器DispatcherServlet

    doDispatch方法943行获取了HandlerAdapter,ctrl+h打开类继承图,找到RequestMappingHandlerAdapter,RequestMappingHandlerAdapter支持HandlerMethod的方法参数和返回类型,HandlerMethod是3.1版本引入的,为参数、返回值和注解提供便捷的封装

    在RequestMappingHandlerAdapter的invokeHandlerMethod方法中,设置ArgumentResolver和ReturnValueHandler

    798行和799行给RequestMappingHandlerAdapter定义的ArgumentResolver和ReturnValueHandler赋值,4.2版本以前在createRequstMapping方法,此方法在4.2已被删除

    827行ServletInvocableHandlerMethod调用invokeAndHandle方法,通过定义的HandlerMethodReturnValueHandler处理返回值,点开invokeAndHandle方法进入ServletInvocableHandlerMethod类

    116行处理通过HandlerMethodArgumentResolver来解析参数,132~133行使用注册过的HandlerMethodReturnValueHandler

    afterPropertiesSet方法实现了InitializingBean接口初始化了Handler和Resolver,简单地说,启动服务才会运行afterPropertiesSet

    517行设置ArgumentResolver,525行设置ReturnValueHandler

    点看getDefaultArgumentResolvers方法,看看它到底做了什么

    getDefaultArgumentResolvers方法把各种HandlerMethodArgumentResolver放入List并返回

    同理,getDefaultReturnValueHandlers方法把各种HandlerMethodReturnValueHandler放入List并返回

    现在在回到ServletInvocableHandlerMethod类,我们发现了returnValueHandlersHandlerMethodReturnValueHandlerComposite类型的,神秘的HandlerMethodReturnValueHandlerComposite是什么?

    查看类实现图,我们发现HandlerMethodReturnValueHandlerComposite继承HandlerMethodReturnValueHandler

    我们可以看到,HandlerMethodReturnValueHandlerComposite类里有HandlerMethodReturnValueHandler类型的list,做过树结构的园友们应该知道,这里用到了组合模式,即类包含自身对象组。但是呢,它的类名后面加上了Composite,不能严格意义上说是组合模式,可以说是组合模式的变种,因为HandlerMethodReturnValueHandler是个Interface,所以不是严格意义上的“组合模式”

    我们用同样的思路寻找与HandlerMethodArgumentResolver对应的Composite类,我在ServletInvocableHandlerMethod没有找到HandlerMethodArgumentResolverComposite,(在4.3版本之前可以在ServletInvocableHandlerMethod找到),不用担心,使用绝招

    快捷键ctrl+shift+r,用idea强大的全局搜索来找HandlerMethodArgumentResolverComposite的踪迹

    这里注意一下,全局搜索选择Scope,才可以在文件所有路径下搜索(包括Maven源码包)

    最后我们看到了HandlerMethodArgumentResolverComposite在InvocableHandlerMethod出现,这个类名觉得有些眼熟吧,它是ServletInvocableHandlerMethod的父类

    ServletInvocableHandlerMethod调用InvocableHandlerMethod的invokeForRequest方法中使用了HandlerMethodArgumentResolverComposite

    打开HandlerMethodArgumentResolverComposite,和HandlerMethodReturnValueHandlerComposite类似,使用组合模式的变种

    好了,参数解析基本流程完毕,我们现在来具体看看支持和参数相映射的注解的参数解析类,对着HandlerMethodArgumentResolver按ctrl+h

    可以看到庞大的类继承图,我们看支持@RequestBody的RequestResponseBodyMethodProcessor类

    可能会有园友好奇,为什么我知道RequestResponseBodyMethodProcessor类支持@RequestBody?

    一个简便的方法是直接看类名,开源项目Spring的代码质量非常高,它们的类名言简意赅,看类名大概就知道它是做什么的;类名如果看不出来,就点进去看注释,注释很规范、详细

    打开RequestResponseBodyMethodProcessor类

    支持带有@RequestBody的参数,支持带有@ResponseBody的返回值

    写一个方法进行测试

       @RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST)
        @ResponseBody
        public Employee testRb(@RequestBody Employee e) {
            return e;
        }

    http://localhost:8080/springmvcdemo/test/testRb,传入参数为{"age":1,"id":2},我用的Postman测试请求,直接浏览器地址栏输入,默认Get请求会报错,不嫌麻烦可以自己手写Ajax,参数类型设置成Json测试

    header写成application/json,请求类型写POST

    Body传入Json格式参数

    现在我们进入resolveArgument方法

     

    127行获取参数信息,128行调用readWithMessageConverters方法获取参数值

    131行创建WebDataBinder,用于校验数据格式是否正确

    点开128行readWithMessageConverters方法,看看它做什么

    148行获取请求信息,如头信息

    我们看到Content-Type正是我们在Postman中设置的"application/json"

    150行获取参数,调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法,父类方法用于从请求信息中读取方法参数值

    152行查看参数注解是否是@RequestBody

    继续深入,进入AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法

    167行从Headers取得Content-Type

    172~175行如果Content-Type为空,默认给我们Content-Type设置"application/octet-stream"

    185行获取Http请求方法

    191行用消息转换器读取请求体

    接下来,我们分析下常用的@RequestParam注解是如何处理参数的

    用这个测试方法

        @RequestMapping("/auth")
        public String auth(@RequestParam String username, HttpServletRequest req) {
            req.getSession().setAttribute("loginUser", username);
            return "redirect:/";
        }

    找到RequestParamMethodArgumentResolver类,该类的核心方法是resolveName方法

    158行获取请求信息

    159行获取MultipartHttpServletRequest请求信息,用于文件上传

    175行获取参数值

    打断点我发现,在RequestParamMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver中,resolveArgument方法会先执行。后续我们自定义的参数解析器主要就是重写resolveArgument方法

     

    97行获取参数名称

    103行调用resolveName方法获取参数值,该方法被AbstractNamedValueMethodArgumentResolver子类RequestParamMethodArgumentResolver实现,刚才我们已经分析过

    我再说下其他常用的HandlerArgumentResolver实现类,就不源码分析了,有时间我会补充上,园友可以自行打断掉调试查看之

    1.PathVariableMethodArgumentResolver

    支持带有@PathVariable注解的参数,用来获得请求url中的动态参数

    2.MatrixVariableMethodArgumentResolver

    支持带有@MatrixVariable注解的参数,顾名思义,矩阵变量,多个变量可以使用“;”分隔

    3.RequestParamMethodArgumentResolver

    支持带有@RequestParam注解的参数,也支持MultipartFile类型的参数,本文已分析

    4.RequestResponseBodyMethodProcessor

    支持带有@RequestBody、@ResponseBody注解的参数,本文已分析

    再看看常用的HandlerMethodReturnValueHandler

    1.ModelAndViewMethodReturnValueHandler

    返回ModelAndView,把view和model信息赋值给ModelAndViewContainer

    2.ViewMethodReturnValueHandler

    返回View

    3.HttpHeadersReturnValueHandler

    返回HttpHeaders

    4.SteamingResponseBodyReturnValueHandler

    返回ResponseEntity<StreamingResponseBody>

    3.实例

    
        @RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST)
        @ResponseBody
        public Employee testRb(@RequestBody Employee e) {
            return e;
        }
    
        @RequestMapping(value="/testCustomObj", produces={"application/xml; charset=UTF-8"},method = RequestMethod.GET)
        @ResponseBody
        public XmlActionResult<Employee> testCustomObj(@RequestParam(value = "id") int id,
                                                       @RequestParam(value = "name") String name) {
            XmlActionResult<Employee> actionResult = new XmlActionResult<Employee>();
            Employee e = new Employee();
            e.setId(id);
            e.setName(name);
            e.setAge(20);
            e.setDept(new Dept(2,"部门"));
            actionResult.setCode("200");
            actionResult.setMessage("Success with XML");
            actionResult.setData(e);
            return actionResult;
        }
    
        @RequestMapping(value = "/testCustomObjWithRp", produces={"application/json; charset=UTF-8"})
        @ResponseBody
        public Employee testCustomObjWithRp(Employee e) {
            return e;
        }
    
        @RequestMapping(value = "/testDate", produces={"application/json; charset=UTF-8"})
        @ResponseBody
        public Date testDate(Date date) {
            return date;
        }

    3.1 测试@RequestBody

    在Postman中输入请求http://localhost:8080/springmvcdemo/test/testRb

    发出请求,进入了RequestResponseBody的resolveArgument方法,参数我们可以看到

    源码分析参照   2.源码分析

    3.2 测试@RequestParam

    在浏览器中输入http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s

    返回结果如下,返回的是XML格式,(下一部分我再叙述MessageConverter部分的知识,我们这里只关注@RequestParam)

    输入请求后,进入了RequestParamMethodArgumentResolver的父类AbstracNamedValueMethodArgumentResolver的reloveArgument方法,因我们有两个@RequestParam,会进入reloveArgument两次

     

    3.3 测试无注解参数为自定义对象

    浏览器输入请求http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s,返回结果如下

    无注解我们怎么找到底是哪个HandlerMethodArgumentResolver实现类在处理呢?只要你认真看了第二部分源码分析,相信你可以轻松找到

    在HandlerMethodArgumentResolverComposite(HandlerMethodArgumentResolver的实现类)第117行getArgumentResolver方法打上断点,看看庐山真面目

    原来,是ServletModelAttributeMethodProcessor为我们处理了自定义对象

    3.4 测试参数为简单对象

    在浏览器输入请求http://localhost:8080/springmvcdemo/test/testDate?date=2018-01-30

    在当前Controller加入InitBinder,使参数规范化传递

        //自定义属性编辑器——日期
        @InitBinder
        public void initBinderDate(WebDataBinder binder) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
        }

    返回了一个Unix时间戳

    在HandlerMethodArgumentResolverComposite的resolveArugument方法打断点,发现被RequestParamMethodArgumentResovler所解析

     在AbstracNamedValueMethodArgumentResolver的reloveArgument方法找到了我们的参数,方法同测试3.2

    4.自定义参数解析器

    自定义参数注解TestObj

    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestObj {
        //参数别名
        String value() default "";
    }

    自定义参数解析器TestObjArgumentResolver实现HandlerMethodArgumentResolver,解决两个自定义类参数传参的问题

    public class TestObjArgumentResolver implements HandlerMethodArgumentResolver {
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(TestObj.class);
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            TestObj testObj = parameter.getParameterAnnotation(TestObj.class);
    
            String alias = getAlias(testObj, parameter);
    
            //拿到obj, 先从ModelAndViewContainer中拿,若没有则new1个参数类型的实例
            Object obj = (mavContainer.containsAttribute(alias)) ?
                    mavContainer.getModel().get(alias) : createAttribute(parameter);
    
    
            //获得WebDataBinder,这里的具体WebDataBinder是ExtendedServletRequestDataBinder
            WebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias);
    
            Object target = binder.getTarget();
    
            if(target != null) {
                //绑定参数
                bindParameters(webRequest, binder, alias);
                //JSR303 验证
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors()) {
                    if (isBindExceptionRequired(binder, parameter)) {
                        throw new BindException(binder.getBindingResult());
                    }
                }
            }
            return target;
        }
    
    
        private Object createAttribute(MethodParameter parameter) {
            return BeanUtils.instantiateClass(parameter.getParameterType());
        }
        //绑定参数
        private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) {
            ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
    
            MockHttpServletRequest newRequest = new MockHttpServletRequest();
    
            Enumeration<String> enu = servletRequest.getParameterNames();
            while(enu.hasMoreElements()) {
                String paramName = enu.nextElement();
                if(paramName.startsWith(alias)) {
                    newRequest.setParameter(paramName.substring(alias.length()+1), request.getParameter(paramName));
                }
            }
            ((ExtendedServletRequestDataBinder)binder).bind(newRequest);
        }
    
        protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
            Annotation[] annotations = parameter.getParameterAnnotations();
            for (Annotation annot : annotations) {
                if (annot.annotationType().getSimpleName().startsWith("Valid")) {
                    Object hints = AnnotationUtils.getValue(annot);
                    binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
                    break;
                }
            }
        }
    
        protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
            int i = parameter.getParameterIndex();
            Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
            boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
    
            return !hasBindingResult;
        }
    
        //生成别名
        private String getAlias(TestObj testObj, MethodParameter parameter) {
            //得到TestObj的属性value,也就是对象参数的简称
            String alias = testObj.value();
            if(alias == null || StringUtils.isBlank(alias)) {
                //如果简称为空,取对象简称的首字母小写开头
                String simpleName = parameter.getParameterType().getSimpleName();
                alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
            }
            return alias;
        }
    
    
    }

    dispatcher-servlet.xml加入我们自定义的参数解析器

    <property name="customArgumentResolvers">
        <list>
           <bean class="org.format.demo.custom.TestObjArgumentResolver" />
        </list>
    </property>

    测试Controller

    @Controller
    @RequestMapping(value = "/foc")
    public class TestObjController {
        @RequestMapping("/test1")
        @ResponseBody
        public Map test1(@TestObj Dept dept, @TestObj Employee emp) {
            Map resultMap = new HashMap();
            resultMap.put("Dept",dept);
            resultMap.put("Emp",emp);
            return resultMap;
        }
    
        @RequestMapping("/test2")
        @ResponseBody
        public Map test2(@TestObj("d") Dept dept, @TestObj("e") Employee emp) {
            Map resultMap = new HashMap();
            resultMap.put("d",dept);
            resultMap.put("e",emp);
            return resultMap;
        }
    
    }

    浏览器输入http://localhost:8080/springmvcdemo/foc/test1?dept.id=1&dept.name=sss&employee.id=3&employee.name=ddf&employee.age=12

    TestObjArgumentResolver中getAlias方法获取别名

    返回结果如下

    浏览器输入http://localhost:8080/springmvcdemo/foc/test2?d.id=1&d.name=sss&e.id=3&e.name=ddf&e.age=12

    参数别名用我们自定义的d和e

    5.总结:

    两大接口:HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler

    ServletInvocableHandlerMethod调用invokeAndHandle方法,使用HandlerMethodReturnValueComposite,使用组合模式,放入HandlerMethodReturnValueHandler的list

    同理HandlerMethodArgumentResolverComposite使用组合模式,放入HandlerMethodArgumentResolver的list

    在RequestMappingHandlerAdapter中invokeHandlerMethod给ArgumentResolvers和ReturnValueHandlers赋值(4.2以前在createRequstMapping方法,此方法已删除)

    afterPropertiesSet方法注入ArgumentResolvers和ReturnValueHandlers到Spring容器

    getDefaultArgumentResolvers设置默认的ArgumentResolvers

    getDefaultReturnValueHandlers设置默认的ReturnValueHandlers

    RequestResponseBodyMethodProcessor负责解析Controller里@RequestBody,支持响应类型是@ResponseBody

    RequestParamMethodArgumentResolver负责解析Controller里@RequestParam

    无注解情况如果是简单对象(如Date,Integer,Doubule等),由RequestParamMethodArgumentResovler处理,复杂对象(如自定义类)由ServletModelAttributeMethodProcessor处理

    resolveArgument解析参数类型和值

    6.参考

    文章难免有不足之处,欢迎指正

    https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-responsebody

  • 相关阅读:
    访问控制模型+强制访问控制
    防火墙体系结构
    信息安全工程师手记
    关于PHPSESSIONID的一些发现
    中级测评师10-物联网
    WAPI学习记录
    1. Jenkins 入门使用
    Springboot druid监控配置
    Springboot 添加数据源报错
    mysql function 查询子级机构
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/10329479.html
Copyright © 2011-2022 走看看