0 使用篇
因为我引用上面的文章中的项目时,tomcat总是启动不起来,所以换了一个简单版的:一个简单的SSM框架实例(使用IDEA)
1 预备知识篇:
1.1 使用tomcat+spring时ApplicationContext的创建
实际上,使用tomcat+spring时,一共会创建两个ApplicationContext,分别是:
- ContextLoaderListener创建根ROOT WebApplicationContext,父节点为空。
- Serlvet创建springmvc相关的WebApplicationContext,其父节点为根ROOT WebApplicationContext。
详见Tomcat+Spring中有几个ApplicationContext?它们的关系是什么样的?
这里再说明一点:Serlvet创建普通WebApplicationContext和ServletContext不是一个东西,前者是spring的applicationContext,后者是java web里用于多个Servlet共享资源的context。
1.1.1 ContextLoaderListener
在web.xml中,会设置以下参数:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
可以看到,在web.xml中定义了监听器ContextLoaderListener,因此tomcat会在启动时运行ContextLoaderListener。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
...
public void contextInitialized(ServletContextEvent event) {
//初始化WebApplicationContext
this.initWebApplicationContext(event.getServletContext());
}
...
}
启动tomcat时会去调用contextInitialized()方法,接着往下看:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//已经存在,则抛错
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
//...log相关,略
long startTime = System.currentTimeMillis();
try {
//1. 如果为空,则会创建(这里的为根Root)WebApplicationContext
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
//2. 设置根Root WebApplicationContext的父为null(这里的parent为null)
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//3. 配置WebApplicationContext并调用最重要的refresh()方法初始化spring容器。
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//4. 将Root WebApplicationContext配置到servletContext里
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
//...忽略一些代码
return this.context;
}
//...忽略catch
}
从上面的代码可以看到,初始化WebApplicationContext主要有以下两步:
- 创建WebApplicationContext:createWebApplicationContext()
- 配置WebApplicationContext并调用最重要的refresh()方法初始化spring容器:configureAndRefreshWebApplicationContext()
1.1.1.1 ContextLoader.createWebApplicationContext(ServletContext sc)
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//1. 根据ServletContext 生成WebApplicationContext的Class对象
Class<?> contextClass = this.determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
//2. 使用反射方法生成实例
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
再看一看determineContextClass方法:
protected Class<?> determineContextClass(ServletContext servletContext) {
//尝试从servletContext中获取参数contextClass,但默认是没有的
String contextClassName = servletContext.getInitParameter("contextClass");
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
...
}
} else {
//会走这,获取WebApplicationContext.class
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
1.1.1.2 ContextLoader.configureAndRefreshWebApplicationContext()
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String configLocationParam;
//1. wac中设置id、ServletContext属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
configLocationParam = sc.getInitParameter("contextId");
if (configLocationParam != null) {
wac.setId(configLocationParam);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//2. 获取web.xml中的<context-param>标签中的contextConfigLocation属性的值,
// 本文为:“classpath:applicationContext.xml”,并存入wac中。
configLocationParam = sc.getInitParameter("contextConfigLocation");
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//3. 初始化wac中的environment的PropertySources,不展开讲
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
}
this.customizeContext(sc, wac);
//4. 调用refresh()方法!
wac.refresh();
}
1.1.1.3 ContextLoaderListener流程图
1.1.2 FrameworkServlet创建WebApplicationContext
创建WebApplicationContext在FrameworkServlet的initWebApplicationContext()方法中
1.1.2.1 initWebApplicationContext()
protected WebApplicationContext initWebApplicationContext() {
//1, 获得根Root WebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//2. 初始化时不会走这部分,略
if (this.webApplicationContext != null) {
...
}
//3. 寻找WebApplicationContext
if (wac == null) {
wac = findWebApplicationContext();
}
//4. 找不到的话就创建一个WebApplicationContext
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//5. onfresh让子类DispatcherServlet来实现
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//6, 获取到WebApplicationContext后,设置到当前servlet内。
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
其中第四步createWebApplicationContext()与1.1.1.1小节的虽然方法名一样,但是参数不同,具体实现有一些差别,而第五步onRefresh()方法则会在第二章中进行介绍。
1.1.2.2 FrameworkServlet.createWebApplicationContext(ApplicationContext parent)
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//1. 获取contextClass
Class<?> contextClass = getContextClass();
//...进行一些判断,略
//2. 根据contextClass生成上下文WebApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//3. 为WebApplicationContext设置属性,parent为根Root WebApplicationContext
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
//4. 再配置一些属性后,会调用refresh()方法初始化spring容器
configureAndRefreshWebApplicationContext(wac);
return wac;
}
可以看到,与1.1.1.1小节相比,前两步都是一样的,但在FrameworkServlet中,configureAndRefreshWebApplicationContext(wac)方法是内置在创建方法中的。
1.1.2.3 FrameworkServlet.configureAndRefreshWebApplicationContext(wac)
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//1. 配置id,servletContext,ServletConfig等属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) { information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//2. 初始化wac中的environment的PropertySources,不展开讲
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//3. refresh方法!
wac.refresh();
}
1.1.2.3 FrameworkServlet流程图
1.2 springmvc总体流程图:
2 分发器DispatcherServlet
2.1 初始化
2.1.1 HttpServletBean的初始化
初始化的部分个人觉得上面的作者还是有所遗漏的,首先是HttpServletBean类的init()方法:
@Override
public final void init() throws ServletException {
//...
try {
//1. 从解析web.xml中DispatcherServlet的参数,例如contextConfigLocation
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//2. 建立一个this(DispatcherServlet本身)的BeanWrapper,后面一系列操作都是为了将pvs作为属性注入到DispatcherServlet中
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) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
//3. 这里为空实现,会交给子类FrameworkServlet来完成
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
可以看到,HttpServletBean的init()方法的主要任务是解析web.xml中DispatcherServlet的参数,并将解析结果pv注入到this中。接着会去调用子类FrameworkServlet的initServletBean()来进行初始化。
2.1.2 FrameworkServlet的初始化
@Override
protected final void initServletBean() throws ServletException {
...日志
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
//默认实现为空
initFrameworkServlet();
}
catch...
...日志
}
initWebApplicationContext()方法在1.1.2.1小节已经介绍。
2.1.3 DispatcherServlet的初始化
但里面的onRefresh()方法由DispatcherServlet介绍:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
//初始化HandlerMapping
initHandlerMappings(context);
//初始化HandlerAdapter
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
2.1.4 初始化流程图
2.2 处理请求
详见SpringMVC源码阅读:核心分发器DispatcherServlet的第3节,附上自己的总结的流程图:
3 HandlerMapping
传送门:SpringMVC工作原理之处理映射[HandlerMapping],这篇讲得很好
实际应用:SpringMVC RESTful 性能优化,(springboot也可以用到,我自己测试的时候,大概写了180个接口,rest是74ms,非rest是76ms,差别不大,不知道是哪出了问题)
补充一下:
- 文章2.1.1小节中,AbstractUrlHandlerMapping等类之所以会在生成时自动调用initApplicationContext()方法,原因在于可以父类ApplicationObjectSupport的setApplicationContext()方法会调用:
public abstract class ApplicationObjectSupport implements ApplicationContextAware {
...
public final void setApplicationContext(ApplicationContext context) throws BeansException {
if (context == null && !this.isContextRequired()) {
...
} else if (this.applicationContext == null) {
...
//注意这里
this.initApplicationContext(context);
}
...
}
- detectHandlers()方法中,获得容器内所有的beanName,这里的容器是springmvc相关的WebApplicationContext,不包括根Root WebApplicationContext。
一图概括:
(注:上图引用自https://www.jianshu.com/p/f04816ee2495)
整个的流程图:
4 拦截器Interceptor
传送门:SpringMVC源码阅读:拦截器
该章内容较为简单,主要阐述了HandlerChain中的另一个组成部分拦截器。
再补充一下filter和interceptor的区别:过滤器(Filter)与拦截器(Interceptor )区别
5 HandlerAdapter
传送门:SpringMVC工作原理之适配器[HandlerAdapter]
和SpringMVC工作原理之参数解析
(注:上图引用自https://www.jianshu.com/p/2bfd65bc9ce4)
整个的流程图:
6 视图解析器
预备知识:
- 使用@RestController或@ResponseBody的话,HandlerAdapter返回的ModelAndView为null。
- Model指的是返回时的数据,而view指的是具体的视图是哪一个。
例如下面的代码中,Model就是(“hello”->“欢迎学习springmvc!”)
view是路径为"/WEB-INF/jsps/index.jsp"的JstlView
@RequestMapping(value = "/test",method = RequestMethod.GET)
public ModelAndView test(@RequestParam String hello){
ModelAndView mv = new ModelAndView();
mv.addObject("hello","欢迎学习springmvc!");
mv.setViewName("index");
return mv;
}
7 <mvc:annotation-driven/>
使用mvc:annotation-driven/标签时,refresh()中解析BeanDefinition的时候,AnnotationDrivenBeanDefinitionParser会专门负责解析mvc:annotation-driven/这个标签。
解析的过程实际上就是注册HandlerMappings,HandlerAdapters,
HandlerExceptionResolvers 等BeanDefinition。