第四部分 Spring IoC的应用

第1节 Spring Ioc基础知识说明

Spring框架的IOC实现如下图所示:

1、先在beans配置文件中添加实例化对象 及全限定类名 及其依赖关系
2、通过配置文件的信息进行读取并调用
image.png
学习注解的时候找对应的xml。

第2节BeanFactory和ApplicationContext的区别

BeanFactory在上一节手写的时候是自定义的一个工厂类,那么在Spring框架的IOC里面 有没有 BeanFactory的类型
无论是javaweb还是javase都用到一个ApplicationContext,那么如果有BeanFactory这个类的话,它和BeanFactory有什么关系呢?
如下图所示:
BeanFactory结构树.png

BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口(它也是一个容器),比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等。

第3节Spring IoC的纯xml模式回顾

纯xml模式下

1、在pom中先引入Spring IoC容器功能

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

可以看到了已经引入相应的插件
image.png

2、applicationContext.xml中的约束头

<?xml version="1.0" encoding="UTF-8"?>
<!--根标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置
   xmlns是命名空间。下面之所以能用bean,是因为引入了beans的xml的相关约束。
   schemaLocation都是成对出现的-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
">

3、java模式下的启动

@Test
public void testIoC() throws Exception {

    // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
    ClassPathXmlApplicationContext applicationContext = 
                    new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

    // 不推荐使用
   //ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");

    // 在容器中通过id来获取对应的实例化对象
    AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
    System.out.println(accountDao);
    applicationContext.close();
}

4、web应用模式下的启动

上面是java模式中的启动,那么web模式的启动是在web.xml中需要添加监听器,通过监听器来启动
如图所示:因为web所有的东西都封装在Web容器中,如果使用web功能,就需要引入Spring的web功能
image.png

1、pom.xml中引入Spring的Web功能依赖

   <!--引入spring web功能-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>

2、Web下面的配置文件

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

  <!--要使用监听器来做容器的初始化工作,就需要将applicationContext配置文件加载并读取进来
    所以下面需要指定一个全局参数-->

  <!--配置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>

注意:首先web应用先去读取web.xml,发现有listener这个组件,就会去执行初始化ContextLoaderListener
监听器,监听器一旦执行,就会执行Spring IOC容器的启动工作,我们的对象呢就能够进行管理了。

同时要注意的是,BeanFactory目前是手动写的,但是Spring容器里面有,所以要删除手写的,改用Spring框架的。那么如何切换呢
其实Web容器一旦启动,Spring容器一初始化,BeanFactory容器已经被放到了ServletContext的上下文当中,其实Spring提供了一个工具类,方便我们去获取这个容器,容器拿到后,自然也就拿到容器里面的对象了

 // 声明出去
 private TransferService transferService = null ; 

 // 初始化方法
 @Override
 public void init() throws ServletException {

     //通过WebApplicationContextUtils工具类,拿到WebApplicationContext上下文
     WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    //通过上下文拿到容器,并获取对应的方法拿代理对象工厂
     ProxyFactory proxyFactory = (ProxyFactory)webApplicationContext.getBean("proxyFactory");
     // 通过代理工厂去获取JDK的动态代理
     transferService = (TransferService) proxyFactory.getJdkProxy(webApplicationContext.getBean("transferService")) ;
 }

第4节 Bean创建的方式以及Bean标签属性回顾

Bean的三种创建方式

下面的方法二、方法三 一般很少使用,除非自己new的对象,在xml中匹配得特别的麻烦,才使用自己new出来的,然后添加到容器当中进行管理。(也就是将自己创建的添加到xml进行配置)

    <!--Spring ioc 实例化Bean的三种方式-->
    <!--方式一:使用无参构造器(推荐)-->
    <bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>

    <!--另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理-->

    <!--方式二:静态方法   锁定到具体的类,  且锁定到具体的静态方法
    理解: 调用CreateBeanFactory类 的静态方法 getInstanceStatic 创建  connectionUtils 对象  -->
    <!--<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" 
                                                            factory-method="getInstanceStatic"/>-->

    <!--方式三:实例化方法   需要配置两个bean,因为无法通过类名调用非静态方法-->
    <!--<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
    <bean id="connectionUtils" factory-bean="createBeanFactory" 
                                                                    factory-method="getInstance"/>-->

测试是否获得bean的实例化对象

@Test
public void testIoC() throws Exception {

    // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
    ClassPathXmlApplicationContext applicationContext = 
                      new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

    AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
    System.out.println(accountDao);

    Object connectionUtils = applicationContext.getBean("connectionUtils");
    System.out.println(connectionUtils);

    applicationContext.close();
}

不同的作用范围的生命周期

单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
一句话总结:单例模式的bean对象生命周期与容器相同。

多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

Bean标签属性

在基于xml的loC配置中, bean标签是最基础的标签。它表示了loC容器中的一个对象。换句话说, 如果一个对象想让spring管理, 在XML的配置中都需要使用此标签配置, Bean标签的属性如下:

  1. id属性:用于给bean提供一个唯一标识。在一个标签内部, 标识必须唯一。
  2. class属性:用于指定创建Bean对象的全限定类名。
  3. name属性:用于给bean提供一个或多个名称。多个名称用空格分隔。
  4. factory-bean属性:用于指定创建当前bean对象的工厂bean的唯一标识。当指定了此属性之后,class属性失效。
  5. factory-method属性:用于指定创建当前bean对象的工厂方法, 如配合factory-bean属性使用,则class属性失效。如配合class属性使用, 则方法必须是static的
  6. scope属性:用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
  7. init-method属性:用于指定bean对象的初始化方法, 此方法会在bean对象装配后调用。必须是一个无参方法
  8. destory-method属性:用于指定bean对象的销毁方法, 此方法会在bean对象销毁前执行。它只能为scope是singleton时起作用。

拓展部分:
scope属性
(1)singleton 默认值
单例对象 :被标识为单例的对象在spring容器中只会存在一个实例
(2)prototype
多例原型:被标识为多例的对象,每次在获得才会被创建,每次创建都是新的对象
(3)request
Web环境下,对象与request生命周期一致
(4)session
Web环境下,对象与session生命周期一致

总结:绝大多数情况下,使用单例singleton(默认值),但是在与struts整合时候,务必要用prototype多例,因为struts2在每次请求都会创建一个新的Action,若为单例,在多请求情况下,每个请求找找spring拿的都是同一个action。

在application.xml配置文件添加bean


    <!--scope:定义bean的作用范围

    singleton:单例,IOC容器中只有一个该类对象,默认为singleton
    prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象,
              Spring只创建对象,不管理对象。绝大多数都是单例模式。
    单例模式才能进行对象管理。如果是多例的,只是帮创建而已,并不帮管理。
    多例不在管理当中肯定调用不到其管理方法
    -->
    <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl"
            scope="singleton" init-method="init" destroy-method="destory">

这是一个单例模式,且指定初始化方法和销毁方法,到时候会调用实例化对象的对应的方法的。

spring会在对象创建之后立刻调用 init-method 对应注解为@PostConstruct
spring容器在关闭并销毁所有容器中的对象之前调用destory-method 对应注解为@PreDestory

在Dao的实现类中添加两个方法

public class JdbcAccountDaoImpl implements AccountDao {

    public void init() {
        System.out.println("初始化方法.....");
    }

    public void destory(){
        System.out.println("销毁方法.....");
    }
...

第5节 Spring DI依赖注入配置回顾

A、DI依赖注入的xml配置(依赖注入分类)

1、按照注入的方式分类

构造函数注入:顾名思义,就是利用带参构造函数实现对类成员的数据赋值。
set方法注入:它是通过类成员的set方法实现数据的注入。(使用最多的)

2、按照注入的数据类型分类

基本类型和String
注入的数据类型是基本类型或者是字符串类型的数据。

其他Bean类型
注入的数据类型是对象类型, 称为其他Bean的原因是, 这个对象是要求出现在loC容器中的。那么针对当前Bean来说, 就是其他Bean了。

复杂类型(集合类型)
注入的数据类型是Aarry, List, Set, Map, Properties中的一种类型。

依赖注入的配置实现之构造函数注入,顾名思义,就是利用构造函数实现对类成员的赋值。它的使用要求是,类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时需要注意的是, 当没有无参构造时, 则必须提供构造函数参数的注入, 否则Spring框架会报错。(构造注入可以有参注入,可以无参注入)

3.1、set注入时xml的配置

set注入使用property标签,如果注入的是另外一个bean那么使用ref属性, 如果注入的是普通值那么使用的是value属性

<!--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"/>

3.2、set注入时代码块编写

private ConnectionUtils connectionUtils;

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

4.1、构造器注入时xml的配置

name:按照参数名称注入,index按照参数索引位置注入, type构造函数的参数类型

<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" index="1" ref="connectionUtils"/>
<constructor-arg name="name" index="2" type="java.lang.String" value="zhangsan"/>
<constructor-arg name="sex" index="3" type="java.lang.Integer"  value="1"/>
<constructor-arg name="money" index="4"  type="java.lang.Float" value="100.6"/>

4.2、构造器注入代码块编写

通过有参构造,传递参数进去。

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

5.1、set注入复杂数据类型

Array、Map

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

5.2、set注入复杂数据类型的代码块编写

在纯xml配置下,普遍就是通过这类方式。
如果是通过注解,那么都不需要set方法,直接在属性上面添加@Autowired。下面会介绍到。

    private String[] myArray;
    private Map<String,String> myMap;

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

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

image.png

B、xml与注解相结合模式回顾

1、xml与注解相结合模式(注解+xml)

注意:

  • 1) 实际企业开发中, 纯xml模式使用已经很少了
  • 2) 引入注解功能, 不需要引入额外的jar
  • 3) xml+注解结合模式, xml文件依然存在, 所以, spring IOC容器的启动仍然从加载xml开始
  • 4) 哪些bean的定义写在xml中, 哪些bean的定义使用注解

写在xml中的bean是:第三方jar中的bean定义在xml, 比如德鲁伊数据库连接池
定义使用注解的bean是:自己开发的bean定义使用注解

2、xml中标签与注解的对应(IoC)

image.png

@Controller用于于控制层类、
@Service用于服务层类、
@Repository用于dao层类的bean
他们都有一个别名是@Component

1、自己开发的bean定义通过@Component转为注解,将bean的id添加进即可(添加到自定义类的上面)
2、维护其bean的依赖关系,xml中的ref,在注解中用@Autowired ( xml模式下是set注入,现在是自动装配)
set注入:对属性创建set方法
自动装配:只需要在属性上添加自动注入注解即可

原来的xml配置如下:

<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils">
  <property name="dataSource" ref="dataSource"/>
</bean>

转为注解模式配置如下:

@Component("connectionUtils")
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource; // 将依赖的类注入进来

   //.....
}

ConnectionUtils属于自定义的类,先在上面添加@Component注解,将原来在xml中配置的id填写上去,然后再对其依赖的属性通过@Autowired进行注入。实现自动装配。再看如下操作:

看我们如何将事务管理器从xml的配置文件上解脱,让它转到注解上来
原xml的配置如下:

<!--事务管理器-->
<bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">
    <property name="ConnectionUtils" ref="connectionUtils"/>
</bean>

转为注解如下进行配置:
xml的id改为写到 @Component里面去,而connectionUtils熟悉通过添加@Autowired进行注入实例化对象

@Component("transactionManager")
public class TransactionManager {
    // 按照类型来注入
    @Autowired
    private ConnectionUtils connectionUtils; // 注入连接工具类

3、DI 依赖注入的注解实现方式 @Autowired(推荐使用)

@Autowired为Spring提供的注解,需要导入包 org.springframework.beans.factory.annotation.Autowired。 @Autowired采取的策略为按照类型注入

public class TransferServiceImpl {
 @Autowired
 private AccountDao accountDao;
}

4、@Qualifier配合@Autowired使用

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

public class TransferServiceImpl {
 @Autowired
 @Qualifier(name="accountDao")  // 这个accountDao是一个具体的注解类的id
 private AccountDao accountDao;
}

5、引用类型的注入

面试题: @AutoWired和@Resource的区别?
@AutoWired默认以类型进行查找,@Resource默认以名称进行查找
@AutoWired(required=false) + @Qualifier(“user”) == @Resource(name=”user”)
其中@Resource注解是jdk1.6后才有的
二、Spring IoC高级应用 - 图7
二、Spring IoC高级应用 - 图8

6、指定扫描路径base-package

(最后一步)第三方的xml和自己写的类的注解,此时xml需要被Spring框架去加载,启动IOC容器,那些注解是需要被识别吧,如何被识别呢?就是配置一个扫描,如何配置一个扫描呢?就需要引入Spring框架的context命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        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/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解扫描,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>

C、纯注解模式回顾

纯注解模式

改造xml+注解模式, 将xml中遗留的内容全部以注解的形式迁移出去, 最终删除xml, 从Java配置类启动对应注解
@Configuration注解, 表明当前类是一个配置类
@ComponentScan注解, 替代context:component-scan
@PropertySource, 引入外部属性配置文件
@lmport引入其他配置类
@Value对变量赋值, 可以直接赋值, 也可以使用 ${ } 读取资源配置文件中的信息
@Bean将方法返回的对象 加入Spring lOC容器

xml配置类迁移出去,并删除xml。那么就要创建一个java类用于配置启动的(用注解替代xml)。

@Configuration // @Configuration 注解表明当前类是一个配置类
@ComponentScan({"com.lagou.edu"}) //扫描包下所有类
@PropertySource({"classpath:jdbc.properties"}) // 引入外部资源文件
/*@Import()*/ // 如果配置类有多个,可以统一的关联到此处来
public class SpringConfig {

    //配置文件传进来了,需要用@Value进行读取${jdbc.driver}里面的值
    @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")  // 添加bean表明该方法的返回对象会加入Spring的IOC容器管理
    public DataSource createDataSource(){
        // 将读取出的值加入进来
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return  druidDataSource;
    }
    // 此时连接池就进入到Spring的IOC容器进行管理
}

java模式下的对容器进行启动

@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模式下容器又如何启动?

没有了xml的配置文件,那只能配置启动类的全限定类名

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

  <!--配置启动类的全限定类名    从原来的xml指定路径改为现在注解的 指定配置类的全限定类名-->
  <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>

第6节 高级特性之lazy-Init延迟加载

2.1 lazy-Init延迟加载

1、Bean的延迟加载(延迟创建)

延迟加载适合单例 Bean不适合多例Bean

ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化就意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。

立即加载就是容器启动把所有都实例化,并放到缓存中。
延迟加载就是当调用getBean的时候才去实例化这个Bean对象

比如
lazy-init=”false”,立即加载,表示在spring启动时,立刻进行实例化

<!--lazy-init:延迟加载,true代表延迟,false代表立即加载,默认是false-->
<bean id="lazyResult" class="com.lagou.edu.pojo.Result"/>
<!--该bean默认的设置为lazy-init="false"-->
<bean id="lazyResult" class="com.lagou.edu.pojo.Result"  lazy-init="false" />

image.gif

如果不让singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean 设置为延迟实例化。

<bean id="lazyResult" class="com.lagou.edu.pojo.Result" lazy-init="true" />

image.gif
设置 lazy-init 为 true ,为延迟加载,它的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第一次向容器通过 getBean 索取 bean 时实例化。

如果是注解模式的话,直接@Lazy即可

2、容器的初始化

/**
 * 测试bean的lazy-init属性
 */
@Test
public void testBeanLazy(){
    ClassPathXmlApplicationContext applicationContext = 
                    new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    Object lazyResult = applicationContext.getBean("lazyResult");
    System.out.println(lazyResult);
    applicationContext.close();
}

3、立即加载&延迟加载时调用栈分析

image.png
image.png

4、如果全局的Bean都设置延迟加载

<beans default-lazy-init="true"    >  ...</beans>  // 这是全局的

5、假设情况

eg1、如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第一次调用时才被实例化的规则

eg2、如果一个 bean 的 scope 属性为 scope=”pototype” 时,即使设置了 lazy-init=”false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化。(因为只有单例才会立即加载,多例不会)

6、应用场景

(1)开启延迟加载一定程度提高容器启动和运转性能
(2)对于不常使用的Bean设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该Bean就占用资源
(3)父Bean应用泛型,当类实例化时通过反射来确定具体类型: 需要设置父Bean为延迟加载

第7节 高级特性值FactoryBean

2.2 FactoryBean 和 BeanFactory

BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,比如ApplicationContext;此处我们重点分析FactoryBean(工厂)
image.png

Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以创建生成某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程

Bean创建的三种方式中的 静态方法 和 实例化方法 和 FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用

// 可以让我们自定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {

    @Nullable
     /* 返回FactoryBean创建的Bean实例,如果isSingleton返回true,
        则该实例会放到Spring容器的单例对象缓存池中Map*/
     T getObject() throws Exception;

     @Nullable
     // 返回FactoryBean创建的Bean类型
     Class<?> getObjectType();

     // 返回作用域是否单例
     default boolean isSingleton() {
     return true;
    } 
}

创建一个Company实例,并通过FactoryBean实例化Company,将实例添加到Spring IOC容器当中进行管理,怎么做呢?需要先创建一个CompanyFactoryBean来实现FactoryBean接口。
他是用来封装一些复杂的bean对象,让我们能够自定义

1、先创建一个Company类

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 +
                '}';
    }
}

2、创建CompanyFactoryBean类并且实现FactoryBean接口

public class CompanyFactoryBean implements FactoryBean<Company> {
    private String companyInfo; // 公司名称,地址,规模
    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = 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 true;
    }
}

3、xml中bean的配置信息

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

测试,获取FactoryBean产生的对象

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

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

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

第8节 高级特性之后置处理器


2.3 后置处理器

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

一个是针对Bean工程的,一个是针对Bean对象的,但是一个产品生产出来是先有工厂,通过工厂来生产的。
工厂初始化完成后(BeanFactory)—>生产一个又一个的 Bean对象

在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一些事情
在Bean对象实例化(并不是Bean的整个生命周期完成)之后,可以使用BeanPostProcessor进行后置处理做一些事情
注意:对象不一定是springbean,而springbean一定是个对象
(走完这个流水线就称之为SpringBean)

SpringBean的生命周期

image.png
image.png
如果生命周期没有走完,算不算一个SpringBean

具体执行的代码块

public class Result implements BeanNameAware,BeanFactoryAware,
                           ApplicationContextAware,InitializingBean, DisposableBean {

    private String status;
    private String message;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Result{" +
                "status='" + status + '\'' +
                ", message='" + message + '\'' +
                '}';
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("注册我成为bean时定义的id:" + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("管理我的beanfactory为:" + beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("高级容器接口ApplicationContext:" + applicationContext);
    }

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


    public void initMethod() {
        System.out.println("init-method....");
    }

    @PostConstruct
    public void postCoustrcut() {
        System.out.println("postCoustrcut");
    }


    @PreDestroy
    public void PreDestroy(){
        System.out.println("PreDestroy...");
    }

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

2.3.1 BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
image.png
该接口提供了两个方法, 分别在Bean的初始化方法前 和 初始化方法后执行, 具体这个初始化方法指的是什么方法, 类似我们在定义bean时, 定义了init-method所指定的方法

在init-method所指定的方法 之前执行,用的是before,
在init-method所指定的方法 之后执行,用的是after

重点提示
定义一个类实现了BeanPostProcessor接口, 默认是会对整个Spring容器中所有的bean进行处理(换句话说所有的bean也就都会经过自定义的处理器中)。如果要对具体的某个bean处理, 可以通过方法参数判断, 两个类型参数分别为Object和String, 第一个参数是每个bean的实例, 第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数, 来判断我们将要处理的具体的bean。(如下代码所示)
注意:处理是发生在Spring容器的实例化和依赖注入之后

流程图的第6步

<bean id="lazyResult" class="com.lagou.edu.pojo.Result"  />

后置处理器里面的两个方法,参数是Object , String ,其中Object 是默认所有bean实例,如果要拦截具体的bean只能通过string来判断,如需要拦截Result对应的bean实例,那么就需要匹配上其id信息,也就是id=lazyResult

/*
 * 拦截实例化之后的对象(实例化了并且属性注入了)
  实现接口还不行,还需要添加@Component,才能让这个类被扫描到
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       // 拦截具体的Bean
        if("lazyResult".equalsIgnoreCase(beanName)) {
            System.out.println("MyBeanPostProcessor  before方法指定拦截处理lazyResult 的bean实例");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("lazyResult".equalsIgnoreCase(beanName)) {
           System.out.println("MyBeanPostProcessor  after方法指定拦截处理lazyResult 的bean实例");
        }
        return bean;
    }
}

流程图第7步

initMethod属性的指定那个方法,如果不用这个属性配置,用@PostConstruct来代替也可以。

  <bean id="lazyResult" class="com.lagou.edu.pojo.Result"   init-method="initMethod"/>
public class Result implements BeanNameAware,BeanFactoryAware,
                            ApplicationContextAware,InitializingBean, DisposableBean {

    private String status;
    private String message;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Result{" +
                "status='" + status + '\'' +
                ", message='" + message + '\'' +
                '}';
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("注册我成为bean时定义的id:" + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("管理我的beanfactory为:" + beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("高级容器接口ApplicationContext:" + applicationContext);
    }

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


    public void initMethod() {
        System.out.println("init-method....");
    }

    @PostConstruct
    public void postCoustrcut() {
        System.out.println("postCoustrcut");
    }


    @PreDestroy
    public void PreDestroy(){
        System.out.println("PreDestroy...");
    }

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

他们都是实例化之后做的一些初始化动作,有先后顺序之别。

image.png

2.3.2 BeanFactoryPostProcessor

BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应用:PropertyPlaceholderConfifigurer

此接口只提供了一个方法,方法参数为ConfifigurableListableBeanFactory,该参数类型定义了一些方法,其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法

方法名字类似我们bean标签的属性, setBeanClassName对应bean标签中的class属性, 所以当我们拿到BeanDefinition对象时, 我们可以手动修改bean标签中所定义的属性值。
BeanDefinition对象:我们在XML中定义的bean标签, Spring解析bean标签成为一个JavaBean,这个JavaBean就是BeanDefinition

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