Spring源码解析(I) 基于SSM看Spring的使用和Spring启动监听

  created  by  鱼鱼 {{tag}}
创建于 2020年07月29日 18:53:11 最后修改于 2020年08月04日 12:14:41

怎么看?

    查看源码的顺序就见仁见智了,比较普遍的做法是从IoC入手,了解容器注入的每一个环节,掌握大致的流程。

使用Spring

使用Spring实现一个IoC

    由于使用的是Spring,所以在这里我们引入比较古老的xml配置文件进行bean的配置,首先定义一个bean:

package com.test;
public class HelloworldBean{
    public void hello(){
        System.out.println("HelloWorld");
    }
}

    配置描述bean的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-2.5.xsd">
    <bean id="helloWorld" class="com.test.HelloWorld">
</beans>

    这样一来就可以使用BeanFactory这个容器来注入bean并使用了:

  Resource resource = new ClassPathResource("application.xml");
  BeanFactory beanFactory = new DefaultListableBeanFactory();
  BeanDefinitionReader beanDefinitionReader = 
              new XmlBeanDefinitionReader((BeanDefinitionRegistry)beanFactory);
  beanDefinitionReader.loadBeanDefinitions(resource);
  HelloworldBean hb = (HelloworldBean)beanFactory.getBean("hellloWorld");
  hb.hello();

    本来有封装好的XmlBeanFactory,这一类现在已经被弃用了,所以采用了他的父类DefaultListableBeanFactory;当然,也可以使用更加方便和常用的ApplicationContext:

    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("application.xml");
    HelloworldBean hb = (HelloworldBean) applicationContext.getBean("hellloWorld");
    hb.hello();

    当然从xml文件读取bean的配置只是其中一种目前用的不多的加载方式,还有基于包扫描等加载bean的方法,此处仅为理解IoC的基本使用。

使用Web容器激活Spring

    让我们回忆一下经典的“SSM”架构的配置,绝大多数的Java项目都是web项目,我们想要为程序提供一个入口,使用Web容器诸如 Tomcat,为其提供配置,让其扫描并加载beans,为此,我们可以定义一下web.xml,指定初始化的类和beans的配置文件路径:

<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_3_1.xsd"  version="3.1" metadata-complete="true">    
    <listener>    
        <description>springListener</description>    
        <!-- 这个listener会监听到web容器的启动事件 -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    
    </listener>    
    <servlet>    
        <servlet-name>seckill-dispatcher</servlet-name>    
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
        <!-- contain spring-dao.xml,spring-service.xml,spring-web.xml   -->    
        <init-param>    
            <param-name>encoding</param-name>    
            <param-value>UTF-8</param-value>    
        </init-param>    
        <init-param>    
            <param-name>contextConfigLocation</param-name>    
            <param-value>classpath:spring/spring-*.xml</param-value>    
        </init-param>    
    </servlet>
    <!-- 以下是SpringMVC的配置  -->    
    <servlet-mapping>    
        <servlet-name>seckill-dispatcher</servlet-name>    
        <url-pattern>/</url-pattern>    
    </servlet-mapping>    
</web-app>

    这样一来当我们启动web容器时,ContextLoaderListener就会监听到启动事件并进行Spring的初始化行为。

Spring启动监听器——ContextLoaderListener

类定义

    在上面的web.xml文件中,我们除去定义了Spring相关配置文件外,还定义了一个listener,最常用的ContextLoaderListener,我们首先看此类的注释:

 /*
 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
 *
 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 17.02.2003
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{

    public ContextLoaderListener() {
    }
    
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    @Override
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}

    此类实现了ServletContextListener接口,其中 后者由Servlet提供,包含了contextInitialized和contextDestroyed两个监听事件(方法),这意味着在servlet容器(例如 Tomcat)启动和销毁时,均会触发此Listener。

    此类也继承了ContextLoader类,这是由Spring实现的上下文加载类,继承是为调用相关方法,ServletContextListener提供的触发方法在其中的实现其实全部交给了其父类ContextLoader中的实现。

ContextLoader的实现-initWebApplicationContext方法

    ContextLoader封装了context的初始化逻辑。

    我们主要研究容器启动时的操作:即initWebApplicationContext方法,最终生成了WebApplicationContext,是一个很常见的ApplicationContext继承,注意里面的中文注释,对于文中获取this.context后的操作暂时不在此篇讲述:

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 {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      //如果非空 是针对Servlet 3.1+自定义Context的情况
      if (this.context == null) {
         //创建一个ApplicationContext
         this.context = createWebApplicationContext(servletContext);
      }
      //对于默认的XmlWebApplicationContext 肯定会进入这一分支
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            //在此处会加载具体的配置项,例如contextConfigLocation
            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;
   }
}

    在createWebpplicationContext方法中:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   //获取Context类
   Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

获取context-determineContextClass方法

    接下来进入determineContextClass方法:

/**
 * Return the WebApplicationContext implementation class to use, either the
 * default XmlWebApplicationContext or a custom context class if specified.
 * @param servletContext current servlet context
 * @return the WebApplicationContext implementation class to use
 * @see #CONTEXT_CLASS_PARAM
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected Class<?> determineContextClass(ServletContext servletContext) {
   //查找有无自定义contextClass,一般没有
   String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }
   //从defaultStrategies获取webApplicationContext类,defaultStrategies的初始化在静态块中
   else {
      //上面的注释已经提到 此处会默认初始化XmlWebApplicationContext类
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      try {
         return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load default context class [" + contextClassName + "]", ex);
      }
   }
}

    这里的WebApplicationContext是一个接口,其通过defaultStrategies默认的实现为XmlWebApplicationContext,即使用web.xml配置加载IoC容器相关的信息,defaultStrategies是一个配置信息类,让我们看看他的定义和初始化:

private static final Properties defaultStrategies;

static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
   }
}


评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

Spring源码解析(I) 基于SSM看Spring的使用和Spring启动监听

Spring源码解析(I) 基于SSM看Spring的使用和Spring启动监听

怎么看?

    查看源码的顺序就见仁见智了,比较普遍的做法是从IoC入手,了解容器注入的每一个环节,掌握大致的流程。

使用Spring

使用Spring实现一个IoC

    由于使用的是Spring,所以在这里我们引入比较古老的xml配置文件进行bean的配置,首先定义一个bean:

package com.test;
public class HelloworldBean{
    public void hello(){
        System.out.println("HelloWorld");
    }
}

    配置描述bean的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-2.5.xsd">
    <bean id="tmphelloWorld" class="com.test.HelloWorld">
</beans>

    这样一来就可以使用BeanFactory这个容器来注入bean并使用了:

  Resource resource = new ClassPathResource("application.xml");
  BeanFactory beanFactory = new DefaultListableBeanFactory();
  BeanDefinitionReader beanDefinitionReader = 
              new XmlBeanDefinitionReader((BeanDefinitionRegistry)beanFactory);
  beanDefinitionReader.loadBeanDefinitions(resource);
  HelloworldBean hb = (HelloworldBean)beanFactory.getBean("hellloWorld");
  hb.hello();

    本来有封装好的XmlBeanFactory,这一类现在已经被弃用了,所以采用了他的父类DefaultListableBeanFactory;当然,也可以使用更加方便和常用的ApplicationContext:

    ApplicationContext applicationContext= new ClassPathXmlApplicationContext("application.xml");
    HelloworldBean hb = (HelloworldBean) applicationContext.getBean("hellloWorld");
    hb.hello();

    当然从xml文件读取bean的配置只是其中一种目前用的不多的加载方式,还有基于包扫描等加载bean的方法,此处仅为理解IoC的基本使用。

使用Web容器激活Spring

    让我们回忆一下经典的“SSM”架构的配置,绝大多数的Java项目都是web项目,我们想要为程序提供一个入口,使用Web容器诸如 Tomcat,为其提供配置,让其扫描并加载beans,为此,我们可以定义一下web.xml,指定初始化的类和beans的配置文件路径:

<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_3_1.xsd"  version="3.1" metadata-complete="true">    
    <listener>    
        <description>springListener</description>    
        <!-- 这个listener会监听到web容器的启动事件 -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    
    </listener>    
    <servlet>    
        <servlet-name>seckill-dispatcher</servlet-name>    
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
        <!-- contain spring-dao.xml,spring-service.xml,spring-web.xml   -->    
        <init-param>    
            <param-name>encoding</param-name>    
            <param-value>UTF-8</param-value>    
        </init-param>    
        <init-param>    
            <param-name>contextConfigLocation</param-name>    
            <param-value>classpath:spring/spring-*.xml</param-value>    
        </init-param>    
    </servlet>
    <!-- 以下是SpringMVC的配置  -->    
    <servlet-mapping>    
        <servlet-name>seckill-dispatcher</servlet-name>    
        <url-pattern>/</url-pattern>    
    </servlet-mapping>    
</web-app>

    这样一来当我们启动web容器时,ContextLoaderListener就会监听到启动事件并进行Spring的初始化行为。

Spring启动监听器——ContextLoaderListener

类定义

    在上面的web.xml文件中,我们除去定义了Spring相关配置文件外,还定义了一个listener,最常用的ContextLoaderListener,我们首先看此类的注释:

 /*
 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
 *
 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 17.02.2003
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{

    public ContextLoaderListener() {
    }
    
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    @Override
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}

    此类实现了ServletContextListener接口,其中 后者由Servlet提供,包含了contextInitialized和contextDestroyed两个监听事件(方法),这意味着在servlet容器(例如 Tomcat)启动和销毁时,均会触发此Listener。

    此类也继承了ContextLoader类,这是由Spring实现的上下文加载类,继承是为调用相关方法,ServletContextListener提供的触发方法在其中的实现其实全部交给了其父类ContextLoader中的实现。

ContextLoader的实现-initWebApplicationContext方法

    ContextLoader封装了context的初始化逻辑。

    我们主要研究容器启动时的操作:即initWebApplicationContext方法,最终生成了WebApplicationContext,是一个很常见的ApplicationContext继承,注意里面的中文注释,对于文中获取this.context后的操作暂时不在此篇讲述:

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 {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      //如果非空 是针对Servlet 3.1+自定义Context的情况
      if (this.context == null) {
         //创建一个ApplicationContext
         this.context = createWebApplicationContext(servletContext);
      }
      //对于默认的XmlWebApplicationContext 肯定会进入这一分支
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            //在此处会加载具体的配置项,例如contextConfigLocation
            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;
   }
}

    在createWebpplicationContext方法中:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   //获取Context类
   Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

获取context-determineContextClass方法

    接下来进入determineContextClass方法:

/**
 * Return the WebApplicationContext implementation class to use, either the
 * default XmlWebApplicationContext or a custom context class if specified.
 * @param servletContext current servlet context
 * @return the WebApplicationContext implementation class to use
 * @see #CONTEXT_CLASS_PARAM
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected Class<?> determineContextClass(ServletContext servletContext) {
   //查找有无自定义contextClass,一般没有
   String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }
   //从defaultStrategies获取webApplicationContext类,defaultStrategies的初始化在静态块中
   else {
      //上面的注释已经提到 此处会默认初始化XmlWebApplicationContext类
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      try {
         return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load default context class [" + contextClassName + "]", ex);
      }
   }
}

    这里的WebApplicationContext是一个接口,其通过defaultStrategies默认的实现为XmlWebApplicationContext,即使用web.xml配置加载IoC容器相关的信息,defaultStrategies是一个配置信息类,让我们看看他的定义和初始化:

private static final Properties defaultStrategies;

static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
   }
}



Spring源码解析(I) 基于SSM看Spring的使用和Spring启动监听2020-08-04鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论