DispatcherServlet
进行说明的,这里我们将从一个请求如何进入spring中开始,到返回结果结束进行说明。
Servlet
规范分析request
请求怎么进入到Spring
中的 spring的web框架的设计是按照servlet
规范来进行扩展的,因此这里需要对Servlet
规范讲一下。
Servlet
规范中的请求处理流程这里直接将翻译过后的原文借用过来:
servlet 完成初始化后,servlet 容器就可以使用它处理客户端请求了。客户端请求由 ServletRequest 类型的请求对象表示。servlet 封装响应并返回给请求的客户端,该响应由 ServletResponse 类型的响应对象表示。这两个对象是由容器通过参数传递到Servlet接口的service方法的。
在 HTTP 请求的场景下,容器提供的请求和响应对象具体类型分别是HttpServletRequest 和 HttpServletResponse。 需要注意的是,由 servlet 容器初始化的某个 servlet 实例在服务期间,可以在其生命周期中不处理任何请求。
分析上面规范的意思就是,一个http
请求在进入到一个servlet
容器之后会被Servlet
接口的service
方法进行处理,请求信息封装在HttpServletRequest
中,请求的处理结果封装在HttpServletResponse
中。
不同Servlet
容器都会实现对Servlet
规范的实现,其中标准的servlet
规范的实现jar包javax.servlet-api
这个jar包在Tomcat里面能找到。我们就在这个jar包中进行第一步的分析
Servlet
与区分不同类型请求HttpServlet
在Servlet
接口中定义了处理请求的service方法,但是其具体的实现在HttpServlet
类中,这里直接进入HttpServlet
类
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
//验证请求跟返回信息封装对象的类型是不是servlet规范中定义的类型的,不是的则抛错
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
//转为HttpServletRequest跟HttpServletResponse类型
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
可以看到其主要的逻辑就是对请求跟返回对象的验证,确保是按照servlet规范来的。后面自己定义了一个service
方法来完成后面 的请求区分逻辑。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
//获取请求的方法值
String method = req.getMethod();
//判断是不是get请求
if (method.equals(METHOD_GET)) {
//判断之前这个请求对象的最后修改时间是否修改过
long lastModified = getLastModified(req);
//如果值为-1,表示当前的servlet对象不支持请求头中的if-modified-since,这时候就需要调用后面的代码逻辑,代价高
if (lastModified == -1) {
doGet(req, resp);
} else {
//如果不是-1,表示支持,然后获取请求头中的if-modified-since参数
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
//如果请求头中的时间小于上次的请求时间,则表示页面需要进行刷新
if (ifModifiedSince < lastModified) {
//设置返回体中的Last-Modified参数
maybeSetLastModified(resp, lastModified);
//进行后面的逻辑
doGet(req, resp);
} else {
//如果两个时间相等则设置返回体的请求状态为304
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
//获取上次的修改时间
long lastModified = getLastModified(req);
//设置修改时间
maybeSetLastModified(resp, lastModified);
//进行后面逻辑
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
可以看到这里作用其实就是对请求中的请求类型进行判断,来选择后面的逻辑。其中对get请求的分析后面多了一些处理,这些处理是为了避免服务器对不必要的逻辑的处理。关于请求中的If-Modified-Since
跟Last-Modified
参数可以参考一下这篇博文HTTP的请求头标签 If-Modified-Since与返回中的Last-Modified
在前面讲spring什么时候以及何时初始化web应用相关上下文的时候,我们讲过了spring中对Servlet
规范实现的类是FrameworkServlet
。其中FrameworkServlet
继承了HttpServletBean
因此里面的doPost
,doDelete
等方法都尤其实现,但是实现都是差不多的,这里就分析常用的post,get请求的处理逻辑。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//进行请求的处理
processRequest(request, response);
}
}
FrameworkServlet
中对请求的分析和处理过程前面分析了一个请求如何进入到spring中的,现在需要做的就是进入到spring中进行分析,
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取当前系统时间,用来计算这个步骤的处理耗时
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取当前服务运行所在地区,在RequestContextFilter中进行处理设值
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//创建相关地区的对象的LocalContext,
LocaleContext localeContext = buildLocaleContext(request);
//获取请求的属性,此时的请求相关的属性会在RequestContextFilter中进行处理设值
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//创建requestAttributes,此时previousAttributes已经包含了request跟response,ServletRequestAttributes是RequestAttributes子类
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//从request获取WebAsyncManager,在filter阶段会创建WebAsyncManager,表示是不是异步相应的请求
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//将FrameworkServlet的内部类RequestBindingInterceptor设置到asyncManager中,用于在异步中初始化跟重新设置FrameworkServlet
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将localeContext跟requestAttributes设置到LocaleContextHolder跟RequestContextHolder中
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 {
//移除threadLocal中的请求相关信息,attribute跟context信息
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//打印结果
logResult(request, response, failureCause, asyncManager);
//发布请求处理完成事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
在这个方法中主要的逻辑还是准备处理请求需要的上下文,参数等。其中,很多参数都是通过拦截器在进入到这个方法之前进行设置的,这里就不进行讲解了,具体的设置的类在注释中已经指明了。现在清理一下逻辑。
LocaleContextHolder
中的ThreadLocal
类型对象localeContextHolder
跟inheritableLocaleContextHolder
中获取,当前服务器允许的区域,这两个对象的设置是在RequestContextFilter
中进行设置的,值是request对象中获取的RequestContextHolder
获取request中的属性,其中这些属性保存在ThreadLocal
类型对象requestAttributesHolder
跟inheritableRequestAttributesHolder
中,这两个对象的设值也是在RequestContextFilter
中进行设置的ServletRequestAttributes
对象,后面处理请求会用到WebAsyncManager
对象,这个是异步处理相关的,会在拦截器中进行创建CallableProcessingInterceptor
对象LocaleContextHolder
跟RequestContextHolder
中 这里对第2,3一级第5步的拦截逻辑进行说明,其中第2跟第3步都是在RequestContextFilter
进行的,这个类继承了OncePerRequestFilter
类,而这个OncePerRequestFilter
类中又包含第5步中获取的对象相关的逻辑,因此先从OncePerRequestFilter
开始
一个请求在同步逻辑完成之后的逻辑都在,上面的processRequest
方法的finally
代码块中,主要可以分为以下几步:
LocaleContext
,RequestAttributes
对象中的ThreadLocal
对象的值 //移除threadLocal中的请求相关信息,attribute跟context信息
resetContextHolders(request, previousLocaleContext, previousAttributes);
//处理异步逻辑
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//打印结果
logResult(request, response, failureCause, asyncManager);
//发布请求处理完成事件
publishRequestHandledEvent(request, response, startTime, failureCause);
这里主要说的是第二步,跟第四步
直接上代码
public void requestCompleted() {
//执行请求的销毁钱会掉
executeRequestDestructionCallbacks();
//更新session的属性
updateAccessedSessionAttributes();
//将当前的请求的活动状态置位false
this.requestActive = false;
}
这里主要就是执行前面我们在处理业务逻辑前的准备信息时候注册的销毁前回调处理的对象,然后处理更新session信息,并将当前请求置位非活跃(结束)的状态。
现在进入到executeRequestDestructionCallbacks
方法
private void executeRequestDestructionCallbacks() {
synchronized (this.requestDestructionCallbacks) {
//依次调用注册的requestDestructionCallbacks
for (Runnable runnable : this.requestDestructionCallbacks.values()) {
runnable.run();
}
//调用完毕之后清除
this.requestDestructionCallbacks.clear();
}
}
可以看到这里的逻辑很简单就是执行对应的run方法,然而问题是这个run方法是哪里设置的。这里直接告知,这个run方法是在创建bean的时候注册的。对应的注册方法如下:
protected final void registerRequestDestructionCallback(String name, Runnable callback) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(callback, "Callback must not be null");
synchronized (this.requestDestructionCallbacks) {
this.requestDestructionCallbacks.put(name, callback);
}
}
接下来看registerRequestDestructionCallback
方法被调用的位置在ServletRequestAttributes
类中
public void registerDestructionCallback(String name, Runnable callback, int scope) {
//如果当前的bean范围是request类型的,则注册request请求结束后会掉的回调
if (scope == SCOPE_REQUEST) {
registerRequestDestructionCallback(name, callback);
}
else {
//如果当前的bean范围是session类型的,则注册session结束后会掉的回调
registerSessionDestructionCallback(name, callback);
}
}
继续跟下去,会发现registerDestructionCallback
方法是AbstractRequestAttributesScope
类中的registerDestructionCallback
方法,而AbstractRequestAttributesScope
实现了Scope
接口。
public void registerDestructionCallback(String name, Runnable callback) {
//从请求的上下文中获取当前请求的属性
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//想属性中注册请求或者会话结束后回调的方法
attributes.registerDestructionCallback(name, callback, getScope());
}
Scope
接口中的registerDestructionCallback
会在创建bean 的时候调用,这里截取部分代码
......
Scope scope = this.scopes.get(mbd.getScope());
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
}
//注册销毁的回调方法
scope.registerDestructionCallback(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
......
可以看到这里创建了一个DisposableBeanAdapter
对象,关于这个对象在前面的bean的生命周期有提到可以去看看。
spring的时间机制,在spring中运用的很广泛。这里就是在请求完成之后发出一个请求处理完毕的事件,这个位置其实是给框架的使用者的一个扩展点。我们可以通过监听这个事件来定义一个请求结束后,我们需要做的事情。
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
if (this.publishEvents && this.webApplicationContext != null) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}
到这里第一个部分的内容就已经讲完了。第二部分是从FrameworkServlet
到DispatcherServlet
的部分讲解了。