Spring2-ioc应用和源码剖析

一:ioc应用

Spring中,BeanFactory的作用是创建和管理bean的,它IoC容器的顶层接⼝,不过只定义了⼀些基础功能。 实际用的最多的是它的实现ApplicationContext,扩展了很多功能,是ioc容器的⾼级接⼝。

ioc容器启动方式:

  • Java环境下启动
    • ClassPathXmlApplicationContext :从类的根路径下加载配置⽂件(推荐使⽤)
    • FileSystemXmlApplicationContext :从磁盘路径上加载配置⽂件
    • AnnotationConfifigApplicationContext :纯注解模式下启动 Spring 容器
    • @Test
          public void testIoC() throws Exception {
      
              // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
              ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
              // 不推荐使用
              //ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");
              
              // 第一次getBean该对象
              Object accountPojo = applicationContext.getBean("accountPojo");
      
      //执行业务………………
      
              applicationContext.close();
          }
  • Web环境下启动(web.xml中配置)
    • 从xml启动容器,先在在applictionContext.xml中配置bean,然后:
    • <!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >
      
      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
      
        <!--配置Spring ioc容器的配置文件-->
        <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <!--使用监听器启动Spring的IOC容器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      </web-app>
    • 从配置类启动容器
    • <!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >
      
      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
      
        <!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
        <context-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
        <!--配置启动类-->
        <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>com.lagou.edu.SpringConfig</param-value>
        </context-param>
        <!--使用监听器启动Spring的IOC容器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      </web-app>

      bean的配置就不是使用xml了,需要使用注解类SpringConfig

    • @Configuration
      @ComponentScan({"com.lagou.edu"})
      @PropertySource({"classpath:jdbc.properties"})
      public class SpringConfig {
      
          @Value("${jdbc.driver}")
          private String driverClassName;
          @Value("${jdbc.url}")
          private String url;
          @Value("${jdbc.username}")
          private String username;
          @Value("${jdbc.password}")
          private String password;
      
      
          @Bean("dataSource")
          public DataSource createDataSource(){
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassName);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(username);
              druidDataSource.setPassword(password);
              return  druidDataSource;
          }
      }

 知道启动方式后,分别来看看xml下和注解下的使用:

1、纯 xml 模式:

1-1:实例化对象的方式

a、无参构造

通过反射调⽤⽆参构造函数
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>

b、使⽤静态⽅法创建(不是简单的创建对象,可能在创建的过程 中会做很多额外的操作)

<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
public class CreateBeanFactory {

    public static ConnectionUtils getInstanceStatic() {
        ConnectionUtils connectionUtils = new ConnectionUtils()
        //做很多操作xxxxxx,赋值什么的
        //……………………………………………………………………………………
        return connectionUtils;
    }
}

c、使用非静态方法创建(作用和静态方法类似)

    <bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
    <bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
public class CreateBeanFactory {
    public ConnectionUtils getInstance() {
        return new ConnectionUtils();
    }
}

1-2:Bean的作用范围及⽣命周期 

认识单例和多例两种,默认单例:

singleton(单例模式)

prototype (原型模式,也叫多例模式)
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
单例模式: singleton
  • 对象出⽣:当创建容器时,对象就被创建了。
  • 对象活着:只要容器在,对象⼀直活着。
  • 对象死亡:当销毁容器时,对象就被销毁了。
多例模式: prototype
  • 对象出⽣:当使⽤对象时,创建新的对象实例。
  • 对象活着:只要对象在使⽤中,就⼀直活着。
  • 对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。

除了scope,还有两个常用属性:

init-method 属性: ⽤于指定 bean 对象的初始化⽅法,此⽅法会在 bean 对象装配后调⽤。必须是⼀个⽆参⽅法。
destory-method 属性: ⽤于指定 bean 对象的销毁⽅法,此⽅法会在 bean 对象销毁前执⾏。它只能为scope singleton 时起作⽤

1-3:属性注入 

构造函数注入:使用constructor-arg标签,类中必须有相应的有参构造

 <constructor-arg name="connectionUtils" ref="connectionUtils"/>
 <constructor-arg name="name" value="zhangsan"/>
 <constructor-arg name="sex" value="1"/>
 <constructor-arg name="money" value="100.6"/>

set注入:使用property标签

//简单属性注入:
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>

//复杂属性注入:
        <property name="myArray">
            <array>
                <value>array1</value>
                <value>array2</value>
                <value>array3</value>
            </array>
        </property>

        <property name="myMap">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
            </map>
        </property>

        <property name="mySet">
            <set>
                <value>set1</value>
                <value>set2</value>
            </set>
        </property>

        <property name="myProperties">
            <props>
                <prop key="prop1">value1</prop>
                <prop key="prop2">value2</prop>
            </props>
        </property>

 实际使用中,纯xml已经很少了,像注入什么的,都是使用注解。

2、xml+注解模式:

ioc容器的启动,仍然从xml开始。但是xml中只配置一些第三方jar包中的bean,以及本项目中的bean扫描路径等等。

  <!--开启注解扫描,base-package指定扫描的包路径-->
    <context:component-scan base-package="com.lagou.edu"/>

    <!--引入外部资源文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--第三方jar中的bean定义在xml中-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

注解解析:

@Component("accountDao") ,注解加在类上
        bean的 id 属性内容直接配置在注解后⾯,如果不配置,默认 为类的类名⾸字⺟⼩写;
        另外提供了@Controller 、 @Service、 @Repository 分别⽤于控制层类、服务层类、 dao 层类的 bean 定义,只是 为了更清晰的区分⽽已
@Scope("prototype") ,默认单例,注解加在类上
@PostConstruct,注解加在⽅法上,针对xml中的init-method标签。
@PreDestory,注解加在⽅法上,针对xml中的destory-method标签。
@Autowired 依赖注入,如果注入的接口有多个实现,使用@Qualififier选择
@Resource注入dao, 同Autowired,清晰区分而已。 注意:jdk11中去掉了,要用需要单独引入依赖包
<dependency>
        <groupId> javax.annotation </groupId>
        <artifactId> javax.annotation-api </artifactId>
        <version> 1.3.2 </version>
</dependency>

 3、纯注解模式(使用最广泛):

@Confifiguration 注解标记一个类为配置类,就不需要ApplicationContext.xml了。
@ComponentScan 注解,替代 context:component-scan

@PropertySource ,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器

下面来补充几个高级特性:

  • lazy-Init=true 开启延迟加载
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
启动ioc容器时,ApplicationContext 实例所有的singleton bean,开启延迟加载后,则会在第一次getBean的时候实例化。 如果bean1中引用了bean2,bean2开启了延迟加载, 那么由于 bean1 被实例 化时需要 bean2,所以bean2 也被实例化,这种情况也符合 延时加载的 bean 在第⼀次调⽤ 时才被实例化的规则。

 开启延迟加载⼀定程度提⾼容器启动和运转性能,对于不长使用的bean可以开启。

  • FactoryBean BeanFactory
BeanFactory 接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理 Bean 的⼀个⼯⼚。
FactoryBean是一种特殊的bean,叫做工厂bean,作用是帮助生成一些普通的bean。我们上面说了,有一些bean的实例化过程,需要做很多事情,就可以借助工厂bean。

使用方式上面xml配置中介绍过。

  •  后置处理器
Spring 提供了两种后处理 bean 的扩展接⼝:
  • BeanPostProcessor:Bean对象实例化对bean进行后置处理
  • BeanFactoryPostProcessor:在BeanFactory初始化之后进⾏后置处理
BeanPostProcessor
public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

提供了两个方法,分别在初始化方法(init-method指定的方法)前后执行。 默认对所有的bean都生效,如果要针对某个bean,用beanName来判定。 beanName代表bean的id或name属性。

注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。

BeanFactoryPostProcessor

BeanFactory 级别的处理,是针对整个Bean的⼯⼚进⾏处理。 比如拿到bean工厂中所有的BeanDefifinition对象,对原始属性进行修改。 
BeanDefifinition是刚把xml中bean标签的内容,读取解析为一个JavaBean,还没有实例化。

二:源码解析

准备工作:

 知道了ioc的使用,其实ioc容器就是spring框架对bean如何创建和管理的一套综合流程的总称。ioc中BeanFactory是顶层容器,是一个接口,定了了所有ioc容器必须遵从的原则, 具体实现和扩展,有子类自己去实现。比如常用的ApplicationContext,其子类ClassPathXmlApplicationContext用于解析xml内容,AnnotationConfigApplicationContext用于解析注解的内容。 当然还有一些其他的,根据场景自由选择或扩展。

源码解析,我们已ClassPathXmlApplicationContext为例,看看如何实现ioc对bean的创建和管理功能。

我们来创建一个JavaBean,观察生命周期中的几个关键节点。

创建MyBean

import org.springframework.beans.factory.InitializingBean;

public class MyBean implements InitializingBean {

    public MyBean() {
        System.out.println("MyBean 构造器...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBean afterPropertiesSet...");
    }
}

创建后置处理器

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    public MyBeanPostProcessor() {
        System.out.println("BeanPostProcessor 实现类构造函数...");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
        if ("myBean".equals(beanName)) {
            System.out.println("BeanPostProcessor 实现类postProcessBeforeInitialization ⽅法被调⽤中......");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException {
        if ("myBean".equals(beanName)) {
            System.out.println("BeanPostProcessor 实现类 postProcessAfterInitialization ⽅法被调⽤中......");
        }
        return bean;
    }
}
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    public MyBeanFactoryPostProcessor() {
        System.out.println("BeanFactoryPostProcessor 实现类构造函数...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
        throws BeansException {
        System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中......");
    }
}

在ApplicationContext.xml中配置bean信息

    <bean id="myBean" class="com.lagou.edu.bean.MyBean"/>
    <bean id="myBeanFactoryPostProcessor"
        class="com.lagou.edu.processor.MyBeanFactoryPostProcessor"/>
    <bean id="myBeanPostProcessor" class="com.lagou.edu.processor.MyBeanPostProcessor"/>

编写test

@Test
    public void testIoc() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        MyBean myBean = applicationContext.getBean(MyBean.class);
        System.out.println(myBean);
    }

开始解析:

打断点,源码解析开始:

1、bean是什么时候创建的?

未设置延迟加载情况下, 容器初始化过程中,已经创建了bean。

2、分析MyBean的构造函数

在构造函数处打个断点,看看什么时候调用构造函数的,之前都做了什么?

 从调用栈中看到,从入口到最终的构造方法,中间走过很多流程。 它的顶层时机点在AbstractApplicationContext 类 的 refresh ⽅法中。 

同理,分别对afterPropertiesSet方法,以及后置处理器中的各个方法打断点查看调用链,发现顶层时机点都在refresh方法中,说明该方法是Spring ioc的核心方法。

分析refresh()方法:

refresh中主要调用了以下方法:

// 第⼀步:刷新前的预处理

prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory。
使用它加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory =
obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在bean的初始化方法前后等执⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();

refresh中我们需要关心的有几步:

第二步、获取BeanFactory 

 通过调用链看到,最终是在AbstractRefreshableApplicationContext的refreshBeanFactory

中,创建了DefaultListableBeanFactory。 但是这里还没有返回,接着就使用loadBeanDefinitions去加载bean信息了。来看看如何加载的?

依次调⽤多个类的 loadBeanDefifinitions ⽅法 ,⼀直执⾏到XmlBeanDefifinitionReader 的 doLoadBeanDefifinitions ⽅法

 如上图,首先把xml文件流信息解析到document中,然后解析document对象,把对应bean标签解析到BeanDefinition中.

继续追踪,直到DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions方法:

 解析默认标签,也就是spring的标签。

 然后就会判断是spring的哪一种标签,我们看bean标签。

继续跟注册方法, 发现是使用BeanDefinitionRegistry注册的,而我们用的DefaultListableBeanFactory就是BeanDefinitionRegistry其中一个子类,进入注册方法发现,就是用beanName为key,beanDefiniton为value,放到一个map中

 到这里,就算是注册完成了。 主要流程包含了读取xml为流,将流解析为Document文档,再解析文档标签中每一个bean标签,生成对应的BeanDefinition,放到map中。主要流程中的时序图如下:

时序图总结来说就是:

1、AbstractBeanDefinitionReader中根据配置文件路径,加载为Resource

 2、调用子类XmlBeanDefinitionReader将resource通过流解析为Document对象。

 3、有了Document文档对象,就可以获取标签并解析了。 XmlBeanDefinitionReader并没有直接解析,而是使用BeanDefinitionDocumentReader来解析

 4、最终,BeanDefinitionDocumentReader使用了BeanDefinitionParserDelegate来解析,BeanDefinitionParserDelegate就是专门解析生成BeanDefinition的。

beanDefinition注册之后(还未实例化),这时候才将beanFactory返回去。

紧接着refresh方法中的第四步和第五步,分别注册和调用了BeanFactory级别的后置处理器。之前说了factory级别的处理器,可以获取并操作BeanFactory中注册的bean的元数据,所以它必须在bean实例化之前就调用。

第六步,会注册bean级别的后置处理器,这里只是注册,后面会在bean实例化后,在初始化方法执行的前后调用。 

其实不管哪一类后置处理器注册,都是从factory中所有的BeanDefinition中,查看哪些是后置处理器,重新放到另一个List中。  比如,bean级别处理器的就是放在下图的list中。

第十一步:bean的实例化

进入DefaultListableBeanFactory的preInstantiateSingletons方法,看到不管是FactroyBean还是普通bean,都是getBean时候实例化的

继续跟代码到bstractBeanFactory的doGetBean方法

 继续跟代码到AbstractAutowireCapableBeanFactory的doCreateBean

 实例化bean并且填充了属性之后,紧接着下一行的initializeBean方法就会调用bean的初始化方法。 

当然,在调用的时候,会将refresh中第六步注册的所有后置处理器拿出来,循环看看是否对当前的bean生效并执行。

到这里,refresh中主要ioc注册及实例化就分析完成了。  要注意的是,上面说了bean的实例化是在getBean的方法中,对于懒加载的bean,容器初始化的时候并不会调用getBean方法,而是第一次使用获取bean的时候才调用并实例化。

 循环依赖的问题:

在bean的实例化过程中,有个问题。 假如A的属性有B,那就要求实例化A的时候,B已经实例化好了。  这时候假如B也依赖A呢,也要求实例化B的时候,A已经好了。 怎么办?

spring使用了三级缓存来解决这个问题:

  • 一级缓存 singletonObjects: 存放完整的bean实例 ,来源于二级缓存
  • 二级缓存 earlySingletonObjects: 存放不完整的bean。   实例化后,还没有填充属性(初始化)的bean实例 (如果该bean是被代理过的,则返回的是代理对象),来源于三级缓存
  • 三级缓存 singletonFactories: 存放一个单例工厂ObjectFactory,通过调用getObject方法,最终调用getEarlyBeanReference(beanName , mbd , bean)获取到不完整的bean(如果该bean是被代理过的,则返回的是代理对象)。

我们假设现在有这样的场景AService依赖BService,BService依赖AService

        1. AService首先实例化,通过ObjectFactory半成品暴露在三级缓存中,并标记为创建中

        2. 填充属性BService,发现BService还未进行过加载,就会先去加载BService

        3. 实例化BService,也通过ObjectFactory半成品暴露在三级缓存,并标记为创建中

        4. BService填充属性AService的时候,先从一级缓存获取不到,再从二级缓存获取不到,最后能够从三级缓存中拿到半成品的ObjectFactory

拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,这时我们会发现能够拿到AService实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象(不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象)。下面是重点,我们发现这个二级缓存好像显得有点多余,可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???

只要两个缓存确实可以做到解决循环依赖的问题,但前提是这个bean没被AOP进行切面代理。 因为被代理后,getEarlyBeanReference中获取到的是代理对象,而每调用一次该法方法获取到的是一个新的代理对象,这样三级缓存中就不能保证是单例了。  所以我们在第一次从三级缓存 singletonFactories取出来之后,马上要放到二级缓存,同时删掉三级缓存。  这样下一个来获取的时候,在二级缓存中拿到的就是同一个对象,哪怕它是代理对象。

当最终bean完全实例化后,就会从二级缓存放到一级缓存中,并删除二级缓存。

bean实例化后先将半成品暴露到三级缓存

 

依赖注入时候,获取bean实例的流程和顺序:

 

猜你喜欢

转载自blog.csdn.net/growing_duck/article/details/125123864