您现在的位置是:首页 >学无止境 >Spring MVC 的调用(12)网站首页学无止境
Spring MVC 的调用(12)
目录
第一步:用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
1. 根据请求的uri拿到对应的HandlerMethod对象
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析,并返回View
SpringMVC流程
第一步:用户发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
第九步:视图解析器像前端控制器返回View
第十步:前端控制器对视图进行渲染
第十一步:前端控制器向用户响应结果
此处需要强调的是
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
源码分析
第一步:用户发起请求到前端控制器(DispatcherServlet)
其实就是前端请求调用到了DispatcherServlet的doService方法,而doService方法又调用到了doDispatch 核心方法:
而 doDispatch 方法负责整个Spring MVC的调度与响应,是核心中的核心:
第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
其实,就是在doDispatch内部调用了 getHandler 方法,它会获取到所有的handlerMapping对象进行遍历,找到符合匹配规则的handlerMapping就直接返回:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappering实例
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//获取HandlerMethod和过滤器链的包装类
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
注意: 以上的for循环遍历所有的handlerMapping类,并且调用每个类的都调用了getHandler方法,是一个策略设计模式,可以避免大量的 if 判断。
这里,我们需要关注一下requestMappingHandlerMapping这个对象,因为上一篇博客中,我们提到了是在requestMappingHandlerMapping创建完以后建立映射关系的。遍历到requestMappingHandlerMapping对象后进入getHanlder方法 内部:
1. 根据请求的uri拿到对应的HandlerMethod对象
其实,就是调用 getHandlerInternal 方法的过程,上一讲我们提到根据 URL 和 requestMappingInfo建立了映射,而requestMappingInfo又和HandlerMethod建立了映射,此处肯定也是按照这个规则逐步去查找HandlerMethod对象的。 下面进行源码验证:
进入这个方法内部:
首先,我们发现它根据URL 拿到了 requestMappingInfo 对象:
其次,根据requestMappingInfo拿HandlerMethod对象,进入这个方法内部进行确认:
第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
主方法:
进入这个方法内部,看看具体是如何 获取 HandlerExecutionChain 对象的
补充说明一下:拦截器是我们自己定义的,它有3个方法分别问前置过滤器、中置过滤器 和 后置过滤器:
package com.xiangxue.jack.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class UserInterceptor implements HandlerInterceptor {
//前置过滤器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("======UserInterceptor用户权限校验=========");
return true;
}
//中置过滤器
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("========UserInterceptor修改modelAndView======");
}
//后置过滤器
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("========UserInterceptor资源释放======");
}
}
而拦截器是在 有 @EnableWebMvc的类, 并且这个类继承了 WebMvcConfigurerAdapter类 中添加进去的。
第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
回到核心方法 doDispatch 中,在这个方法内部调用了 getHandlerAdapter 进行获取处理器适配器的:
进入这个方法内部, 看看具体是如何获取到处理器适配器的:
第五步:处理器适配器去执行Handler
第六步:Handler执行完给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView
第五、六、七步可以合起来一起分析。第三步的时候说到了拦截器,其实在执行hand方法之前会进行 拦截器 中的前置过滤器的调用:
成功进入到了我们自己写的拦截器类中,并且调用到了前置过滤器方法:
言归正传,处理器去执行handler,其实就是调用到我们的Controller类的对应方法而已。就是进入到业务类中,进行实际的业务调用。最终返回的数据类型包装成ModelAndView返回给处理器适配器, 而处理器适配器向前端控制器返回ModelAndView。
还需要补充一点,中置过滤器的调用时序, 是当 ha.handle 掉完以后, 也就是 Controller 里面具体方法调用完以后才轮到中置过滤器调用。 而中置拦截器可以修改 modelAndView。
成功调到中置过滤器
ModelAndView就是最原始的Spring MVC返回值,如果使用了@ResponseBody注解或者其他注解改变返回值,那就没有ModelAndView了。
ModelAndView演示 :
首先定义jsp, 因为ModelAndView需要按照特定的规则去解析,需要有对应的jsp文件
重写中置过滤器
设置MVC 的jsp路径:
Controller 类:
调用结果:
URL: http://localhost:9090/user/queryUser?language=tw
页面:
URL: http://localhost:9090/user/queryUser?language=zh
页面:
第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析,并返回View
还是核心方法 doDispatch方法体中,在执行完中置过滤器以后,我们就开始进行视图渲染的方法调用了。
源码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果异常不为空
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
//视图渲染,响应视图
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
//后置过滤器
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
核心方法就是 render(mv, request, response), 它负责视图的渲染与响应
获取到视图解析器:
进入方法以后,我们发现视图解析器会根据我们实际业务方法,生成各种各样的View,然后根据当前的方法获取到最优的View并进行返回,直到返回到前端控制器(DispatcherServlet)中.
我们根据上图中的步骤逐步进行源码解析:
1、创建视图View
调用 createView 方法创建新视图 View:
最终会创建视图View,并且对视图进行各种属性的设置,比如视图的URL:
2. 根据ContentType 以及 ViewName等很多的条件,选取最佳的 View
而ContentType 则是定义在jsp页面中的:
第九步:视图解析器像前端控制器返回View
这一步最简单,就是根据第八步选取的View一直进行返回,直到DispatcherServlet中。
第十步:前端控制器对视图进行渲染
核心方法:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//把响应数据设置到request对象中
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
//获取到跳转的地址
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
其实,就是一个forward请求转发到对应的 jsp 页面而已。
第十一步:前端控制器向用户响应结果
请求都转发到 jsp 页面了, 这一步确实不知道该说些什么了。 就此结束本篇。
后置过滤器:
后置过滤器是最后调用的,目的就是释放一些资源,总之就是最后干的事情。最终会调到我们自己定义的拦截器中的后置过滤器的方法中:
自定义的后置拦截器中的后置过滤器方法:
我们在使用Spring mvc的时候,经常会使用各种各样的注解进行参数的传递,比如 @RequestParam 、@PathVariable、@ModelAttribute、@CookieValue、@RequestHeader等各种各样的注解。下一篇会专门针对这些注解,挑出几个常用的进行分析。