1. 目录 [TOC]
2. 说明 梳理源码就从最原始的方式开始,使用xml加入tomcat是最直接引用spring的方式。 之所以不用springboot,虽然它搭建简单,但springboot脚手架为我们做了很多事,引入spring的地方不直观,不利于分析源码。
首先配置web工程,web工程大家都知道是通过tomcat启动的,在web.xml配置参数,调用spring对应的类,这样spring就启动了
3. 依赖关系 4. 源码解析 4.1. tomcat的启动 tomcat的启动流程咱就不分析了,这里主要从Tomcat提供的钩子函数开始。主要配置在web.xml
中 主要配置有:
filter: 配置过滤器
listener:配置监听器,这也是spring启动最主要的钩子函数,必须继承javax.servlet.ServletContextListener
servlet: 配置servlet启动,这里主要是spring-mvc请求的钩子函数
context-param:自定义配置,需要的时候可以从ServletContext中获取
welcome-file-list:首页,这个暂时不考虑
启动配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > <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 > <listener > <listener-class > com.kewen.listener.MySessionListener</listener-class > </listener > <listener > <listener-class > com.kewen.listener.MyRequestListener</listener-class > </listener > <filter > <filter-name > characterEncodingFilter</filter-name > <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class > <init-param > <param-name > encoding</param-name > <param-value > UTF-8</param-value > </init-param > </filter > <filter-mapping > <filter-name > characterEncodingFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <servlet > <servlet-name > dispatcher</servlet-name > <servlet-class > com.kewen.MyDispatcherServlet</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 > dispatcher</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > <welcome-file-list > <welcome-file > /page/hello.jsp</welcome-file > </welcome-file-list > </web-app >
这里配置了监听器org.springframework.web.context.ContextLoaderListener
,Tomcat执行到org.apache.catalina.core.StandardContext#listenerStart()
方法的时候会执行监听器方法listener.contextInitialized(event);
,通过此方法调用起xml自定义配置的监听器,此处配置了spring的类org.springframework.web.context.ContextLoaderListener
,从名字可以看出,这是spring的上下文加载监听器,也就是spring上下文初始化的入口函数。
另外,通过<servlet-mapping>
标签和<servlet>
标签映射浏览器路径对应的servlet类,这里配置的时候直接配置了/
路径,即所有路径均对应MyDispaterServlet
类,springmvc即通过此配置映射请求,进入处理的,这个后续在解析。
4.2. spring 上下文初始化 我们已经得知,spring用org.springframework.web.context.ContextLoaderListener
来初始化容器的,我们看看它的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener () { } public ContextLoaderListener (WebApplicationContext context) { super (context); } @Override public void contextInitialized (ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed (ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
4.2.1. 调起spring tomcat通过无参构造创建类,调起ServletContextListener#contextInitialized()
方法,ServletContextListener
重写此方法,调用内部方法initWebApplicationContext()
,此时正式进入到spring流程,除了ServletContext
需要用到外,我们不在关注tomcat的其余东西。
4.2.2. ContextLoaderListener ContextLoaderListener
继承至ContextLoader
,自己类本身就只是为了唤起spring的加载流程,唤起了之后主要就是ContextLoader
类的职责了,正式开始初始化spring
4.2.3. ContextLoader
ContextLoader
上下文加载器,用于加载spring的全流程。 接下来我们分析ContextLoader
源码。
4.2.3.1. ContextLoader#initWebApplicationContext()
我们本着最小代码流程的原则来分析仅启动用到的分支逻辑,对于未用到的暂不分析,这样可以直观的保证整个流程疏通,再扩展将会变得容易。
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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!" ); } servletContext.log("Initializing Spring root WebApplicationContext" ); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started" ); } long startTime = System.currentTimeMillis(); try { if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } if (this .context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this .context; if (!cwac.isActive()) { if (cwac.getParent() == null ) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this .context; } else if (ccl != null ) { currentContextPerThread.put(ccl, this .context); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms" ); } return this .context; } catch (RuntimeException | Error ex) { logger.error("Context initialization failed" , ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
此处主要创建上下文,然后调用配置和刷新方法configureAndRefreshWebApplicationContext
4.2.3.2. ContextLoader#configureAndRefreshWebApplicationContext()
源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 protected void configureAndRefreshWebApplicationContext (ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null ) { wac.setId(idParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null ) { wac.setConfigLocation(configLocationParam); } ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null ); } customizeContext(sc, wac); wac.refresh(); }
4.2.3.3. ContextLoader#customizeContext()
主要加载ApplicationContextInitializer并执行初始化方法initialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 protected void customizeContext (ServletContext sc, ConfigurableWebApplicationContext wac) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException (String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]" , initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } this .contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } AnnotationAwareOrderComparator.sort(this .contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this .contextInitializers) { initializer.initialize(wac); } }
4.2.3.4. XmlWebApplicationContext#refresh()
刷新完成之后spring就正式启动了,关于刷新流程,专门用一节来讲,涉及到整个初始化过程
总结
spring通过ContextLoaderListener
实现tomcat的ServletContextListener
,从而唤起spring的初始化
ContextLoaderListener
的initWebApplication()
方法开始初始化上下文
初始化上下文中首先会创建上下文,创建的时候首先找servletcontext中有没,实际没有,然后根据spring自带的配置ContextLoader.properties
指定webApplication
初始化完成后从ServletContext
中获取父上下文(第一次创建肯定没)
配置和刷新
将servletContext
中存储的xml中的配置信息传给上下文(主要是contextConfigLocation:classpath:applicationContext.xml
即指定spring配置文件)
初始化环境变量
加载springxml中自定义的ApplicationContextInitializer
并执行initialize()
方法( 从web.xml中加载配置contextInitializerClasses)
刷新容器