全方位解析Spring IoC:(五)Bean的扩展

329 阅读30分钟

由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp…

Bean的继承

bean的定义中会包含很多配置的信息,其中包括构造参数、属性值和特定于容器的信息,例如初始化方法、静态工厂方法名称等,这很有可能会造成项目存在大量重复bean定义。为了避免这种情况,Spring提供了bean的继承,即我们可以通过继承的方式从父定义中继承bean中的配置信息,而在子定义中则按照需要对某些值进行新增或覆盖。

一般来说,在IoC容器中bean会被定义为RootBeanDefinition,而继承父定义的bean则被定义为ChildBeanDefinition。但这也不是绝对的,如果我们直接通过ApplicationContext以编程的方式将bean注册为ChildBeanDefinition也是可以的。只不过我们更普遍地会在XMLbean定义中使用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 onautowire modedependency checksingletonlazy 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>

对于被标记为abstractbean仅仅只能作为子定义的模版而不能被实例化(如果强行获取会报错)。也正因如此,IoC容器在执行preInstantiateSingletons()方法时会忽略被标记为abstractbean(默认情况下,ApplicationContext会预实例化所有单例bean。如果我们只想将某个bean作为子定义的模版(指定了class属性),则需要确保将其abstract属性设置为true,否则ApplicationContext将试图预实例化它)。

需要注意,与XML配置不同的是在Java配置中并没有bean定义继承的概念(类级别的继承层次结构与此无关)。

更多详情可阅读一下资料:

Bean的注解

@Autowired

对于@Autowired注解,我们可以将它标记在构造器、字段或配置方法(含Setter方法)上,这样Spring就会根据依赖注入的机制完成自动装配了。

  • 构造器的自动装配

    如果给定的bean只有一个构造器被标注了@Autowiredrequired属性为truerequired属性默认为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.OptionalJDK8特性)或者在参数上标注JSR-305注解@NullableSpring 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(包含指定requiredtrue)的方法和字段都视为必须注入。而对于非必需的依赖我们可以通过将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来实现的,所以这意味着我们不能在BeanPostProcessorBeanFactoryPostProcessor(如果有)中应用这些注解来进行依赖注入。

更多资料可阅读:

@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容器会将firstInjectBeansecondInjectBean注入到injectBeanCollection,而thirdInjectBeanfourInjectBean注入到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,则会根据类型匹配出对应的beanprimary)。例如,下面oneInjectBean字段会首先查找名称为oneInjectBeanbean,如果查找失败则会继续通过OneInjectBean类型匹配出对应的beanprimary)。

@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.propertiesapplication.yml文件中读取属性配置了。另外需要注意,因为@Value注解的处理实际是在BeanPostProcessor中执行的,所以我们不能在BeanPostProcessorBeanFactoryPostProcessor中使用@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.PostConstructjavax.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来标识需注入的依赖可为空。

而相比于@Autowiredjava.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.Namedjavax.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中的@NamedJSR-250中的ManagedBean注解的元注解衍生类并不能发挥作用,即不能通过元注解组合的方式衍生出新的业务注解。

更多详情可阅读如下资料:

Bean的作用域(自定义)

Springbean作用域机制是可扩展的,我们不但可以定义自己的作用域,甚至还可以重新定义现有除了singletonprototype的作用域(不推荐)。那么,如果我们要自定义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方法的第一个参数是与作用域关联的唯一名称,例如singletonprototype等;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

BeanFactoryPostProcessorBeanFactory的后置处理器。在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 &mdash;
 * 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(继承了BeanPostProcessorbean),然后将它们(所有)及其直接引用的bean进行实例化并注册到容器中。后续容器在实例化其它bean时会有序地调用BeanPostProcessor实例对bean实例进行前置处理postProcessBeforeInitialization()和后置处理postProcessAfterInitialization(),前者会在bean实例在初始化前(在容器调用InitializingBean.afterPropertiesSet()init方法前)对其进行操作或修改;而后者则会在bean实例初始化后对其进行操作或修改。

实际上,在BeanPostProcessor中我们可以对bean实例执行任何操作,例如对bean实例封装一层代理。我们经常使用Spring AOP的一部分基础类就是通过BeanPostProcessorbean实例封装动态代理实现的,不过需要注意的是对于这些bean实例(无论是BeanPostProcessor实例还是它直接引用的bean实例)都是无法使用Spring AOP进行处理的,即没有切面aspect可以织入它们。若存在这样的情况(将需要织入BeanPostProcessorbean注入到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需要使用非单例(prototypebean 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方式的方法注入了。

更多详情可阅读: