本文最后更新于:2022-10-13T22:33:10+08:00
Spring 简介
Spring是Java的以一种应用程序开发框架,因其性能优秀,易于测试,重用性高而被众多Java开发者欢迎。它是一个轻量级的框架,可以用于开发任何的Java应用程序。Spring的官方网站如下:Spring | Home
Spring家族生态中包括一系列项目(Spring |
Projects ),这些项目均以Spring
Framework框架为基础,可以将其视为Spring基础设施。
Spring Framework具有如下特性:
非侵入式:使用Spring
Framework开发应用程序的时候,Spring对应用程序本身结构的影响非常小,对功能性组件只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能够将组件结构进一步简化,使得应用程序变得结构清晰,简洁优雅。
控制反转:指的是IOC,Inversion of
Control。我们将资源的控制交由Spring来管理。
面向切面编程:指的是AOP,Aspect Oriented
Programming,在不修改源代码的基础上增强代码功能。
IOC容器:IOC容器包含并管理组件对象的生命周期,组件受到容器化的管理,对程序员屏蔽了组件创建过程中的大量细节,极大降低了使用门槛,大幅度提高了开发效率。
声明式:我们仅需使用一些声明,就可以让框架代为实现一些功能,降低了复杂度
一站式:在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 <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”来设置,需要使用对应的标签:
1 2 3 <property name ="id" > <null > </null > </property >
在XML文件中有一些特殊字符不能直接写在字符串中,例如大于小于号等。如果我们需要赋值这样的特殊字符串,可以通过XML实体的方式进行转义,也可以利用CDATA节的方式进行赋值。
第一种方式使用XML实体,例如小于号对应的XML实体为<
,大于号对应的XML实体为>
1 <property name ="name" value ="< 张三 > " />
第二种方式使用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
属性,默认情况下不开启自动装配。我们常用的自动装配属性有两种byType
和byName
。
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的具体生命周期如下:
Bean对象创建(反射调用无参构造)
给Bean对象设置属性
Bean对象初始化之前的操作(由Bean的后置处理器负责)
Bean对象初始化操作(指定Bean的初始化方法)
Bean对象初始化之后的操作(由Bean的后置处理器赋值)
Bean对象准备就绪,可以使用
Bean对象销毁操作(指定Bean的销毁方法)
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向程序员屏蔽了复杂的组件创建过程和细节,只提供了最简洁的使用。