由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp…
Bean
的继承
在bean
的定义中会包含很多配置的信息,其中包括构造参数、属性值和特定于容器的信息,例如初始化方法、静态工厂方法名称等,这很有可能会造成项目存在大量重复bean
定义。为了避免这种情况,Spring
提供了bean
的继承,即我们可以通过继承的方式从父定义中继承bean
中的配置信息,而在子定义中则按照需要对某些值进行新增或覆盖。
一般来说,在IoC
容器中bean
会被定义为RootBeanDefinition
,而继承父定义的bean
则被定义为ChildBeanDefinition
。但这也不是绝对的,如果我们直接通过ApplicationContext
以编程的方式将bean
注册为ChildBeanDefinition
也是可以的。只不过我们更普遍地会在XML
的bean
定义中使用parent
来设置其父定义,这同时也会让当前bean
被注册为ChildBeanDefinition
(子定义)。
<bean id="simpleBean" class="com.example.SimpleBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="childSimpleBean" class="com.example.SimpleBean" parent="simpleBean" >
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
通过这种方式,子定义会从父定义中继承相对应的属性和方法,包括作用域、初始化方法、销毁方法或者静态工厂方法等(如有指定都可覆盖父定义);而剩余的其他配置总会以子定义为准(忽略父定义),包括depends on
、autowire mode
、dependency check
、singleton
和lazy init
。另外,较为特殊的class
属性也能被继承,如果子定义中没有指定class
属性是可以从父定义中继承来使用的;而如果子定义对父定义的class
属性进行覆盖则必须与父定义兼容,即必须接受父类的属性。需要注意,如果父定义并未指定其class
属性,则需要将它的abstract
属性设置为true
。
<bean id="simpleBean" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="childSimpleBean" class="com.example.SimpleBean" parent="simpleBean" >
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
对于被标记为abstract
的bean
仅仅只能作为子定义的模版而不能被实例化(如果强行获取会报错)。也正因如此,IoC
容器在执行preInstantiateSingletons()
方法时会忽略被标记为abstract
的bean
(默认情况下,ApplicationContext
会预实例化所有单例bean
。如果我们只想将某个bean
作为子定义的模版(指定了class
属性),则需要确保将其abstract
属性设置为true
,否则ApplicationContext
将试图预实例化它)。
需要注意,与
XML
配置不同的是在Java
配置中并没有bean
定义继承的概念(类级别的继承层次结构与此无关)。更多详情可阅读一下资料:
Bean
的注解
@Autowired
对于@Autowired
注解,我们可以将它标记在构造器、字段或配置方法(含Setter
方法)上,这样Spring
就会根据依赖注入的机制完成自动装配了。
-
构造器的自动装配
如果给定的
bean
只有一个构造器被标注了@Autowired
且required
属性为true
(required
属性默认为true
),则表示在实例化bean
时会通过此构造器进行自动装配。需要注意,对于required
属性为true
的@Autowired
只能被标注在一个构造器上。@Component public class SimpleBean { private final OneInjectBean oneInjectBean; private final TwoInjectBean twoInjectBean; @Autowired public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
如果给定的
bean
存在多个构造器被标注了required
属性为false
的@Autowired
(即有多个候选者),则在实例化bean
时会选择能满足匹配最多依赖的构造器进行自动装配;如果标注的多个构造器都没有满足时(没有候选者满足匹配),则会选择primary
/default
进行自动装配(如果存在)。@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; // 如果其他都不匹配时则会选择此构造函数,同时此处的@Autowired(required=false)并不是必须的 @Autowired(required=false) // 非必须 public SimpleBean() { } @Autowired(required=false) public SimpleBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } // 预先被选择作自动装配 @Autowired(required=false) public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
另外,从
Spring Framework 4.3
开始,如果给定的bean
只定义了一个构造器,即使没有添加@Autowired
注解该构造器也会被用来执行自动装配。而如果存在有多个构造器可用且没有primary/default
构造器,则必须至少在一个构造器上标注@Autowired
注解。@Component public class SimpleBean { private final OneInjectBean oneInjectBean; private final TwoInjectBean twoInjectBean; public SimpleBean(OneInjectBean oneInjectBean, TwoInjectBean twoInjectBean) { this.oneInjectBean = oneInjectBean; this.twoInjectBean = twoInjectBean; } }
需要注意:
- 对于标注了
@Autowired
注解的构造器并不必须是public
的。 - 对于将
@Autowired
注解标注在构造器上时,它的required
属性是针对所有参数的。
- 对于标注了
-
字段的自动装配
如果在字段上标注
@Autowired
注解,则它会在构造器执行(注入)完成后在任何配置方法被调用(注入)前对其进行自动装配。@Component public class SimpleBean { @Autowired private final OneInjectBean oneInjectBean; @Autowired private final TwoInjectBean twoInjectBean; }
此处需要注意,对于标注了
@Autowired
注解的字段并不必须是public
的。 -
方法的自动装配
如果在配置方法(
Setter
方法是配置方法中的一个特例)上标注@Autowired
注解,则方法上所有参数都将会被自动装配。@Component public class SimpleBean { private OneInjectBean oneInjectBean; private TwoInjectBean twoInjectBean; @Autowired public void injectOneInjectBean(OneInjectBean oneInjectBean) { this.oneInjectBean = oneInjectBean; } // Setter方法是配置方法注入的特例 @Autowired public void setTwoInjectBean(TwoInjectBean twoInjectBean) { this.twoInjectBean = twoInjectBean; } }
需要注意:
- 对于标注了
@Autowired
注解的配置方法并不必须是public
的。 - 对于将
@Autowired
注解标注在配置方法上时,它的required
属性是针对所有参数的。
- 对于标注了
根据上文所述,如果我们要将@Autowired
注解标注在方法(含构造方法和配置方法)上时,它的required
属性是针对所有参数的。而如果我们需要对部分方法参数忽略required
属性的语义,那么我们可以通过将参数声明为java.util.Optional
(JDK8
特性)或者在参数上标注JSR-305
注解@Nullable
(Spring Framework 5.0
特性)。
@Component
public class SimpleBean {
private final Optional<OneInjectBean> oneInjectBean;
private final Optional<TwoInjectBean> twoInjectBean;
@Autowired
public SimpleBean(Optional<OneInjectBean> oneInjectBean, Optional<TwoInjectBean> twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
@Component
public class SimpleBean {
private final OneInjectBean oneInjectBean;
private final TwoInjectBean twoInjectBean;
@Autowired
public SimpleBean(@Nullable OneInjectBean oneInjectBean, @Nullable TwoInjectBean twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
对于
@Autowired
注解,我们可以通过它的required
属性来标注依赖是否必须注入,默认情况下对于所有标注了@Autowired
(包含指定required
为true
)的方法和字段都视为必须注入。而对于非必需的依赖我们可以通过将required
设置为false
让容器跳过不满足的注入点,否则注入可能会因运行时“未找到类型匹配”错误而失败。
另外,对于数组(Array
)、集合(Collection
)或哈希表(Map
)等依赖的自动装配IoC
容器会将所有与之类型相匹配的bean
进行注入。其中,所声明Map
的键(KEY
)必须是字符串(用于表示bean
名称,即其键(KEY
)即为bean
名称);而所声明Collection
会按照Ordered
接口或@Order
注解(标准@Priority
注解也可以)所指定的顺序值进行排序(如果存在),如果不存在Ordered
接口或@Order
注解则会按照注册到容器的顺序进行排序。
- 对于数组(
Array
)、集合(Collection
)或哈希表(Map
)等依赖的自动装配必须至少匹配一个bean
,否则将会启动发生错误(默认)。- 对于标准
javax.annotation.Priority
注解是不能声明在@Bean
方法上的,如果需要类似的功能可以使用@Order
注解或者@Primary
注解来代替。@Order
注解仅仅可能会影响到依赖注入的优先级,而不会影响bean
的启动顺序(启动顺序可由依赖关系或@DependsOn
来决定)。
@Component
public class SimpleBean {
@Autowired
private InjectBean[] injectBeanArray;
@Autowired
private Collection<InjectBean> injectBeanCollection;
@Autowired
private Map<String,InjectBean> injectBeanMap;
}
需要注意,
@Autowired
、@Inject
、@Value
和@Resource
注解是通过BeanPostProcessor
来实现的,所以这意味着我们不能在BeanPostProcessor
或BeanFactoryPostProcessor
(如果有)中应用这些注解来进行依赖注入。更多资料可阅读:
@Primary
对于@Primary
注解,主要用于标注某个指定bean
具有最高的依赖优先级。在自动装配时存在多个bean
候选者匹配到单值依赖项而引发错误时,我们可以通过@Primary
注解将某个候选者(bean
)标注为primary
(最高优先级),使得IoC
容器可以选出唯一的候选者而避免错误的发生。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
基于上述配置,下面会将firstMovieCatalog
自动装配到MovieRecommender
:
@Component
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
更多资料可阅读:
@Qualifier
对于@Qualifier
注解,主要用于通过指定bean
限定符的方式缩小依赖(类型)匹配的范围。
与
@Primary
注解相比,@Qualifier
注解对于bean
的选择进行了更多的控制。
@Configuration
public class MovieConfiguration {
@Bean
@Qualifier("firstMovieCatalog")
public MovieCatalog firstMovieCatalog() { ... }
@Bean
@Qualifier("firstMovieCatalog")
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
-
作用于字段
@Component public class MovieRecommender { @Autowired @Qualifier("firstMovieCatalog") private MovieCatalog movieCatalog; // ... }
-
作用于参数
@Component public class MovieRecommender { private MovieCatalog movieCatalog; @Autowired public void MovieRecommender(@Qualifier("firstMovieCatalog") MovieCatalog movieCatalog) { this.movieCatalog = movieCatalog; } // ... }
实际上,即使没有添加
@Qualifier
注解,bean
也会存在默认限定符,即bean
名称。从本质上讲@Autowired
注解是通过限定符值来驱动依赖注入的,所以即使使用了默认限定符(bean
名称)其语义上始终都是通过限定符来缩小类型匹配的范围的。
需要注意,@Qualifier
注解所指定的限定符与bean id
所表示的唯一性有所不同,@Qualifier
限定符想表达的是特定组件的特征标识而不必须是唯一性标识,仅仅充当过滤bean
的标准。也就是说对于类型集合的依赖注入,我们可以将相同@Qualifier
限定符注入到一起(@Qualifier
适用于类型集合)。。
@Configuration
public class MyConfig {
@Bean
@Qualifier("injectBeanCollection")
public InjectBean firstInjectBean() { ... }
@Bean
@Qualifier("injectBeanCollection")
public InjectBean secondInjectBean() { ... }
@Bean
@Qualifier("injectBeanMap")
public InjectBean thirdInjectBean() { ... }
@Bean
@Qualifier("injectBeanMap")
public InjectBean fourInjectBean() { ... }
// ...
}
@Component
public class SimpleBean {
@Autowired
@Qualifier("injectBeanCollection")
private Collection<InjectBean> injectBeanCollection;
@Autowired
@Qualifier("injectBeanMap")
private Map<String, InjectBean> injectBeanMap;
}
如上述例子所示,IoC
容器会将firstInjectBean
和secondInjectBean
注入到injectBeanCollection
,而thirdInjectBean
和fourInjectBean
注入到injectBeanMap
。
实际上,
Spring
并不首推将@Autowired
注解应用于通过名称来标识需要注入的依赖,而是更推荐使用JSR-250
的@Resource
注解。因为虽然@Resource
注解与@Autowired
注解作用相似,但是语义上却有所不同。@Resource
注解在语义上定义为通过其唯一名称标识特定目标组件(与类型无关);而@Autowired
注解的语义则为在按类型选出候选的bean
后,再根据所指定限定符从中选出匹配的候选者(与类型有关)。更多详情可阅读一下资料:
@Resource
对于@Resource
注解(JSR-250
标准),它主要用于标记当前应用程序所需要的资源(例如,bean
依赖)。在Spring
中,IoC
容器会对标注了@Resource
注解的属性字段和配置方法(包含Setter
方法,但不包含构造方法)执行依赖注入。
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Resource(name="oneInjectBean")
public void setOneInjectBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
@Resource(name="twoInjectBean")
public void setTwoInjectBean(TwoInjectBean twoInjectBean) {
this.twoInjectBean = twoInjectBean;
}
}
@Component
public class SimpleBean {
@Resource(name="oneInjectBean")
private OneInjectBean oneInjectBean;
@Resource(name="twoInjectBean")
private TwoInjectBean twoInjectBean;
}
其中,我们需要在@Resource
注解上设置name
属性来指定需要注入的bean
名称。若没有设置name
属性,Spring
则会根据字段名称或方法参数名称为其生成默认名称。另外,与@Autowired
类似的是如果无法通过名称寻找出相应的bean
,则会根据类型匹配出对应的bean
(primary
)。例如,下面oneInjectBean
字段会首先查找名称为oneInjectBean
的bean
,如果查找失败则会继续通过OneInjectBean
类型匹配出对应的bean
(primary
)。
@Component
public class SimpleBean {
@Resource
private OneInjectBean oneInjectBean;
}
更多详情可阅读:
@Value
对于@Value
注解,它主要用于向被标注了注解的属性字段和配置方法/构造方法参数提供值表达式,通过它我们就可以轻易地完成表达式驱动或属性驱动的依赖注入。一般,我们会@Value
注解中使用SpEL
(Spring Expression Language
)表达式来注入值(#{systemProperties.myProp}
风格或者${my.app.myProp}
风格)。
@Component
public class SimpleBean {
@Value("#{systemProperties['user.catalog'] + 'Catalog' }")
private String injectValueString;
@Value("#{{'Thriller': 100, 'Comedy': 300}}")
private Map<String, Integer> injectValueMap;
}
@Component
public class SimpleBean {
@Value("${properties.injectValue}")
private String injectValueString;
// 提供默认值
@Value("${properties.injectValue:defaultValueString}")
private String injectDefaultValueString;
}
对于${my.app.myProp}
风格的属性占位符是通过Spring
提供的一个默认较宽松的嵌入值解析器来实现的,使用此解析器会尝试解析属性值,在无法解析的情况下会将属性名称(也就是属性key
)作为值注入。如果我们想对不存在的值保持较为严格的控制,应该声明一个PropertySourcesPlaceholderConfigurer
类型的bean
。
@Configuration
public class AppConfig {
// 通过这种方式配置的PropertySourcesPlaceholderConfigurer,@Bean方法必须是静态的
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
通过上述配置我们可以确保Spring
在初始化时将所有占位符都解析成功,如果存在任何占位符不能被解析则会初始化失败并抛出异常。
在
Spring Boot
中会默认配置一个PropertySourcesPlaceholderConfigurer
类型的bean
,通过它我们就可以从application.properties
和application.yml
文件中读取属性配置了。另外需要注意,因为@Value
注解的处理实际是在BeanPostProcessor
中执行的,所以我们不能在BeanPostProcessor
或BeanFactoryPostProcessor
中使用@Value
注解。更多详情可阅读一下资料:
@PostConstruct
/@PreDestroy
对于@PostConstruct
/@PreDestroy
注解,它们主要用于标注生命周期的初始化回调方法和销毁回调方法。
@Component
public class SimpleBean {
@PostConstruct
public void init() {
// initialization...
}
@PreDestroy
public void destroy() {
// destruction...
}
}
在使用@PostConstruct
/@PreDestroy
注解前,我们需要将CommonAnnotationBeanPostProcessor
注册到IoC
容器让其生效。CommonAnnotationBeanPostProcessor
不仅可以识别JSR-250
生命周期相关的注解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
(在Spring 2.5
中引入),还能识别和处理@Resource
注解。
更多详情可阅读如下资料:
JSR 330
注解
自Spring3.0
开始,Spring
提供了对JSR-330
标准注解(依赖注入)的支持,这些注解会被Spring
以相同的方式进行扫描的。
对于JSR-330
标准注解的使用,我们必须添加相关的Jar
包。而如果使用Maven
,则可以添加如下依赖到pom.xml
配置文件中:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject
对于@Inject
注解,它主要作用与@Autowired
相似(可将@Autowired
替代为@Inject
),即标记需要自动装配的依赖(标记注入点)。其中@Inject
注解可用在属性字段、配置方法和构造方法上。
import javax.inject.Inject;
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
private ThirdInjectBean thirdInjectBean;
@Inject
public SimpleBean(OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解相同,我们可以使用Provider
来声明bean
的依赖注入点,从而允许根据需要访问生命周期更短的bean
;或者允许通过Provider.get()
方法来延迟获取bean
实例。
import javax.inject.Inject;
import javax.inject.Provider;
@Component
public class SimpleBean {
private Provider<OneInjectBean> oneInjectBean;
private Provider<TwoInjectBean> twoInjectBean;
@Inject
public SimpleBean(Provider<OneInjectBean> oneInjectBean, Provider<TwoInjectBean> twoInjectBean) {
this.oneInjectBean = oneInjectBean;
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解相同,我们可以使用java.util.Optional
和@Nullable
来标识需注入的依赖可为空。
而相比于
@Autowired
,java.util.Optional
和@Nullable
可能更适用在@Inject
上,因为@Inject
注解并没有required
属性。
import javax.inject.Inject;
@Component
public class SimpleBean {
private Optional<OneInjectBean> oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
public void setTwoInjectBean(Optional<OneInjectBean> oneInjectBean){
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(@Nullable TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Autowired
注解类似,我们可以配合JSR-330
标准提供的@Named
注解(作用与@Qualifier
注解类似)对注入bean
的限定符进行指定。
import javax.inject.Inject;
import javax.inject.Named;
@Component
public class SimpleBean {
private OneInjectBean oneInjectBean;
@Inject
public SimpleBean(@Named("oneInjectBean") OneInjectBean oneInjectBean) {
this.oneInjectBean = oneInjectBean;
}
}
更多详情可阅读如下资料:
@Named
/@ManagedBean
在JSR 330
的标准注解中,我们可以使用@javax.inject.Named
或javax.annotation.ManagedBean
来代替@Component
来标注容器中的组件。
import javax.inject.Inject;
import javax.inject.Named;
// 与@Component具有同样的特性:默认不指定名称同样可以
// @Named
// @ManagedBean
// @ManagedBean("simpleBean") could be used as well
@Named("simpleBean")
public class SimpleBean {
private OneInjectBean oneInjectBean;
private TwoInjectBean twoInjectBean;
@Inject
public void setTwoInjectBean(OneInjectBean oneInjectBean){
this.oneInjectBean = oneInjectBean;
}
@Inject
public void setTwoInjectBean(TwoInjectBean twoInjectBean){
this.twoInjectBean = twoInjectBean;
}
}
与@Component
相反,JSR-330
中的@Named
和JSR-250
中的ManagedBean
注解的元注解衍生类并不能发挥作用,即不能通过元注解组合的方式衍生出新的业务注解。
更多详情可阅读如下资料:
Bean
的作用域(自定义)
Spring
的bean
作用域机制是可扩展的,我们不但可以定义自己的作用域,甚至还可以重新定义现有除了singleton
和prototype
的作用域(不推荐)。那么,如果我们要自定义bean
的作用域,则需要实现org.springframework.beans.factory.config.Scope
接口。
/**
* Strategy interface used by a {@link ConfigurableBeanFactory},
* representing a target scope to hold bean instances in.
* This allows for extending the BeanFactory's standard scopes
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON "singleton"} and
* {@link ConfigurableBeanFactory#SCOPE_PROTOTYPE "prototype"}
* with custom further scopes, registered for a
* {@link ConfigurableBeanFactory#registerScope(String, Scope) specific key}.
*
* <p>{@link org.springframework.context.ApplicationContext} implementations
* such as a {@link org.springframework.web.context.WebApplicationContext}
* may register additional standard scopes specific to their environment,
* e.g. {@link org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST "request"}
* and {@link org.springframework.web.context.WebApplicationContext#SCOPE_SESSION "session"},
* based on this Scope SPI.
*
* <p>Even if its primary use is for extended scopes in a web environment,
* this SPI is completely generic: It provides the ability to get and put
* objects from any underlying storage mechanism, such as an HTTP session
* or a custom conversation mechanism. The name passed into this class's
* {@code get} and {@code remove} methods will identify the
* target object in the current scope.
*
* <p>{@code Scope} implementations are expected to be thread-safe.
* One {@code Scope} instance can be used with multiple bean factories
* at the same time, if desired (unless it explicitly wants to be aware of
* the containing BeanFactory), with any number of threads accessing
* the {@code Scope} concurrently from any number of factories.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @since 2.0
* @see ConfigurableBeanFactory#registerScope
* @see CustomScopeConfigurer
* @see org.springframework.aop.scope.ScopedProxyFactoryBean
* @see org.springframework.web.context.request.RequestScope
* @see org.springframework.web.context.request.SessionScope
*/
public interface Scope {
/**
* Return the object with the given name from the underlying scope,
* {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
* if not found in the underlying storage mechanism.
* <p>This is the central operation of a Scope, and the only operation
* that is absolutely required.
* @param name the name of the object to retrieve
* @param objectFactory the {@link ObjectFactory} to use to create the scoped
* object if it is not present in the underlying storage mechanism
* @return the desired object (never {@code null})
* @throws IllegalStateException if the underlying scope is not currently active
*/
Object get(String name, ObjectFactory<?> objectFactory);
/**
* Remove the object with the given {@code name} from the underlying scope.
* <p>Returns {@code null} if no object was found; otherwise
* returns the removed {@code Object}.
* <p>Note that an implementation should also remove a registered destruction
* callback for the specified object, if any. It does, however, <i>not</i>
* need to <i>execute</i> a registered destruction callback in this case,
* since the object will be destroyed by the caller (if appropriate).
* <p><b>Note: This is an optional operation.</b> Implementations may throw
* {@link UnsupportedOperationException} if they do not support explicitly
* removing an object.
* @param name the name of the object to remove
* @return the removed object, or {@code null} if no object was present
* @throws IllegalStateException if the underlying scope is not currently active
* @see #registerDestructionCallback
*/
@Nullable
Object remove(String name);
/**
* Register a callback to be executed on destruction of the specified
* object in the scope (or at destruction of the entire scope, if the
* scope does not destroy individual objects but rather only terminates
* in its entirety).
* <p><b>Note: This is an optional operation.</b> This method will only
* be called for scoped beans with actual destruction configuration
* (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
* Implementations should do their best to execute a given callback
* at the appropriate time. If such a callback is not supported by the
* underlying runtime environment at all, the callback <i>must be
* ignored and a corresponding warning should be logged</i>.
* <p>Note that 'destruction' refers to automatic destruction of
* the object as part of the scope's own lifecycle, not to the individual
* scoped object having been explicitly removed by the application.
* If a scoped object gets removed via this facade's {@link #remove(String)}
* method, any registered destruction callback should be removed as well,
* assuming that the removed object will be reused or manually destroyed.
* @param name the name of the object to execute the destruction callback for
* @param callback the destruction callback to be executed.
* Note that the passed-in Runnable will never throw an exception,
* so it can safely be executed without an enclosing try-catch block.
* Furthermore, the Runnable will usually be serializable, provided
* that its target object is serializable as well.
* @throws IllegalStateException if the underlying scope is not currently active
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
* @see DestructionAwareBeanPostProcessor
*/
void registerDestructionCallback(String name, Runnable callback);
/**
* Resolve the contextual object for the given key, if any.
* E.g. the HttpServletRequest object for key "request".
* @param key the contextual key
* @return the corresponding object, or {@code null} if none found
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
Object resolveContextualObject(String key);
/**
* Return the <em>conversation ID</em> for the current underlying scope, if any.
* <p>The exact meaning of the conversation ID depends on the underlying
* storage mechanism. In the case of session-scoped objects, the
* conversation ID would typically be equal to (or derived from) the
* {@link javax.servlet.http.HttpSession#getId() session ID}; in the
* case of a custom conversation that sits within the overall session,
* the specific ID for the current conversation would be appropriate.
* <p><b>Note: This is an optional operation.</b> It is perfectly valid to
* return {@code null} in an implementation of this method if the
* underlying storage mechanism has no obvious candidate for such an ID.
* @return the conversation ID, or {@code null} if there is no
* conversation ID for the current scope
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
String getConversationId();
}
在Scope
接口中有四种方法:
-
Scope#get
方法从基础作用域返回对象:Object get(String name, ObjectFactory<?> objectFactory)
例如,会话作用域实现返回会话作用域的
bean
,如果不存在则会将bean
绑定session
后(以供将来引用)返回一个bean
的新实例。 -
Scope#remove
方法从基础作用域中删除对象:Object remove(String name)
例如,会话作用域实现从底层会话中删除会话作用域的
bean
并且返回该对象,如果没有找到指定名称的bean
实例则返回null
。 -
Scope#registerDestructionCallback
方法注册了一个回调,当此作用域对象被销毁时会调用此方法(回调):void registerDestructionCallback(String name, Runnable destructionCallback)
-
Scope#getConversationId
方法获取基础作用域的对话标识符:String getConversationId()
这个标识符对于每个作用域都是不同的。对于会话作用域的实现,此标识符可以是会话标识符。
在实现自定义Scope
实现后,我们需要通过ConfigurableBeanFactory#registerScope
方法向IoC
容器注册新的Scope
作用域。
void registerScope(String scopeName, Scope scope);
其中,registerScope
方法的第一个参数是与作用域关联的唯一名称,例如singleton
、prototype
等;registerScope
方法的第二个参数是向IoC
容器注册和使用的自定义Scope
实例。
为了便于理解,下面我们编写了一个自定义Scope
实现,并向IoC
容器完成注册。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
这样我们就可以创建遵守自定义Scope
范围规则的bean
定义了:
<bean id="..." class="..." scope="thread">
或
@Scope(scopeName="thread")
@Component
public class SimpleBean {
}
更多详情可阅读以下资料:
Bean
的后置处理器
BeanFactoryPostProcessor
BeanFactoryPostProcessor
是BeanFactory
的后置处理器。在BeanFactory
实例化后,IoC
容器会在BeanFactoryPostProcessor
中(如有)读取Metadata
配置,并在实例化任何bean
(除了BeanFactoryPostProcessor
)之前提供入口让其可对Metadata
配置进行修改。简单来说,BeanFactoryPostProcessor
即是可在容器实例化任何bean
(除了BeanFactoryPostProcessor
)前对读取的Metadata
配置进行修改。另外,如果存在多个BeanFactoryPostProcessor
进行处理时,我们可以通过继承接口Ordered
来指定其执行的顺序。
BeanFactoryPostProcessor
的作用域是容器级的,即BeanFactoryPostProcessor
仅仅在当前容器有效。- 在
BeanFactoryPostProcessor
中与bean
实例一起工作从技术上讲是可行的(例如,通过使用BeanFactory.getBean()
),但是这样做会导致过早实例化而违背了标准容器的生命周期,从而可能产生一些负面的影响,比如绕过了bean
后处理器BeanPostProcessor
的处理。BeanFactoryPostProcessor
的延迟初始化标记会被忽略,因为如果没有其他bean
引用BeanFactoryPostProcessor
的情况下该后处理器将根本不会被实例化,这并不是预期的结果(对于下文提及的BeanPostProcessors
同样如此)。
/**
* Factory hook that allows for custom modification of an application context's
* bean definitions, adapting the bean property values of the context's underlying
* bean factory.
*
* <p>Useful for custom config files targeted at system administrators that
* override bean properties configured in the application context. See
* {@link PropertyResourceConfigurer} and its concrete implementations for
* out-of-the-box solutions that address such configuration needs.
*
* <p>A {@code BeanFactoryPostProcessor} may interact with and modify bean
* definitions, but never bean instances. Doing so may cause premature bean
* instantiation, violating the container and causing unintended side-effects.
* If bean instance interaction is required, consider implementing
* {@link BeanPostProcessor} instead.
*
* <h3>Registration</h3>
* <p>An {@code ApplicationContext} auto-detects {@code BeanFactoryPostProcessor}
* beans in its bean definitions and applies them before any other beans get created.
* A {@code BeanFactoryPostProcessor} may also be registered programmatically
* with a {@code ConfigurableApplicationContext}.
*
* <h3>Ordering</h3>
* <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
* {@link org.springframework.core.PriorityOrdered} and
* {@link org.springframework.core.Ordered} semantics. In contrast,
* {@code BeanFactoryPostProcessor} beans that are registered programmatically
* with a {@code ConfigurableApplicationContext} will be applied in the order of
* registration; any ordering semantics expressed through implementing the
* {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
* programmatically registered post-processors. Furthermore, the
* {@link org.springframework.core.annotation.Order @Order} annotation is not
* taken into account for {@code BeanFactoryPostProcessor} beans.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 06.07.2003
* @see BeanPostProcessor
* @see PropertyResourceConfigurer
*/
@FunctionalInterface
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
对于BeanFactoryPostProcessor
的使用,我们可以像其他bean
一样去部署。特别地,如果其声明在ApplicationContext
中时,ApplicationContext
会在适当的时候将其运行使得可以改变定义在容器内的Metadata
配置(如有)。
更多详情请阅读:
BeanPostProcessor
BeanPostProcessor
接口定义了两个接口方法让我们可以实现它来提供自定义实例化逻辑、依赖关系解析逻辑等(可覆盖容器的默认行为)。当我们配置了多个BeanPostProcessor
实例时,可以通过实现Ordered
接口并设置其中的order
属性来控制每个BeanPostProcessor
实例的执行顺序。
BeanPostProcessor
的作用域是容器级的,即BeanPostProcessor
仅仅在当前容器有效。也就是说,如果您在一个容器中定义BeanPostProcessor
,它只会对该容器中的bean
进行后置处理post-process
,而不会对另一个容器中的bean
进行后置处理post-process
。
/**
* Factory hook that allows for custom modification of new bean instances —
* for example, checking for marker interfaces or wrapping beans with proxies.
*
* <p>Typically, post-processors that populate beans via marker interfaces
* or the like will implement {@link #postProcessBeforeInitialization},
* while post-processors that wrap beans with proxies will normally
* implement {@link #postProcessAfterInitialization}.
*
* <h3>Registration</h3>
* <p>An {@code ApplicationContext} can autodetect {@code BeanPostProcessor} beans
* in its bean definitions and apply those post-processors to any beans subsequently
* created. A plain {@code BeanFactory} allows for programmatic registration of
* post-processors, applying them to all beans created through the bean factory.
*
* <h3>Ordering</h3>
* <p>{@code BeanPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
* {@link org.springframework.core.PriorityOrdered} and
* {@link org.springframework.core.Ordered} semantics. In contrast,
* {@code BeanPostProcessor} beans that are registered programmatically with a
* {@code BeanFactory} will be applied in the order of registration; any ordering
* semantics expressed through implementing the
* {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
* programmatically registered post-processors. Furthermore, the
* {@link org.springframework.core.annotation.Order @Order} annotation is not
* taken into account for {@code BeanPostProcessor} beans.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 10.10.2003
* @see InstantiationAwareBeanPostProcessor
* @see DestructionAwareBeanPostProcessor
* @see ConfigurableBeanFactory#addBeanPostProcessor
* @see BeanFactoryPostProcessor
*/
public interface BeanPostProcessor {
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other {@code BeanPostProcessor} callbacks.
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
在IoC
容器启动时,它会自动检测在Metadata
声明的BeanPostProcessor
(继承了BeanPostProcessor
的bean
),然后将它们(所有)及其直接引用的bean
进行实例化并注册到容器中。后续容器在实例化其它bean
时会有序地调用BeanPostProcessor
实例对bean
实例进行前置处理postProcessBeforeInitialization()
和后置处理postProcessAfterInitialization()
,前者会在bean
实例在初始化前(在容器调用InitializingBean.afterPropertiesSet()
或init
方法前)对其进行操作或修改;而后者则会在bean
实例初始化后对其进行操作或修改。
实际上,在BeanPostProcessor
中我们可以对bean
实例执行任何操作,例如对bean
实例封装一层代理。我们经常使用Spring AOP
的一部分基础类就是通过BeanPostProcessor
对bean
实例封装动态代理实现的,不过需要注意的是对于这些bean
实例(无论是BeanPostProcessor
实例还是它直接引用的bean
实例)都是无法使用Spring AOP
进行处理的,即没有切面aspect
可以织入它们。若存在这样的情况(将需要织入BeanPostProcessor
的bean
注入到BeanPostProcessor
)将会看到如下日志信息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).
需要注意,如果我们是通过容器自动检测的方式对
BeanPostProcessor
进行注册和实例化(推荐),则必须将其声明类型限定为BeanPostProcessor
(含其实现类),不然容器将不能在创建其它bean
之前通过类型自动检查到它,从而导致部分bean
在实例化时无法被其所处理(BeanPostProcessor
需要比较早的进行初始化以至于可以在其他bean
实例化时对它们进行处理)。而如果我们想通过程序方式注册和实例化BeanPostProcessor
,也可以使用Spring
提供的ConfigurableBeanFactory#addBeanPostProcessor
方法来完成,不过需要注意的是通过程序的方式注册BeanPostProcessor
实例将不会遵循Ordered
接口的语意,其执行顺序将取决于其注册顺序。另外,相比于自动注册,通过程序方式注册的BeanPostProcessor
实例将在自动注册的BeanPostProcessor
之前执行。更多详情请阅读:
Bean
的方法注入
当协作bean
之间的生命周期有所不同时,通过简单的依赖注入就可能会出现问题。如果单例bean A
需要使用非单例(prototype
)bean B
,容器是无法在每次需要bean A
时都为bean A
提供新的bean B
实例,因为容器只创建单例bean A
一次,所以只有一次设置属性的机会。
一个解决方案是放弃控制反转。我们可以使用ApplicationContext
在每次bean A
需要时调用ApplicationContext#getBean
来请求容器获取Bean 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
框架耦合在一起。因此,Spring
提供了另一种高级功能Method Injection
,让我们可以干净利落地处理此情况。
Lookup Method Injection
Lookup Method Injection
是一种可覆盖bean
实例方法的能力。Lookup Method Injection
实现Method Injection
的方式主要是通过CGLIB
库中的字节码来动态生成重写了指定方法的子类,并且在所覆盖方法中返回所指定的(相同容器中的)bean
实例。
需要注意,因为
Lookup Method Injection
是通过CGLIB
动态代理来实现的,所以需要被覆盖的bean
和需要被覆盖的方法都不能是final
。另外,Lookup Method
不适用于工厂方法,特别是不适用于Configuration
类中的@Bean
方法。因为在这种情况下容器并不负责创建实例,所以无法动态创建运行时生成的即时子类。
这里以CommandManager
类为例,我们将通过Lookup Method Injection
的方式覆盖createCommand()
方法来获取bean
实例。
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();
}
对于被注入的方法(即,被覆盖的方法)签名格式如下所示:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
需要注意,如果被注入的方法是
abstract
,那么在动态生成的子类中将会实现它;而如果被注入的方法是非abstract
,那么在动态生成的子类中将会覆盖原始类中的方法。
其中,对于Lookup Method Injection
我们同样可以以XML
的方式和以Java
的方式进行配置,下面将分别对其进行阐述:
-
通过
XML
的方式在
XML
方式的配置中,我们通过lookup-method
标签指定需要注入的方法createCommand
(例子),这样每次调用createCommand
方法时都会返回新的myCommand
实例(bean
实例)。需要注意,这里每次都返回新实例是因为作用域是prototype
;而如果作用域是singleton
,则每次返回的是相同的实例。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="myCommand"/> </bean>
如果在
XML
基础上结合Java
注解的方式进行,那么我们可以使用@Lookup
注解来代替lookup-method
标签的声明(在需要注入的方法上标注@Lookup
注解)。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"></bean>
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup("myCommand") protected abstract Command createCommand(); }
上述我们可以看到
@Lookup
注解的属性上标注了被注入的bean
名称来指示容器执行对该bean
的查询和注入。而为了更简化地使用,我们也可以直接不加属性的标注@Lookup
注解,这样容器就会通过类型匹配的方式获取和注入目标bean
(解析方法返回类型)。<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"></bean>
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract Command createCommand(); }
-
通过
Java
的方式在
Java
的配置中,我们可以直接在@Component
类中通过@Lookup
注解来指定需要注入的方法,其中原理与上述一致。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } }
@Component public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract Command createCommand(); }
当然,更好的做法是提供非
abstract
类和非abstract
方法,让其通过覆盖的方法进行注入。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } }
@Component public class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup public Command createCommand(){ return null; } }
除此之外,对于
@Bean
方法我们也可以通过内部类的方式来实现相同的功能。@Configuration public class MyConfig { @Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } } }
更多详情可阅读:
Arbitrary Method Replacement
除了通过Lookup Method Injection
实现方法注入外,我门还可以使用一种以替换的方式实现的方法注入(不太有用),即将bean
中的任意方法替换为另一个方法实现。在基于XML
的配置中,我们可以使用replace-method
标签来指定将现有方法替换为另一个方法。
这里以MyValueCalculator
类为例,我们将通过Arbitrary Method Replacement
的方式替换computeValue
方法。
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
首先,我们需要实现org.springframework.beans.factory.support.MethodReplacer
接口提供新的方法定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
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 ...;
}
}
然后,在原有的bean
定义中添加<replaced-method/>
元素,指定MethodReplacer
实例(bean
实例)并在其中使用一个或多个<arg-type/>
标签来指示被替换方法的方法签名(仅当存在多个方法重载时才需要指定方法签名)。
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
这样,我们就完成了Arbitrary Method Replacement
方式的方法注入了。
更多详情可阅读: