《Spring in Action》上给了一张 Spring MVC 最最基本大致处理流程图
解释:
① DispatcherServlet 是 SpringMVC 中的核心控制器,负责接收 Request 并将 Request 转发给对应的处理组件
② HanlerMapping 是 SpringMVC 中 完 成 url 到 Controller 映 射 的 组 件 。DispatcherServlet 接 收 Request, 然 后 从HandlerMapping 查 找 处 理 Request 的Controller
③ Controller 处理 Request,并返回 ModelAndView 对象,Controller 是 SpringMVC中负责处理 Request 的组件,ModelAndView 是封装结果视图的组件
④、⑤、⑥视图解析器解析 ModelAndView 对象并返回对应的视图给客户端
根据 Spring MVC 工作机制,从三个部分来分析 Spring MVC 的源代码。
一,初始化。包括ApplicationContext 初始化时用 Map 保存所有 url 和 Controller 类的对应关系;
二,查找。根据请求 url 找到对应的 Controller,并从 Controller 中找到处理请求的方法;
三,处理返回。Request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图。
附上全文源码神图一张
打开DispatcherServlet类继承图
DispatcherServlet继承自HttpServlet,它的本质就是一个Servlet,这就是为什么需要在web.xml通过url-mapping为DispatcherServlet配置映射请求的原因。
Servlet初始化必然是看init方法,在父类HttpServletBean中
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
//....
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
创建了ServletConfigPropertyValues,负责取到web.xml中contextConfigLocation,并addPropertyValue(),在PropertyValues可以看到取到的值
还用this创建了BeanWrapper,一个实体包装类,简单地说,BeanWrapper提供分析和操作JavaBean的方案,如值的set/get方法、描述的set/get方法以及属性的可读可写性
ResourceLoader读取到servletContext和classLoader,servletContext装载了我们刚才的dispatcher-servlet.xml,classLoader找到我们的字节码文件并追踪到我们的jar包路径,还有很多属性不一一介绍
最后关键是initServletBean,在FrameworkServlet中
核心代码:
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
主要就是获取初始化 IOC 容器,最终会调用 refresh()方法 ,refresh流程就不说了,然后会调用onRefresh,在DisptcherServlet 中实现了
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//初始化策略
protected void initStrategies(ApplicationContext context) {
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//handlerMapping
initHandlerMappings(context);
//初始化参数适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//FlashMap 管理器
initFlashMapManager(context);
}
到这一步就完成了Spring MVC的九大组件(九大组件最后附上)的初始化,其中就包含了url和Controller的 关 系 建 立
目前主流的三种mapping 如下:
1.SimpleUrlHandlerMapping:基于手动配置 url 与control 映射
2.BeanNameUrlHandlerMapping: 基于ioc name 中已 “/” 开头的Bean时行 注册至映射.
3.RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映射
现在基本都是用的@RequestMapping,对应处理类是RequestMappingHandlerMapping
1.父类实现了InitializingBean接口,导致创建RequestMappingHandlerMapping到容器回调初始化方法afterPropertiesSet(populateBean之后有个initializeBean)
2.afterPropertiesSet中调用了initHandlerMethods解析,这个就是解析url与controller映射
3.initHandlerMethods=》processCandidateBean=》detectHandlerMethods (调用链),逻辑依次
3.1基本获取容器所有Bean,
3.2筛选是否isHandler(根据是否有Controller和RequestMapping注解),
3.3注册到MappingRegistry(运用读写锁,加到mappingLookup、urlLookup等等,都是map,第一个路径与method关系,第二个单路径(可以正则))
this.urlLookup.add(url, mapping);//kv:字符串、RequestMappingInfo数组(更详细的请求信息包括get post各种)
this.mappingLookup.put(mapping, handlerMethod);//kv: RequestMappingInfo、处理方法handlerMethod
DispatcherServlet是个Servlet 核心处理方法即是doService() 开始,主要逻辑是doDispatch
/**
* //处理实际调度处理器
* //处理程序将通过按顺序的servlet的处理器映射器获得。
* //处理器适配器将通过查询servlet的安装的处理器适配器来获得
* //找到支持处理程序类的第一个。
* //所有HTTP方法都由此方法处理。 这取决于处理器适配器或处理程序
* //自己决定哪些方法是可以接受的。
* @param request//请求当前HTTP请求
* @param response//响应当前的HTTP响应
* @throws Exception //任何类型的处理失败的例外
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request; //processedRequest是经过checkMultipart方法处理过的request请求
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//2.通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
// 确定当前请求的处理程序。
mappedHandler = getHandler(processedRequest); //解析第一个方法
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
// 确定当前请求的处理程序适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //解析第二个方法
// 如果处理程序支持,则处理最后修改的头文件。
String method = request.getMethod(); //得到当前的http方法。
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) { //处理http的head方法。这种方法应该很少用
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//4.1调用HandlerExecutionChain的interceptor
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4.2执行解析handler中的args,调用(invoke) controller的方法。得到视图
// Actually invoke the handler. 实际上调用处理程序。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //解析第三个方法
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//4.3调用HandlerExecutionChain的interceptor
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//5.解析视图、处理异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Error err) {
//......
}
}
其中getHandler(processedRequest)方法实际上就是从 HandlerMapping 中找到 request url 和Controller 的对应关系。也就是 Map
dispatch后面就是找到处理器对应正确的适配器、执行拦截器pre方法,再就是真正执行,通过适配器的handle方法,内部反射获取处理器方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取 ModelAndView 结果视图 。
最后一步:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
实际上拿到的适配器是RequestMappingHandlerAdapter ,打开handle方法源码:
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
return handleInternal(request, response, (HandlerMethod) handler);
}
核心处理逻辑在适配器的handleInternal=》invokeHandlerMethod=》invokeAndHandle=》invokeForRequest方法中:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
首先,完成 Request 中的参数和方法参数上数据的绑定。Spring MVC 中提供两种 Request 参数到方法中参数的绑
定方式:
1、通过注解进行绑定,@RequestParam。
2、通过参数名称进行绑定。
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam(“name”),就可以将 request 中参数 name 的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件,来获取方法的参数名称。asm 框架是一个字节码操作框架 .
最后,运用反射把绑定好的参数,传进去调用Controller的方法了doInvoke(args)。