一、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日志了!

关键日志分析:

可以结合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的源代码

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

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

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

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

结果是还是报错:

不过比想象中好很多,只有一个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生命周期的更多相关文章

  1. 【Spring Framework】Spring IOC详解及Bean生命周期详细过程

    Spring IOC 首先,在此之前,我们就必须先知道什么是ioc,ioc叫做控制反转,也可以称为依赖注入(DI),实际上依赖注入是ioc的另一种说法, 1.谁控制谁?: 在以前,对象的创建和销毁都是 ...

  2. spring源码分析系列5:ApplicationContext的初始化与Bean生命周期

    回顾Bean与BeanDefinition的关系. BeanFactory容器. ApplicationContext上下文. 首先总结下: 开发人员定义Bean信息:分为XML形式定义:注解式定义 ...

  3. Spring BeanFactory 初始化 和 Bean 生命周期

    (version:spring-context-4.3.15.RELEASE) AbstractApplicationContext#refresh() public void refresh() t ...

  4. Spring Framework核心概念之Bean生命周期管理

    目录 Spring Bean的生命周期 相关接口的分类 测试SpringBean生命周期的Demo程序 小结 Spring Bean的生命周期 Spring容器既Application或者WebApp ...

  5. Spring源码分析:Spring IOC容器初始化

    概述: Spring 对于Java 开发来说,以及算得上非常基础并且核心的框架了,在有一定开发经验后,阅读源码能更好的提高我们的编码能力并且让我们对其更加理解.俗话说知己知彼,百战不殆.当你对Spri ...

  6. SpringBoot IoC启动流程、初始化过程及Bean生命周期各个阶段的作用

    目录 SpringBoot IoC启动流程.初始化过程及Bean生命周期各个阶段的作用 简述 首先明确IoC容器是啥 准备-SpringApplication的实例化 启动-SpringApplica ...

  7. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  8. 整理在Spring IOC容器初始化后可以处理特定逻辑的多种实现方式

    Spring框架的核心是依赖注入.切面:Spring Boot是在Spring框架的基础上为其提供许多默认配置.默认约定(约定优于配置),从而达到减少或减化配置进而可开箱即用.快速上手:Spring ...

  9. Spring Boot 启动源码解析结合Spring Bean生命周期分析

    转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...

随机推荐

  1. python2与python3在windows下共存

    python有python2(工业版)和python3,有时候我们会希望电脑上既有python2也有python3,!假设我们已经安装好,python2和python3了, 接下来我们找到python ...

  2. 解析大型.NET ERP系统 窗体、查询、报表二次开发

    详细介绍Enterprise Solution 二次开发的流程步骤,主要包括数据输入窗体(Entry Form),查询(Query/Enquiry),报表(Report)三个重要的二次开发项目. 数据 ...

  3. CSS实现点击事件及实践

    实现原理利用:target,把a标签自身的href以及id设置成一样的. 案例1:实现元素的切换 HTML: <div id="box"> <a href=&qu ...

  4. webpack学习笔记

    1.安装webpack npm install webpack -g 2.进入项目目录,初始化 npm init 3.将webpack安装到项目依赖中 npm install webpack --sa ...

  5. java错误

    Workspace in use or cannot be created, choose a different one. 出现这种情况一般是workspace的配置文件中出现了.lock文件(wo ...

  6. Distribution of Data Through OCAF Tree

    Distribution of Data Through OCAF Tree OCAF数据的分配 eryar@163.com 一.作者注 Annotation 本文档主要用于说明OCAF(Open C ...

  7. struts2简单数据验证

    当表单数据提交到后台后通常要对数据进行校验,以登录为例,后台拿到用户名密码后会判断是否正确,正确的话会跳转到网站用户登录成功的页面,如果不正确的话会提示用户输入不正确. 首先在struts.xml配置 ...

  8. CSS三列布局

    × 目录 两侧定宽中间自适应 两列定宽一侧自适应 中间定宽两侧自适应一侧定宽两列自适应三列自适应总结 前面的话 前面已经介绍过单列定宽单列自适应和两列自适应的两列布局.本文介绍三列布局,分为两侧定宽中 ...

  9. Java多线程系列--“JUC集合”06之 ConcurrentSkipListSet

    概要 本章对Java.util.concurrent包中的ConcurrentSkipListSet类进行详细的介绍.内容包括:ConcurrentSkipListSet介绍ConcurrentSki ...

  10. 检查点(Checkpoint)过程如何处理未提交的事务

    每次我讲解SQL Server之前,我都会先简单谈下当我们执行查询时,在SQL Server内部发生了什么.执行一个SELECT语句非常简单,但是执行DML语句更加复杂,因为SQL Server要修改 ...