基于注解的配置是否比 XML“更好” ?简短的回答是“视情况而定”。
长的答案是每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注解在它们的声明中提供了很多上下文,从而导致更短和更简洁的配置。然而,XML 擅长连接组件,而无需接触它们的源代码或重新编译它们。一些开发人员更喜欢将装配靠近源码,而其他人则认为带注解的类不再是 POJO,而且配置变得分散且更难控制。
无论选择哪种,Spring 都可以容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过其 JavaConfig 选项,Spring 允许以非侵入性方式使用注解,而无需触及目标组件源代码,并且在工具方面,Spring Tools for Eclipse 支持所有配置样式。
基于注解的配置,依赖字节码元数据来连接组件而不是尖括号声明。开发人员不使用 XML 来描述 bean 连接,而是通过在相关类、方法或字段声明上使用注解将配置移动到组件类本身中。如示例:AutowiredAnnotationBeanPostProcessor 中所述,将 BeanPostProcessor 与注解结合使用是扩展 Spring IoC 容器的常用方法。例如,Spring 2.0 引入了使用 @Required 注解强制执行必需属性的可能性。 Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。从本质上讲,@Autowired 注解提供了与 Autowiring Collaborators 中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5 还添加了对 JSR-250 注解的支持,例如 @PostConstruct 和 @PreDestroy。 Spring 3.0 添加了对包含在 javax.inject 包中的 JSR-330(Java 依赖注入)注解的支持,例如 @Inject 和 @Named。
注解注入在 XML 注入之前执行。因此,XML 配置会覆盖通过这两种方法连接的属性的注释。
可以将post-processors注册为单独的 bean 定义,也可以通过在基于 XML 的 Spring 配置中包含以下标记来隐式注册它们(注意包含上下文命名空间):
1 |
|
context:annotation-config/ 元素隐式注册以下后处理器:
ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessorEventListenerMethodProcessor
context:annotation-config/ 仅在定义它的同一应用程序上下文中查找 bean 上的注释。这意味着,如果您将 context:annotation-config/ 放在 DispatcherServlet 的 WebApplicationContext 中,它只会检查控制器中的 @Autowired bean,而不检查您的服务。有关更多信息,请参阅 DispatcherServlet。
@Required
@Required 注释适用于 bean 属性 setter 方法,如下例所示:
1 | public class SimpleMovieLister { |
此注释表明必须在配置时通过 bean 定义中的显式属性值或通过自动装配来填充受影响的 bean 属性。如果尚未填充受影响的 bean 属性,则容器将引发异常。这允许急切和显式失败,避免以后出现 NullPointerException 实例等。我们仍然建议您将断言放入 bean 类本身(例如,放入 init 方法)。这样做会强制执行那些必需的引用和值,即使您在容器外部使用该类。
RequiredAnnotationBeanPostProcessor 必须注册为 bean 才能启用对 @Required 注释的支持。
@Required annotation 和 RequiredAnnotationBeanPostProcessor 从 Spring Framework 5.1 开始正式弃用,赞成使用构造函数注入进行所需设置(或 InitializingBean.afterPropertiesSet() 的自定义实现或自定义 @PostConstruct 方法以及 bean 属性 setter 方法)。
@Autowired
JSR 330 的 @Inject 注解可以用来代替 Spring 的 @Autowired 注解在本节包含的示例中。请参阅此处了解更多详情。
您可以将 @Autowired 注释应用于构造函数,如以下示例所示:
1 | public class MovieRecommender { |
从 Spring Framework 4.3 开始,如果目标 bean 仅定义了一个构造函数,则不再需要在此类构造函数上添加 @Autowired 注释。但是,如果有多个构造函数可用并且没有主/默认构造函数,则必须至少用 @Autowired 注释构造函数之一,以便指示容器使用哪个构造函数。有关详细信息,请参阅有关构造函数解析的讨论。
您还可以将 @Autowired 注释应用于传统的 setter 方法,如以下示例所示:
1 | public class SimpleMovieLister { |
您还可以将注释应用于具有任意名称和多个参数的方法,如以下示例所示:
1 | public class MovieRecommender { |
您也可以将 @Autowired 应用于字段,甚至将其与构造函数混合使用,如下例所示:
1 | public class MovieRecommender { |
确保您的目标组件(例如,MovieCatalog 或 CustomerPreferenceDao)由您用于 @Autowired-annotated 注入点的类型一致声明。否则,注入可能会因运行时“未找到类型匹配”错误而失败。
对于通过类路径扫描找到的 XML 定义的 bean 或组件类,容器通常预先知道具体类型。但是,对于@Bean 工厂方法,您需要确保声明的返回类型具有足够的表现力。对于实现多个接口的组件或由其实现类型可能引用的组件,请考虑在您的工厂方法中声明最具体的返回类型(至少与引用您的 bean 的注入点要求的一样具体)。
您还可以通过将 @Autowired 注释添加到需要该类型数组的字段或方法来指示 Spring 从 ApplicationContext 提供特定类型的所有 bean,如以下示例所示:
1 | public class MovieRecommender { |
这同样适用于类型化集合,如以下示例所示:
1 | public class MovieRecommender { |
如果您希望数组或列表中的项目按特定顺序排序,您的目标 bean 可以实现 org.springframework.core.Ordered 接口或使用 @Order 或标准 @Priority 注释。否则,它们的顺序遵循容器中相应目标 bean 定义的注册顺序。
您可以在目标类级别和 @Bean 方法上声明 @Order 注释,可能用于单个 bean 定义(在多个定义使用相同 bean 类的情况下)。 @Order 值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,这是由依赖关系和 @DependsOn 声明决定的正交问题。
请注意,标准 javax.annotation.Priority 注释在 @Bean 级别不可用,因为它不能在方法上声明。它的语义可以通过@Order 值结合@Primary 在每个类型的单个bean 上建模。
只要预期的键类型是字符串,即使是类型化的 Map 实例也可以自动装配。映射值包含预期类型的所有 bean,键包含相应的 bean 名称,如以下示例所示:
1 | public class MovieRecommender { |
默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配失败。对于声明的数组、集合或映射,至少需要一个匹配元素。
默认行为是将带注释的方法和字段视为指示所需的依赖项。您可以更改此行为,如下例所示,通过将不可满足的注入点标记为非必需(即,通过将 @Autowired 中的 required 属性设置为 false),使框架能够跳过不可满足的注入点:
1 | public class SimpleMovieLister { |
如果非必需方法的依赖项(或其依赖项之一,如果有多个参数)不可用,则根本不会调用它。在这种情况下,根本不会填充非必填字段,而是保留其默认值。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于 Spring 的构造函数解析算法可能潜在地处理多个构造函数,@Autowired 中的 required 属性具有一些不同的含义。构造函数和工厂方法参数在默认情况下是有效的,但在单构造函数场景中有一些特殊规则,例如多元素注入点(数组、集合、映射)在没有匹配的 bean 可用时解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明——例如,声明为没有 @Autowired 注释的单个公共构造函数。
任何给定的 bean 类只有一个构造函数可以声明 @Autowired 并将 required 属性设置为 true,指示当用作 Spring bean 时自动装配的构造函数。因此,如果 required 属性保留其默认值 true,则只能使用 @Autowired 注释单个构造函数。如果多个构造函数声明了注释,它们都必须声明 required=false 才能被视为自动装配的候选对象(类似于 XML 中的 autowire=constructor)。将选择通过匹配 Spring 容器中的 bean 可以满足的依赖项数量最多的构造函数。如果没有一个候选可以满足,那么将使用主/默认构造函数(如果存在)。类似地,如果一个类声明了多个构造函数,但没有一个用 @Autowired 注释,则将使用主/默认构造函数(如果存在)。如果一个类只声明一个构造函数开始,它将始终被使用,即使没有注释。请注意,带注释的构造函数不必是公共的。
推荐使用 @Autowired 的 required 属性,而不是 setter 方法上已弃用的 @Required 注释。将 required 属性设置为 false 表示自动装配不需要该属性,如果不能自动装配,则忽略该属性。另一方面,@Required 更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。
或者,您可以通过 Java 8 的 java.util.Optional 表达特定依赖项的非必需性质,如以下示例所示:
1 | public class SimpleMovieLister { |
从 Spring Framework 5.0 开始,您还可以使用 @Nullable 注释(任何包中的任何类型 — 例如,来自 JSR-305 的 javax.annotation.Nullable)或仅利用 Kotlin 内置的空安全支持:
1 | public class SimpleMovieLister { |
您还可以将 @Autowired 用于众所周知的可解析依赖项的接口:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher 和 MessageSource。这些接口及其扩展接口(例如 ConfigurableApplicationContext 或 ResourcePatternResolver)会自动解析,无需特殊设置。以下示例自动装配 ApplicationContext 对象:
1 | public class MovieRecommender { |
@Autowired、@Inject、@Value 和 @Resource 注释由 Spring BeanPostProcessor 实现处理。这意味着您不能在自己的 BeanPostProcessor 或 BeanFactoryPostProcessor 类型(如果有)中应用这些注释。这些类型必须使用 XML 或 Spring @Bean 方法显式“连接”。
使用@Primary 微调基于注解的自动装配
由于按类型自动装配可能会导致多个候选对象,因此通常需要对选择过程进行更多控制。实现此目的的一种方法是使用 Spring 的 @Primary 注释。 @Primary 表示当多个 bean 是自动装配到单值依赖项的候选者时,应优先考虑特定 bean。如果候选中恰好存在一个主要 bean,则它成为自动装配的值。
考虑以下将 firstMovieCatalog 定义为主要 MovieCatalog 的配置:
1 |
|
使用上述配置,以下 MovieRecommender 自动装配到 firstMovieCatalog:
1 | public class MovieRecommender { |
相应的bean定义如下:
1 |
|
使用限定符微调基于注释的自动装配
@Primary 是一种有效的方法,当可以确定一个主要候选对象时,可以通过多个实例按类型使用自动装配。当您需要对选择过程进行更多控制时,可以使用 Spring 的 @Qualifier 注解。您可以将限定符值与特定参数相关联,缩小类型匹配的范围,以便为每个参数选择一个特定的 bean。在最简单的情况下,这可以是一个简单的描述性值,如以下示例所示:
1 | public class MovieRecommender { |
您还可以在单个构造函数参数或方法参数上指定 @Qualifier 注释,如以下示例所示:
1 | public class MovieRecommender { |
以下示例显示了相应的 bean 定义。
1 |
|
- 具有主要限定符值的 bean 与构造函数参数相连具有相同的值。
- 具有操作限定符值的 bean 与构造函数参数相连具有相同的值。
对于回退匹配,bean 名称被视为默认限定符值。因此,您可以使用 main 的 id 而不是嵌套的 qualifier 元素来定义 bean,从而导致相同的匹配结果。但是,尽管您可以使用此约定按名称引用特定 bean,但 @Autowired 从根本上讲是关于带有可选语义限定符的类型驱动注入。这意味着限定符值,即使使用 bean 名称回退,在类型匹配集中始终具有缩小语义。它们不会在语义上表达对唯一 bean id 的引用。好的限定符值是 main 或 EMEA 或persistent,表示独立于 bean id 的特定组件的特征,在匿名 bean 定义(例如前面示例中的定义)的情况下可以自动生成。
限定符也适用于类型化集合,如前所述 — 例如,适用于 Set
在类型匹配的候选中,让限定符值根据目标 bean 名称进行选择,不需要在注入点使用 @Qualifier 注释。如果没有其他解析指示符(例如限定符或主标记),对于非唯一依赖的情况,Spring 将注入点名称(即字段名称或参数名称)与目标 bean 名称进行匹配并选择同名候选人,如果有的话。
也就是说,如果您打算按名称表达注解驱动的注入,请不要主要使用 @Autowired,即使它能够在类型匹配的候选中按 bean 名称进行选择。相反,请使用 JSR-250 @Resource 注释,该注释在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。 @Autowired 有相当不同的语义:按类型选择候选 bean 后,指定的 String 限定符值仅在那些类型选择的候选中考虑(例如,将帐户限定符与标记有相同限定符标签的 bean 匹配)。
对于本身定义为集合、映射或数组类型的 bean,@Resource 是一个很好的解决方案,通过唯一名称引用特定的集合或数组 bean。也就是说,从 4.3 开始,您也可以通过 Spring 的 @Autowired 类型匹配算法匹配集合、Map 和数组类型,只要元素类型信息保留在 @Bean 返回类型签名或集合继承层次结构中即可。在这种情况下,您可以使用限定符值在相同类型的集合中进行选择,如上一段所述。
从 4.3 开始,@Autowired 还考虑注入的自引用(即对当前注入的 bean 的引用)。请注意,自注入是一种后备方法。对其他组件的常规依赖始终具有优先级。从这个意义上说,自我推荐不参与常规的候选人选择,因此特别是从不主要。相反,它们总是以最低的优先级结束。在实践中,您应该仅将自引用用作最后的手段(例如,通过 bean 的事务代理调用同一实例上的其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托 bean。或者,您可以使用@Resource,它可以通过其唯一名称获取返回当前 bean 的代理。
尝试将 @Bean 方法的结果注入同一配置类也是一种有效的自引用场景。要么在实际需要的方法签名中延迟解析此类引用(而不是配置类中的自动装配字段),要么将受影响的@Bean 方法声明为静态方法,将它们与包含的配置类实例及其生命周期分离。否则,仅在回退阶段考虑此类 bean,而将其他配置类上的匹配 bean 选为主要候选对象(如果可用)。
@Autowired 适用于字段、构造函数和多参数方法,允许通过参数级别的限定符注释来缩小范围。相比之下,@Resource 仅支持带有单个参数的字段和 bean 属性 setter 方法。因此,如果您的注入目标是构造函数或多参数方法,您应该坚持使用限定符。
您可以创建自己的自定义限定符注释。为此,请定义注释并在定义中提供 @Qualifier 注释,如以下示例所示:
1 |
|
然后,您可以在自动装配的字段和参数上提供自定义限定符,如以下示例所示:
1 | public class MovieRecommender { |
接下来,您可以提供候选 bean 定义的信息。您可以添加
1 |
|
在 Classpath Scanning 和 Managed Components 中,您可以看到一种基于注释的替代方案,以在 XML 中提供限定符元数据。具体来说,请参阅提供带有注释的限定符元数据。
在某些情况下,使用没有值的注释可能就足够了。当注释用于更通用的目的并且可以应用于多种不同类型的依赖项时,这可能很有用。例如,您可以提供一个离线目录,当没有可用的 Internet 连接时可以搜索该目录。首先,定义简单的注解,如下例所示:
1 |
|
然后将注解添加到要自动装配的字段或属性中,如下例所示:
1 | public class MovieRecommender { |
现在 bean 定义只需要一个限定符类型,如以下示例所示:
1 | <bean class="example.SimpleMovieCatalog"> |
除了简单值属性之外,您还可以定义自定义限定符注释,这些注释接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则 bean 定义必须匹配所有此类属性值才能被视为自动装配候选者。例如,请考虑以下注释定义:
1 |
|
在这种情况下 Format 是一个枚举,定义如下:
1 | public enum Format { |
要自动装配的字段使用自定义限定符进行注释,并包含两个属性的值:流派和格式,如以下示例所示:
1 | public class MovieRecommender { |
最后,bean 定义应该包含匹配的限定符值。此示例还演示了您可以使用 bean 元属性代替
1 |
|
使用泛型作为自动装配限定符
除了@Qualifier 注释之外,您还可以使用 Java 泛型类型作为限定的隐式形式。例如,假设您有以下配置:
1 |
|
假设前面的bean实现了一个泛型接口,(即Store
1 |
|
通用限定符也适用于自动装配列表、Map 实例和数组。以下示例自动装配通用列表:
1 | // Inject all Store beans as long as they have an <Integer> generic |
使用 CustomAutowireConfigurer
CustomAutowireConfigurer 是一个 BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注解类型,即使它们没有使用 Spring 的 @Qualifier 注解进行注解。以下示例显示了如何使用 CustomAutowireConfigurer:
1 | <bean id="customAutowireConfigurer" |
AutowireCandidateResolver 通过以下方式确定自动装配候选者:
- 每个 bean 定义的自动装配候选值
元素上可用的任何 default-autowire-candidates 模式 - @Qualifier 注释和向 CustomAutowireConfigurer 注册的任何自定义注释的存在
当多个 bean 有资格作为自动装配候选时,“主要”的确定如下:如果候选中恰好有一个 bean 定义的主要属性设置为 true,则选择它。
使用@Resource 注入
Spring 还通过在字段或 bean 属性 setter 方法上使用 JSR-250 @Resource 注释 (javax.annotation.Resource) 来支持注入。这是 Java EE 中的常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 端点中。对于 Spring 管理的对象,Spring 也支持这种模式。
@Resource 采用 name 属性。默认情况下,Spring 将该值解释为要注入的 bean 名称。换句话说,它遵循按名称语义,如以下示例所示:
1 | public class SimpleMovieLister { |
如果未明确指定名称,则默认名称源自字段名称或 setter 方法。如果是字段,则采用字段名称。在 setter 方法的情况下,它采用 bean 属性名称。下面的例子将把名为 movieFinder 的 bean 注入到它的 setter 方法中:
1 | public class SimpleMovieLister { |
随注释提供的名称由 CommonAnnotationBeanPostProcessor 知道的 ApplicationContext 解析为 bean 名称。如果显式配置 Spring 的 SimpleJndiBeanFactory,则可以通过 JNDI 解析名称。但是,我们建议您依赖默认行为并使用 Spring 的 JNDI 查找功能来保留间接级别。
在没有指定显式名称的 @Resource 用法的唯一情况下,与 @Autowired 类似,@Resource 查找主要类型匹配而不是特定的命名 bean 并解析众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher 和消息源接口。
因此,在以下示例中,customerPreferenceDao 字段首先查找名为“customerPreferenceDao”的 bean,然后回退到 CustomerPreferenceDao 类型的主要类型匹配:
1 | public class MovieRecommender { |
使用@Value
@Value 通常用于注入外化属性:
1 |
|
使用以下配置:
1 |
|
以及以下 application.properties 文件:
1 | =MovieCatalog |
在这种情况下,目录参数和字段将等于 MovieCatalog 值。
Spring 提供了一个默认的宽松嵌入值解析器。它将尝试解析属性值,如果无法解析,则属性名称(例如 ${catalog.name})将作为值注入。如果你想对不存在的值保持严格的控制,你应该声明一个 PropertySourcesPlaceholderConfigurer bean,如下例所示:
1 |
|
使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer 时,@Bean 方法必须是静态的。
如果无法解析任何 ${} 占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用 setPlaceholderPrefix、setPlaceholderSuffix 或 setValueSeparator 等方法来自定义占位符。
Spring Boot 默认配置一个 PropertySourcesPlaceholderConfigurer bean,它将从 application.properties 和 application.yml 文件中获取属性。
Spring 提供的内置转换器支持允许自动处理简单的类型转换(例如到 Integer 或 int)。多个逗号分隔的值可以自动转换为 String 数组,无需额外的努力。
可以提供如下默认值:
1 |
|
Spring BeanPostProcessor 在幕后使用 ConversionService 来处理将 @Value 中的 String 值转换为目标类型的过程。如果您想为您自己的自定义类型提供转换支持,您可以提供您自己的 ConversionService bean 实例,如下例所示:
1 |
|
当 @Value 包含 SpEL 表达式时,该值将在运行时动态计算,如下例所示:
1 |
|
SpEL 还支持使用更复杂的数据结构:
1 |
|
使用@PostConstruct 和@PreDestroy
CommonAnnotationBeanPostProcessor 不仅可以识别 @Resource 注释,还可以识别 JSR-250 生命周期注释:javax.annotation.PostConstruct 和 javax.annotation.PreDestroy。在 Spring 2.5 中引入,对这些注解的支持提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方案。假设 CommonAnnotationBeanPostProcessor 已在 Spring ApplicationContext 中注册,则在生命周期中与相应的 Spring 生命周期接口方法或显式声明的回调方法相同的点调用带有这些注释之一的方法。在以下示例中,缓存在初始化时预填充并在销毁时清除:
1 | public class CachingMovieLister { |
各种生命周期机制组合的效果请参见生命周期机制组合。
与@Resource 一样,@PostConstruct 和 @PreDestroy 注释类型是 JDK 6 到 8 的标准 Java 库的一部分。 然而,整个 javax.annotation 包在 JDK 9 中与核心 Java 模块分离,最终在 JDK 11 中删除. 如果需要,现在需要通过 Maven Central 获取 javax.annotation-api 工件,只需像任何其他库一样将其添加到应用程序的类路径中即可。