在配置Spring时,注解是否比XML更好?
基于注解的配置的引入提出了这样一个问题:这种方法是否比XML "更好"。简短的回答是 "视情况而定"。长的答案是,每种方法都有它的优点和缺点,而且,通常是由开发者来决定哪种策略更适合他们。由于它们的定义方式,注解在其声明中提供了大量的上下文,导致了更短、更简洁的配置。然而,XML擅长于在不触及源代码或重新编译的情况下对组件进行注入。一些开发者更喜欢在源码附近注入,而另一些人则认为带注解的类不再是POJO,此外,配置变得分散,更难控制。
不管是哪种选择,Spring都能适应这两种风格,甚至将它们混合在一起。值得指出的是,通过其 JavaConfig 选项,Spring允许以非侵入性的方式使用注解,而不触及目标组件的源代码,在工具方面,所有的配置风格都被 Spring Tools for Eclipse、Visual Studio Code 和 Theia 所支持。
基于注解的配置提供了XML设置的替代方案,它依靠字节码元数据来注入组件而不是XML声明。开发者通过在相关的类、方法或字段声明上使用注解,将配置移入组件类本身,而不是使用XML来描述bean的布线。正如 示例: AutowiredAnnotationBeanPostProcessor 中提到的,将 BeanPostProcessor 与注解结合使用是扩展Spring IoC容器的常见手段。例如,@Autowired 注解提供了与 注入协作者(Autowiring Collaborators) 中所描述的相同的功能,但控制范围更细,适用性更广。此外,Spring还提供了对JSR-250注解的支持,如 @PostConstruct 和 @PreDestroy,以及对JSR-330(Java的依赖注入)注解的支持,该注解包含在 jakarta.inject 包中,如 @Inject 和 @Named。
1. 使用 @Autowired
你可以将 @Autowired 注解应用于构造函数,如下例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你也可以将 @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;
}
// ...
}
你也可以将 @Autowired
应用于字段,甚至将其与构造函数混合,如下例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你也可以指示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
值可以影响注入点的优先级,但要注意它们不会影响singleton的启动顺序,这是一个由依赖关系和 @DependsOn
声明决定的正交问题。
请注意,标准的 jakarta.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可用时,自动注入就会失败。在声明的数组、collection或map的情况下,预计至少有一个匹配的元素。
默认行为是将注解的方法和字段视为表示必须的依赖关系。你可以改变这种行为,就像下面的例子所展示的那样,通过将其标记为非必需(即通过将 @Autowired
中的 required
属性设置为 false
),使框架能够跳过一个不可满足的注入点。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果一个非必须(required)的方法(或者在有多个参数的情况下,它的一个依赖关系)不可用,那么它将根本不会被调用。在这种情况下,一个非必须(required)字段将根本不会被填充,而是将其默认值留在原地。
换句话说,将 required
属性设置为 false
表示相应的属性对于自动注入来说是可选的,如果该属性不能被自动注入,它将被忽略。这使得属性可以被分配默认值,这些默认值可以通过依赖注入选择性地被重写。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法有可能处理多个构造函数,所以 @Autowired
中的 required
属性有一些不同的含义。构造函数和工厂方法参数实际上是默认需要的,但在单构造函数的情况下有一些特殊的规则,比如多元素注入点(数组、collection、map)如果没有匹配的Bean,则解析为空实例。这允许一种常见的实现模式,即所有的依赖关系都可以在一个独特的多参数构造函数中声明—例如,声明为一个没有 @Autowired
注解的单一公共构造函数。
任何给定的Bean类中只有一个构造函数可以声明 @Autowired
,并将 required
属性设置为 true
,表示该构造函数在用作Spring Bean时要自动注入。因此,如果 required
属性的默认值为 true
,则只有一个构造函数可以使用 @Autowired
注解。如果有多个构造函数声明该注解,它们都必须声明 required=false
,才能被视为自动注入的候选者(类似于XML中的 autowire=constructor
)。具有最大数量的依赖关系的构造函数将被选中,这些依赖关系可以通过Spring容器中的匹配Bean来满足。如果没有一个候选者可以被满足,那么将使用一个主要的/默认的构造函数(如果存在的话)。同样地,如果一个类声明了多个构造函数,但没有一个是用 @Autowired
注解的,那么将使用一个主要/默认构造函数(如果存在的话)。如果一个类一开始只声明了一个构造函数,那么即使没有注解,它也会被使用。请注意,被注解的构造函数不一定是公共的(public)。
另外,你可以通过Java 8的 java.util.Optional
来表达特定依赖的非必须性质,正如下面的例子所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始,你也可以使用 @Nullable 注解(任何包中的任何类型—?例如JSR-305中的 javax.annotation.Nullable),或者直接利用Kotlin内置的 null-safety 支持。
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你也可以对那些众所周知的可解析依赖的接口使用 @Autowired
。BeanFactory
、ApplicationContext
、Environment
、ResourceLoader
、ApplicationEventPublisher
和 MessageSource
。这些接口和它们的扩展接口,如 ConfigurableApplicationContext
或 ResourcePatternResolver
,将被自动解析,不需要特别的设置。下面的例子是自动注入一个 ApplicationContext
对象。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
、@Inject
、@Value
和 @Resource
注解是由Spring BeanPostProcessor
实现处理的。这意味着你不能在你自己的 BeanPostProcessor
或 BeanFactoryPostProcessor
类型(如果有的话)中应用这些注解。这些类型必须通过使用XML或Spring @Bean
方法明确地 "注入"。
2. 用 @Primary
对基于注解的自动注入进行微调
因为按类型自动注入可能会导致多个候选者,所以经常需要对选择过程进行更多的控制。实现这一目标的方法之一是使用Spring的 @Primary
注解。@Primary
表示,当多个Bean是自动注入到一个单值(single value)依赖的候选者时,应该优先考虑一个特定的Bean。如果在候选者中正好有一个主要(primary)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 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>
3. 用 Qualifiers 微调基于注解的自动注入
当可以确定一个主要的候选者时,@Primary
是按类型使用自动布线的一种有效方式,有几个实例。当你需要对选择过程进行更多控制时,你可以使用Spring的 @Qualifier
注解。你可以将限定符的值与特定的参数联系起来,缩小类型匹配的范围,从而为每个参数选择一个特定的bean。在最简单的情况下,这可以是一个普通的描述性值,如下面的例子所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
你也可以在单个构造函数参数或方法参数上指定 @Qualifier
注解,如以下例子所示:
public class MovieRecommender {
private final MovieCatalog movieCatalog;
private final 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 qualifier 值的bean与具有相同 qualifier 值的构造函数参数相注入
(2)具有 action qualifier 值的bean与具有相同 qualifier 值的构造器参数相注入。
对于回退匹配(fallback match),Bean的名字被认为是默认的限定符值。因此,你可以用 main
的 id
来定义Bean,而不是嵌套的限定符元素,导致同样的匹配结果。然而,尽管你可以使用这个约定来引用特定的Bean的名字,但 @Autowired
从根本上说是关于类型驱动的注入,并带有可选的语义限定词。这意味着限定符的值,即使有Bean名称的回退,也总是在类型匹配的集合中具有缩小的语义。它们在语义上并不表达对唯一Bean id
的引用。好的限定符值是 main
或 EMEA
或 persistent
,表达了独立于Bean id
的特定组件的特征,在匿名Bean定义的情况下,如前面的例子中,它可能是自动生成的。
如前所述,qualifier 也适用于类型化(泛型)的集合—例如,适用于 Set<MovieCatalog>
。在这种情况下,所有匹配的bean,根据声明的限定词,被作为一个集合注入。这意味着限定词不一定是唯一的。相反,它们构成过滤标准。例如,你可以用相同的限定词值 "action" 来定义多个 MovieCatalog
Bean,所有这些都被注入到一个用 @Qualifier("action")
注解的 Set<MovieCatalog>
中。
也就是说,如果你打算通过名字来表达注解驱动的注入,请不要主要使用 @Autowired
,即使它能够在类型匹配的候选者中通过bean的名字来选择。相反,使用JSR-250的 @Resource
注解,它在语义上被定义为通过其唯一的名称来识别特定的目标组件,而声明的类型与匹配过程无关。@Autowired
具有相当不同的语义。在通过类型选择候选Bean后,指定的 String
qualifier 值只在这些类型选择的候选中被考虑(例如,将 account
qualifier 与标有相同 qualifier 标签的Bean匹配)。
对于那些本身被定义为集合、Map
或数组类型的Bean,@Resource
是一个很好的解决方案,它通过唯一的名称来引用特定的集合或数组Bean。也就是说,从4.3版本开始,你也可以通过Spring的 @Autowired
类型匹配算法来匹配集合、Map
和数组类型,只要在 @Bean
返回类型签名或集合继承层次中保留元素类型信息。在这种情况下,你可以使用 qualifier 值在相同类型的集合中进行选择,如上一段所述。
从4.3版开始,@Autowired
也考虑到了用于注入的自我引用(也就是对当前注入的Bean的引用)。请注意,自我注入是一种回退(fallback)。对其他组件的常规依赖总是具有优先权。在这个意义上,自我引用不参与常规的候选选择,因此特别是永远不会是主要的(primary)。相反,他们总是以最低的优先级结束。在实践中,你应该把自引用作为最后的手段(例如,通过Bean的事务代理调用同一实例上的其他方法)。在这种情况下,可以考虑将受影响的方法分解到一个单独的委托Bean中。另外,你也可以使用 @Resource
,它可以通过唯一的名字获得一个回到当前Bean的代理。
@Autowired
适用于字段、构造函数和多参数方法,允许在参数级别上通过 qualifier 注解来缩小范围。相比之下,@Resource
只支持字段和只有一个参数的bean属性setter方法。因此,如果你的注入目标是构造函数或多参数方法,你应该坚持使用 qualifier。
你可以创建你自己的自定义 qualifier 注解。要做到这一点,请定义一个注解,并在你的定义中提供 @Qualifier
注解,如下面的例子所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你可以在自动注入的字段和参数上提供自定义 qualifier,如下例所示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,你可以提供候选Bean定义的信息。你可以添加 <qualifier/>
标签作为 <bean/>
标签的子元素,然后指定 type
和 value
来匹配你的自定义qualifier注解。type是与注解的全限定类名相匹配的。另外,如果不存在名称冲突的风险,作为一种方便,你可以使用简短的类名。下面的例子演示了这两种方法:
<?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提供限定符元数据。具体来说,请看 用注解提供 Qualifier 元数据。 在某些情况下,使用没有值的注解可能就足够了。当注解服务于一个更通用的目的并且可以应用于几个不同类型的依赖关系时,这可能是有用的。例如,你可以提供一个 offline 目录,在没有互联网连接的情况下可以进行搜索。首先,定义简单的注解,如下面的例子所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注解添加到要自动注入的字段或属性中,如下面的例子所示:
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
(1)这一行添加了 @Offline 注解。
现在,Bean定义只需要一个 qualifier type
,如下面的例子所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
(1)这个元素指定了 qualifier。
你也可以定义自定义的 qualifier 注解,除了简单的 value 属性之外,还接受命名的(named)属性,或者代替简单的值属性。如果在要自动注入的字段或参数上指定了多个属性值,那么Bean定义必须与所有这些属性值相匹配才能被认为是自动注入的候选者。作为一个例子,考虑下面的注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这种情况下,Format
是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
要自动注入的字段用自定义 qualifier 注解,并包括两个属性的值: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的定义应该包含匹配的 qualifier 值。这个例子还展示了你可以使用Bean元属性来代替 <qualifier/>
元素。如果有的话,<qualifier/>
元素及其属性优先,但如果没有这样的qualifier,自动注入机制就会回到 <meta/>
标签中提供的值,就像下面例子中的最后两个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>
4. 使用泛型作为自动注入 Qualifier
除了 @Qualifier
注解外,你还可以使用Java泛型作为隐含的限定形式。例如,假设你有下面的配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设前面的Bean实现了一个泛型接口,(即 Store<String>
和 Store<Integer>
),你可以 @Autowire
Store
接口,泛型被用作qualifier,如下例所示:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型 qualifier 也适用于自动注入list、Map
实例和数组。下面的例子是自动注入一个泛型 List:
// 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;
5. 使用 CustomAutowireConfigurer
CustomAutowireConfigurer 是一个 BeanFactoryPostProcessor,可以让你注册自己的自定义 qualifier 注解类型,即使它们没有用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
值。 - 在
<beans/>
元素上可用的任何默认的autowire-candidates
pattern。 - 存在
@Qualifier
注解和任何用CustomAutowireConfigurer
注册的自定义注解。
当多个Bean有资格成为自动注入的候选者时,“primary” 的确定方法如下。如果候选Bean定义中正好有一个 Primary
属性被设置为 true
,它就被选中。
6. 用 @Resource
注入
Spring还支持通过在字段或Bean属性设置方法上使用JSR-250 @Resource
注解(jakarta.annotation.Resource
)进行注入。这是Jakarta EE中的一种常见模式:例如,在JSF管理的Bean和JAX-WS端点。对于Spring管理的对象,Spring也支持这种模式。
@Resource
需要一个 name
属性。默认情况下,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;
}
}
在没有明确指定名称的 @Resource
使用的特殊情况下,与 @Autowired
类似,@Resource
找到一个主要的类型匹配,而不是一个特定的命名的 bean,并解析众所周知的可解析的依赖:BeanFactory
、ApplicationContext
、 ResourceLoader
、ApplicationEventPublisher
和 MessageSource
接口。
因此,在下面的例子中,customerPreferenceDao
字段首先寻找名为 "customerPreferenceDao" 的Bean,然后回退到 CustomerPreferenceDao
类型的 primary 类型匹配
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; (1)
public MovieRecommender() {
}
// ...
}
(1)context 字段是根据已知的可解析依赖类型注入的:ApplicationContext。
7. 使用 @Value
@Value
通常用于注入外部化properties
@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提供了一个默认的宽松的嵌入式值解析器(value resolver)。它将尝试解析属性值,如果无法解析,属性名称(例如 ${catalog.name}
)将被注入作为值。如果你想对不存在的值保持严格的控制,你应该声明一个 PropertySourcesPlaceholderConfigurer
Bean,如下面的例子所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
当使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer
时,@Bean
方法必须是 static
的。
使用上述配置可以确保在任何 ${}
占位符无法解析的情况下Spring初始化失败。也可以使用 setPlaceholderPrefix
、setPlaceholderSuffix
或 setValueSeparator
等方法来定制占位符。
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如转换为 Integer
或 int
)。多个逗号分隔的值可以自动转换为 String
数组,无需额外的操作。
可以提供一个默认值,如下所示:
@Component
public class MovieRecommender {
private final String catalog;
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;
}
}
8. 使用 @PostConstruct
和 @PreDestroy
CommonAnnotationBeanPostProcessor 不仅可以识别 @Resource 注解,还可以识别JSR-250生命周期注解:jakarta.annotation.PostConstruct 和 jakarta.annotation.PreDestroy。在Spring 2.5中引入,对这些注解的支持为 初始化回调和销毁回调中描述的生命周期回调机制提供了一个替代方案。只要在Spring ApplicationContext 中注册了 CommonAnnotationBeanPostProcessor,携带这些注解之一的方法就会在生命周期中与相应的Spring生命周期接口方法或明确声明的回调方法在同一时间被调用。在下面的例子中,缓存在初始化时被预先填充,在销毁时被清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
关于结合各种生命周期机制的效果
与 @Resource
一样,@PostConstruct
和 @PreDestroy
注解类型在JDK 6到8中是标准Java库的一部分。然而,整个 javax.annotation
包在JDK 9中从核心Java模块中分离出来,最终在JDK 11中被删除。从Jakarta EE 9开始,该包现在住在 jakarta.annotation
中。如果需要,现在需要通过Maven中心获得 jakarta.annotation-api
工件,只需像其他库一样添加到应用程序的classpath中即可。
大家好,我是Doker品牌的Sinbad,欢迎点赞和评论,大家的鼓励是我们持续更新的动力!或者加微信进入技术群聊!