springmvc加载过程及源码分析
什么是springmvc
springmvc是基于的spring的一个web层框架。
这张图是spring的架构体系,从中可以看出springmvc和struts一样都是属于一个web层框架,是spring框架的一部分。
springmvc和mvc有什么区别
mvc是一种设计模式,而springmvc是一个表现层框架。springmvc可以说是对mvc设计模式的一种很好的实现。
springmvc加载过程
- 用户发起请求到前端控制器(DispatcherServlet)
- 前端控制器将请求转发给处理映射器(HandlerMapping)
- 处理器映射器(HandlerMapping)根据请求的url查找到对应的handler,并返回一个(HandlerExecutionChain,HandlerExecutionChain包括找到的hander和interceptor(有))给前端控制器(DispatcherServlet)
- 前端控制器调用处理器适配器(HandlerAdapter)执行handler
- 执行handler完毕后返回一个ModelAndView给前端控制前(DispatcherServlet)
- 前端控制器调用视图解析器,请求视图解析,视图解析完后返回view对象给前端控制器
- 前端控制器进行视图渲染,视图渲染就是将模型数据(在ModelAndView对象中)填充到request域中。
- 前端控制器向用户响应结果。
在分析源码之前先看下DispatcherServlet的继承体系
通过这个继承体系可以知道DispatcherServlet本质就是一个servlet,关于servlet的知识可以参考《servlet简介》,servlet入口是init方法,我们看下HttpServletBean中这个init方法源码,从源码中可以看出这个init方法用了final修饰说明这个方法是不能被子类继承的,由此可以HttpServletBean中的init方法就是sevlet的入口。
public final void init() throws ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
}
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
//模板方法,做一些初始化的工作,可以在子类中实现
this.initBeanWrapper(bw);
//将初始化的值设置到dispatchsevlet中(contextConfigLocation)
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
this.initServletBean();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
}
}
ServletConfigPropertyValues是HttpServletBean的内部静态内,在构造过程会通过传入的servletconfig对象将web.xml的配置的参数设置到ServletConfigPropertyValues的内部。
通过BeanWrapper来构建DispatcherServlet。BeanWrapper是spring提供的一个来操作javabean的属性的工具,使用它可以直接修改javabean 的属性,用法如下
package com.linewell.springmvc.pojo;
public class Book {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.linewell.springmvc.pojo;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyValue;
public class BeanWrapperTest {
public static void main(String[] args) {
Book book = new Book();
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(book);
beanWrapper.setPropertyValue("name","MySql从删库到跑路");
System.out.println(book.getName());
PropertyValue value = new PropertyValue("name", "java从入门到放弃");
beanWrapper.setPropertyValue(value);
System.out.println(book.getName());
}
}
打印结果:
再往下看这里有一个执行了一个initServletBean方法,
initServletBean方法里面什么都没有
这说明子类中应该是对它进行了实现,我们看下它的子类FrameworkServlet,果然在代码里面找到了initServletBean方法,源码如下:
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var5) {
this.logger.error("Context initialization failed", var5);
throw var5;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms");
}
}
其中最重要的两行代码
this.initWebApplicationContext()表示初始化WebApplicationContext属性,WebApplicationContext是继承ApplicationContext接口的接口,该属性也是ApplicationContext容器的上下文。
this.initFrameworkServlet()未做任何处理,主要是由子类继承来做一些操作。
进入initWebApplicationContext方法,源码如下:
protected WebApplicationContext initWebApplicationContext() {
//创建父容器,并将父容器和子容器做一个关联
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//以ContextAttribute为key在ApplicationContext获取WebApplicationContext,一般不会设置,所以这里一般为空
wac = this.findWebApplicationContext();
}
if (wac == null) {
//创建WebApplicationContext对象,并把根上下文设置为WebApplicationContext的父上下文
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//WebApplicationContext创建成功后,就会进行onRefresh操作
this.onRefresh(wac);
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
接下来我们看下这个onRefresh方法,这个方法在其子类(DispatchServlet)中实现源码如下
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
方法中只有一个方法,进入到这个initStrategies中,源码如下
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这个时候springmvc就开始去加载对应的一些模块中主要的组件,比如initMultipartResolver用来springmvc处理文件的上传,initLocaleResolver(context)用来处理国际话语言相关的一些操作(目前为止没有接触到。。。。)initThemeResolver().这个是用来处理一些有关动态更换样式的支持(主题)。好像也没有使用过。。。initHandlerMappings()这个很重要处理我们经常听到的有关url和controller的映射关系,initHandlerAdapters()处理映射有关的适配相关。initHandlerExceptionResolvers(context);springmvc有关异常的处理。initRequestToViewNameTranslator(context)处理请求到视图名称的一个转换。initViewResolvers()处理视图。初始化操作到这里基本已经结束。
DispatcherServlet处理请求过程
前面提到DispatcherServlet本质就是一个servlet,那么处理的请求的方法就是service方法,而DispatcherServlet中并没有这个方法,我们看下其父类(FrameworkServlet),在父类找到了这个service方法
我们进入这个processRequest方法,在里面可以看到这么一句
这个方法在FrameworkServlet是一个抽象的方法
protected abstract void doService(HttpServletRequest httpservletrequest,
HttpServletResponse httpservletresponse) throws Exception;
这说明其子类中肯定对它进行了重写,我们看下它的子类也就是DispatcherServlet中的这个方法,在这个doService方法找到了这么一个方法
源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
这个方法才是真正处理我们请求的方法。下面通过一个例子结合源码来分析springmvc的执行流程
使用配置的方式来加载controller
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>dispatch</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatch</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
spring-mvc.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="/demo1" class="com.linewell.springmvc.Demo1.BeanController"></bean>
</beans>
controller如下
package com.linewell.springmvc.Demo1;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BeanController implements Controller {
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
System.out.println("BeanController run。。。。。。。");
return null;
}
}
断点位置
这一步根据请求的url获取handler,进入方法里面
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
这里有两个HandlerMapping,它会根据你配置的方法来决定使用哪一个 HandlerMapping,通过HandlerMapping来查找Handler并返回一个HandlerExcutionChain(执行链,包括handler和interceptor)对象, 我们看下这个HandlerExcutionChain对象
由此可以看出返回的HandlerExcutionChain中包含一个处理器和拦截器,返回HandlerExcutionChain给前端控制器之后,前端控制器会调用
处理器适配器,如下图
进入getHandlerAdpter方法,源码如下
这里有三个拦截器,通过进一步的分析
由此看出SimpleControllerHandlerAdapter这个适配器会匹配上,最终由这个适配器来执行handler并返回一个ModelAndView。
当使用注解的方式来加载controller时,处理器映射器使用的是RequestMappingHandlerMapping
处理器适配器使用的是RequestMappingHandlerAdapter
有兴趣的可以自行debug调试看下。本人菜鸟一个,如有不对的地方还请各位大佬不吝赐教。