1. IoC容器
本章介绍Spring的控制反转,即IoC容器.
1.1 Spring IoC容器和bean简介
本章介绍了Spring Framework实现的控制反转(IoC)原理。IoC也称为依赖注入(DI)。它是对象定义依赖关系(即为它所定义的属性赋值)的过程,对像的这些属性值只能通过其构造函数的参数(用构造函数创建bean),工厂方法的参数(通过工厂方法创建bean),set方法(通过set方法构造bean),或直接从一个工厂方法获取来进行设置。这些工作本来是要由对象自己来完成的,而现在刚好相反,即交给由容器来完成(即容器会注入这些对象),因此也被称为控制反转。
org.springframework.beans
和org.springframework.context
是spring IoC容器的基础。BeanFactory
接口提供了一种能管理任何类型对象的配置机制。ApplicationContext
接口是BeanFactory
的一个子接口,它使得与spring AOP的集成更容易,还提供了国际化的消息处理,事件发布,构建特定的应用层上下文(比如在web应用中使用WebApplicationContext)等功能。
在Spring中,被spring IoC容器管理的对象都称为bean。
bean之间的依赖关系都反应在容器的配置元数据(如xml配置文件等)中。
1.2 容器概述
用ApplicationContext
代表IoC容器,它负责实例化,配置,装配bean。容器通过获取它们的构造器来实例化并配置,通过读取配置元数据来装配这些bean。配置元数据的形式有xml,java注解,java类或方法或属性。
ApplicationContext
提供了几个开箱即用的接口实现,如ClassPathXmlApplicationContext
,FileSysstemXmlApplicationContext
等。
1.2.1 配置元数据
元数据的配置方式有三种:
- 基于注解的配置:从Spring 2.5开始。
- 基于Java的配置:从Spring 3.0开始。在Java类中使用
@Configuration
,@Bean
,@Import
,@DependsOn
注解。 - 基于XML的配置:从Spring诞生开始。bean配置为
。
1.2.2 实例化容器
给ApplicationContext
提供配置元数据所在的路径字符串作为其实现类的构造参数即可,就可以从文件系统、Java类路径下加载配置数据。
如下示例:xml文件放在resource目录下。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
services.xml:
daos.xml:
在以上示例中,spring容器便可根据xml文件来实例化和装配相应的bean。
编写xml配置元数据:
- 其根元素为
。 -
代表一个bean。 - 不同的业务场景的配置文件可以定义在不同的xml文件中,可通过`
元素引用它们。
示例:
使用
要注意以下 几点:
- 当前这个文件必须要和import引入的3个文件在同一个目录或类路径中。
- 引入的文件必须在当前文件的下方(即同目录或其所在目录的子目录中),也就是说不建议引用其父目录的文件。
- 建议使用相对路径,以免使用应用程序的配置与特定的文件位置绝对耦合。可以使用
${}
占位符。
1.2.3 使用容器
通过T getBean(String name, Class
可以获取bean实例。
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List userList = service.getUsernameList();
最灵活的使用方式是GenericApplicationContext
与Reader相结合(如XmlBeanDefinitionReader来读取xml文件),但一般不用:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
同一个ApplicationContext
可以与多个Reader组合使用,以从不同的配置资源读取配置。但在实际项目中不这样使用,这样就会与Spring api耦合。
1.3 Bean概述
Spring IoC容器管理bean,这些bean都是通过配置元数据(如xml
在容器内部,这些bean是以BeanDefinition对象存在的,相关信息如下表:
属性 | 说明 |
---|---|
class | |
name | bean名称 |
scope | 作用域 |
constructor arguments | |
properties | |
autowiring mode | |
lazy_initialization mode | |
initialization method | |
destruction method |
1.3.1 bean的命名
每个bean都可以有一个或多个标识符,但它们在容器内必须是唯一的。
在xml配置中,可以通过
元素的id
或name
属性来指定bean的标识符,但是id
属性只允许指定一个id. 我们可以通过name
属性指定别名,多个别名之间用逗号、分号或空格分开。
bean的id和name属性不是必需的,如果你没有提供,容器会默认为它提供一个唯一的名称。
如果想通过名称来引用bean,则要用元素。
bean的命名规则:以小写开头的驼峰命名方式,对未命名的bean,spring会以全限定类名加#
加数字来命名(如: com.xzz.chaper01.section03.TestServiceImpl#0)
在bean定义外定义bean的别名。
在基于xml的配置中配置如下:
1.3.2 实例化bean
在xml配置中,必须要提供
元素的class
属性值,这是你要容器管理 的bean的全限定类名称,在容器内部它会被作为一个BeanDefinition
对象的class
属性。你可以通过以下两种方式来使用这个class
属性:
- 直接指定一个bean的类名时,容器会通过反射调用这个类的构造方法来创建bean,这就相当于new操作。
- 通过指定类中的静态工厂方法来实例化bean,工厂方法可能返回相同的类型也可能返回不同的类型。
注:如果要为一个静态的内部类配置bean,应当使用二进制类名,例如在com.example.Foo
类中有一个静态内部类Bar
,此时class属性应当为com.example.Foo$Bar
。用$
分隔内部类名与外部类名。
通过构造方法实例化
spring IoC容器能管理任何类,这些类只需要有一个默认的(也就是无参)构造器。如:
使用静态工厂方法实例化
使用类中包含的静态工厂方法(方法要返回一个对象)实例化,通过factory-method
属性指定静态工厂方法名称,同时无需指定静态工厂方法的返回类型。
如:
注:createInstance
必须是一个静态方法。
使用实例工厂方法实例化
通过容器中已存在的bean的一个非静态方法来创建一个新的bean,使用这种机制,要使class
属性为空,通过factory-method
属性指定这个实例方法,在factory-bean
属性指定这个已存在的bean名称。例:
一个工厂bean可以有多个工厂方法。
1.4 依赖
1.4.1依赖注入DI
基于构造函数的依赖注入
基于构造函数的DI是由容器调用具有一个或多个参数的构造函数来完成,每个参数表示一个依赖项。
- 当参数之间无继承关系,即不存在潜在的歧义,则spring可按类型自动匹配。则可如下配置:
示例类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
xml配置:
- 当参数是基本类型时,spring无法确定值的类型,故无法按类型匹配。例:
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
xml配置1:
显示指定参数的类型,如下:
xml配置2:
指定参数的位置(位置0表示第一个参数),这还可以解决具有相同类型的两个参数的歧义,如下:
xml配置3:
使用构造参数名称:
基于Setter的依赖注入
这是在调用无参构造函数或无参静态工厂方法实例化bean之后,再调用bean的setter方法来实现的。即便是某些属性已经通过构造函数进行了依赖注入,也同样可通过setter再次进行注入。要实现setter注入只需在类中包含setter方法即可。
两种注入方式的选择?
基于构造函数的DI,可将bean打造为一个不可变对象且确保所需依赖项不为null。但是大量的构造函数会使代码变得很糟糕。
setter方式DI则较为灵活得多,bean创建后可通过set方法重新配置。
循环依赖
如果用构造函数进行注入,则会可能会产生循环依赖的场景。如A和B相互注入。
spring在加载时会检查并发现配置问题,spring默认会预实例化bean,这样就可以在容器启动时发现问题而不是到需要使用某个bean时才发现.
依赖注入示例
set注入
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
// xml配置:
构造函数注入
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
xml配置:
静态工厂方法注入:
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
xml:
注:工厂方法的参数由
元素提供,与使用构造函数完全相同
1.4.2 依赖和配置详细介绍
- 直接赋值(用于基本类型,String等)
直接通过
元素的value
属性指定, spring的转换服务会把这值从String类型转换为属性对应的类型.
示例:
以上配置的另一种形式,p命名空间
,这是非常简洁的:
还可以通过配置java.util.Properties
实例来实现注入:
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
spring容器会通过JavaBeans的PropertyEditor
机制将
元素中的字符转换为java.util.Properties实例
.
-
idref
元素
例:
// 注意: 验证有异常???????????????????????
-
ref
元素
通过ref
元素的bean属性可以在同一个容器或其父容器中引用任何bean的id或name,不管它们是否在同一个xml中.示例:
ref
元素的parent
属性可以引用当前容器的父容器中的bean, parent
的值为其父容器中bean的id或name.
示例:
class="org.springframework.aop.framework.ProxyFactoryBean">
- 内部bean
位于
元素内的或
可以定义内部bean.如:
内部bean不需要指定id或name,即使你指定了容器也会忽略它,因为它总是匿名的且是由外部bean创建的,所以不能引用内部bean, 但是有一种另外,就是在自定义作用域时接收销毁回调,例如在一个单例bean中有一个request-scope
的内部bean, 但是在销毁回调时允许它参与.这不常见.因为内部bean通常是共享外部bean的作用域
- 集合注入
元素分别用来设置java中,, List,Set, Map, Properties
类型的数据.如:
[email protected]
[email protected]
`[email protected]
just some string
- 集合的合并
spring容器也支持集合的合并,你可以定义一个父集合,然后子集合可以继承它,此时子集合的值就是父集合和子集合的并集,子集可以覆盖父集中的值。
示例:
[email protected]
[email protected]
[email protected]
[email protected]
注意:要在
元素中定义merge=true
.
,,
`元素,元素的顺序将被保持,即所有父集中的值都在子集值之前。
集合全并的限制:
不能合并不同的集合类型,如list与map,还有必须要子集上指定merge
属性,在父集上指定是无用的。
强类型的集合
从java1.5支持泛型后,你可以使用强类型的集合,这时你将会使用到spring的类型转换支持,会将配置的值转换为对应的类型。
- Null值和空字符串
示例:
- XML 配置p命名空间
p命名空间允许你使用bean
元素的属性代替`元素。
示例:
// 引用bean
注意:使用p命名空间时如果属性是以ref结尾会引起冲突
- xml配置c命名空间
c命名空间用于构造函数注入时替代
元素.
在通过参数名称时使用c命名空间与p命名空间类似.
c命名空间还可使用参数位置来引用, 由于xml语法的限制, 即xml的属性不能以数字开头,所以索引号前要加上_
, 示例:
- 复合属性
spring可以在set注入时使用复合属性,但前提是这个路径上的除了最后一个以外其他的值都不能为null, 否则会抛出NPE示例:
1.4.3 使用depends-on
如果一个bean依赖另一个bean,即这另一个bean被设置为这个bean的一个属性,这样可以通过元素来完成注入,但是在有些情况下,一个bean对另一个bean的依赖是间接的,那么这时就可以通过
depends-on
来完成。示例:
如果依赖多个bean,则给定多个bean的id或名称(它们之间用逗号,分号,空隔分隔)。
注:depends-on
即可以在初始化时指定依赖项,也可以在销毁时指定依赖项,但这仅仅限于单例模式下,但销毁时指定依赖项时,会先销毁依赖项,然后再销毁它本身。因此depends-on
属性可以控制销毁顺序。
1.4.4 bean的懒加载(lazy-init)
懒加载就是告诉容器只有在第一次使用到这个bean时才实例化它。在xml配置中通过
指定,即意味着lazy-init
默认为false
:如:
注:如果一个非lazy的bean依赖一个lazy的bean,spring容器在启动时会实例化这个lazy的bean,因为要保证非lazy的bean的依赖完整性。
可能在容器级别控制lazy-init. 如下:
1.4.5 自动装配
spring容器可以自动解析bean之间的依赖关系,它具有以下优势:
- 可以大大减少配置属性或构造参数。
- 可以自动更新配置,如你想添加一个依赖,你无需修改配置文件便可做到。
在xml配置中,可以通过
元素的autowire
属性来指定自动装配模式。自动装配模式有以下4种:
在byType
和constructor
模式下,可以实现数组和集合的注入。另外如果存在一个强类型的map,且其key为String
类型,如果容器中存在与其value类型匹配的bean,也会自动注入,此时map实例的key为bean的名称,value为bean实例。
自动装配的局限性
显式定义的依赖总是会覆盖自动装配,不能自动装配基本类型,
String
以及由它们组成的数组,这是由设计限制的。不如显式注入精确。
-
可能有多个bean符合自动装配条件,这对于数组,集合,Map来说不是什么问题,但是如果依赖项只能是一个单例的,这将会抛出异常。这有以下几种解决方式:
- 显式定义依赖。
- 通过设置
autowire-candidate=false
来避免被自动装配。 - 在
元素上指定primary
属性来指定一个bean为主选项。
从自动装配中排除一个bean
在xml配置下,如果一个bean的autowire-candidate
属性被设置为false
,容器不会将其自动装配,包有@Autowired注解的形式也无法被自动装配。但这仅仅对于byType
的模式,对于byName
的模式不会有影响。
也可以通过bean名称匹配模式来限制自动装配,即在
元素中设置default-autowire-candidates
属性即可。如你想自动装配名称是以Repository
结尾的bean,你只需设置default-autowire-candidates=*Repository
即可,如果有多个,用逗号分隔。
注意:单个bean的autowire-candidate
属性拥有更高的优先级。
1.4.6 方法注入
假设单例的bean A
依赖一个非单例(prototype)的bean B
,且在A的某个方法上调用它,但是容器在创建bean A
时仅实例化一次,即只会为A设置这个属性一次,即无法在A每次需要B时得到一个新的实例B。即不同生命周期的bean之间的依赖问题.
- 一种解决方式是放弃控制反转,即你可以使A实现
ApplicationContextAware
接口, 在A每次需要B的时候都调用getBean("B")来获取新的B实例,实现如下:
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
上面的示例是不可取的,因为业务代码与Spring框架发生了耦合.
查找(Lookup)方法注入
查找方法注入是指容器通过重写容器所管理的bean的方法,并返回对另外一bean的查找结果, 查找方法注入通常涉及到一个原型(prototype)类型的bean,即上面的场景。spring是通过CGLIB动态代理来生成一个子类而实现方法的重写。
注:
- 被重写的类不能是
final
的,被重写的方法也不能是final
的。- 查找方法注入不能与工厂方法同时使用,特别是配置类中的
@Bean
方法。因为在这种情况下容器不负责创建实例,因此不能动态地创建运行时生成的子类。
示例:spring将会动态地重写createCommand()
。
package fiona.apple;
// no more Spring imports!相比前面的示例,没有添加更多 的依赖。
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
//xml配置:
被重写的方法的格式如下:
[abstract] method(no-args);
如果这个方法是abstract
,将由生成的子类实现这个方法,反之,动态生成的子类会重写这个方法。
基于java的配置:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
注:通常会给查找方法提供一个默认实现,以满足spring的组件扫描,因为在这种情况下abstract
方法会被忽略。
任意方法的替代
将容器中现有的bean所定义的方法替换为其他方法。在xml配置中配置
元素。
示例:
public class MyValueCalculator {
//想要替换的方法
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
替换的方法的实现,重新定义一个类并实现MethodReplacer
接口,注重写方法的返回值类型与原方法的返回值要兼容.否则会抛出类型转换异常.
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
xml配置:
String
1.5 Bean的作用域
spring定义的六种作用域:
Scope | 描述 |
---|---|
singleton | 单例作用域,默认值,一个类在容器中只存在一个bean实例 |
prototype | 一个类在容器中可以有任意多个bean实例 |
request | 在web环境下有效,每个HTTP请求仅有一个单例bean |
session | 在web环境有效,每个HTTP session仅有一个单例bean |
application | 在web环境有效,每个ServletContext下仅有一个单例bean |
websocket | 在web环境有效,每个WebSocket有一个单例bean |
1.5.1 单例作用域singleton
一个bean定义在容器中仅存在一个实例,在容器中的任何地方引用这个bean定义都将返回这个它。
1.5.2 原型作用域prototype
当你每次请求这个bean时都会返回一个新的bean实例。因此对于无状态的bean通常使用单例模式,而对于有状态的bean则使用原型模式。
xml配置示例:
与其他作用域不同的是,spring不管理原型bean的生命周期,容器只会实例化,配置以及其他方法创建原型bean,然后将其交给使用者,除此之外不会对其做更多的记录。因此对于原型bean的生命周期来说,初始化回调方法会被调用,但是不会调用其销毁方法。因此调用者必须清理这些对象以释放资源(可以使用容器的后置处理器bean post-processor
来进行处理)在某种程度上可以将其看作是new
操作的替代。
1.5.3 单例bean依赖原型bean
见方法注入
1.5.4 request,session,application, webSocket作用域
这些作用域仅在web环境下有效。如果在非web环境使用,则会抛出IllegalStateException
,以提示bean的作用域未知。
- 初始化web配置
如果在spring web mvc中,所有的请求都是通过DispatcherServlet
进行处理的,因此无需作任何配置,DispatcherServlet
已公开了所有的状态。
如果在Servlet 2.5的容器中处理DispatcherServlet
以外的请求,如在struts中,需要注册org.springframework.web.context.request.RequestContextListener, ServletRequestListener
。
如果在Servlet 3.0以上的容器中,这可以通过WebApplicationInitializer
接口以编程的方式实现。
对于一些较旧的容器,要在web.xml中作如下配置:
...
org.springframework.web.context.request.RequestContextListener
...
另外,如果spring的RequestContextListener
实现有问题,则可考虑RequestContextFilter
,即配置如下:
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
其实DispatcherServlet, RequestContextListener,RequestContextFilter
都是做的同样的事件,即将HTTP请求对象绑定到服务该请求的线程上。这就使得request,session
作用域在这个调用链上可用。
request 作用域
示例:
每次通过HTTP请求loginAction
时都会创建一个新的bean,即bean的作用域在HTTP请求级别。当这个请求完成后,这个bean也将被丢弃。
JAVA配置或注解驱动:
@RequestScope
@Component
public class LoginAction {
// ...
}
session作用域
其理解与request
类似
@SessionScope
@Component
public class UserPreferences {
// ...
}
application作用域
它的作用域在ServletContext
级别,它将被作为ServletContext
的一个常规属性来存储。这与spring 容器的单例模式有点类似,但是它与之有两点不同:它是在每个ServletContext
中是单例的,而不是每个ApplicationContext
。二是它只是作用ServletContext的一个属性可见。
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
有作用域的bean作为依赖
如果你想将一个request
作用域的bean注入到一个更长生命周期的bean中,你应该注入一个代理对象来替代这个request
作用域的bean。这可以通过`
1.如果在两个singleton作用域的bean之间使用
,通过引用一个序列化的中间代理, 从而在反序列化时重新获取目标bean.
- 如果在一个原型
prototype
bean中定义,每次通过使用代理对象都将创建一个新的bean,并调用其相关方法。
示例:
在上面的示例中,单例bean userService
被注入了一个session
作用域的bean,关键点在于userService
是单例的,即它只会实例化一次,因此注入userPreferences
bean也只会被注入一次。但这并不是我们期望的。因此容器会向userService
bean注入一个代理对象,但是userService
bean并不知道它是一个代理,当userService
调用userPreferences
bean的方法时,实际上是调用的代理对象(拥有与userPreferences bean完全相同的对外公共接口)的方法,然后由代理对象取得作用域中的bean并调用相关方法。
选择代理对象的代理类型
默认情况下,使用
标记一个bean时,是使用CGLIB
代理。注:CGLIB
只能代理public
方法。
另外,也可以通过设置
元素的proxy-target-class
属性为false
来使用标准的JDK代理(基于接口生成的代理),这意味着你无需引入其它的依赖,但是这必须要其至少实现一个接口,且也只能通过接口bean来实现注入。
示例:
1.5.5自定义作用域
spring的作用域是可扩展的,你可以自定义你的作用域,也能够重新定义已有的(singleton和prototype除外)作用域。
创建一个自定义的作用域
要想集成你自定义的作用域到spring容器,必须实现org.springframework.beans.factory.config.Scope
接口。
Scope
接口中的四个方法:
Object get(String name, ObjectFactory objFactory) |
---|
从底层作用域中返回一个对象。
Object remove(String name) |
---|
从底层作用域中移除一个对象
void registerDestructionCallback(String name, Runnable destructionCallback) |
---|
注册作用域作用域或作用域中的对象被销毁时的回调方法。
String getConversationId() |
---|
获取作用域的id.每个作用域之间是不同的。
使用自定义作用域
通过下面的方法将作用域注册到容器
void registerScope(String scopeName, Scope scope)
这个方法位于ConfigurableBeanFactory
接口。
1.6 自定义bean的性质
1.6.1 生命周期回调
通过实现spring的InitializingBean
和DisposableBean
接口,可以管理bean的生命周期。对于前者容器会调用afterPropertiesSet()
方法,对于后者容器会调用destory()
方法以允许你在初始化bean或销毁bean时执行某些操作。
最佳实践是用JSR-250的@PostConstruct
和@PreDestory
注解。
如果你不想使用注解也不想实现接口,则可通过init-method
和destroy-method
属性来定义。
除了回调以外,还可以实现Lifecycle
接口来参与容器自身的启动和关闭过程。
初始化回调
void afterPropertiesSet() throws Exception |
---|
这个方法位于InitializingBean
接口中,它将在一个bean的所有必需属性设置完成后执行。
通过不会实现这个接口,因为这会与spring耦合。
基于xml配置:init-method
属性:
基于java: 通过@PostConstruct
注解一个bean的实例方法。或指定@Bean
元素的initMethod
属性。
销毁回调
|void destory() throws Exception|
这个方法位于DisposableBean
接口。在容器销毁时回调。
基于xml配置:destroy-method
属性。
基于java配置:通过@PreDestroy
注解一个bean的实例方法。或指定@Bean
元素的destroyMethod
属性。
在xml配置中,可以通过定义
元素的default-destroy-method
属性来定义默认的销毁可初始化方法,这样就无需在每个bean上配置相应的属性。但这要求初始化或销毁回调方法的命名要统一,通常可用init
, initialize
, destory
, dispose
等定义。以显示标准化。但此时你也可以在bean中定义以覆盖beans元素中的相关定义。
spring容器会保证在设置完bean的所有属性后立即调用初始化回调方法,如果存在AOP代理,此时AOP代理不会在这之前执行,因为AOP代理是在bean完全被创建后才开始的。但是如果目标bean与代理是分开定义的,这可能会引起一些问题。
组合使用生命周期的控制机制
如果对同一个bean配置了多个不同名称的初始化或销毁回调方法,其执行顺序为:
- 注解定义的方法
@PostConstruct
或PreDestroy
- 实现的InitializingBean或DisposableBean接口定义的。
- 自定义配置的初始化或销毁回调方法。
注:如果有同名的方法,只会执行一个。
启动和关闭回调
Lifecycle
接口为所有有生命周期需求的对象定义了基本方法。
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
spring管理的任何对象都可以实现这个接口,当ApplicationContext
接收到了启动或停止指令时,它将会级联调用容器中所有实现了Lifecycle
的定义。
在某些情况下,如果你想知道一个对象是否在另一个对象之前启动,SmartLifecycle
接口定义了另一个选项int getPhase()
。在启动时phase最低的对象先启动,在停止时则是最后停止。其默认值为0,因此任何负值的都会在标准组件之前启动,正值的则在其后。
在非web的容器中优雅地关闭容器
给容器注册一个关闭钩子,这样即可确保在容器关闭前会调用所有的销毁方法(必须正确配置销毁方法)。
示例:
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 在容器上添加一个关闭钩子
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2 ApplicationContextAware 和 BeanNameAware
如果一个类实现了ApplicationContextAware接口,这个实例的bean可以获取这个容器的引用。可以将这个applicationContext
转其子类实现以暴露更多的接口。这样可以编程式的操作容器。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
但是一般不这样使用,这会与spring耦合,从spring2.5之后可以直接通过@Autowired注入ApplicationContext。
如果一个类实现了BeanNameAware
接口,将可得到相应的bean的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
它会在bean设置完普通属性之后,初始化回调方法之前调用。
1.6.3 其他的Aware接口
接口名称 | 注入依赖 |
---|---|
ApplicationContextAware | 声明的ApplicationContext |
ApplicationEventPublisherAware | 容器的事件publisher |
BeanClassLoaderAware | 加载bean的Class loader |
BeanFactoryAware | 声明的BeanFactory |
BeanNameAware | 声明的bean的名称 |
MessageSourceAware | 配置的消息解析策略 |
NotificationPublisherAware | JMX通知发布者 |
ResourceLoaderAware | 配置加载器,用于底层资源访问 |
ServletConfigAware | 当前容器中的Servlet配置,在web下有效 |
ServletContextAware | 当前的ServletContext |
1.7 Bean定义的继承
一个bean定义包含大量的配置信息,一个子bean定义可以继承父bean定义的配置,也可覆盖父bean中的定义,也可增加自已的配置。在xml配置中,通过bean
元素的parent
属性指定父bean定义。示例如下:
子bean如果没有指定class属性,将会继承父bean的class定义。但也可覆盖它,但前提是与父bean的class兼容并接受父bean中定义的属性。
如果父bean没有指定class属性,则必须在其bean元素中配置abstract="true"
。
示例:
当一个bean定义中配置了abstract=true
,它不会被实例化,也不能被注入到其它bean中。它仅作为一个模板使用。
1.8 容器扩展点
1.8.1 使用BeanPostProcessor自定义bean
通过BeanPostProcessor
接口定义的回调方法你可以定义你自己的实例化逻辑,依赖关系逻辑等。如果你想在容器完成初始化,配置,和初始化之后执行一些逻辑,你可以定义一个或多个BeanPostProcessor
实现。此时通过实现Ordered接口并提供order属性可以控制它们的执行顺序。
- BeanPostProcessor操作的是bean实例
- BeanPostProcessor的作用域是每个容器,即不会跨容器执行,即使用它们在同一个继承体系也不会。
如果一个bean被注册为一个post-processer,对于容器创建的每个bean容器都将执行回调。
ApplicationContext能发现配置元数据中所有实现了BeanPostProcessor
接口的bean并将它们注册为后置处理器,以便后续创建bean时能用到它。
注意:使用在配置类中使用@Bean 工厂方法定义post-processor时,返回类型为这个类本身或者为BeanPostProcesser
接口类型。另外,容器不能够在完全创建它之前发现它。
注: 通过编程方式定义BeanPostProcessor时,尽管推荐的方法是通过容器的自动发现(配置为bean)机制注册它们,但也可以使用ConfigurableBeanFactory
的addBeanPostProcessor
方法来注册,这在某些情况下是有好处的。但此时的注册不会遵循其Ordered接口定义的顺序,而是按照注册编码顺序。
示例一:
自定义一个BeanPostProcessor
,当容器实例化每个bean时都将调用toString()
方法并在控制台输出结果。
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
配置xml:
Test:
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
1.8.2 使用BeanFactoryPostProcessor
自定义配置元数据
BeanPostProcessor
与BeanFactoryPostProcessor
类似,但不同的是BeanFactoryPostProcessor
操作的是bean的配置元数据,spring容器允许在除BeanFactoryPostProcessor
这外的其他bean被实例化前读取配置元数据并改变它们。
可以配置多个BeanFactoryPostProcessor
, 也能通过Ordered
接口控制它们的顺序。
示例:PropertyPlaceholderConfigurer
xml配置:
properties文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
从spring2.5开始,提供了context命名空间来定义:
// 有多个文件时用逗号分隔
注:
PropertyPlaceholderConfigurer
不仅可以从指定的properties查找配置,默认情况下它还会查找JAVA系统属性。可以通过systemPropertiesMode
属性来设置这种行为:0表示never; 1默认,如果无法从指定文件中找到有效值则检查系统属性;2表示在检查指定配置文件之前先检查系统属性(如果有则以系统属性为准),系统属性会覆盖properties文件属性。
使用PropertyPlaceholderConfigurer
自定义类名,当只能在运行时确定特定的实现类时这是非常有效的。
classpath:com/foo/strategy.properties
custom.strategy.class=com.foo.DefaultStrategy
注:如果指定的类名无效,会抛出异常。
示例:PropertyOverrideConfigurer
这也是一个bean factory post-processor,用于覆盖原始的值(原始定义可能有值也可能没有值),由于它不与bean的定义直接关联,所以无法直接从配置文件中看出覆盖关系。
其定义格式如下:
beanName.property=value
示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
# 复合属性,注意除了sammy可为null以外,其它的均不可为null。
foo.fred.bob.sammy=123
xml配置:
注:如果定义了多个properties属性文件,则最后一个胜出。
1.8.3 使用FactoryBean自定义实例化逻辑
org.springframework.beans.factory.FactoryBean
接口:它的实现类是工厂对象。
它在容器的实例化逻辑中是一个可插拔的插件,如果你有复杂的初始化代码,相对于(潜在的)冗长的XML,用Java更好地表达,那么你可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。它定义了三个方法:
Object getObject()
: 返回这个工厂创建的实例。
boolean isSingleton()
: 是否返回单例。
Class getObjectType()
: 返回第一个方法的对象类型。
这个接口在Spring中得到了大量的运用,Spring提供了大约50多个实现。
注:当你想要获取到FactoryBean实例本身而不是它创建的bean时,在调用applicationContext的getBean()
方法时,在bean的id前加上符号&
。
1.9 基于JAVA注解的配置
注解注入和xml注入:注解注入发生在xml注入之前,所以后者将会覆盖前者。
在xml配置中,使JAVA注解生效,则需要配置:
注:
1.9.1 @Required(已废弃)
用于set方法上,在初始化时必须提供其对应的属性值,否则会抛出异常。
从spring 5.1起,已废弃了这个注解。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
1.9.2 @Autowired
可用JSR-330的@Inject
代替。
- 用于注解构造函数
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从spring4.3开始,如果bean只定义了一个构造函数,那么可以省略它,但是如果定义了多个,则至少在一个上面使用它。
- 用于set方法
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
- 用于任意多个参数且任意名称的方法
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
- 用于属性,甚至可与构造函数混合使用
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注: 使用@Autowired
注解是通过类型自动装配。
- 注解数组或集合类型属性可相关set方法
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
public class MovieRecommender {
private Set movieCatalogs;
@Autowired
public void setMovieCatalogs(Set movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
注:可以使用目标bean实现Ordered
接口或使用@Order
或@Priority
注解来实现其在数组或list中的顺序,若不定义则将按照它们的注册顺序排序。
@Order
注解可以用在类级别也可用在@Bean方法级别。但是它不会影响bean的实例化顺序。@Priority
注解只能用于类级别,用于方法级别时无效。
- 用于注解Map类型
此时要求Map的key为String类型,注入时key为bean的名称。
public class MovieRecommender {
private Map movieCatalogs;
@Autowired
public void setMovieCatalogs(Map movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
注 使用@Autowired时,表示相应的bean是必须项,如果没有则会抛出异常。可通过required=false
属性配置来改写这种默认行为。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
注:从spring5.0开始,用以用注解@Nullable
来允许值为null.
由于这些注解是由spring的BeanPostProcessor中实现的,所在不能在自定义的BeanPostProcessor中使用它们。此时应通过xml或@Bean显式的注入。
1.9.3 @Primary
通过类型自动装配时,在容器中可能有多个可以注入的bean,此时我们可以通过@Primary
指定被注入的bean.
在xml配置中则通过bean的primary=true来指定。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
xml:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
1.9.4 @Qualifier
使用@Qualifier
可以作更细粒度的配置。它可以通过指定bean的限定符来注入指定的bean.限定符默认为bean的id。
示例:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
-
可用于具休的构造函数参数
public class MovieRecommender {private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}// ...
}
相应的xml配置:
注:好的@Qualifier
名称定义是main, persistent
等,即能反应其独立与id的组件特征。
用于集合类型注入
此时为多个bean定义相同的qualifier值。自定义qualifier注解
示例:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
使用:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
bean定义:使用
元素并指定type
和value
属性,如果不存在类名冲突则可使用简单类名。示例如下:
当注解具有一般通用性时,可以不定义value。示例:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
// xml配置
定义多个属性的组合,只有满足这所有的属性时就会自动注入,示例:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
// 枚举
public enum Format {
VHS, DVD, BLURAY
}
// 使用
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
// xml配置, 可用bean元素的meta元素代替qualifier元素(但其优先):
1.9.5 泛型注入
除了@Qualifier
注解以外,还可以使用java泛型来作为隐式的限定形式,示例:
// bean定义
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
// 假设以上的bean是实现了一个泛型接口,如`Store`和`Store`, 你能通过`@Autowired`注入`Store`接口且此时泛型相当于qualifier.
@Autowired
private Store s1; // qualifier, injects the stringStore bean
@Autowired
private Store s2; // qualifier, injects the integerStore bean
// 在注入List,Map, Array时也可使用:
// Inject all Store beans as long as they have an generic
// Store beans will not appear in this list
@Autowired
private List> s;
1.9.6 CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,你可以通过它注册你自己的qualifier注解(无需使用@Qualifier
注解)。
示例:
example.CustomQualifier
1.9.7 @Resource
这是JSR-250定义的注解,可以用于属性或属性对应的set方法上。它有一个name
属性,表示bean名称,即它是基于名称的注入。如果没有指定名称,则默认为属性名或对应的set方法所对应的属性名。
示例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
1.9.8 @PostConstruct
和 @PreDestroy
见前面生命周期中的描述