SpringMVC 源码分析之 FrameworkServlet

@Override

protected void service(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());

if (httpMethod == HttpMethod.PATCH || httpMethod == null) {

processRequest(request, response);

}

else {

super.service(request, response);

}

}

可以看到,在该方法中,首先获取到当前请求方法,然后对 patch 请求额外关照了下,其他类型的请求统统都是 super.service 进行处理。

然而在 HttpServlet 中并未对 doGet、doPost 等请求进行实质性处理,所以 FrameworkServlet 中还重写了各种请求对应的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其实就是除了 doHead 之外的其他方法都重写了。

我们先来看看 doDelete、doGet、doPost 以及 doPut 四个方法:

@Override

protected final void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

@Override

protected final void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

@Override

protected final void doPut(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

@Override

protected final void doDelete(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

可以看到,这里又把请求交给 processRequest 去处理了,在 processRequest 方法中则会进一步调用到 doService,对不同类型的请求分类处理。

doOptions 和 doTrace 则稍微有些差异,如下:

@Override

protected void doOptions(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {

processRequest(request, response);

if (response.containsHeader(“Allow”)) {

return;

}

}

super.doOptions(request, new HttpServletResponseWrapper(response) {

@Override

public void setHeader(String name, String value) {

if (“Allow”.equals(name)) {

value = (StringUtils.hasLength(value) ? value + ", " : “”) + HttpMethod.PATCH.name();

}

super.setHeader(name, value);

}

});

}

@Override

protected void doTrace(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

if (this.dispatchTraceRequest) {

processRequest(request, response);

if (“message/http”.equals(response.getContentType())) {

return;

}

}

super.doTrace(request, response);

}

可以看到这两个方法的处理多了一层逻辑,就是去选择是在当前方法中处理对应的请求还是交给父类去处理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 变量默认都是 false,因此默认情况下,这两种类型的请求都是交给了父类去处理。

2.processRequest


我们再来看 processRequest,这算是 FrameworkServlet 的核心方法了:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

long startTime = System.currentTimeMillis();

Throwable failureCause = null;

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

try {

doService(request, response);

}

catch (ServletException | IOException ex) {

failureCause = ex;

throw ex;

}

catch (Throwable ex) {

failureCause = ex;

throw new NestedServletException(“Request processing failed”, ex);

}

finally {

resetContextHolders(request, previousLocaleContext, previousAttributes);

if (requestAttributes != null) {

requestAttributes.requestCompleted();

}

logResult(request, response, failureCause, asyncManager);

publishRequestHandledEvent(request, response, startTime, failureCause);

}

}

这个方法虽然比较长,但是其实它的核心就是最中间的 doService 方法,以 doService 为界,我们可以将该方法的内容分为三部分:

  1. doService 之前主要是一些准备工作,准备工作主要干了两件事,第一件事就是从 LocaleContextHolder 和 RequestContextHolder 中分别获取它们原来保存的 LocaleContext 和 RequestAttributes 对象存起来,然后分别调用 buildLocaleContext 和 buildRequestAttributes 方法获取到当前请求的 LocaleContext 和 RequestAttributes 对象,再通过 initContextHolders 方法将当前请求的 LocaleContext 和 RequestAttributes 对象分别设置到 LocaleContextHolder 和 RequestContextHolder 对象中;第二件事则是获取到异步管理器并设置拦截器。

  2. 接下来就是 doService 方法,这是一个抽象方法,具体的实现在 DispatcherServlet 中,这个松哥放到 DispatcherServlet 中再和大家分析。

  3. 第三部分就是 finally 中,这个里边干了两件事:第一件事就是将 LocaleContextHolder 和 RequestContextHolder 中对应的对象恢复成原来的样子(参考第一步);第二件事就是通过 publishRequestHandledEvent 方法发布一个 ServletRequestHandledEvent 类型的消息。

经过上面的分析,大家发现,processRequest 其实主要做了两件事,第一件事就是对 LocaleContext 和 RequestAttributes 的处理,第二件事就是发布事件。我们对这两件事分别来研究。

2.1 LocaleContext 和 RequestAttributes

LocaleContext 和 RequestAttributes 都是接口,不同的是里边存放的对象不同。

2.1.1 LocaleContext

LocaleContext 里边存放着 Locale,也就是本地化信息,如果我们需要支持国际化,就会用到 Locale。

国际化的时候,如果我们需要用到 Locale 对象,第一反应就是从 HttpServletRequest 中获取,像下面这样:

Locale locale = req.getLocale();

但是大家知道,HttpServletRequest 只存在于 Controller 中,如果我们想要在 Service 层获取 HttpServletRequest,就得从 Controller 中传参数过来,这样就比较麻烦,特别是有的时候 Service 中相关方法都已经定义好了再去修改,就更头大了。

所以 SpringMVC 中还给我们提供了 LocaleContextHolder,这个工具就是用来保存当前请求的 LocaleContext 的。当大家看到 LocaleContextHolder 时不知道有没有觉得眼熟,松哥在之前的 Spring Security 系列教程中和大家聊过 SecurityContextHolder,这两个的原理基本一致,都是基于 ThreadLocal 来保存变量,进而确保不同线程之间互不干扰,对 ThreadLocal 不熟悉的小伙伴,可以看看松哥的 Spring Security 系列,之前有详细分析过(公号后台回复 ss)。

有了 LocaleContextHolder 之后,我们就可以在任何地方获取 Locale 了,例如在 Service 中我们可以通过如下方式获取 Locale:

Locale locale = LocaleContextHolder.getLocale();

上面这个 Locale 对象实际上就是从 LocaleContextHolder 中的 LocaleContext 里边取出来的。

需要注意的是,SpringMVC 中还有一个 LocaleResolver 解析器,所以前面 req.getLocale() 并不总是获取到 Locale 的值,这个松哥在以后的文章中再和小伙伴们细聊。

2.1.2 RequestAttributes

RequestAttributes 是一个接口,这个接口可以用来 get/set/remove 某一个属性。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

img

img

img

img

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

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-9vDDJIJI-1712813372197)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值