Spring IOC超详解

Spring IOC 应⽤

1.Spring IoC基础

在这里插入图片描述

1.1BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等。
在这里插入图片描述
在pom.xml中引入:

<!--引入Spring IoC容器功能-->
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.1.12.RELEASE</version>
 </dependency>

启动 IoC 容器的⽅式:

  • Java Se环境下启动IoC容器

(1).ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)

// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 不推荐使用
//ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");
// 第一次getBean该对象
Object accountPojo = applicationContext.getBean("accountPojo");
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");

(2).FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件(3).AnnotationConfigApplicationContext:纯注解模式下启动Spring容器

  • Web环境下启动IoC容器
<!--引入spring web功能-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.1.12.RELEASE</version>
</dependency>

(1).从xml启动容器

<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>

在servlet中获取被spring创建的对象:

WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
transferService = (TransferService) webApplicationContext.getBean("transferService");

(2).从配置类启动容器

1.2纯xml模式

  • xml ⽂件头
<?xml version="1.0" encoding="UTF-8"?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
">
  • 实例化Bean的三种⽅式

⽅式⼀:使⽤⽆参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。

<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>

另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理:
⽅式⼆:使用静态方法创建(加static)
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情 况。

<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>

⽅式三:使⽤实例化⽅法创建(不加static)
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。

<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
  • Bean的作用范围及⽣命周期

作⽤范围的改变:在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围(主要有五种)。

作用域描述
singleton在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。

 <!--scope:定义bean的作用范围
singleton:单例,IOC容器中只有一个该类对象,默认为singleton
prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象,Spring只创建对象,不管理对象
-->
<!--配置service对象-->
<bean id="transferService" class="com.lagou.service.impl.TransferServiceImpl" scope="singleton"></bean>

单例模式:
singleton对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
单例模式的bean对象⽣命周期与容器相同
多例模式:
prototype对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
多例模式的bean对象,spring框架只负责创建,不负责销毁

  • Bean标签属性

在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:
id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
class属性:⽤于指定创建Bean对象的全限定类名。
name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

  • DI 依赖注入的xml配置

按照注⼊的⽅式分类:
(1).构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
(2).set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)

按照注⼊的数据类型分类:
(1)基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
(2)其他Bean类型:注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。
(3)复杂类型(集合类型): 注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。

public class JdbcAccountDaoImpl implements AccountDao {

    private ConnectionUtils connectionUtils;
    private String name;
    private int sex;
    private float money;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public void setMoney(float money) {
        this.money = money;
    }




    public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
        this.connectionUtils = connectionUtils;
        this.name = name;
        this.sex = sex;
        this.money = money;
    }



    private String[] myArray;
    private Map<String,String> myMap;
    private Set<String> mySet;
    private Properties myProperties;

    public void setMyArray(String[] myArray) {
        this.myArray = myArray;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyProperties(Properties myProperties) {
        this.myProperties = myProperties;
    }

<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">


        <!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性-->
        <!--<property name="ConnectionUtils" ref="connectionUtils"/>
        <property name="name" value="zhangsan"/>
        <property name="sex" value="1"/>
        <property name="money" value="100.3"/>-->


        <!--<constructor-arg index="0" ref="connectionUtils"/>
        <constructor-arg index="1" value="zhangsan"/>
        <constructor-arg index="2" value="1"/>
        <constructor-arg index="3" value="100.5"/>-->
        <!--name:按照参数名称注入,index按照参数索引位置注入-->
        <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 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>

    </bean>

在使⽤构造函数注⼊时,涉及的标签是constructor-arg,该标签有如下属性:
name:⽤于给构造函数中指定名称的参数赋值。
index:⽤于给构造函数中指定索引位置的参数赋值。
value:⽤于指定基本类型或者String类型的数据。
ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。

依赖注⼊的配置实现之set⽅法注⼊:利⽤字段的set⽅法实现赋值的注⼊⽅式
在使⽤set⽅法注⼊时,需要使⽤property标签,该标签属性如下:
name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)value:指定注⼊的数据。它⽀持基本类型和String类型。
ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。
复杂数据类型注入,指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。

1.3 xml与注解相结合模式

注意:
1)实际企业开发中,纯xml模式使⽤已经很少了
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)哪些bean的定义写在xml中,哪些bean的定义使⽤注解:
第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池

<!--引入外部资源文件-->
<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>

⾃⼰开发的bean定义使⽤注解

xml形式对应的注解形式
标签@Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已
标签的scope属性@Scope(“prototype”),默认单例,注解加在类上
标签的init-method属性@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
标签的destory-method属性@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法

DI 依赖注⼊的注解实现⽅式

  • @Autowired(推荐使⽤)
    @Autowired采取的策略为按照类型注⼊。
public class TransferServiceImpl { 
	@Autowired 
	private AccountDao accountDao; 
}

如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
@Qualifier告诉Spring具体去装配哪个对象:

public class TransferServiceImpl { 
	// @Autowired 按照类型注入 ,如果按照类型无法唯一锁定对象,可以结合@Qualifier指定具体的id
    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;
}
  • @Resource
    @Resource 默认按照 ByName ⾃动注⼊。
public class TransferService {
	@Resource  
	private AccountDao accountDao;  
	@Resource(name="studentDao")  
	private StudentDao studentDao;  
	@Resource(type="TeacherDao")  
	private TeacherDao teacherDao;  
	@Resource(name="manDao",type="ManDao")  
	private ManDao manDao; 
}

如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
@Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包:

<dependency> 
	<groupId>javax.annotation</groupId> 
	<artifactId>javax.annotation-api</artifactId> 
	<version>1.3.2</version>
</dependency>

applicationContext.xml被spring框架加载,启动Spring Ioc容器。注解也得被识别,需要配置一个注解的扫描:

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

1.4 纯注解模式

改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动

// @Configuration 注解表明当前类是一个配置类
@Configuration
@ComponentScan({"com.lagou.edu"})
@PropertySource({"classpath:jdbc.properties"})
/*@Import()*/
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;
    }
}

Java SE环境下启动IoC容器:

@Test
    public void testIoC() throws Exception {
        // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
        System.out.println(accountDao);

    }

Web环境下启动IoC容器:


```xml
 <!--告诉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>
  • @Configuration 注解,表明当前类是⼀个配置类
  • @ComponentScan 注解,替代 context:component-scan
  • @PropertySource,引⼊外部属性配置⽂件
  • @Import 引⼊其他配置类
  • @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
  • @Bean 将⽅法返回对象加⼊ SpringIOC 容器

2.Spring IOC高级特性

2.1lazy-Init 延迟加载

Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singletonbean。

<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init=“false”,⽴即加载,表示在spring启动时,⽴刻进⾏实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" /> 

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
也可以在容器层次中通过在 元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:

<beans default-lazy-init="true"> 
<!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
应⽤场景
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能
(2)对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

2.2 FactoryBean 和 BeanFactory

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。

FactoryBean 的底层源码:

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
	// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
    @Nullable
    T getObject() throws Exception;

	// 返回FactoryBean创建的Bean类型
    @Nullable
    Class<?> getObjectType();
    
	// 返回作⽤域是否单例
    default boolean isSingleton() {
        return true;
    }
}
public class Company {
    private String name;
    private String address;
    private int scale;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getScale() {
        return scale;
    }

    public void setScale(int scale) {
        this.scale = scale;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", scale=" + scale +
                '}';
    }
}

public class CompanyFactoryBean implements FactoryBean<Company> {
    private String companyInfo; // 公司名称,地址,规模

    @Override
    public Company getObject() throws Exception {
        // 模拟创建复杂对象Company
        Company company = new Company();
        String[] strings = companyInfo.split(",");
        company.setName(strings[0]);
        company.setAddress(strings[1]);
        company.setScale(Integer.parseInt(strings[2]));
        return company;
    }

    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean"> 
	<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>

测试,获取FactoryBean产⽣的对象:

Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}

测试,获取FactoryBean,需要在id之前添加“&”

Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09

2.3 SpringBean生命周期图

  • 注意:对象不⼀定是springbean,⽽springbean⼀定是个对象
    在这里插入图片描述

2.4 后置处理器

Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessorBeanFactoryPostProcessor,两者在使⽤上是有所区别的。

  • ⼯⼚初始化(BeanFactory)—> Bean对象
  • 在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
  • 在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情
BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。
(处理是发⽣在Spring容器的实例化和依赖注⼊之后)
在这里插入图片描述
该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法。
定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。

BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型⽤:PropertyPlaceholderConfigurer(替换xml文件中的占位符)

<property name="url" value="${jdbc.url}"/>

在这里插入图片描述
此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法:
在这里插入图片描述
其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法:
在这里插入图片描述
⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。

BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
在这里插入图片描述

注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象

Spring IOC源码深度剖析

Spring源码构建:

  • github上选择版本是5.1:https://github.com/spring-projects/spring-framework/tree/5.1.x(这是一个gradle工程)
  • 安装gradle 5.6.3(类似于maven):https://gradle.org/releases/
  • 导⼊(耗费⼀定时间)
  • 编译⼯程 [⼯程—>tasks—>compileTestJava](顺序:core-oxm-context-beans-aspects-aop)

3. Spring IoC容器初始化主体流程

3.1 Spring IoC的容器体系

IoC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系⾮常聪明,需要使⽤哪个层次⽤哪个层次即可,不必使⽤功能⼤⽽全的。
BeanFactory 顶级接⼝⽅法栈如下:
在这里插入图片描述
BeanFactory 容器继承体系:
在这里插入图片描述
通过其接⼝设计,我们可以看到我们⼀贯使⽤的 ApplicationContext 除了继承BeanFactory的⼦接⼝,还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。

3.2 Bean生命周期关键时机点

创建⼀个类 LagouBean ,让其实现⼏个特殊的接⼝,并分别在接⼝实现的构造器、接⼝⽅法中断点,观察线程调⽤栈,分析出 Bean 对象创建和管理关键点的触发时机。
在这里插入图片描述
LagouBean.java:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @Description:
 * @title: LagouBean
 * @Author szh
 * @Date: 2021/12/27 22:02
 * @Version 1.0
 */
public class LagouBean implements InitializingBean, ApplicationContextAware {

//	private ItBean itBean;
//
//	public void setItBean(ItBean itBean) {
//		this.itBean = itBean;
//	}

	/**
	 * 构造函数
	 */
	public LagouBean(){
		System.out.println("LagouBean 构造器...");
	}


	/**
	 * InitializingBean 接口实现
	 */
	public void afterPropertiesSet() throws Exception {
		System.out.println("LagouBean afterPropertiesSet...");
	}

	public void print() {
		System.out.println("print方法业务逻辑执行");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("setApplicationContext....");
	}
}

BeanPostProcessor 接⼝实现类:

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

/**
 * @Description:
 * @title: MyBeanPostProcessor
 * @Author szh
 * @Date: 2021/12/27 22:03
 * @Version 1.0
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

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

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

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if("lagouBean".equals(beanName)) {
			System.out.println("BeanPostProcessor 实现类 postProcessAfterInitialization 方法被调用中......");
		}
		return bean;
	}
}

BeanFactoryPostProcessor 接⼝实现类:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * @Description:
 * @title: MyBeanFactoryPostProcessor
 * @Author szh
 * @Date: 2021/12/27 22:03
 * @Version 1.0
 */
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

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

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

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="
	    http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
">

	<!--循环依赖问题-->
<!--	<bean id="lagouBean" class="com.lagou.edu.LagouBean">-->
<!--		<property name="ItBean" ref="itBean"/>-->
<!--	</bean>-->
<!--	<bean id="itBean" class="com.lagou.edu.ItBean">-->
<!--		<property name="LagouBean" ref="lagouBean"/>-->
<!--	</bean>-->


	<bean id="myBeanFactoryPostProcessor" class="com.szh.spring.test.MyBeanFactoryPostProcessor"/>
	<bean id="myBeanPostProcessor" class="com.szh.spring.test.MyBeanPostProcessor"/>


	<bean id="lagouBean" class="com.szh.spring.test.LagouBean">
	</bean>


	<!--aop配置-->
	<!--横切逻辑-->
	<!--<bean id="logUtils" class="com.lagou.edu.LogUtils">
	</bean>

	<aop:config>
		<aop:aspect ref="logUtils">
			<aop:before method="beforeMethod" pointcut="execution(public void com.lagou.edu.LagouBean.print())"/>
		</aop:aspect>
	</aop:config>-->


</beans>

IoC 容器源码分析⽤例:

@Test
	public void testIoC() {
		// ApplicationContext是容器的高级接口,BeanFacotry(顶级容器/根容器,规范了/定义了容器的基础行为)
		// Spring应用上下文,官方称之为 IoC容器(错误的认识:容器就是map而已;准确来说,map是ioc容器的一个成员,叫做单例池,
		// singletonObjects,容器是一组组件和过程的集合,包括BeanFactory、单例池、BeanPostProcessor等以及之间的协作流程)

		/**
		 * Ioc容器创建管理Bean对象的,Spring Bean是有生命周期的
		 * 构造器执行、初始化方法执行、Bean后置处理器的before/after方法、:AbstractApplicationContext#refresh#finishBeanFactoryInitialization
		 * Bean工厂后置处理器初始化、方法执行:AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors
		 * Bean后置处理器初始化:AbstractApplicationContext#refresh#registerBeanPostProcessors
		 */

		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
		System.out.println(lagouBean);
	}

在这里插入图片描述 Bean对象创建的⼏个关键时机点代码层级的调⽤都在AbstractApplicationContext 类 的 refresh ⽅法中:
在这里插入图片描述

3.3 Spring IoC容器初始化主流程

Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() ⽅法中,我们查看 refresh ⽅法来俯瞰容器创建的主体流程。

public void refresh() throws BeansException, IllegalStateException {
	// 对象锁  -> ApplicationContext对象的refresh和close方法不能同时调用
    synchronized(this.startupShutdownMonitor) {
    	// 第⼀步:刷新前的预处理
        this.prepareRefresh();
        /* 第⼆步: 获取BeanFactory;默认实现是DefaultListableBeanFactory 加载BeanDefition 
        并注册到 BeanDefitionRegistry */
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
        this.prepareBeanFactory(beanFactory);

        try {
        	// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
            this.postProcessBeanFactory(beanFactory);
            // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
            this.registerBeanPostProcessors(beanFactory);
            // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
            this.initMessageSource();
            // 第⼋步:初始化事件派发器
            this.initApplicationEventMulticaster();
            // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
            this.onRefresh();
            // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
            this.registerListeners();
            /* 第⼗⼀步: 初始化所有剩下的⾮懒加载的单例bean 初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性) 
            填充属性 初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法) 
            调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处 */
            this.finishBeanFactoryInitialization(beanFactory);
            /* 第⼗⼆步: 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,
            并且发布事件 (ContextRefreshedEvent) */
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

4.BeanFactory创建流程

4.1获取BeanFactory流程

在这里插入图片描述
在这里插入图片描述

4.2 BeanDefinition加载解析及注册子流程

(1)该流程涉及到如下⼏个关键步骤
**Resource定位:**指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂件,并将其封装成Resource对象。
**BeanDefinition载⼊ :**把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到 IoC 容器

(2)过程分析

Step 1:⼦流程⼊⼝在 AbstractRefreshableApplicationContext#refreshBeanFactory ⽅法中
在这里插入图片描述
**Step 2:**依次调⽤多个类的 loadBeanDefinitions ⽅法 —> AbstractXmlApplicationContext —>AbstractBeanDefinitionReader —> XmlBeanDefinitionReader ⼀直执⾏到XmlBeanDefinitionReader 的 doLoadBeanDefinitions ⽅法
在这里插入图片描述
**Step 3:**我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions ⽅法,期间产⽣了多次重载调⽤,我们定位到最后⼀个
在这里插入图片描述
此处我们关注两个地⽅:⼀个createRederContext⽅法,⼀个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions⽅法,先进⼊createRederContext ⽅法:
在这里插入图片描述
我们可以看到,此处 Spring ⾸先完成了 NamespaceHandlerResolver 的初始化。我们再进⼊ registerBeanDefinitions ⽅法中追踪,调⽤了DefaultBeanDefinitionDocumentReader#registerBeanDefinitions ⽅法
在这里插入图片描述
进⼊ doRegisterBeanDefinitions ⽅法:
在这里插入图片描述
进⼊ parseBeanDefinitions ⽅法:
在这里插入图片描述
进⼊ parseDefaultElement ⽅法:
在这里插入图片描述
进⼊ processBeanDefinition ⽅法:
在这里插入图片描述
⾄此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition的。
在这里插入图片描述
可以在DefaultListableBeanFactory中看到此Map的定义:

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

在这里插入图片描述

5.Bean创建流程

通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory) 处:
在这里插入图片描述
进⼊finishBeanFactoryInitialization()内部:
在这里插入图片描述
继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例

在这里插入图片描述
继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法:
在这里插入图片描述
接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法
在这里插入图片描述
进⼊doCreateBean⽅法看看,该⽅法关注两块重点区域:

  • 创建Bean实例,此时尚未设置属性
    在这里插入图片描述
  • 给Bean填充属性,调⽤初始化⽅法,应⽤BeanPostProcessor后置处理器
    在这里插入图片描述

6.lazy-init 延迟加载机制原理

lazy-init 延迟加载机制分析:
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。
DefaultListableBeanFactory.java 中:

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				// 判断是否是 FactoryBean
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged(
									(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
				/* 如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑和 懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的 */
					getBean(beanName);
				}
			}
		}

总结:

  • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次进⾏getBean时候才进⾏初始化并依赖注⼊
  • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来

7.Spring IoC循环依赖问题

7.1 什么是循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
在这里插入图片描述
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
Spring中循环依赖场景有:
构造器的循环依赖(构造器注⼊)
Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。

7.2 循环依赖处理机制(三级缓存机制)

在这里插入图片描述

  • 单例 bean 构造器参数循环依赖(无法解决)

  • prototype 原型 bean循环依赖(无法解决)
    对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。

  • 单例bean通过setXxx或者@Autowired进行循环依赖

Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前。
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

  • Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
  • ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
  • Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
  • ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例(ClassA通过spring容器获取到ClassB,完成了对象初始化操作。)
  • 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值