Spring笔记(1)-IOC之基于XML管理Bean

Spring 简介

Spring是Java的以一种应用程序开发框架,因其性能优秀,易于测试,重用性高而被众多Java开发者欢迎。它是一个轻量级的框架,可以用于开发任何的Java应用程序。Spring的官方网站如下:Spring | Home

Spring家族生态中包括一系列项目(Spring | Projects),这些项目均以Spring Framework框架为基础,可以将其视为Spring基础设施。

Spring Framework具有如下特性:

  1. 非侵入式:使用Spring Framework开发应用程序的时候,Spring对应用程序本身结构的影响非常小,对功能性组件只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能够将组件结构进一步简化,使得应用程序变得结构清晰,简洁优雅。
  2. 控制反转:指的是IOC,Inversion of Control。我们将资源的控制交由Spring来管理。
  3. 面向切面编程:指的是AOP,Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
  4. IOC容器:IOC容器包含并管理组件对象的生命周期,组件受到容器化的管理,对程序员屏蔽了组件创建过程中的大量细节,极大降低了使用门槛,大幅度提高了开发效率。
  5. 声明式:我们仅需使用一些声明,就可以让框架代为实现一些功能,降低了复杂度
  6. 一站式:在IOC和AOP的基础上,可以整合各种企业应用的开源框架以及优秀的第三方类库,并且Spring生态中的项目覆盖领域也即为广泛,能够满足大部分的需求。

上面的特性可能听起来比较抽象,随着后续的学习,我们会渐渐理解对应的含义。

IOC

介绍

IOC,全称为Inversion of Control,意为控制反转。在传统的程序开发中,我们会使用到很多实体类,如果需要使用到某个类的对象时,我们会直接使用new进行生成,这是正向控制的思想。而控制反转指的是将获取资源的方式进行反转,我们将类交由容器来进行管理,在需要使用的时候,由容器将资源进行主动推送,我们不需要知道容器是如何创建资源对象的,只需要接收资源即可。

Spring中的IOC容器是IOC思想的一种落地实现。我们将IOC容器中管理的类称为组件,也称为Bean。我们将类交由IOC容器进行管理,后续需要获取对应的Bean对象时,也需要从IOC容器中获取。由IOC容器来管理对象的生命周期,我们只需要在需要的时候获取资源即可。

Spring中,IOC容器的相关继承体系如下,选取了一些重要接口和类进行展示:

  • BeanFactory:这是IOC容器的基本实现,是Spring内部使用的接口,面向Spring内部,一般不提供给开发人员使用。
  • ApplicationContext:BeanFactory的子接口,提供了更多的高级特性。面向Spring的使用者,几乎在所有的场合,我们都是使用ApplicationContext而非更底层的BeanFactory。
  • ConfigurableApplicationContext:ApplicationFactory的子接口,相比于ApplicationFactory,扩展了更多特性,包括容器启动、刷新、关闭等方法。

主要用到的实现类如下:

  • ClassPathXmlApplicationContext:通过读取类路径下XML格式的配置文件来创建IOC容器对象
  • FileSystemXmlApplicationContext:通过文件系统读取XML格式的配置文件来创建IOC容器对象
  • WebApplicationContext:专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入ServletContext域中

在上面的实现类中,我们一般使用ClassPathXmlApplicationContext较多,因为在不同环境中文件系统路径不一定对应,使用类路径便于程序的移植。

快速开始

首先创建一个Maven工程,然后引入相关依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!--Spring Framework-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

之后我们创建一个实体类,如下:

1
2
3
4
5
public class HelloWorld {
public void sayHello() {
System.out.println("Hello Spring!");
}
}

如果我们要访问该类,调用其中的sayHello方法,在传统方式下,我们会手动new一个HelloWord对象,然后调用其中的sayHello方法。但是在IOC思想下,我们需要将实体类交由IOC容器进行管理,然后从IOC容器中获取对应对象。

在Spring中,通过XML配置文件进行IOC容器的配置,因此我们需要在resources目录下创建配置文件。选择new -> XML Configuration File -> Spring Config,配置文件的名称无具体要求,这里我们命名为applicationContext.xml,内容如下:

1
2
3
4
5
6
<?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">
<bean id="helloworld" class="com.syh.spring.bean.HelloWorld"/>
</beans>

在其中,我们配置了需要交给IOC容器管理的类,包括它的ID以及对应的全类名。然后可以测试代码如下,运行发现能够正常完成功能。

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
helloWorld.sayHello();

上面的代码中,我们首先需要创建一个IOC容器,创建容器需要使用XML配置文件,因此给定对应的文件位置。之后,我们通过getBean方法获得到对应的对象之后,进行强制转换,之后就可以得到对应的类了。然后调用相应的方法,即可达到效果。

从上面的过程中,我们可以发现,我们只创建了IOC容器,而我们需要的HelloWorld对象则是从IOC容器中进行获取的。中间的过程实际上是由Spring框架帮助我们完成的,通过getBean方法,IOC容器能够知道我们需要哪个类对象,找到对应Class对象后可以通过反射进行创建,之后返回给用户。并且Spring返回给我们的对象,默认是单例的。

Spring底层默认通过反射技术调用组件类的无参构造来创建组件对象,因此需要保证组件类有一个无参构造。如果没有无参构造,则会抛出异常BeanCreationException

基于XML管理Bean

Bean对象的获取

前面我们通过id进行Bean对象的获取,实际上还有其他的方式进行对象的获取,这里进行相关说明。我们在配置文件中有如下Bean对象的说明:

1
<bean id="helloworld" class="com.syh.spring.bean.HelloWorld"/>

方式一:通过id进行获取

id属性指定了Bean的唯一标识,所以根据bean标签的id属性可以唯一获取到对应的组件对象,这种方式获取到的是Object对象,还需要进行类型的强转。

1
HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");

方式二:通过类型进行获取

我们也可以通过类的类型进行获取。一般来说,在一个配置文件中,一种类型的Bean对象只会存在一个,因此我们能够根据类型获取到对应的对象。这种情况下,不需要进行类型的强制转换。

1
HelloWorld helloWorld = context.getBean(HelloWorld.class);

需要注意的是,根据类型类型获取Bean时,要求IOC容器中有且只有一个类型匹配的Bean。但是这不是说配置文件中同一个Class只能出现一次,在id不同的情况下可以出现多次。这种方式一般是为了获取多例对象,可以但是没有必要,因为后续我们会有其他方式获取多例。

方式三:通过id和类型进行获取

结合id和类型进行获取,这种情况下,不需要进行类型的强制转换。

1
HelloWorld helloWorld = context.getBean("helloworld", HelloWorld.class);

我们通常情况下会使用方式二来获取Bean对象。如果组件实现了接口或者继承了父类,我们可以通过接口类型和父类类型来获取该Bean对象,但是前提都是我们能够找到的Bean对象唯一。而实际上,根据类型来获取Bean对象的时候,在满足Bean唯一性的前提下,只要满足对象.instanceof(指定的Class对象)返回为true,则认为与类型匹配,则能够获取。

依赖注入

在入门案例中,我们的实体类是没有成员变量的。而如果有成员变量的话,我们需要为其进行赋值。在Spring中,对象初始化时成员变量的赋值,需要通过依赖注入来完成。对于不同类型的成员变量,注入的方式也有所不同。

我们首先准备下面的实体类Reader,便于后续的测试:

1
2
3
4
public class Reader {
private Integer id;
private String name;
}

上面省略了构造方法、get,set方法、toString方法等。注意这里一定要有一个空参构造。后续可能会在这个类中增加其他的成员变量进行测试,同时会添加对应的get,set方法,修改toString方法,后面就不再进行说明了。

我们的测试方法如下,如果直接配置,没有指定对应的属性,那么输出中,成员变量都是null。后续我们将说明不同类型成员变量的赋值方式。

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Reader reader = context.getBean(Reader.class);
System.out.println(reader);

set方法注入

利用set方法,我们可以设置类中属性的值,对应格式如下:

1
2
3
4
<bean id="reader" class="com.syh.spring.bean.Reader">
<property name="id" value="1"/>
<property name="name"><value>张三</value></property>
</bean>

利用property标签来进行属性的赋值,其中name指定属性的名称,value指定属性值。目前演示的是字面量的初始化。这里的value即可以以属性的方式给定,也可以以子标签的形式给定。

特殊值处理:

  • null值

null值的给定不能直接通过“null”来设置,需要使用对应的标签:

1
2
3
<property name="id">
<null></null>
</property>
  • 特殊字符串:

在XML文件中有一些特殊字符不能直接写在字符串中,例如大于小于号等。如果我们需要赋值这样的特殊字符串,可以通过XML实体的方式进行转义,也可以利用CDATA节的方式进行赋值。

第一种方式使用XML实体,例如小于号对应的XML实体为&lt;,大于号对应的XML实体为&gt;

1
<property name="name" value="&lt; 张三 &gt;"/>

第二种方式使用CDATA节,这是XML中的一种特殊标签,形式为![CDATA[...]],在其中写的内容会被原样解析:

1
2
3
<property name="name">
<value><![CDATA[< 张三 >]]></value>
</property>

构造器注入

如果我们的类中有构造器,我们也可以直接在bean中指定构造器的参数:

1
2
3
4
<bean id="reader" class="com.syh.spring.bean.Reader">
<constructor-arg value="1"/>
<constructor-arg value="张三"/>
</bean>

默认情况下按照顺序给参数进行赋值。在constructor-arg标签中还有两个属性可以进一步描述构造器参数:

  • index:指定参数所在位置的索引,从0开始
  • name:指定参数的名称

类类型赋值

前面的属性均为字面量的赋值,下面介绍类类型的赋值。我们先增加一个类Book,定义如下:

1
2
3
4
public class Book {
private Integer isbn;
private String bookName;
}

之后在Reader中添加对应的属性:

1
2
3
4
5
public class Reader {
private Integer id;
private String name;
private Book readingBook;
}

为类类型赋值有三种方式,分别是引用外部已声明的Bean,引用内部Bean以及使用级联方式。

方式一:引用外部Bean

要引用外部已经声明的Bean,那么我们需要首先声明一个Bean对象,然后再使用ref进行引用:

1
2
3
4
5
6
7
8
9
10
<bean id="book" class="com.syh.spring.bean.Book">
<property name="isbn" value="98211234"/>
<property name="bookName" value="一本好书"/>
</bean>

<bean id="reader" class="com.syh.spring.bean.Reader">
<property name="id" value="100"/>
<property name="name" value="张三"/>
<property name="readingBook" ref="book"/>
</bean>

方式二:引用内部Bean

引用内部Bean即在一个Bean的内部声明另一个Bean。内部Bean只能用于给属性赋值,不能在外部通过IOC容器进行获取,因此可以省略id属性

1
2
3
4
5
6
7
8
9
10
<bean id="reader" class="com.syh.spring.bean.Reader">
<property name="id" value="100"/>
<property name="name" value="张三"/>
<property name="readingBook">
<bean class="com.syh.spring.bean.Book">
<property name="isbn" value="98211234"/>
<property name="bookName" value="一本好书"/>
</bean>
</property>
</bean>

方式三:级联方式

使用级联方式,我们需要依次为类的属性进行赋值。注意这种方式一定要先引用某个bean为属性赋值,之后才能使用级联方式更新属性。相比之下这种方式用的并不是很多。

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="book" class="com.syh.spring.bean.Book">
<property name="isbn" value="98211234"/>
<property name="bookName" value="一本好书"/>
</bean>

<bean id="reader" class="com.syh.spring.bean.Reader">
<property name="id" value="100"/>
<property name="name" value="张三"/>
<property name="readingBook" ref="book"/>
<property name="readingBook.isbn" value="98211235"/>
<property name="readingBook.bookName" value="二本好书"/>
</bean>

数组类型赋值

Reader类中添加对应的数组成员变量:

1
2
3
4
5
6
public class Reader {
private Integer id;
private String name;
private Book readingBook;
private int[] borrowingBookIds;
}

在bean标签中,我们可以使用array标签为数组类型进行赋值:

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="reader" class="com.syh.spring.bean.Reader">
<property name="id" value="100"/>
<property name="name" value="张三"/>
<property name="readingBook" ref="book"/>
<property name="borrowingBookIds">
<array>
<value>1</value>
<value>3</value>
<value>5</value>
</array>
</property>
</bean>

列表类型赋值

Reader类中添加对应的列表成员变量:

1
2
3
4
5
6
7
public class Reader {
private Integer id;
private String name;
private Book readingBook;
private int[] borrowingBookIds;
private List<Book> returningBooks;
}

与数组类型类似,在bean标签中,我们可以通过list标签为列表类型进行赋值,下面的配置文件中省略了其他不相关的赋值语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<bean id="book1" class="com.syh.spring.bean.Book">
<property name="isbn" value="1"/>
<property name="bookName" value="book-1"/>
</bean>

<bean id="book2" class="com.syh.spring.bean.Book">
<property name="isbn" value="3"/>
<property name="bookName" value="book-2"/>
</bean>

<bean id="book3" class="com.syh.spring.bean.Book">
<property name="isbn" value="5"/>
<property name="bookName" value="book-3"/>
</bean>

<bean id="reader" class="com.syh.spring.bean.Reader">
<!--其他配置...-->
<property name="returningBooks">
<list>
<ref bean="book1"/>
<ref bean="book2"/>
<ref bean="book3"/>
</list>
</property>
</bean>

另一种方式是将list对象定义在外部,然后通过ref进行引用。但是在配置文件中,我们不能直接使用list标签,而需要引入util的约束,使用util:list标签:

1
2
3
4
5
6
7
8
9
10
11
<util:list id="bookList">
<ref bean="book1"/>
<ref bean="book2"/>
<ref bean="book3"/>
</util:list>

<bean id="reader" class="com.syh.spring.bean.Reader">
<!--其他配置...-->
</property>
<property name="returningBooks" ref="bookList"/>
</bean>

如果需要为Set集合类型赋值,只需要将上面的list标签改为set标签即可。

Map类型赋值

Reader类中添加对应的Map成员变量:

1
2
3
4
5
6
7
8
public class Reader {
private Integer id;
private String name;
private Book readingBook;
private int[] borrowingBookIds;
private List<Book> returningBooks;
private Map<String, Book> bookMap;
}

类比上面的操作,我们也可以通过bean标签中的map标签来对Map成员变量进行赋值。在map标签中还需要指定entry子标签,其中需要指定key,value属性,或者使用对应的子标签。

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="reader" class="com.syh.spring.bean.Reader">
<!--其他配置...-->
<property name="bookMap">
<map>
<entry key="AM" value-ref="book1"/>
<entry>
<key><value>PM</value></key>
<ref bean="book3"/>
</entry>
</map>
</property>
</bean>

类似的,我们也可以将Map定义在外部,然后通过ref进行引用:

1
2
3
4
5
6
7
8
9
<util:map id="bookMap">
<entry key="AM" value-ref="book1"/>
<entry key="PM" value-ref="book2"/>
</util:map>

<bean id="reader" class="com.syh.spring.bean.Reader">
<!--其他配置...-->
<property name="bookMap" ref="bookMap"/>
</bean>

p命名空间

我们可以引入p命名空间,之后就可以通过属性的方式为bean的各个属性赋值。

1
2
3
<bean id="readerP" class="com.syh.spring.bean.Reader" 
p:id="1" p:name="李四"
p:readingBook-ref="book" p:bookMap-ref="bookMap"></bean>

外部属性文件引入

在配置文件中,我们还可以引入属性文件,之后就可以使用对应的属性文件了。

例如在resources目录下,还存在一个外部属性文件jdbc.properties,其中记录了数据库连接的相关数据:

1
2
3
4
jdbc.user=root
jdbc.password=123123
jdbc.url=jdbc:mysql://localhost:3306/ssm_data
jdbc.driver=com.mysql.cj.jdbc.Driver

要想使用该外部属性文件,我们需要在配置文件中进行引入才能使用:

1
2
3
4
5
6
7
8
9
<!-- 引入外部属性文件 -->
<context:property-placeholder location="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.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

自动装配

在依赖注入小节,我们通过手动指定的方式,为每个属性进行了初始值的指定。不过在Spring中,提供了一种自动装配的机制,为我们完成ref的自动匹配。自动装配可以帮助我们解决ref的赋值,它指的是根据指定的策略,在IOC容器中匹配某个bean,自动为bean中的类类型的属性或者接口类型的属性进行赋值。

自动装配对应bean标签中的autowire属性,默认情况下不开启自动装配。我们常用的自动装配属性有两种byTypebyName

byType表示根据要赋值属性的类型,在IOC容器中匹配某个Bean然后为属性赋值。

  • 如果通过类型没有找到任何一个匹配的Bean,则不装配,使用默认值。如果没有默认值则为null。
  • 如果通过类型找到了多个类型匹配的Bean,则会抛出异常NoUniqueBeanDefinitionException

例如下面的配置文件,Reader对象的readingBook需要一个Book类型的对象,而在IOC中正好有一个对应的Bean,那么就执行自动装配。

1
2
3
4
5
6
7
8
9
<bean id="reader" class="com.syh.spring.bean.Reader" autowire="byType">
<property name="id" value="123"/>
<property name="name" value="张三"/>
</bean>

<bean id="book" class="com.syh.spring.bean.Book">
<property name="isbn" value="123456"/>
<property name="bookName" value="算术"/>
</bean>

如果我们输出获取到的Reader对象,内容如下所示。可以发现除了readingBook,其中泛型含有Book类型的列表和Map也进行了匹配。

1
2
3
Reader{id=123, name='张三', readingBook=Book{isbn=123456, bookName='算术'}, 
borrowingBookIds=null, returningBooks=[Book{isbn=123456, bookName='算术'}],
bookMap={book=Book{isbn=123456, bookName='算术'}}}

byName表示在IOC以要赋值属性的名称为目标检索其他Bean的id,如果匹配到id与属性名相同,则为属性进行赋值。

  • 如果通过属性名匹配id找不到任何一个Bean,则不装配,使用默认值。
  • byName情况下不会出现匹配到多个Bean的情况,因为如果匹配到了多个Bean,意味着这些Bean的id属性是相同的,而配置文件中不允许id属性值有重复,会直接抛出Configuration problem异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<bean id="reader" class="com.syh.spring.bean.Reader" autowire="byName">
<property name="id" value="123"/>
<property name="name" value="张三"/>
</bean>

<bean id="readingBook" class="com.syh.spring.bean.Book">
<property name="isbn" value="123456"/>
<property name="bookName" value="算术"/>
</bean>

<util:map id="bookMap">
<entry key="AM">
<bean class="com.syh.spring.bean.Book">
<property name="isbn" value="123"/>
<property name="bookName" value="book-1"/>
</bean>
</entry>
<entry key="PM">
<bean class="com.syh.spring.bean.Book">
<property name="isbn" value="456"/>
<property name="bookName" value="book-2"/>
</bean>
</entry>
</util:map>

输出如下,发现其中Map也可以按照名称进行相应的匹配。

1
2
3
Reader{id=123, name='张三', readingBook=Book{isbn=123456, bookName='算术'}, 
borrowingBookIds=null, returningBooks=null,
bookMap={AM=Book{isbn=123, bookName='book-1'}, PM=Book{isbn=456, bookName='book-2'}}}

Bean对象

作用域与生命周期

前面我们说到Spring中IOC容器给我们提供的默认是单例对象,这是由bean标签中的scope属性来进行控制的。scope的可选值有以下两种:

  • singleton:默认取值,单例对象。表示在IOC容器中,这个Bean对象始终为单例,在IOC容器初始化的时候就进行创建。
  • prototype:多例,表示在IOC容器中,这个Bean对象有多个实例,在获取对应Bean对象的时候才创建对象。

Bean的具体生命周期如下:

  1. Bean对象创建(反射调用无参构造)
  2. 给Bean对象设置属性
  3. Bean对象初始化之前的操作(由Bean的后置处理器负责)
  4. Bean对象初始化操作(指定Bean的初始化方法)
  5. Bean对象初始化之后的操作(由Bean的后置处理器赋值)
  6. Bean对象准备就绪,可以使用
  7. Bean对象销毁操作(指定Bean的销毁方法)
  8. IOC容器关闭

针对上面的生命周期,我们可以创建一个类来进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Life {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
System.out.println("2.给Bean对象设置属性");
this.name = name;
}

Life() {
System.out.println("1.Bean对象创建");
}

public void initMethod() {
System.out.println("4.Bean对象初始化操作");
}

public void destroyMethod() {
System.out.println("7.Bean对象销毁操作");
}
}

在配置文件中,我们需要将该类配置为Bean,并且指定初始化方法以及销毁方法:

1
2
3
<bean id="life" class="com.syh.spring.bean.Life" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="life"/>
</bean>

之后模拟使用Bean对象:

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("lifeContext.xml");
Life life = context.getBean(Life.class);
System.out.println("6.Bean对象准备就绪,可以使用");

输出如下:

1
2
3
4
1.Bean对象创建
2.给Bean对象设置属性
4.Bean对象初始化操作
6.Bean对象准备就绪,可以使用

这是因为我们使用的是ApplicationContext,其中并没有提供IOC容器的关闭功能。我们可以改用ConfigurableApplicationContext,然后调用对应的close方法:

1
2
3
4
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("lifeContext.xml");
Life life = context.getBean(Life.class);
System.out.println("6.Bean对象准备就绪,可以使用");
context.close();

得到如下的输出:

1
2
3
4
5
1.Bean对象创建
2.给Bean对象设置属性
4.Bean对象初始化操作
6.Bean对象准备就绪,可以使用
7.Bean对象销毁操作

目前我们还有Bean对象初始化前后的操作没有测试。这两个阶段由Bean的后置处理器负责。我们需要创建一个类实现BeanPostProcessor接口,实现其中的对应方法,然后配置到IOC容器当中。需要注意的是,Bean的后置处理器不是单独针对某一个Bean生效,而是针对IOC容器中的所有Bean对象。

首先创建类,实现对应接口,重写其中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("3.Bean对象初始化之前的操作");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5.Bean对象初始化之后的操作");
return bean;
}
}

将其配置到配置文件中:

1
<bean id="myBeanPostProcessor" class="com.syh.spring.bean.MyBeanPostProcessor"/>

重新执行测试方法,输出如下:

1
2
3
4
5
6
7
1.Bean对象创建
2.给Bean对象设置属性
3.Bean对象初始化之前的操作
4.Bean对象初始化操作
5.Bean对象初始化之后的操作
6.Bean对象准备就绪,可以使用
7.Bean对象销毁操作

上面的演示使用的是默认scope,即单例对象。如果配置的是多例,则销毁阶段不由IOC容器管理。

FactoryBean

我们首先考虑一个简单的工厂模式,我们有一个工厂类BookFactory,如果我们要获得Book对象,那么首先需要获取工厂类对象。在IOC思想指导下,我们应该将这个工厂类也交由IOC进行管理,因此全流程就变成了我们从IOC容器中获取工厂类对象,然后再由工厂类对象获得对应的需求类。

在Spring IOC中,针对工厂模式的管理提供一种FactoryBean的机制。通过这种机制,我们可以直接从IOC容器中获取到需求类,也就是这里例子中的Book对象,而不需要先获取工厂类,再获取需求类。

FactoryBean是一个接口,我们的工厂类可以实现其中的方法,然后将其配置在配置文件中。例如下面,我们实现了一个简单的BookFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BookFactory implements FactoryBean<Book> {
@Override
public Book getObject() throws Exception {
return new Book();
}

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

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

其中需要实现三个方法:

  • getObject:提供一个对象交给IOC容器管理
  • getObjectType:设置所提供对象的类型
  • isSingleton:所提供的对象是否是单例

将其正常配置在配置文件中:

1
<bean id="bookFactory" class="com.syh.spring.bean.BookFactory"/>

从下面的测试方法中,我们可以看到我们获取到的直接是Book对象而非BookFactory对象:

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("factoryContext.xml");
Book bean = context.getBean(Book.class);
System.out.println(bean);

总结来说,FactoryBean这是Spring提供的一种整合第三方框架的常用机制。与普通的Bean不同,如果我们配置了FactoryBean类型的Bean,在获取Bean的时候,得到的并不是在配置文件中class属性对应的类对象,而是getObject方法的返回值。通过这种机制,Spring向程序员屏蔽了复杂的组件创建过程和细节,只提供了最简洁的使用。


Spring笔记(1)-IOC之基于XML管理Bean
http://example.com/2022/10/13/Spring笔记-1-IOC之基于XML管理Bean/
作者
EverNorif
发布于
2022年10月13日
许可协议