从启动日志看Spring IOC的初始化和Bean生命周期

时间:2022-05-23 08:16:27

一、Tomcat中启动IoC容器的日志

启动Tomcat等容器时,控制台每次都打印出一些日志。

最近刚好在研究Spring源码,所以换个角度,从启动日志来简单的看看Spring的初始化过程!

以下是Tomcat启动时日志,截取Spring部分。

//-------------------------------------
//从这里开始Spring的初始化
十一月 10, 2015 8:52:03 上午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
十一月 10, 2015 8:52:03 上午 org.springframework.web.context.ContextLoader initWebApplicationContext

//ApplicationContext的预处理prepareRefresh
信息: Root WebApplicationContext: initialization started
十一月 10, 2015 8:52:04 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing Root WebApplicationContext: startup date [Tue Nov 10 08:52:04 CST 2015]; root of context hierarchy

//XmlBeanDefinitionReader的loadBeanDefinitions,加载bean
十一月 10, 2015 8:52:04 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
//<!-- bean定义的配置文件 -->
//<context-param>
//    <param-name>contextConfigLocation</param-name>
//    <param-value>classpath:applicationContext.xml</param-value>
//</context-param>
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
//初始化单例的bean
//由于applicationContext.xml中定义了数据源所以有dataSource、jdbcTemplate,定义了事务所以有transactionManager、txAdvice
十一月 10, 2015 8:52:06 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6f1ad0: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,dataSource,jdbcTemplate,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,serviceMethod,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0,txAdvice,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy

//初始化C3P0数据库连接池
//mchange是C3P0的依赖包
十一月 10, 2015 8:52:06 上午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
十一月 10, 2015 8:52:07 上午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]

十一月 10, 2015 8:52:08 上午 org.springframework.web.context.ContextLoader initWebApplicationContext
信息: Root WebApplicationContext: initialization completed in 4470 ms
//到这里完成了WebApplicationContext的初始化
//-------------------------------------

二、大概流程:

1、初始化方法是在ContextLoader.initWebApplicationContext()中完成的;

2、首先进行的是预处理操作:AbstractApplicationContext.prepareRefresh();

3、然后从资源(这里是applicationContext.xml)中读取bean的解析、加载bean:XmlBeanDefinitionReader.loadBeanDefinitions();

4、实例化单例的bean,实例化scope="singleton"(默认)且无lazy-init="true"的bean:DefaultListableBeanFactory.preInstantiateSingletons();

5、注册C3P0数据库连接池:C3P0Registry.banner();

6、到此完成了WebApplicationContext的初始化!

从上面的步奏可以大概看出Spring初始化的步奏,当然每个方法都隐藏了很多细节,后面再逐个慢慢品味吧!

------------------------------------------------------------------------------------------------------

三、打开Debug日志,进一步观察:

默认的Spring自带了log4j,但是需要我们配置。如果不配置,像debug没有打印出来,下面我们将debug等信息输出到控制台,便于我们分析源码!

在web.xml中声明log4.properties,在资源文件中配置:(调到Debug级别)

log4j.rootLogger=DEBUG, R
log4j.appender.R = org.apache.log4j.ConsoleAppender
log4j.appender.R.Target = System.out
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern =[SSH] %p %d{yyyy-MM-dd HH:mm:ss.SSS} %c %m(%L) %n

然后,我们就可以看到debug日志了!

从启动日志看Spring IOC的初始化和Bean生命周期

关键日志分析:

可以结合Bean的生命周期一起分析:

从启动日志看Spring IOC的初始化和Bean生命周期

为了便于观察,applicationContext.xml中只配置最基本的bean

    <bean id="book" name="book" class="com.sky.vo.Book" init-method="productBook" destroy-method="destroyBook">
        <property name="title" value="小王子"/>
    </bean>

    <bean id="myBeanFactoryPostFactory" class="com.sky.processor.MyBeanFactoryPostFactory"/>

    <bean id="myBeanPostProcessor" class="com.sky.processor.MyBeanPostProcessor"/>

四、下面是日志的分析过程:

搜索web.xml中的配置信息

Adding [servletContextInitParams] PropertySource with lowest search precedence(107) 

XmlWebApplicationContext

XmlWebApplicationContext Refreshing Root WebApplicationContext: startup date [Thu Nov 19 11:18:22 CST 2015]; root of context hierarchy(513)

从XML中读取bean定义信息

XmlBeanDefinitionReader Loading XML bean definitions from class path resource [applicationContext.xml](315) 

使用到了Reader,从applicationContext.xml中读到三个bean(book和myBeanPostProcessor,还有BeanFactoryPostFactory)

  注:定义了后置处理器,除了实现BeanPostProcessor接口,然后像普通bean一样在xml中配置即可!

XmlBeanDefinitionReader Loaded 3 bean definitions from location pattern [classpath:applicationContext.xml](216)

给ApplicationContext配置BeanFactory,中找到<bean id="book">,<bean id="myBeanPostProcessor">的定义

XmlWebApplicationContext Bean factory for Root WebApplicationContext:   org.springframework.beans.factory.support.DefaultListableBeanFactory@148d148:     defining beans [book,myBeanFactoryPostFactory,myBeanPostProcessor]; root of factory hierarchy(543) 

首先实例化单例的BeanFactoryPostProcessor,Spring容器后置处理器

DefaultListableBeanFactory Creating shared instance of singleton bean 'myBeanFactoryPostFactory'(215)
DefaultListableBeanFactory Creating instance of bean 'myBeanFactoryPostFactory'(432) 

调用了他的构造方法,于是控制台打印了:MyBeanFactoryPostProcessor构造初始化

为了解决bean的循环依赖,也把这个容器后置处理器像普通bean一样缓存起来了

Eagerly caching bean 'myBeanFactoryPostFactory' to allow for resolving potential circular references(506) 

到这一步时,完成了Spring容器后置处理器的实例化

DefaultListableBeanFactory Finished creating instance of bean 'myBeanFactoryPostFactory'(460) 

同时马上调用了容器后置处理器的方法,打印出了:MyBeanFactoryPostProcessor调用了postProcessBeanFactory

接下来,实例化BeanPostProcessor后置处理器

DefaultListableBeanFactory Creating shared instance of singleton bean 'myBeanPostProcessor'(215)
DefaultListableBeanFactory Creating instance of bean 'myBeanPostProcessor'(432) 

到这里时,完成了后置处理器的初始化,构造方法被调用了:

控制台输出了:这是自定义的BeanPostProcessor 初始化

然后,像普通的bean那样,缓存下来,以解决潜在的bean循环依赖的问题

DefaultListableBeanFactory Eagerly caching bean 'myBeanPostProcessor' to allow for resolving potential circular references(506) 

继续执行,完成了BeanPostProcessor这个后置处理器的实例化

DefaultListableBeanFactory Finished creating instance of bean 'myBeanPostProcessor'(460) 

给ApplicationContext配置其他,如国际化资源、ApplicationContext事件、主题,这里暂时不太明白作用

XmlWebApplicationContext Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@1fb4479](810)
XmlWebApplicationContext Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster':
 using default [org.springframework.context.event.SimpleApplicationEventMulticaster@1c57594](834) 
UiApplicationContextUtils Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.ResourceBundleThemeSource@1feeacb](85) 

预实例化单例bean

DefaultListableBeanFactory Pre-instantiating singletons in
org.springframework.beans.factory.support.DefaultListableBeanFactory@fe4495: defining beans [book]; root of factory hierarchy(598)

创建book的bean

DefaultListableBeanFactory Creating shared instance of singleton bean 'book'(215)
DefaultListableBeanFactory Creating instance of bean 'book'(432) 

到这里后,开始调用了构造函数,输出了我写的sysout: 控制台:"调用了Book默认构造函数"

缓存id为book的bean,解决潜在的循环引用!

DefaultListableBeanFactory Eagerly caching bean 'book' to allow for resolving potential circular references(506) 

到这里后,调用了setting注入属性title,控制台输出了我写在setTitle方法中语句:set注入了书本标题为:小王子

在setting注入后,随即输出的是:bean处理器:bean创建前,表示在注入后,调用了BeanPostProcessor的postProcessBeforeInitialization方法(看方法名也很直观)

调用InitializingBean接口的afterPropertiesSet方法

DefaultListableBeanFactory Invoking afterPropertiesSet() on bean with name 'book'

这时控制台输出写在afterPropertiesSet的语句:InitializingBean,相当于init-method

调用生命周期方法init-method,在ApplicationContext.xml中bean中配置:

DefaultListableBeanFactory Invoking init method  'productBook' on bean with name 'book'(1612)

这里输出了我自定义的init-method为birth方法的语句:书本初始化init-method

然后这里调用了Bean后置处理器的postProcessAfterInitialization方法,因为控制台打印了:bean处理器:bean创建后

到这里完成了bean的实例化

DefaultListableBeanFactory Finished creating instance of bean 'book'(460) 

接下来和后置处理器相关,不太明白这里的返回是什么意思?

DefaultListableBeanFactory Returning cached instance of singleton bean 'myBeanFactoryPostFactory'(246)
DefaultListableBeanFactory Returning cached instance of singleton bean 'myBeanPostProcessor'(246) 

没有找到生命周期处理器,可能是我们没有定义,所以使用默认的。看不懂,不过看表述是这个意思。

XmlWebApplicationContext Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor@dd7469](861) 

返回缓存了这个处理器

DefaultListableBeanFactory Returning cached instance of singleton bean 'lifecycleProcessor'(246)

接下来的日志,更看不懂!总之,大概是JNDI相关的。。。

PropertySourcesPropertyResolver Searching for key 'spring.liveBeansView.mbeanDomain' in [servletConfigInitParams](81)
PropertySourcesPropertyResolver Searching for key 'spring.liveBeansView.mbeanDomain' in [servletContextInitParams](81)
PropertySourcesPropertyResolver Searching for key 'spring.liveBeansView.mbeanDomain' in [jndiProperties](81)
JndiTemplate Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain](150)
PropertySourcesPropertyResolver Could not find key 'spring.liveBeansView.mbeanDomain' in any property source. Returning [null](103) 

将Spring容器设置到Servlet容器(Web容器)中:

ContextLoader Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT](326) 

五、正确的关闭Tomcat,而非kill时:

销毁单例的bean,从这里也可以看到,Spring容器对prototype的bean不理会

DefaultListableBeanFactory Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@148d148: defining beans
[book,myBeanFactoryPostFactory,myBeanPostProcessor]; root of factory hierarchy(444) 

调用DisposableBean接口的destroy方法

DisposableBeanAdapter Invoking destroy() on bean with name 'book'(226) 

打印出来:DisposableBean,相当于destroy-method

调用bean的destroy-method

DisposableBeanAdapter Invoking destroy method 'destroyBook' on bean with name 'book'(302) 

打印:书本被销毁destroy-method

六、几个不小问题

1、如果name="book"的bean设置lazy-init="true",则启动时不会实例化这个bean,日志中只会打印MyPostBeanProcessor实例化;

2、如果name="book"的bean设置scope="prototype",同样不会在启动时实例化,也不会缓存;什么时候实例化?getBean

而且prototype的bean,IoC容器只负责反射实例化,后续的生命周期不负责,相当于只new,当然像后置处理器等还是要执行的

3、BeanPostProcessor是IoC的一个扩展点,让用户有机会修改bean!而BeanFactoryPostProcessor是让用户有机会修改IoC容器的扩展点!

4、Spring的开闭原则:对扩展开放,对修改关闭??不太理解!

5、init-method和InitializingBean.afterPropertiesSet区别?

  最终作用都一样bean构造后执行初始化的方法

  由于是接口,命名是限定死的,init-method属性更灵活

  其他同样作用的方法,一种特殊的BeanPostProcessor,系统自动的CommonAnnotationBeanPostProcessor

    -由于用标签开启了,<context:annotation-config />,作用类同<bean class="...CommonAnnotationBeanPostProcessor"/>

其中Spring自带的注解@PostConstruct、@PreDestroy,用来修饰Book的另外两个方法,这样同时存在三种初始化方法!

CommonAnnotationBeanPostProcessor Invoking init method on bean 'book

  初始时机:@PreDestroy----->InitializingBean------->init-method

    注销时机:@PreDestroy----->DisposableBean----->destroy-method

6、另外,如果Bean需要持有BeanFactory或ApplicationContext,则可以通过实现BeanFactoryAware和ApplicationContextAware接口获取到,(通过@AutoWired应该也行吧!),调用时期:在实例化后setting的过程中:其实很容易理解,这和普通属性的注入是一样的嘛!

所以:打印

set注入了书本标题为:小王子
注入Bean工厂,通过BeanFactoryAware注入了BeanFactory
注入Spring应用上下文,通过ApplicationContextWare注入ApplicationContext

所以,生命周期图:

从启动日志看Spring IOC的初始化和Bean生命周期

-------------------------------------------------------------------------------------------------------

七、覆盖Spring的源代码

按照网上很多博客,先从GitHub中下载Spring源码,然后使用Gradle编译转换成Eclipse可以导入的工程,但是貌似由于网络原因,一直失败,感觉非常麻烦!

后来想了想,要修改源码,其实还有一个小技巧,就是覆盖jar中文文件!!!

尝试1:直接将源码包下的单个文件夹spring-web下的org文件,复制到用来测试的Spring工程下(已经用pom.xml引入了Spring的所有包),引入之后,一大片报错!缺少很多相关的jar包,总不可能一个个找出来吧!只有放弃!

尝试2:如果我只是修改单个文件呢,如ContextLoader.java,单独复制这个文件会怎样?

结果是还是报错:

从启动日志看Spring IOC的初始化和Bean生命周期

不过比想象中好很多,只有一个servlet包找不到。原来是之前发布的方式是Tomcat提供的,所以这里pom.xml中显示引入

<!-- 添加Servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>

重新编译后,终于不再报错了!

打开文件ContextLoader,修改几行代码,看看运行时是否执行了!

方便起见,修改几行日志文件:

        //servletContext.log("Initializing Spring root WebApplicationContext");
        servletContext.log("你正在启动Spring容器:WebApplicationContext!!!!!");
        if (logger.isInfoEnabled()) {
            //logger.info("Root WebApplicationContext: initialization started");
            logger.info("Spring容器开始初始化:Root WebApplicationContext: initialization started");
        }

重新启动Tomcat,打印的日志:

从启动日志看Spring IOC的初始化和Bean生命周期