继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Spring 5 中文解析核心篇-IoC容器之基于注解的容器配置

youngitman
关注TA
已关注
手记 53
粉丝 1
获赞 7
1.9 基于注解的容器配置

Spring配置注解比XML配置更好?

基于注解的配置介绍抛出一个问题,是否比XML方式更好。简单的回答是看场景。具体的描述是每种方式各有利弊,通常的,这个由开发者去决定更适合他们的策略。由于这种定义的方式,注解在声明中提供了大量的上下文,导致更短和更简洁的配置。XML擅长于连接组件,而不需要修改它们的源代码或重新编译它们。一些开发人员倾向于闭源,而另一些人则认为被注释的类不再是pojo,而且配置变得分散且更难控制。

无论怎么选择,Spring能够兼容两种风格甚至是混合使用。值得指出的是通过JavaConfig,Spring允许以无侵入式方式使用,不需要接触目标组件源代码。在工具方面,所有的配置风格通过Spring的Tools Eclipse工具支持。

基于注解的配置提供了XML设置的替代方法,它依靠字节码元数据来连接组件,而不是尖括号声明(不需要xml的格式配置)。替换使用XML去描述bean,开发者只需移动配置到类本身并通过在关联的类、方法、字段上使用注解。在RequiredAnnotationBeanPostProcessor中提到,结合使用BeanPostProcessor和注释是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注解 强制执行必需属性的可能性。Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。实际上,@Autowired注解提供了相同的能力,在 Autowiring Collaborators中被描述,但是提供了更细粒度和更多的能力。Spring2.5增加对JSR250支持 ,例如:@PostConstruct@PreDestroy。Spring3.0增加对JSR-330 (Java的依赖注入)注解包含在包javax.inject,例如@Inject@Named。更多关于注解详情能在相关的文章中找到。

注解注入在XML注入之前被执行。因此,XML配置覆盖注解对属性的两种连接方式。

你可以注册他们作为独立的bean定义,但是他们也可以通过基于XML包含下面的标签被隐的注册(注意包含context命名空间)。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

隐式的注册后置处理器包括:

<context:annotation-config />仅在定义它的相同应用程序上下文中查找关于bean的注解。意思是,如果你在WebApplicationContext中为DispatcherServlet配置< context:annotation-config/> ,它仅仅检查@Autowired在你的Controller层并且不会在你的Service层。查看DispatcherServlet 更多信息。

1.9.1 @Required

@Required注解应用到bean属性的Setter方法,类似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

这个注解表示bean属性在运行时必须被填充赋值,在一个bean定义中显示赋值或自动装配。如果受影响的bean属性没有被填充值容器抛出一个异常。这允许更早的显示异常,避免以后再出现NullPointerException实例等。我们仍然推荐你把断言放入到bean类本身中(例如,放入初始化方法)。这样做会强制执行那些必需的引用和值,即使你在容器外部使用该类也是如此。

@Required注解在Spring5.1中被正式的不推荐使用,更好的方式是,使用构造函数注入要求的设置(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性设置器方法)。

1.9.2 @Autowired

JSR 330的@Inject注解能够使用在Spring的@Autowired注解的地方。

可以在构造方法上使用@Autowired,类似下面例子:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

从Spring4.3开始,如果目标bean仅仅定义以一个构造函数开始,那么构造函数上的@Autowired注解是不必要的。然而,如果有一些构造函数是有效的并且没有主要默认的构造函数,则至少有一个构造函数必须被注解为@Autowired,目的是引导容器选择其中一个去使用。查看构造方法解析详情。

你也可以应用@Autowired注解到传统的Setter方法,类似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

你可以应用@Autowired注解到任意方法名和多个参数,类似下面例子:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

你可以应用@Autowired注解到字段甚至混合到构造方法,类似下面例子:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...
}

确保你的目标组件(例如,MovieCatalogCustomerPreferenceDao)是一致性的通过类型声明,也就是使用基于@Autowired注入点。否则,注入可能由于在运行时出现“找不到类型匹配”错误而失败。

对于基于XML定义的bean或者组件类通过类路径扫描,容器通常知道具体的类型。然而,对于@Bean工厂方法,你需要确保声明的返回类型足够的表达。对于实现多个接口的组件,或者对于实现类型可能引用的组件,请考虑在工厂方法中声明最具体的返回类型。至少与指向bean的注入点所要求的一样具体(备注:尽可能返回具体的实现类型)。

你还可以引导Spring从ApplicationContext中提供特定类型的所有bean,通过将@Autowired注解添加到需要该类型数组的字段或方法中,类似下面例子:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

类型化集合也是如此,类似下面例子:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}

如果你想数组或集合中的元素在指定的顺序下被排序,那么你的目标bean可以实现org.springframework.core.Ordered接口、@Order或标准的@Priority注解。否则,它们的顺序遵循在容器中对应目标bean定义注册的顺序。

你可以在目标类级别和在@Bean方法上申明@Order注解,可能是针对单个bean定(多个定义情况下使用相同的bean类)。@Order值可能会影响注入点的优先级,不会影响单例bean的启动顺序,这是由依赖关系和@DependsOn声明确定。

注意:标准的javax.annotation.Priority注解在@Bean级别是无效的,因为它不能被声明在方法上。它的语义可以通过@Order值与@Primary结合在每种类型的单个bean上。

甚至类型Map实例也能被自动装配只要期望的key类型是String。map的值包含了所有期望类型的bean,并且这些key包含对应bean的名称,像下面的例子展示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

默认情况下,当没有匹配的候选bean对应给定的注入点的时候自动装配将失败。在这种情况下申明数组、集合或map至少有一个期望匹配的元素。

默认行为是将带注解的方法和字段视为指示所需的依赖项。你可以改变这种行为,像下面这个例子,通过将框架标记为不需要,从而使框架可以跳过不满意的注入点(通过在@Autowired属性中的required设置为false)。说明:满足注意条件就注入,不满足条件就跳过注入。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
		//自动注入不满足就不注入
    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不需要填充非必需字段,将其默认值保留在适当的位置。注入构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能要处理多个构造函数,@Autowired中的required属性有一些不同的含义。

注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能会处理多个构造函数,因此@Autowired中必填属性的含义有所不同。默认情况下,有效地需要构造函数和工厂方法参数,但是在单构造函数场景中有一些特殊规则,例如,如果没有匹配的Bean可用,则将多元素注入点(数组,集合,映射)解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明-例如,申明一个简单的公共构造函数不需要@Autowired注解。

任何给定的bean类只有一个构造函数可以声明@Autowired并设置属性required为true,指示当使用Spring Bean时构造函数自动装配。因此,如果必填属性保留为默认值true,则只能使用@Autowired注解单个构造函数。如果有多个构造函数声明注解,那么它们都必须声明required=false,以便被认为是自动装配的候选对象(类似于XML中的autowire=constructor)。将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数如果没有一个候选者满意,则将使用主要的/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但都没有使用@Autowired进行注解,则将使用主要的/默认构造函数(如果存在)。如果一个类仅声明一个单一的构造函数开始,即使没有注释,也将始终使用它。请注意,带注解的构造函数不必是公共的。

在setter方法上,建议使用@Autowiredrequired属性,而建议使用@Required注解。设置required属性为false则表明这个属性不是必须的对于自动装配意图,并且这个属性如果不能被自动装配将被忽略。换句话说,@Required更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。

另外,你可以通过Java 8的java.util.Optional来表达特定依赖项的非必需性质,类似以下示例所示:

public class SimpleMovieLister {

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

从Spring5.0后,你可以使用@Nullable注解(任何包装中的任何种类-例如,JSR-305的javax.annotation.Nullable)或者只是利用Kotlin内置的null安全支持:

public class SimpleMovieLister {

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

你可以为这些接口使用@Autowired,这是众所周知的可注入的依赖:BeanFactory, ApplicationContext, Environment, ResourceLoader,ApplicationEventPublisher, 和 MessageSource。这些接口和它们的拓展接口,例如,ConfigurableApplicationContextResourcePatternResolver是自动地解析,没有特殊的安装需要。下面的例子主动注入一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired@Inject@Value@Resource注解是通过Spring的BeanPostProcessor实现处理的。这意味着你不能应用这些注解在你自己的BeanPostProcessorBeanFactoryPostProcessor类中。这些类型必须被通过XML或者Spring @Bean方法连接。

参考代码:com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer

1.9.3 使用@Primary对基于注解的自动装配调整

因为通过类型的自动装配可能导致多个候选者,通常有必要需要更多的选择处理。一种可以完成的方式是使用Spring的@Primary注解。@Primary表示当多个bean自动装配到单值依赖时这个bean是我们更期望的值。如果主要的bean在限定符候选bean中时,它将被自动装配。

考虑下面的配置,这个配置定义firstMovieCatalog作为主要的MovieCatalog类型候选值:

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

通过前面的配置,下面的MovieRecommender类中自动装配firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

对应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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
	 <!--作为主要的bean-->
    <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>
1.9.4 使用Qualifiers对基于注解的自动装配调整

@Primary是一种根据类型使用自动装配的有效方法,当可以确定一个主要候选对象时,可以使用多个实例。当你需要更多的控制选择处理时,你可以使用Spring的@Qualifier注解。你可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便每个参数选择特定的bean。在这个简单的例子中,这可以是简单的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

你也可以指定@Qualifier注解在各个构造函数参数或方法参数上,类似下面的例子:

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

    // ...
}

下面的例子显示对应的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

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

</beans>
  1. 具有main限定符值的Bean与构造函数参数连接,构造函数参数使用相同的值进行限定
  2. 带有action限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。

为了回退匹配,bean的名字是默认的限定符。因此,你可以定义bean的id为main替代嵌入的限定符元素,导致相同的匹配结果。然而,即使你能使用这个约定通过名字指定bean,@Autowired基本上是关于带有可选语义限定符的类型驱动的注入(意思是@Autowired中带有require限定符)。这意味着限定符值总是有缩小类型匹配集合的语义,即使bean名为回退名称。他们从语义上不能表达引用一个唯一bean的id。好的限定符值是mainEMEApersistent,它们表示独立于bean id的特定组件的特征,对于匿名bean定义(如前面示例中的定义),可以自动生成这些特征。

限定符也能应用到类型化的集合,像前面讨论的-例如,Set。在这种例子中,所有匹配的bean,根据声明的限定符,作为一个集合被注入。这暗示限定符没有必要是唯一的。相反,它们构成了过滤标准。例如,你可以定义具有相同限定符值action的多个MovieCatalog Bean,所有这些都注入到以@Qualifier(“ action”)注解的Set 中。

在类型匹配的候选者中,让限定符值选择目标bean名称,在注入点不需要@Qualifier注解。如果没有其他解析指示器(例如限定符或primary标记),对于非唯一依赖情况,Spring将注入点名(即字段名或参数名)与目标bean名相匹配,并选择同名的候选项(如果有的话)。

也就是说,如果你打算试图通过名称表示注解驱动注入,不要主要地使用@Autowired,即使它有通过名称在类型匹配的候选者间选择的能力。相反,使用JSR-250@Resource注解,它是语义地通过使用唯一名称定义标识一个指定目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:按类型选择候选bean之后,指定的字符串限定符值仅在那些类型选择的候选中被考虑(例如,匹配一个account限定符与bean被标记相同的限定符标签)。

对于定义为集合、Map、数组类型的bean,@Resource是一个好的解决方案,通过唯一的名称引用指定的集合或数组bean。也就是说,在Spring4.3以后你可以匹配一个Map和数组类型通过Spring的@Autowired类型匹配算法,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中。在这种场景中,你可以使用限定符值去相同类型集合选择,如前一段所述。

在Spring4.3以后,@Autowired还考虑了注入的自我引用(也就是说,引用回当前注入的bean)。注意自我注入是一种后备。对其他组件的常规依赖始终优先。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其是绝不是主要的。相反,它们总是以最低优先级结束。实际上,你应该仅将自我引用用作最后的手段(例如,在相同实例上通过bean的事物代理调用其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托Bean。或者,你可以使用@Resource,它可以通过其唯一名称获取返回到当前bean的代理。

尝试将@Bean方法的结果注入相同的配置类也是一种有效的自引用方案。要么在实际需要的方法签名中懒惰地解析此类引用(与配置类中的自动装配字段相对),要么将受影响的@Bean方法声明为静态,将其与包含的配置类实例及其生命周期脱钩。否则,仅在回退阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。

@Autowired应用到字段、构造函数和多参数方法,在参数级别允许使用限定符注解缩小选择范围。相反,@Resource仅仅支持字段和bean属性Setter方法简单参数。因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。

你可以创建你自己的自定义限定符注解。为了这样做,定义一个注解和提供@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定义提供信息。你可以增加标签的子标签并且指定typevalue为匹配你的自定义限定符注解。这个类型是注解的全限定类名的匹配。或者,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面例子展示两种方式:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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>

Classpath扫描和组件管理中,你可以看到在XML中基于注解另类的提供限定符元数据,查看Providing Qualifier Metadata with Annotations

在某些场景中,使用注解不需要值可能就满足。这是非常有用的,当注解应用更一般的用途和可以应用于几种不同类型的依赖项时。例如,你可以提供一个脱机目录,当没有Internet连接可用时可以进行搜索。首先,定义相同的注解,类似下面的例子:

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

}

然后,增加注解到字段或者属性去自动装配,类似下面例子:

public class MovieRecommender {

    @Autowired
    @Offline //1
    private MovieCatalog offlineCatalog;

    // ...
}
  1. 这一行添加@Offline注解

接下来,bean的定义仅需要一个限定符type,类似下面例子:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> //1
    <!-- inject any dependencies required by this bean -->
</bean>
  1. 这个元素指定了限定符

你还可以定义自定义限定符注解,这些注解除了接受简单value属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则Bean定义必须与所有此类属性值匹配才能被视为自动装配候选。例如,请考虑以下注释定义:

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

    String genre();

    Format format();
}

在这个例子中Format是一个枚举,定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

字段装配字段是被自定义限定符注解并且保护genre和format属性值,类似下面例子:

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定义应该包含匹配的限定符值。这个例子展示你可以使用bean元数据属性替换元素。如果可用,元素及其属性优先,但是如果不存在这样的限定符,则自动装配机制将回退到标记内提供的值,如下面示例中的最后两个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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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>
1.9.5 使用泛型作为自定装配限定

除了@Qualifier注解,你也可以使用Java泛型类型作为隐式的限定。例如,假设你有下面的配置:

@Configuration
public class MyConfiguration {

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

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

假设前面的bean实现一个泛型接口,(也就是说,Store和Store),你可以使用@Autowire装配Store接口并且泛型作为一个限定符,类似下面的例子:

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

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

当自动装配集合、Map接口和数组的时泛型限定符也可以使用。下面的例子装配Store泛型为integer的List:

// 注入所有泛型为<Integer> 的Store的bean
// Store<String> 的bean不会出现在List中
@Autowired
private List<Store<Integer>> s;

参考代码:com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer

1.9.6 使用CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许你注册你自己的自定义限定符注解类型,甚至他们不被注解Spring的@Qualifier。下面的例子展示怎样去使用CustomAutowireConfigurer

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

AutowireCandidateResolver通过下面确定自动装配候选者:

  • 每个bean定义的autowire-candidate
  • 在元素上任何default-autowire-candidates模式有效
  • @Qualifier注解和任何自定义注解存在注册CustomAutowireConfigurer

当多个bean限定符作为自动装配候选者时,primary的确定类似下面:如果候选者bean中有一个bean被定义primary属性值被设置为true,它将被选择。

参考代码:com.liyong.ioccontainer.starter.XmlQualifierIocContainer

1.9.7 @Resource注入

Spring也支持通过使用JSR-250注解(javax.annotation.Resource)在字段或bean属性Setter方法上注入。这是在JavaEE通用的模式,在JSF管理bean和JAX-WS端点。Spring提供这个模式去管理Spring的bean。

@Resource采用一个名字属性。默认情况下,Spring将该值解释为要注入的Bean名称。换而言之,它遵循通过名字的语义,如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") //1
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
  1. 这行注入@Resource

如果显示指定名称,这个默认名是从字段或Setter方法名获取。如果是字段,则采用字段名称。在使用setter方法的情况下,它采用bean属性名称。以下示例将把名为movieFinder的bean注入其setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

与注解一起提供的名称被CommonAnnotationBeanPostProcessor所感知的ApplicationContext解析为bean名称。如果你显示的配置Spring的SimpleJndiBeanFactory,这个名字能通过JNDI解析。然而,我们推荐你依赖默认的行为并且使用Spring的JNDI能力去保留间接的级别。

在没有明确指定名称的@Resource使用的特殊情况下(类似于@Autowired), @Resource会找到一个主类型匹配而不是一个特定的命名bean,并解析我们熟知的可解析依赖项:BeanFactoryApplicationContextResourceLoader, ApplicationEventPublisherMessageSource接口。

因此,在下面的例子中,customerPreferenceDao字段首先查找bean名字为customerPreferenceDao然后回退查找CustomerPreferenceDao主要匹配的类型:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; //1

    public MovieRecommender() {
    }

    // ...
}
  1. context字段是基于已知的可解析依赖项类型:ApplicationContext注入的。
1.9.8 使用@Value

@Value典型的应用去注入外部属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

下面是配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

application.properties配置

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于MovieCatalog值。

Spring提供了一个默认的宽松内嵌值解析器。它将尝试去解析属性值并且如果不能被解析,属性名(例如,${catalog.name})将被作为值注入。如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer bean,如以下示例所示:

@Configuration
public class AppConfig {
     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}

当使用JavaConfig配置PropertySourcesPlaceholderConfigurer@Bean方法必须是static。

如果任何${}占位符不能被解析,使用上面的配置能够确保Spring初始化失败。它也可以使用方法类似setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator去自定义占位符。

Spring Boot 通过默认的PropertySourcesPlaceholderConfigurerbean配置,它能从application.propertiesapplication.yml文件获取属性。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为Integer或int)。多个逗号分隔的值能够自动的转换为String数组不需要额外的操作。

它也可能提供一个默认值类似下面:

@Component
public class MovieRecommender {

    private final String catalog;
    //defaultCatalog为默认值
    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

Spring BeanPostProcessor在使用ConversionService处理将@Value中的String值转换为目标类型的过程。如果你想提供转换支持为你自己的自定义类型,你可以提供你自己的ConversionService bean实例类似下面例子:

@Configuration
public class AppConfig {
    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

@Value包含SpEl表达式时候,这个值将被在运行时动态地计算,类似下面例子:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL还可以使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

参考代码:com.liyong.ioccontainer.starter.XmlValueIocContainer

1.9.9 使用@PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor不仅仅识别@Resource注解也能识别JSR-250生命周期注解:javax.annotation.PostConstructjavax.annotation.PreDestroy。在Spring 2.5中引入的对这些注解的支持为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。CommonAnnotationBeanPostProcessor在Spring的ApplicationContext中被注入,在生命周期的同一点上(备注:同一类回调方法,比如:销毁方法),与相应的Spring生命周期接口方法或显式声明的回调方法调用带有其中一个注解的方法。在下面的例子中,在以下示例中,缓存在初始化时预先填充,并在销毁时清除。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 填充缓存
    }

    @PreDestroy
    public void clearMovieCache() {
        // 清除缓存
    }
}

有关组合各种生命周期机制的详细信息,查看组合生命周期机制

类似@Resource一样,@PostConstruct@PreDestroy注解类型是从JDK6到JDK8的标准Java库的一部分。然而,整个javax.annotation包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api可以通过Maven中央库获取,简单地增加到应用的类路径下面和其他库一样。

参考代码:com.liyong.ioccontainer.starter.XmlLifecycleIocContainer

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址: http://youngitman.tech

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP