Java – Spring – 基于XML管理bean

简介

Spring IoC的其中一种管理实现,Spring 通过配置xml管理bean的方式。

所谓IoC,指的是我们的实现类,并不通过我们自己进行实例化,而是通过xml配置的模式交由 Spring 进行管理,获取对象也直接由Spring提供。

 

实现思路

 

Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要
无参构造器时,没有无参构造器,则会抛出下面的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name
'helloworld' defined in class path resource [applicationContext.xml]: Instantiation of bean
failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed
to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested
exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld.<init>()

 

 

 

实现环境配置

引入依赖

Spring依赖包相对比较多,主要引入spring-context包,系统会自动引入相关包的依赖项。

<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

 

 

 

创建类

创建一个实体类,这个实体类将交由Spring管理,当我们需要使用这个类时,直接在Spring中获取

public class HelloWorld {
        public void sayHello(){
            System.out.println("helloworld");
        }
    }

 

 

创建Spring的配置文件

 

在Spring的配置文件中配置bean

 

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


    <!--
        配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
        通过bean标签配置IOC容器所管理的bean
        属性:
        id:设置bean的唯一标识
        class:设置bean所对应类型的全类名
    -->
    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.Emp"></bean>

</beans>

 

 

创建测试类测试

@Test
    public void testHelloWorld(){
        ApplicationContext ac = new
                ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
        helloworld.sayHello();
    }

 

 

获取bean

通过上面的配置后,就可以把实体类交给Spring进行处理,而我们无需直接new实体类,通过Spring自动创建类对象,我们只要获取Spring创建的对象就可以了。

根据id获取

由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。
上个实验中我们使用的就是这种方式。

    @Test
    public void testHelloWorld(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld bean = ac.getBean("HelloWorldOne");
        bean.sayHello();
    }

 

根据类型获取

    @Test
    public void testHelloWorld(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld bean = ac.getBean(HelloWorld.class);
        bean.sayHello();
    }

 

 

根据id和类型

    @Test
    public void testHelloWorld(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
        bean.sayHello();
    }

 

 

例外情况获取bean

多个相同bean的获取

当beans中包含了多个相同类型的 bean 时

<bean id="helloworldOne" class="cn.unsoft.spring.bean.HelloWorld"></bean>
<bean id="helloworldTwo" class="cn.unsoft.spring.bean.HelloWorld"></bean>

只能通过以下两种方式获取

 

    @Test
    public void testHelloWorld(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld bean = ac.getBean("helloworldOne", HelloWorld.class);
        HelloWorld bean = ac.getBean("helloworldOne");
        bean.sayHello();
    }

 

使用以下方法获取会报异常

HelloWorld bean = ac.getBean("HelloWorld.class");

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean
of type 'com.atguigu.spring.bean.HelloWorld' available: expected single matching bean but
found 2: helloworldOne,helloworldTwo

 

使用接口获取bean

当这个实现类实现了某个接口时,我们也可以通过接口获取bean.但前提是,这个接口有且仅有这个实现类对接口实现了。

// 定义了一个接口
public interface UserInterFace { }

// 定义了一个实现实体类
public class User implements UserInterFace {}

// 可以通过获取接口来获得User实体类对象
@Test
    public void testHelloWorld(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserInterFace bean = ac.getBean(UserInterFace.class);
        sout(bean);
    }

 

如果有两个以上的实体类实现了这个接口,那么则不可以通过接口获取实体类对象,因为bean不唯一。

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

 

注入赋值

创建User实体类

public class User implements UserInterFace {
    
    public User() {
    }

    public User(int id, String username, String password, int age, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.email = email;
    }

    private int id;
    private String username;
    private String password;
    private int age;
    private String email;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}

 

 

 

使用 property 赋值

通过直接赋值属性来注入数据到实体类。

    <bean id="User" class="cn.unsoft.mybatis.pojo.User">
       <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
       <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
       -->
       <!-- value属性:指定属性值 -->
        <property name="username" value="张三"></property>
        <property name="password" value="123456"></property>
    </bean>

注意:这里的 name 属性,与成员变量无关,与getXXX和setXXX有关

 

 

使用 构造器(constructor-arg) 赋值

    <bean id="User" class="cn.unsoft.mybatis.pojo.User">

        <constructor-arg name="username" value="张三" type="java.lang.String"></constructor-arg>
        <constructor-arg name="password" value="123456" type="java.lang.Integer"></constructor-arg>
    </bean>

注意:使用 constructor-arg  构造器赋值和实体类的构造方法有关,如果构造器中出现类型冲突时(如参数个数一样,且类型一样),可以通过增加 name 或 type 属性指定对应构造器。

 

 

特殊值赋值

null值

对于 property 标签中的 value 属性只允许对字面量进行赋值,如果是null值,value="null"  只会把【null】值当作文本形处理

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="username" value="null"></property>
    </bean>

username = 'null' 而非 null

可在标签插槽中定义null值

        <property name="username">
            <null />
        </property>

 

 

特殊符号值

在 xml 规范中,【<】和【>】号在xml中是有特殊意义的,它代表了一个标签的开头与结尾,所以它在任何时候,都不能作为字面量表达。

要表达【<】和【>】号只能使用它们的转义表达符【&lt;】和【&gt;】表示

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="username" value="a&lt;&gt;b"></property>
    </bean>

username = 'a<>b'

 

value标签

除了可以在属性中定义 value 属性外,也可以在插槽中使用 value 标签进行赋值

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="username">
            <value>张三</value>
        </property>
    </bean>

 

 

使用CDATA区赋值

针对部分特殊字符与xml规范发生冲突的影响,xml提供一种原生输出的特殊标签,包裹在它里面的任何字符都会原生输出,以字面量形式输出。

保CDATA区不可用在属性值中,只能用在标签插槽中。

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="username">
            <value><![CDATA[ a<>b ]]></value>
        </property>
    </bean>

 

对类类型进行赋值(ref属性)

在多对一或一对多等情况下,实体类会包含其它实体类的属性(或成员变量),value 属性在实体类数据前不可用。

perperty 标签除了使用 value 属性值可对实体类中的字面量进行赋值外,还支持使用引用类型的赋值 ref 标签。

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="clazz" ref="ClazzBean"></property>
    </bean>
    
// 定义一个引用类型的 bean 其它 bean 可以通过 ref 属性引用
    <bean id="ClazzBean" class="cn.unsoft.mybatis.pojo.Clazz">
        <property name="clazzID" value="1111"></property>
        <property name="clazzName" value="最强班"></property>
    </bean>

也可以在插槽中定义ref

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="clazz">
             <ref bean="ClazzBean"></ref>
         </property>
    </bean>
    
// 定义一个引用类型的 bean 其它 bean 可以通过 ref 属性引用
    <bean id="ClazzBean" class="cn.unsoft.mybatis.pojo.Clazz">
        <property name="clazzID" value="1111"></property>
        <property name="clazzName" value="最强班"></property>
    </bean>

 

对类类型进行赋值(内部bean方式)

Spring 的 IoC 支持通过级联的方式对类类型数据进行赋值,但需要预先对类类型的数据进行实例化,转化到xml中就是需要预先给类类型进行bean的定义,在Spring中提供内部bean功能,即在 property 中包含bean标签。

内bean只允许在bean内部使用,不能通过getBean 获取其实例化。

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="clazz">
            <bean id="clazzInner" class="cn.unsoft.mybatis.pojo.Clazz">
                <property name="clazzID" value="1111"></property>
                <property name="clazzName" value="远航班"></property>
            </bean>
        </property>
    </bean>

 

对数组类型进行赋值

Spring 的 bean 约束中提供了 array 标签,使用 array 标签对数组进行赋值

// 对数组中的数据进行赋值
    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby">
            <array>
                // 如果数组值为普通字面量数据时使用 value 赋值
                <value>字面量数据</value>

                // 如果数组成员为对象实例时,可以使用 ref 赋值
                <ref bean="hobbyBean"></ref>
            </array>
        </property>
    </bean>

 

对List集合进行赋值使用 list 标签

// 对List集合中的数据进行赋值
    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby">
            <list>
                // 如果数组值为普通字面量数据时使用 value 赋值
                <value>字面量数据</value>

                // 如果数组成员为对象实例时,可以使用 ref 赋值
                <ref bean="hobbyBean"></ref>
            </list>
        </property>
    </bean>

 

使用 util 约束对List集合进行赋值,使用 util:list  标签

// 对List集合中的数据进行赋值
    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby">
            <util:list>
                // 如果数组值为普通字面量数据时使用 value 赋值
                <value>字面量数据</value>

                // 如果数组成员为对象实例时,可以使用 ref 赋值
                <ref bean="hobbyBean"></ref>
            </util:list>
        </property>
    </bean>

注意:util 约束不在 bean 约束中,所以要使用 util 约束中的标签需要引入约束包

xmlns:util="http://www.springframework.org/schema/util"

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby">
            <ref bean="hobbyList"></ref>
        </property>
    </bean>
    
    <util:list id="hobbyList">
        <ref bean="hobby"></ref>
        <ref bean="hobby"></ref>
        <ref bean="hobby"></ref>
    </util:list>

 

对Map类型进行赋值

Spring 的 bean 约束中提供了 map 标签,使用 map 标签对数组进行赋值

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby">
            <map>
                <entry key="字面量key值" value="字面量value值"></entry>
                <entry key-ref="keyRef" value-ref="valueRef"></entry>
            </map>
        </property>
    </bean>

 

使用 util 约束对Map集合进行赋值,使用 util:map  标签

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User">
        <property name="hobby" ref="hobbyMap"></property>
    </bean>

    <util:map id="hobbyMap">
        <entry key="字面量key值" value="字面量value值"></entry>
        <entry key-ref="keyRef" value-ref="valueRef"></entry>
    </util:map>

注意:util 约束不在 bean 约束中,所以要使用 util 约束中的标签需要引入约束包

 

使用 p 约束进行赋值

通过引入【p】约束,对各个属性进行赋值

    <bean id="HelloWorldOne" class="cn.unsoft.mybatis.pojo.User"
    p:username="李四"
          p:age="20"
          p:password="123456"
          p:clazz-ref="ClazzRef"
    ></bean>

注意:p  约束不在 bean 约束中,所以要使用 p  约束中的标签需要引入约束包

xmlns:p="http://www.springframework.org/schema/p"

 

作用域Scope

作用域是指实体类在Spring中的实例化模式,默认实体类在Spring中为singleton(单例模式)

可以在bean中设置scope属性设置其实例是否为多例。

// 设置为多例
<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" scope="prototype"></bean>

singleton : 单例模式

prototype : 多例模式

 

bean的生命周期

对于由Spring所管理的实体类,具有执行的生命周期:

1.bean对象创建(调用无参构造器)

    public User() {
        System.out.println("生命周期1:调用无参构造方法");
    }

 

 

2.给bean对象设置属性

    public int getId() {
        System.out.println("生命周期2:给对象设置属性");
        return id;
    }

 

 

3.bean对象初始化之前操作(由bean的后置处理器负责)

初始化前的操作由Spring提供的接口BeanPostProcessor 中的方法 postProcessBeforeInitialization

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("生命周期3:后置处理器postProcessBeforeInitialization");
        return bean;
    }

 

4.bean对象初始化(需在配置bean时指定初始化方法)

在bean配置中可以设定指定实体类中某一方法作为Spring对bean初始化时的方法

<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" init-method="initMethod"></bean>

 

通过实现 InitializingBean接口方法实现属性装配后执行方法

public class UserServiceImpl implements UserService, InitializingBean {

  @Override
  public void afterPropertiesSet() throws Exception{
      ...
   }

}

 

 

5.bean对象初始化之后操作(由bean的后置处理器负责)

初始化前的操作由Spring提供的接口BeanPostProcessor 中的方法postProcessAfterInitialization

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("生命周期3:后置处理器postProcessAfterInitialization");
        return bean;
    }

 

6.bean对象就绪可以使用

在这个生命周期中,对象将被创建完毕,可由getBean获取对象实例

 

7.bean对象销毁(需在配置bean时指定销毁方法)

在bean配置中可以设定指定实体类中某一方法作为Spring对bean销毁时的方法

<bean id="UserBean" class="cn.unsoft.mybatis.pojo.User" destroy-method="destoryMethod"></bean>

 

 

8.IOC容器关闭

ioc.close();

当ioc容器关闭时,会调用【7.bean对象销毁】的方法,并关闭ioc容器。

 

注意

生命周期在单例模式中,在ioc创建完成时,即会进行,无需到达getBean时才进行。

如果实体类为多例模式时,在ioc创建完成时,生命周期不会被执行,而是在getBean时才执行。

 

FactoryBean 工厂Bean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

在Spring中的bean配置中,通常我们在xml中配置bean的属性数,同时也可以使用FactoryBean工厂Bean代为创建bean对象,通过实现接口的方式代替bean的配置,从而省略我们在xml中配置bean的配置操作。

FactoryBean 实现Spring的一个 FactoryBean<T> 接口,提供三个方法

public class UserFactoryBean implements FactoryBean<User> {}

 

【getObject】方法:当Spring需要通过FactoryBean取到实体类对象时,会调用此方法取得实例对象

    public User getObject() throws Exception {
        return new User();
    }

 

【getObjectType】方法:当Spring需要获取这个实体类的类型时,会调用此方法取得实体类的类型

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

 

【isSingleton】方法:返回这个实体类类型的模式是单例模式还是多例模式

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }

 

静态 FactoryBean

静态 FactoryBean 指的是创建一个类,创建一个静态方法,该静态方法用于返回Bean对象

静态 FactoryBean 的作用是,可以在创建Bean对象之前,可以做一些自定义操作,如对Bean对象的属性设置等

public class MyBeanFactory{

   public static Bean getBean(){
      ...... 初始化操作
      return new Bean();
   }
}

在XML中,可以定义Bean

<bean id="Bean" class="cn.unsoft.beanfactory.MyBeanFactory" factory-method="getBean"></bean>

 

案例:Spring 获取非自定义Bean(1)

案例一:通过Spring 获取 SimpleDataFormat 对象,通过 Spring 管理对象的方式,对日期进行解析

// 创建 SimpleDateFormat 对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse("2023-02-21 12:00:00");

分析:

 

1.通过创建 SimpleDateFormat 对象,需要先 new 一个 SimpleDateFormat 对象,在Spring管理中,就可以使用 <bean> 来让 Spring 自动创建对象

2.SimpleDateFormat 对象调用对象方法 parse ,在Spring中,可以使用【factory-bean】来使用一个已存在的bean中的方法。

 

    <!-- 利用 Spring 先创建一个 SimpleDateFormat 对象实例 -->
    <bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
        <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
    </bean>
    <!-- 使用 factory-bean 来获取在 Spring 中已有的实例对象,并通过使用 factory-method 定义调用的方法 -->
    <bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
        <constructor-arg name="source" value="2023-02-21 12:00:00"></constructor-arg>
    </bean>

 

 

通过调用 getBean 就能实现执行 Spring 中管理的Bean对应的方法。

        // 利用 XML 和 Spring 自动创建 SimpleDateFormat 实例对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Object data = ac.getBean("date");
        System.out.println(data);

 

 

案例:Spring 获取非自定义Bean(2)

案例一:通过Spring 获取 Connection 对象,来获得 JDBC 连接池

Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///database", "root", "root");

通过以上的代码,我们分析如下:

1.要使用DriverManager之前,需要调用 Class.forName 方法,该方法,在Spring 中可以定义为一个静态工厂方法

2.DriverManager.getConnection 实际上也是一个静态工厂方法,因此可以使用XML配置如下

    <bean id="clazz" class="java.lang.Class" factory-method="forName">
        <constructor-arg name="className" value="com.mysql.cj.jdbc.Driver"></constructor-arg>
    </bean>

    <bean id="connection" class="java.sql.DriverManager" factory-method="getConnection">
        <constructor-arg name="url" value="jdbc:mysql:///database"></constructor-arg>
        <constructor-arg name="user" value="root"></constructor-arg>
        <constructor-arg name="password" value="root"></constructor-arg>
    </bean>

 

 

导入properties文件

Spring 除了可以管理我们自己的实体类外,还可以管理其它第三方类库,比如DataSource 的Durid ,或Mybatis 等。

但第三方类库可能需要从外部导入数据,Spring 支持导入properties文件,使用【${}】语法取得数据

使用【context】 约束【property-placeholder】 标签导入properties文件,就可以使用 properties 文件中的数据了

// 导入 jdbc 文件
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.Driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

 

测试用例自动创建IOC

我们在测试中,经常需要在测试用例代码中,创建如下代码来获取Bean对象

ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
ioc.getBean(Bean类型)

我们可以在测试用例中,通过 @ContextConfiguration 注解省略上面的代码,让测试用例自动帮我们创建ioc

// Spring 配合 JUnit 输出日志文件
@RunWith(SpringJUnit4ClassRunner.class)
// 通过 @ContextConfiguration 注解导入 IOC Bean 文件
// 注意需要在前面加 classpath 用于表示是项目内文件路径
@ContextConfiguration("classpath:ioc.xml")
public class JdbcTemplateTest {

    // 在成员变量中设定要取出的Bean对象,我们就可以在其它地方使用这个Bean类了
    // 前提是该类型已经在 xml 中配置了 Bean 类,或配置了自动装配
    @Autowired
    private JdbcTemplate jdbcTemplate;

}

 

    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

 

Spring XML 解析 Bean 原理

Spring容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的 BeanDefinition 存储到一个名为 beanDefinitionMap Map 集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjectsMap集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。

 

 

BeanDefinition 对象由 DefaultListableBeanFactory 类进行管理,其中在XML中解析的Bean 成为BeanDefinition后都将存储在 DefaultListableBeanFactory

 

Bean 实例化的基本流程

加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象;

BeanDefinition存储在一个名为beanDefinitionMapMap<String,BeanDefinition>;

ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;

创建好的Bean实例对象,被存储到一个名为singletonObjectsMap<String,Object>;

当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

 

 

 

 

 

Bean 后处理器

Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册

BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:

 

BeanFactoryPostProcessor

Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行。

我们知道,Spring 会在XML 中解析出所有 <bean> 标签,并预先存放到一个叫 BeanDefinitionMap 的地方,BeanDefinitionMap 保存的则是【未实例化,但准备实例化】的Bean

而当Bean被实例化后,这些Bean会以BeanDefinition对象的形式转存到singletonObjects 中保存。

 

BeanFactoryPostProcessor 将会在 Bean 进行实例化之前执行,意味着,在BeanFactoryPostProcessor 实现阶段,所有的 Bean 都还没有被实例化,此时,我们可以通过实现方法postProcessBeanFactory 中提交的beanFactory 来自定义BeanDefinition插入BeanDefinitionMap 中。

注意:beanFactory 类型是 ConfigurableListableBeanFactory 类,实际上是DefaultListableBeanFactory 的子类,ConfigurableListableBeanFactory 类中没有 registerBeanDefinition 插入(注册)BeanDefinition方法,因此可以通过强转实现。

 

BeanDefinitionRegistryPostProcessor

为了方便使用registerBeanDefinition方法,BeanFactoryPostProcessor 有一个子接口BeanDefinitionRegistryPostProcessor,可以使用该接口中的postProcessBeanDefinitionRegistry 方法,直接调用registerBeanDefinition插入BeanDefinition

 

关于BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的执行先后顺序

BeanDefinitionRegistryPostProcessor. postProcessBeanDefinitionRegistry 先执行

BeanDefinitionRegistryPostProcessor.postProcessBeanFactory 其次

BeanFactoryPostProcessor. postProcessBeanFactory 最后执行。

 

如果遇到多个实现BeanDefinitionRegistryPostProcessor 接口的,那么执行顺序如下:

MyBeanDefinitionRegistryPostProcessor1 -> postProcessBeanDefinitionRegistry

MyBeanDefinitionRegistryPostProcessor2 -> postProcessBeanDefinitionRegistry

MyBeanDefinitionRegistryPostProcessor1 -> postProcessBeanFactory

MyBeanDefinitionRegistryPostProcessor2 -> postProcessBeanFactory

MyBeanFactoryPostProcessor1 -> postProcessBeanFactory

MyBeanFactoryPostProcessor2 -> postProcessBeanFactory

 

BeanPostProcessor

BeanPostProcessor 是在Bean实例化完成之后,即将把实例化对象存储到 singletonObjects 之前的操作,属于最后的Bean处理操作,也是最后执行的操作。

在处理BeanPostProcessor接口方法之前,Bean会先被实例化,

postProcessBeforeInitialization 方法为 实例化 Bean 对象存入singletonObjects 之前执行的方法

postProcessAfterInitialization 方法为 实例化 Bean 对象存入singletonObjects 之后执行的方法,如果方法返回 null.singletonObjects 中将不存入Bean对象

 

Spring Bean 生命周期

Bean 大致生命周期图

 

Bean的实例化阶段:

Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singletonBean通过反射进行实例化;

 

 

Bean的初始化阶段:

Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、 spring高频面试题Bean的循环引用问题都是在这个阶段体现的;

Bean实例属性填充:

      普通属性注入

注入普通属性,Stringint或存储基本类型的集合时,直接通过set方法的反射设置进去;

      单向对象注入(A对象的成员中有B对象)

注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;

      双向对象注入(A对象中有B,B对象中有A

注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题(重点)

双向注入流程图

其中这三个缓存的定义如下

当存在UserService和UserDao时,UserService中需要注入UserDao,UserDao中需要注入UserSrevice时,它们的执行流程如下

 

 

Bean的完成阶段:

经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。

 

Aware 发现接口

Aware 接口是用于在Bean中获得上层内容的接口。

例如:我们使用 ClassPathXmlApplicationContext 创建Bean容器 -> 代码如下

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

容器对象【applicationContext】在 Main 方法中被调,而 applicationContext.xml 中配置了多个Bean.

 

在applicationContext对象创建时,applicationContext.xml 中多个Bean 也同时被 Spring 实例化并管理。

问题:我如何在 Spring Bean 中获取到 applicationContext 容器对象?

解决方法:实现Aware 接口

Aware接口 回调方法 作用
ServletContextAware setServletContext(ServletContext context) Spring框架回调方法注入ServletContext对象,web环境下才生效
BeanFactoryAware setBeanFactory(BeanFactory factory) Spring框架回调方法注入beanFactory对象
BeanNameAware setBeanName(String beanName) Spring框架回调方法注入当前Bean在容器中的beanName
ApplicationContextAware setApplicationContext(ApplicationContext applicationContext) Spring框架回调方法注入applicationContext对象

以下是 UserService 实现 ApplicationContextAware 接口,在UserService 中获取到 ApplicationContext 容器的方法:

public class UserServiceImpl implements UserService, ApplicationContextAware {

    /**
     * 在 UserService Bean 中获取到 ApplicationContext 对象,并对其进行操作
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 使用 applicationContext
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
}

 

 

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Java – Spring – 基于XML管理bean
简介 Spring IoC的其中一种管理实现,Spring 通过配置xml管理bean的方式。 所谓IoC,指的是我们的实现类,并不通过我们自己进行实例化,而是通过xml配置的模式交由 Spring 进行管理,获取对象……
<<上一篇
下一篇>>