Spring核心技术之IoC容器(Version 5.1.3.RELEASE)之三

本文详细介绍了Spring框架中容器扩展点的使用,包括BeanPostProcessor和BeanFactoryPostProcessor的实现方式,以及如何通过注解进行配置。探讨了依赖注入的微调方法,如@Primary和@Qualifier的使用,和自定义qualifier的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

容器扩展点

BeanPostProcessor进行bean自定义

例子:Hello World级别样例

例子:RequiredAnnotationBeanPostProcessor

BeanFactoryPostProcessor配置元数据

例子:类名替换PropertyPlaceholderConfigurer

例子:PropertyOverrideConfigurer

FactoryBean自定义初始化逻辑

基于注解的配置

@Required

@Autowired

@Primary微调自动依赖注入

Qualifiers微调基于注解的依赖注入

泛型Qualifier自动注入

CustomAutowireConfigurer

@Resource

@PostConstruct 和 @PreDestroy


容器扩展点

通常开发人员并不需要定义ApplicationContext的实现的子类,Spring容器可以通过插入特殊的集成接口的实现进行扩展。

BeanPostProcessor进行bean自定义

BeanPostProcessor接口定义了一些回调方法可以用来实现一些实例化逻辑、依赖解析逻辑等。如果想在spring容器初始化一个bean之后做一些自定义的逻辑,可以使用BeanPostProcessor来实现。

可以配置多个BeanPostProcessor实例,并且可以通过order属性控制执行顺序,只有当该实例也同时实现了Ordered接口,才能设置该order属性。

BeanPostProcessor作用在bean实例上,也就是说Spring容器在实例化一个bean之后BeanPostProcessor才起作用。BeanPostProcessor实例是容器作用域的,如果你在一个容器内定义了一个BeanPostProcessor,它只会作用在这个容器内的bean。也就是说,在一个容器中的BeanPostProcessor不会作用在其它的容器内,即使这两个容器在一个继承体系内。

org.springframework.beans.factory.config.BeanPostProcessor接口有两个回调方法。当一个类被注册为容器的后处理器,对于这个容器中的所有bean的容器初始化方法(InitializingBean.afterPropertiesSet()、init())之前和之后均会执行一个回调。一个后处理器可以对bean实例做任何操作,包括忽略所有回调方法。后处理器通常会检查回调接口或通过一个代理包装bean,spring的一些AOP实现就是通过后处理器提供代理逻辑的。

ApplicationContext会自动检测配置的BeanPostProcessor,并把它们注册为后处理器。注意,在完全创建完成之前,ApplicationContext并不能通过类型自动探测BeanPostProcessor实例,由于BeanPostProcessor需要在其它bean之前进行初始化,因此过早进行类型检测可能会出问题。

编程式注册BeanPostProcessor实例

如前所述,建议采取ApplicationContext自动扫描的方式注册BeanPostProcessor实例,也可以通过ConfigurableBeanFactory的addBeanPostProcessor方法进行注册,这种方式在你注册BeanPostProcessor之前需要做一些逻辑判断或在一个上下文继承体系中对后处理器进行拷贝时比较有用。需要注意的是通过这种方式注册的后处理器并不遵循Ordered接口,且编程式注册的后处理器总是在自动扫描的实例之前起作用,不论设置的order属性是怎样的。

BeanPostProcessor实例和AOP自动代理

容器会对BeanPostProcessor接口的实现类进行特殊处理,所有的BeanPostProcessor实例和它们直接引用的bean都会在启动时进行初始化,之后所有的BeanPostProcessor实例会被注册并作用在容器中的bean上。由于AOP自动代理也是通过BeanPostProcessor进行实现的,因此BeanPostProcessor实例及其所引用的实例的自动代理都是不合适的,不要对它们进行切面织入。

如果通过自动注入或@Resource在BeanPostProcessor中注入了其它的Bean,Spring在搜索类型匹配的依赖时可能会进入与期望不符的Bean中,而致使其不再适合对其它Bean进行处理。

例子:Hello World级别样例

第一个例子展示基本的使用,该样例自定义了一个BeanPostProcessor实现,实现在容器启动创建bean时调用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 version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

InstantiationTracingBeanPostProcessor的定义是十分简单的,甚至没有为其定义名称,因为它也是一个bean,因此也可以为其注入其它bean。

通过如下代码进行测试

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

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);
    }

}

将会得到如下输出:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子:RequiredAnnotationBeanPostProcessor

通过回调接口或者通过注解并配合BeanPostProcessor是对Spring IoC容器进行扩展的常用方式。其中一个实例就是RequiredAnnotationBeanPostProcessor.

BeanFactoryPostProcessor配置元数据

BeanFactoryPostProcessor接口的语法与BeanPostProcessor是类似的,一个主要区别是:BeanFactoryPostProcessor作用在配置元数据上,Spring容器会让BeanFactoryPostProcessor读取配置数据并在初始化除BeanFactoryPostProcessor之外的bean之前对配置数据进行变更。

可以配置多个BeanFactoryPostProcessor实例,并通过order属性控制顺序(需要实现Ordered接口)。

如果想对配置元数据生成的bean实例进行变更,可以使用BeanPostProcessor,不过从技术上来说,使用BeanFactoryPostProcessor也是可行的,但是这么做会导致bean的提前初始化,违反容器标准的生命周期,从而导致一些负面影响,如绕过后处理器。

为了改变容器中定义的配置元数据,当在ApplicationContext声明了bean工厂后处理器后,其会自动运行。spring定义了一些bean工厂后处理器,如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。

ApplicationContext会自动检测容器内部实现了BeanFactoryPostProcessor接口的bean,在合适的时候会把它们作为bean工厂后处理器。

如同BeanPostProcessor,你通常也不会把BeanFactoryPostProcessor进行懒初始化。如果没有别的bean引用Bean(Factory)PostProcessor,它们也不会懒初始化,把它们标记为懒初始化会被忽略。

例子:类名替换PropertyPlaceholderConfigurer

可以通过PropertyPlaceholderConfigurer从标准的Java Properties格式的文件中提取bean定义中涉及的属性值。这样做可以部署特定环境的应用,不需要修改主配置文件。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

上面的例子示出了从外部的Properties文件中配置属性,在运行时PropertyPlaceholderConfigurer会被应用在元数据上并替换DataSource的一些属性,被替换的值是以${property-name}占位符的形式,这与Ant、Log4j、JSP EL格式是相同的。

Properties格式文件中配置的信息如下:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

${jdbc.username}会被替换成sa,在该配置文件中的匹配占位符的键值也会被替换,你也可以自定义占位符的前缀和后缀。

Spring2.5引入了context命名空间,可以通过如下方式配置PropertyPlaceholderConfigurer,可以通过location属性指定配置文件,如果多个配置文件,以逗号进行分割

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertyPlaceholderConfigurer不仅会从指定的配置文件中读取配置,对于从配置文件未找到的配置项,也会从Java的System属性中读取。可以通过设置systemPropertiesMode属性对这种行为进行定制,可取的值如下:

  • never (0): Never check system properties.

  • fallback (1): Check system properties if not resolvable in the specified properties files. This is the default.

  • override (2): Check system properties first, before trying the specified properties files. This lets system properties override any other property source.

可以通过PropertyPlaceholderConfigurer对类名进行替换,如果想在运行时动态的指定一个具体的实现类时是有用的。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

例子:PropertyOverrideConfigurer

PropertyOverrideConfigurer,类似于PropertyPlaceholderConfigurer,但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。如果起覆盖作用的 Properties文件没有某个bean属性的内容,那么缺省的上下文定义将被使用。
注意:bean 工厂的定义并 不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻明显地知道覆盖配置是否被使用了。在有多个PorpertyOverrideConfigurer对用一个bean属性定义了不同的值的时候,最后一个将取胜(取决于覆盖的机制)。

<context:property-override location="classpath:override.properties"/>

FactoryBean自定义初始化逻辑

可以实现org.springframework.beans.factory.FactoryBean接口做为对象的工厂。FactoryBean是spring容器初始化时进行扩展的一个切入点,如果有比较复杂的初始化逻辑,这些逻辑最好是通过Java代码形式进行表述而不是通过XML方式,此时你可以创建自己的FactoryBean。

FactoryBean提供了如下方法:

  • Object getObject(): Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.

  • boolean isSingleton(): Returns true if this FactoryBean returns singletons or false otherwise.

  • Class getObjectType(): Returns the object type returned by the getObject() method or null if the type is not known in advance

当通过ApplcationContex的getBean方法t获取FactoryBean实例本身而不是该FactoryBean产生的bean时,需要在id前加&,如对于id为myBean的FactoryBean,方法getBean("myBean")会返回该FactoryBean产生的bean,getBean("&myBean")才会返回该FactoryBean本身。

基于注解的配置

作为XML配置的一种替代形式,基于注解的配置采取字节码元数据的形式。如在例子:RequiredAnnotationBeanPostProcessor中所述,通过BeanPostProcessor配合注解是对Spring容器进行扩展的一种常用方式。

注解形式会在XML之前进行,因此在同时使用注解和XML时,XML配置的依赖注入会覆写注解配置。

如前面一样,你可以分别定义每个bean,不过也可以通过如下方式隐式注册

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

注册的后处理器包括AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor 和前面提到的 RequiredAnnotationBeanPostProcessor。

<context:annotation-config/>只会寻找其所在的上下文被注解的bean,也就是说,如果把<context:annotation-config/>放在WebApplicationContext,它只会寻找控制器中被@Autowired注解的bean,而不会寻找services中的内容。

@Required

该注解用于属性setter方法,如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

该注解表明在配置期间,该属性必须通过具体值或自动注入方式进行设置,否者将会抛出异常。

该注解在Spring Framework 5.1不再建议使用,对于必须的属性,建议通过构造器或自定义的InitializingBean.afterPropertiesSet()

@Autowired

JSR 330 的 @Inject注解可以取代spring的@Autowired

可以把该注解用于构造器,如下所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

在Spring Framework 4.3中,对于如上的只有一个构造器的情况,@Autowired注解可以省略,不过对于具有多个构造器的情况,则需要用该注解指定采用哪个构造器

该注解也可以用于setter方法

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;
    }

    // ...
}

可以在数组形式的属性或方法入参上加入该注解,获取ApplicationContext中所有该类型的bean

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

也同样适用于泛型集合类

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

甚至可以注入泛型的Map类型,其中的键为所注入的bean的名称

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

默认情况下,如果没有找到任何匹配的类型时,自动注入会失败。这种默认行为是把方法、构造器、属性的依赖作为必须依赖对待的。可以如下方式改变这种默认行为

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

对于每个类,最多只能有一个构造器能标记为必须的,可以为多个构造器注解为required=false,在这种情况下,spring会挑选参数最多的且依赖满足的构造器。

@Autowired注解的required的属性相比@Required注解,更建议采用前者。

此外,还可以采用Java8中的java.util.Optional表明某属性不是必需的,如下所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

在Spring Framework 5.0中,也可以用JSR-305的javax.annotation.Nullable

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

@Autowired, @Inject, @Resource, 和@Value注解是通过BeanPostProcessor进行处理的,因此不能在BeanPostProcessor 和 BeanFactoryPostProcessor类型中采用这些注解。这些类型必须通过XML或@Bean显式连接

@Primary微调自动依赖注入

通过类型进行依赖注入可能会有多个匹配项,一般情况下需要对该匹配过程做一定控制,一种方式是通过spring的@Primary注解。该注解表示对于单一值依赖注入时把当前bean作为首选值。

考虑如下配置类

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

在如上配置情况下,如下的MovieRecommender会被注入firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

等价的XML配置如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

Qualifiers微调基于注解的依赖注入

@Primary很好的解决了当有多个类型匹配时把其中某个bean作为注入项的情况。如果想要对选择过程做更多的控制,可以考虑使用spring的@Qualifier注解。通过为该注解指定参数可以缩窄匹配的类型到指定bean。

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配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

bean名称也是qualifier的默认值。因此,你也可以定义bean的id为main替换内部的qualifier元素。

qualifier也适用于类型集合,如前面提到的Set<MovieCatalog>,在这种情况下,所有符合指定qualifier值的匹配的bean会被注入进去。这也就表明qualifier并不要求时唯一的,而只是一个过滤条件。你可以定义具有相同qualifier值为“action”的MovieCatalog的bean,以@Qualifier("action")注解的bean都会注入到Set<MovieCatalog>集合中。

在类型匹配时,通过qualifier值以bean名称进行匹配,并不需要在注入点加入@Qualifier注解。在没有显式进行指定qualifier和 a primary时,对于不唯一依赖的情况,spring会选择bean名称与注入点名称相同的bean进行注入。

如果想要通过注解方式实现通过名称依赖注入,可以使用JSR-250 @Resource注解,该注解会仅通过名称进行匹配,而不会考虑类型。@Autowired的匹配逻辑截然不同,其首先会通过类型进行匹配,之后在这些匹配的类型中以指定的qualifier值进行筛选。

对于集合、Map、数组类型的bean,@Resource是一个更好的解决方案。对于spring 4.3,只要@Bean注解的方法的返回类型中保留元素类型,也可以通过@Autowired对这些类型进行匹配。在这种情况下,也可以通过qualifier值在相同类型的集合中进行选择。

在spring 4.3中,也考虑了自引用的依赖注入,但除非迫不得已,不要使用这种依赖方式。这种方式主要用于如数据库事务相关的bean中调用同一个bean的其它方法,想要加入事务控制。

可以定义自己的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定义时,通过<bean>子元素<qualifier/>的type和value指定成你定义的qualifier注解匹配的信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

在一些情况下,可以不必指定value值,此时注解本身作为qualifier筛选条件

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}
public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

此时qualifier仅需要指定type

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- inject any dependencies required by this bean -->
</bean>

你也可以在自定义的qualifier注解内部加入新的属性,当对属性或参数指定了多个注解内部参数值时,需要匹配所有的参数才能被自动依赖注入进去。

@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;

    // ...
}

在进行bean定义时,需要包含匹配的qualifier值。如下xml配置同时也展示了可以通过bean定义的meta属性来替换<qualifier/>元素

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

泛型Qualifier自动注入

除了使用@Qualifier注解之外,泛型类型也可以作为一种隐式的qualifier,考虑如下配置

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假设上面的bean实现了泛型接口Store<String> 和Store<Integer>,可以通过Store接口自动注入,并利用泛型作为qualifier

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

这种方式也适用于泛型集合

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它可以注册自定义的qualifier注解类型,即使该注解并没有配置@Qualifier注解。

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver通过如下方式选择注入的bean:

  • The autowire-candidate value of each bean definition

  • Any default-autowire-candidates patterns available on the <beans/> element

  • The presence of @Qualifier annotations and any custom annotations registered with the CustomAutowireConfigurer

@Resource

spring支持通过@Resource注解对属性或setter方法进行依赖注入。@Resource注解有一个name属性,默认情况下,spring把该值做为注入的bean的选项。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有显式指定name属性值,会使用属性或方法入参名称。如下实例会注入名称为movieFinder的bean

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

在未明确指定name属性值未能匹配的情况下,@Resource会通过类型进行匹配。如下示例中,customerPreferenceDao属性首先会找名称为customerPreferenceDao的bean,在没有找到时进而通过寻找类型为CustomerPreferenceDao的bean:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}

@PostConstruct 和 @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注解,同时也支持JSR-250生命周期相关的注解:javax.annotation.PostConstruct 和 javax.annotation.PreDestroy,这提供了一种初始化和销毁时回调的可选方式。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值