SpringMVC 九大组件之 HandlerAdapter 深入分析


RequestMappingHandlerAdapter 继承自 AbstractHandlerMethodAdapter,我们先来看看 AbstractHandlerMethodAdapter。

3.1 AbstractHandlerMethodAdapter

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

private int order = Ordered.LOWEST_PRECEDENCE;

public AbstractHandlerMethodAdapter() {

super(false);

}

public void setOrder(int order) {

this.order = order;

}

@Override

public int getOrder() {

return this.order;

}

@Override

public final boolean supports(Object handler) {

return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));

}

protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

@Override

@Nullable

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

return handleInternal(request, response, (HandlerMethod) handler);

}

@Nullable

protected abstract ModelAndView handleInternal(HttpServletRequest request,

HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

@Override

public final long getLastModified(HttpServletRequest request, Object handler) {

return getLastModifiedInternal(request, (HandlerMethod) handler);

}

protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);

}

AbstractHandlerMethodAdapter 还是比较简单的,前面我们说的三个方法还是没变,只不过三个方法里边分别调用了 supportsInternal、handleInternal 以及 getLastModifiedInternal,而这些 xxxInternal 都将在子类 RequestMappingHandlerAdapter 中被实现。另外 AbstractHandlerMethodAdapter 实现了 Ordered 接口,意味着在配置的时候还可以设置优先级。

这就是 AbstractHandlerMethodAdapter,比较简单。

3.2 RequestMappingHandlerAdapter

RequestMappingHandlerAdapter 承担了大部分的执行工作,通过前面的介绍,我们已经大致知道 RequestMappingHandlerAdapter 中主要实现了三个方法:supportsInternal、handleInternal 以及 getLastModifiedInternal。其中 supportsInternal 总是返回 true,意味着在 RequestMappingHandlerAdapter 中不做任何比较,getLastModifiedInternal 则直接返回 -1,所以对它来说,最重要的其实是 handleInternal 方法。

整体上来说,RequestMappingHandlerAdapter 要解决三方面的问题:

  1. 处理请求参数。

  2. 调用处理器执行请求。

  3. 处理请求响应。

三个问题第一个最复杂!大家想想我们平时定义接口时,接口参数是不固定的,有几个参数、参数类型是什么都不确定,有的时候我们还利用 @ControllerAdvice 注解标记了一些全局参数,等等这些都要考虑进来,所以这一步是最复杂的,剩下的两步就比较容易了。对于参数的处理,SpringMVC 中提供了很多参数解析器,在接下来的源码分析中,我们将一步一步见识到这些参数解析器。

3.2.1 初始化过程

那么接下来我们就先来看 RequestMappingHandlerAdapter 的初始化过程,由于它实现了 InitializingBean 接口,因此 afterPropertiesSet 方法会被自动调用,在 afterPropertiesSet 方法中,完成了各种参数解析器的初始化:

@Override

public void afterPropertiesSet() {

// Do this first, it may add ResponseBody advice beans

initControllerAdviceCache();

if (this.argumentResolvers == null) {

List resolvers = getDefaultArgumentResolvers();

this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

}

if (this.initBinderArgumentResolvers == null) {

List resolvers = getDefaultInitBinderArgumentResolvers();

this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

}

if (this.returnValueHandlers == null) {

List handlers = getDefaultReturnValueHandlers();

this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);

}

}

private void initControllerAdviceCache() {

if (getApplicationContext() == null) {

return;

}

List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

List requestResponseBodyAdviceBeans = new ArrayList<>();

for (ControllerAdviceBean adviceBean : adviceBeans) {

Class<?> beanType = adviceBean.getBeanType();

if (beanType == null) {

throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);

}

Set attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);

if (!attrMethods.isEmpty()) {

this.modelAttributeAdviceCache.put(adviceBean, attrMethods);

}

Set binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);

if (!binderMethods.isEmpty()) {

this.initBinderAdviceCache.put(adviceBean, binderMethods);

}

if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {

requestResponseBodyAdviceBeans.add(adviceBean);

}

}

if (!requestResponseBodyAdviceBeans.isEmpty()) {

this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);

}

}

这里首先调用 initControllerAdviceCache 方法对包含了 @ControllerAdvice 注解的类进行处理(如果有小伙伴对 @ControllerAdvice 注解的使用还不熟悉,可以在公众号【江南一点雨】后台回复 666 或者 ssm,有松哥的入门教程,里边都有关于 @ControllerAdvice 的讲解),具体的处理思路如下:

  1. 首先查找到所有标记了 @ControllerAdvice 注解的 Bean,将查找结果保存到 adviceBeans 变量中。

  2. 接下来遍历 adviceBeans,找到对象中包含 @ModelAttribute 注解的方法,将查找的结果保存到 modelAttributeAdviceCache 变量中。

  3. 找到对象中包含 @InitBinder 注解的方法,将查找的结果保存到 initBinderAdviceCache 变量中。

  4. 查找实现了 RequestBodyAdvice 或者 ResponseBodyAdvice 接口的类,并最终将查找结果添加到 requestResponseBodyAdvice 中。实现了 RequestBodyAdvice 或者 ResponseBodyAdvice 接口的类一般需要用 @ControllerAdvice 注解标记,关于这两个接口的详细用法,读者可以参考松哥之前的文章,传送门如何优雅的实现 Spring Boot 接口参数加密解密?。另外这里还需要注意,找到的 adviceBean 并没有直接放到全局变量中,而是先放在局部变量中,然后才添加到全局的 requestResponseBodyAdvice 中,这种方式可以确保 adviceBean 始终处于集合的最前面。

这就是 initControllerAdviceCache 方法的处理逻辑,主要是解决了一些全局参数的处理问题。

我们再回到 afterPropertiesSet 方法中,接下来就是对 argumentResolvers、initBinderArgumentResolvers 以及 returnValueHandlers 的初始化了。

前两个参数相关的解析器,都是通过 getDefaultXXX 方法获取的,并且把获取的结果添加到 HandlerMethodArgumentResolverComposite 中,这种 xxxComposite,大家一看就知道是一个责任链模式,这个里边管理了诸多的参数解析器,但是它自己不干活,需要工作的时候,它就负责遍历它所管理的参数解析器,让那些参数解析器去处理参数问题。我们先来看看它的 getDefaultXXX 方法:

private List getDefaultArgumentResolvers() {

List resolvers = new ArrayList<>(30);

// Annotation-based argument resolution

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));

resolvers.add(new RequestParamMapMethodArgumentResolver());

resolvers.add(new PathVariableMethodArgumentResolver());

resolvers.add(new PathVariableMapMethodArgumentResolver());

resolvers.add(new MatrixVariableMethodArgumentResolver());

resolvers.add(new MatrixVariableMapMethodArgumentResolver());

resolvers.add(new ServletModelAttributeMethodProcessor(false));

resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));

resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));

resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));

resolvers.add(new RequestHeaderMapMethodArgumentResolver());

resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));

resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));

resolvers.add(new SessionAttributeMethodArgumentResolver());

resolvers.add(new RequestAttributeMethodArgumentResolver());

// Type-based argument resolution

resolvers.add(new ServletRequestMethodArgumentResolver());

resolvers.add(new ServletResponseMethodArgumentResolver());

resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));

resolvers.add(new RedirectAttributesMethodArgumentResolver());

resolvers.add(new ModelMethodProcessor());

resolvers.add(new MapMethodProcessor());

resolvers.add(new ErrorsMethodArgumentResolver());

resolvers.add(new SessionStatusMethodArgumentResolver());

resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

if (KotlinDetector.isKotlinPresent()) {

resolvers.add(new ContinuationHandlerMethodArgumentResolver());

}

// Custom arguments

if (getCustomArgumentResolvers() != null) {

resolvers.addAll(getCustomArgumentResolvers());

}

// Catch-all

resolvers.add(new PrincipalMethodArgumentResolver());

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

resolvers.add(new ServletModelAttributeMethodProcessor(true));

return resolvers;

}

private List getDefaultInitBinderArgumentResolvers() {

List resolvers = new ArrayList<>(20);

// Annotation-based argument resolution

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));

resolvers.add(new RequestParamMapMethodArgumentResolver());

resolvers.add(new PathVariableMethodArgumentResolver());

resolvers.add(new PathVariableMapMethodArgumentResolver());

resolvers.add(new MatrixVariableMethodArgumentResolver());

resolvers.add(new MatrixVariableMapMethodArgumentResolver());

resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));

resolvers.add(new SessionAttributeMethodArgumentResolver());

resolvers.add(new RequestAttributeMethodArgumentResolver());

// Type-based argument resolution

resolvers.add(new ServletRequestMethodArgumentResolver());

resolvers.add(new ServletResponseMethodArgumentResolver());

// Custom arguments

if (getCustomArgumentResolvers() != null) {

resolvers.addAll(getCustomArgumentResolvers());

}

// Catch-all

resolvers.add(new PrincipalMethodArgumentResolver());

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

return resolvers;

}

这两个方法虽然挺长的,但是内容都比较简单。在 getDefaultArgumentResolvers 方法中,解析器被分为四类:

  1. 通过注解标记的参数对应的解析器。

  2. 通过类型解析的解析器。

  3. 自定义的解析器。

  4. 兜底的解析器(大部分前面搞不定的参数后面的都能搞定,简单数据类型就是在这里处理的)。

getDefaultInitBinderArgumentResolvers 方法也是类似的,我就不再赘述。

所谓的解析器实际上就是把参数从对应的介质中提取出来,然后交给方法对应的变量。如果项目有需要,也可以自定义参数解析器,自定义的参数解析器设置到 RequestMappingHandlerAdapter#customArgumentResolvers 属性上,在调用的时候,前两种参数解析器都匹配不上的时候,自定义的参数解析器才会有用,而且这个顺序是固定的无法改变的。

关于参数解析器本身,如果大家不是特别熟悉的话,可以看看松哥之前的文章:

3.2.2 请求执行过程

根据前面的介绍,请求执行的入口方法实际上就是 handleInternal,所以这里我们就从 handleInternal 方法开始分析:

@Override

protected ModelAndView handleInternal(HttpServletRequest request,

HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ModelAndView mav;

checkRequest(request);

// Execute invokeHandlerMethod in synchronized block if required.

if (this.synchronizeOnSession) {

HttpSession session = request.getSession(false);

if (session != null) {

Object mutex = WebUtils.getSessionMutex(session);

synchronized (mutex) {

mav = invokeHandlerMethod(request, response, handlerMethod);

}

}

else {

// No HttpSession available -> no mutex necessary

mav = invokeHandlerMethod(request, response, handlerMethod);

}

}

else {

// No synchronization on session demanded at all…

mav = invokeHandlerMethod(request, response, handlerMethod);

}

if (!response.containsHeader(HEADER_CACHE_CONTROL)) {

if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {

applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);

}

else {

prepareResponse(response);

}

}

return mav;

}

不过这个 handleInternal 方法看起来平平无奇,没啥特别之处。仔细看起来,它里边就做了三件事:

  1. checkRequest 方法检查请求。

  2. invokeHandlerMethod 方法执行处理器方法,这个是核心。

  3. 处理缓存问题。

那么接下来我们就对这三个问题分别进行分析。

checkRequest

protected final void checkRequest(HttpServletRequest request) throws ServletException {

// Check whether we should support the request method.

String method = request.getMethod();

if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {

throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);

}

// Check whether a session is required.

if (this.requireSession && request.getSession(false) == null) {

throw new HttpSessionRequiredException(“Pre-existing session required but none found”);

}

}

检查就检查了两个东西:

  1. 是否支持请求方法

当 supportedMethods 不为空的时候,去检查是否支持请求方法。默认情况下,supportedMethods 为 null,所以默认情况下是不检查请求方法的。如果需要检查,可以在注册 RequestMappingHandlerAdapter 的时候进行配置,如果在构造方法中设置 restrictDefaultSupportedMethods 变量为 true,那么默认情况下只支持 GET、POST、以及 HEAD 三种请求,不过这个参数改起来比较麻烦,默认在父类 AbstractHandlerMethodAdapter 类中写死了。

  1. 是否需要 session

如果 requireSession 为 true,就检查一下 session 是否存在,默认情况下,requireSession 为 false,所以这里也是不检查的。

invokeHandlerMethod

接下来就是 invokeHandlerMethod 方法的调用了:

@Nullable

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,

HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);

try {

WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

if (this.argumentResolvers != null) {

invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);

}

if (this.returnValueHandlers != null) {

invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

}

invocableMethod.setDataBinderFactory(binderFactory);

invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();

mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

modelFactory.initModel(webRequest, mavContainer, invocableMethod);

mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);

asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

asyncManager.setTaskExecutor(this.taskExecutor);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-a0M5q3BN-1712812343061)]

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

[外链图片转存中…(img-rNLTIEfH-1712812343062)]

[外链图片转存中…(img-EFrRZ3Lk-1712812343062)]

[外链图片转存中…(img-qPHE1vbN-1712812343062)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-yVNsDtXF-1712812343062)]

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值