深入解析 spring 启动过程

2018-06-22 16:49:51   最后更新: 2018-06-24 16:54:06   访问数量:115




此前,我们介绍了很多关于 spring 使用的日志,但是 spring 究竟是如何启动的呢?他的工作机制又是怎样的呢?

本文我们就来剖开源码,深入解读 Spring 的启动、类加载过程

 

传统上,我们一般会在 web.xml 中配置 Servlet 监听器

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>techlog</display-name> <!-- Web全局参数 --> <context-param> <param-name>webAppRootKey</param-name> <param-value>techlog</param-value> </context-param> <!-- 加载spring根环境 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext*.xml</param-value> </context-param> <!-- servlet映射到springmvc --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc/springmvc-servlet.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

 

 

j2ee 标准规定,servlet 容器需要在应用项目启动时,给应用项目初始化一个 ServletContext 作为公共环境容器存放公共信息。ServletContext 中的信息都是由容器提供的

web 容器会首先创建一个 ServletContext 并完成初始化工作,一个容器启动的所有信息都会放到这个 ServletContext 中

 

web.xml 文件中配置的 org.springframework.web.context.ContextLoaderListener 类实现了 ServletContextListener 接口的两个方法:contextInitialized 与 contextDestroyed

当 web 容器(如 tomcat 或 jetty)启动时,根据规范,容器会自动调用 contextInitialized 方法,而当容器销毁时,会自动调用 contextDestroyed 方法

容器初始化时,调用 ContextLoaderListener 类的 contextInitialized 方法,这个方法会初始化一个启动上下文,这个上下文被称为根上下文,即 WebApplicationContext,其实现类是 XmlWebApplicationContext,这个就是spring的IoC容器,其对应的 Bean 定义的配置由 web.xml 中的 context-param 标签指定,初始化完成后,spring 调用 ServletContext 的 setAttribute 方法,将 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为属性 key 放入 ServletContext 中,同时,他会将 context-param 指定的配置文件路径下或默认的 /WEB-INF/ 下的 applicationContext.xml 文件的配置文件加载并初始化

contextLoaderListener 监听器初始化完毕后,容器开始初始化 web.xml 中配置的Servlet,最常见的就是 DispatcherServlet,DispatcherServlet 在执行 initStrategies 方法初始化时,会调用 ServletContext 的 getAttribute 方法,通过 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 从 ServletContext 中获取根上下文,并将根上下文设置为自己的父上下文环境,然后创建自己上下文环境,加载 web.xml 中 contextConfigLocation 对应的 xml

接着,其他 Servlet 初始化,他们都会将 WebApplicationContext 设置为自己的父上下文环境,同时初始化自己的 ApplicationContext

当 spring 执行 ApplicationContext 的 getBean 方法时,只有在自己的 context 中找不到对应的 bean 时,才会在父 ApplicationContext 中寻找

这也就解释了为什么我们要在 applicationContext.xml 中排除所有 controller 而要在 spring-mvc 中 include 所有 controller,因为 applicationContext.xml 是在 ContextLoaderListener 初始化过程中加载的,而此时只需要加载 service、dao 等必要的配置信息,controller 是 DispatcherServlet 所需要,只要让 DispatcherServlet 初始化时加载即可

 

在 Spring3.1 开始可以使用 Servlet3.0 技术,实现通过代码编写注册核心控制器

按照 Servlet3.0 规范,Servlet 启动时,会按照 @Order 注解参数的顺序加载 war、jar 包中所有实现了 WebApplicationInitializer 接口的类

 

上一篇日志中,我们介绍了如何配置通过 WebApplicationInitializer 启动 Spring 项目,并且最终给出了一个相当精炼的简化版本代码:

通过 WebApplicationInitializer 初始化 Servlet 及 Filter

@Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(RootConfig.class); servletContext.addListener(new ContextLoaderListener(rootAppContext)); AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext(); servletAppContext.register(WebConfig.class); DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext); ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet); registration.setLoadOnStartup(1); registration.addMapping("/"); registration.setAsyncSupported(true); }

 

 

可以看到,这里我们在 ServletContext 中设置了根 ApplicationContext 与 DispatcherServlet,实际上与上述所说的 web.xml 的配置信息是一样的,只是这里我们没有使用 XmlWebApplicationContext 作为根 ApplicationContext,取而代之,我们使用了 AnnotationConfigWebApplicationContext,在 AnnotationConfigWebApplicationContext 中,不再进行 xml 的解析,而是直接对传入的 class 对象进行了解析

同时,传入的配置信息也不再是 applicationContext.xml 的配置,而是 class 对象的 List

这之后,由于 ServletContext 中注册了 listener -- ContextLoaderListener,整个启动过程又回到了上面我们分析依赖 web.xml 启动的过程中了

 

 

 






技术帖      技术分享      容器      java      spring      j2ee      servlet      dispatcherservlet      webxml     


京ICP备15018585号