Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
Posted 热爱编程的大忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上相关的知识,希望对你有一定的参考价值。
Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
引言
前面已经详细介绍过了RequestMappingHandlerMapping是如何在初始化方法中搜集容器中所有标注了@Controller或者@RequestMapping注解的Bean的,然后解析将映射关系保存到映射中心。
在请求到来时,通过request请求对象提供的信息,去注册中心匹配获取到合适的,分别经过精确匹配或者模糊匹配,然后再进行最佳匹配,最终返回一个匹配上的HandlerMethod,交给父类AbstractHandlerMapping包装为HandlerExecutionChain,内部添加合适的拦截器。
无论是URL的精确匹配还是模糊匹配,最终都要交给对应的HandlerMethod持有的RequestMappingInfo,进行条件匹配,如果不满足,则返回null。
如果没有HandlerMethod能够处理当前请求,那么再判断是否部分匹配,即请求路径匹配上了,但是不满足RequestMappingInfo中其他限制条件,如请求头限制等,此时检查不满足条件的请求,抛出对应的异常。如果是URL没有匹配上,则返回给AbstractHandlerMapping的handler结果为null,最终在doDispatch方法中的noHandlerFound方法中抛出404异常。
Spring MVC注解Controller源码流程解析–映射建立
Spring MVC注解Controller源码流程解析–定位HandlerMethod
Spring MVC注解Controller源码流程解析—请求匹配中的容错处理
本文将对RequestMappingHandlerAdapter如何调用执行RequestMappingHandlerMapping返回的handler,即HandlerMethod的过程做出详细分析。
RequestMappingHandlerAdapter
RequestMappingHandlerAdapter调用执行HandlerMethod方法的过程比较复杂,这里先对RequestMappingHandlerAdapter调用执行HandlerMethod方法中涉及到的组件和思路进行讲解,最终再走一遍源码,大家就会非常清晰了。
对于RequestMappingHandlerAdapter来说,它的主要职责有以几个:
- 解析控制器方法的参数列表,并从request请求对象中获取到相关参数值,并保存起来 (这个过程还涉及到参数类型转换问题,需要求助Spirng提供的类型转换模块支持)
- 反射执行控制器方法,将先前准备好的参数值列表传入
- 控制器方法执行完毕后,处理返回结果,并将返回结果统一转换为ModelAndView,供SpringMVC后续视图渲染组件使用
方法参数解析器
对于控制器方法参数解析而言,由于Spring支持多种注解形式来提示从哪里获取参数值,参数key是什么等等,因此如果使用一个参数解析器完成所有注解的解析,那么就成狗屎代码了,因此Spring采用一个参数解析器负责解析一个注解的形式:
spring常见的方法参数解析器有:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3
我们经常在Controller代码中使用的形式有:
static class Controller
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "$JAVA_HOME") String home1, // spring环境上下文中获取数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/id
@RequestHeader("Content-Type") String header, //请求头获取参数值
@CookieValue("token") String token, //cookie中获取参数值
@Value("$JAVA_HOME") String home2, // spring环境上下文中获取参数值 $ #
HttpServletRequest request, // request, response, session ...
@ModelAttribute("abc") User user1, //非简单对象类型的数据封装: name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
)
static class User
private String name;
private int age;
public String getName()
return name;
public void setName(String name)
this.name = name;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
@Override
public String toString()
return "User" +
"name='" + name + '\\'' +
", age=" + age +
'';
参数方法解析器单独使用案例:
- 准备controller控制器对象-上面给出的
- 准备mockRequest
public static void main(String[] args) throws Exception
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
//多个解析器组合--组合模式体系
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// false 表示必须有 @RequestParam
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
);
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters())
//获取当前方法参数上的注解名
String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
//设置好spring提供的方法参数名解析器
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
if (composite.supportsParameter(parameter))
// 支持此参数
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
System.out.println("模型数据为:" + container.getModel());
else
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
说明:
又添加了一遍@RequestParam和@ModelAttribute相关的注解解析器,并且设置对省略注解情况的参数解析,同时必须放在参数解析器列表的末尾。
参数解析器列表挑选的原理是找到第一个能够support支持处理当前参数的,然后直接返回该参数处理器进行解析处理。
如果要添加自定义的参数解析器:
自定义参数解析器优先于默认参数解析器被调用。
参数解析器小结:
- 初步了解 RequestMappingHandlerAdapter 的调用过程
- 控制器方法被封装为 HandlerMethod
- 准备对象绑定与类型转换
- 准备 ModelAndViewContainer 用来存储中间 Model 结果
- 解析每个参数值
- 解析参数依赖的就是各种参数解析器,它们都有两个重要方法
- supportsParameter 判断是否支持方法参数
- resolveArgument 解析方法参数,返回参数值
- 常见参数的解析
- @RequestParam
- 省略 @RequestParam
- @RequestParam(defaultValue)
- MultipartFile
- @PathVariable
- @RequestHeader
- @CookieValue
- @Value
- HttpServletRequest 等
- @ModelAttribute
- 省略 @ModelAttribute
- @RequestBody
- 组合模式在 Spring 中的体现
- @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 $ # 中获取
方法参数名解析器
正常情况下,java的class文件中是不会保存方法参数名相关信息的,如果要保留有以下两种方式:
- 编译时加上-parameters参数,此时javac编译器在编译时,会在class文件中生成对应的参数表,此时我们通过反射就可以直接拿到参数名
// 1. 反射获取参数名
Method foo = Bean2.class.getMethod("foo", String.class, int.class);
for (Parameter parameter : foo.getParameters())
System.out.println(parameter.getName());
- 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
1. 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
2. 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)
// 2. 基于 LocalVariableTable 本地变量表--这里借助Spring提供的方法参数名解析器来操作asm解析局部变量表获取参数名
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
大部分编译器在编译时都会添加-g参数
spring对以上两种方式解析获取参数名都提供了支持:
DefaultParameterNameDiscoverer 也是采用了组合模式,内部组合了多种ParameterNameDiscoverer实现类:
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer
public DefaultParameterNameDiscoverer()
if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage())
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
//默认添加了对两种参数获取方式的支持
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
DefaultParameterNameDiscoverer内部会依次尝试先根据反射从方法参数表获取参数名,失败了再尝试从局部变量表获取参数名。
类型转换体系
由于历史遗留原因,Spring目前的类型转换体系结构分为了两套架构,一套是基于JDK提供的PropertyEditor接口实现的,一套是Spring自己单独开发的Converters体系。
底层第一套转换接口与实现:
- Printer 把其它类型转为 String
- Parser 把 String 转为其它类型
- Formatter 综合 Printer 与 Parser 功能
- Converter 把类型 S 转为类型 T
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
- FormattingConversionService 内部维护一组Converters集合,用于对外其他类型转换服务
底层第二套转换接口:
- PropertyEditor 把 String 与其它类型相互转换
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
高层接口与实现:
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义的propertyEditor类型转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property – 调用对象的getter和setter方法完成赋值
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field — 反射调用字段完成赋值
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能 ,还有一点很关键,对象属性值来源于request对象
简单的使用演示
- SimpleTypeConverter: 仅支持简单的类型转换
public class TestSimpleConverter
public static void main(String[] args)
// 仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(number);
System.out.println(date);
- BeanWrapperImpl: 基于getter和setter方法完成属性赋值,如果不提供getter和setter方法,则会抛出异常
public class TestBeanWrapper
public static void main(String[] args)
// 利用反射原理, 为 bean 的属性赋值
MyBean target = new MyBean();
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
wrapper.setPropertyValue("a", "10");
wrapper.setPropertyValue("b", "hello");
wrapper.setPropertyValue("c", "1999/03/04");
System.out.println(target);
static class MyBean
private int a;
private String b;
private Date c;
//自行提供getter和setter方法
...
- DirectFieldAccessor: 基于反射获取字段设置值
public class TestFieldAccessor
public static void main(String[] args)
// 利用反射原理, 为 bean 的属性赋值
MyBean target = new MyBean();
DirectFieldAccessor accessor = new DirectFieldAccessor(target);
accessor.setPropertyValue("a", "10");
accessor.setPropertyValue("b", "hello"<以上是关于Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC注解Controller源码流程解析---请求匹配中的容错处理
Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析
Spring MVC 基础注解之@RequestMapping@Controller
Spring MVC 常用注解@Controller,@RequestMapping,Model和ModelAndView