https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html
使用注解配置Spring比使用XML更好吗?
引入基于注解的配置引发了一个问题:这种方法是否比XML“更好”?简短的答案是“视情况而定”。长答案是每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于注解的定义方式,它们在声明中提供了大量上下文,使得配置更短、更简洁。然而,XML在不触及源代码或重新编译它们的情况下擅长连接组件。一些开发人员更喜欢将连接靠近源代码,而其他人则认为带注解的类不再是POJO,并且配置变得分散且更难控制。
无论选择哪种方式,Spring都能适应这两种风格,甚至可以将它们混合在一起。值得一提的是,通过JavaConfig选项,Spring允许以非侵入性的方式使用注解,而无需触及目标组件的源代码。在工具方面,所有配置风格都得到了Spring Tools for Eclipse、Visual Studio Code和Theia的支持。
基于注解的配置提供了XML设置的替代方案,它依赖于字节码元数据来连接组件,而不是XML声明。开发人员不再使用XML来描述bean的连接,而是通过在相关类、方法或字段声明上使用注解,将配置移动到组件类本身中。例如,结合使用BeanPostProcessor
和注解是扩展Spring IoC容器的常见手段。此外,Spring还支持JSR-250注解,如@PostConstruct
和@PreDestroy
,以及支持JSR-330(Java的依赖注入)注解,这些注解包含在jakarta.inject
包中,如@Inject
和@Named
。
注解注入在XML注入之前执行。因此,对于通过这两种方法注入的属性,XML配置会覆盖注解。
像往常一样,你可以将后处理器(post-processors)注册为单个bean定义,但也可以通过在基于XML的Spring配置中包含以下标签来隐式注册(注意包含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/>
元素隐式注册了以下后处理器(post-processors:):
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EventListenerMethodProcessor
<context:annotation-config/>
只会查找在其定义的同一应用程序上下文中的bean上的注解。这意味着,如果将<context:annotation-config/>
放在DispatcherServlet
的WebApplicationContext
中,它只会检查控制器中的@Autowired
bean,而不是你的服务。
使用@Autowired
在此部分包含的示例中,可以使用JSR 330的@Inject
注解代替Spring的@Autowired
注解。
可以将@Autowired
注解应用于构造函数,如下例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring Framework 4.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;
}
// ...
}
确保目标组件(例如,MovieCatalog
或CustomerPreferenceDao
)始终由用于@Autowired
注解注入点的类型声明。否则,由于运行时出现“未找到类型匹配”的错误,注入可能会失败。
对于XML定义的bean或通过类路径扫描找到的组件类,容器通常事先知道具体类型。然而,对于@Bean
工厂方法,你需要确保声明的返回类型足够明确。对于实现多个接口的组件或可能通过其实现类型引用的组件,请考虑在你的工厂方法上声明最具体的返回类型(至少与引用你的bean的注入点所要求的那样具体)。
还可以通过将@Autowired
注解添加到期望该类型数组的字段或方法上,指示Spring从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;
}
// ...
}
如果希望数组或列表中的条目按特定顺序排序,你的目标bean可以实现org.springframework.core.Ordered
接口或使用@Order
或标准@Priority
注解。否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。
可以在目标类级别和@Bean
方法上声明@Order
注解,可能针对单个bean定义(如果有多个使用相同bean类的定义)。@Order
值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,这是由依赖关系和@DependsOn
声明确定的正交问题。
请注意,标准的jakarta.annotation.Priority
注解在@Bean
级别不可用,因为它不能在方法上声明。其语义可以通过@Order
值与每个类型的单个bean上的@Primary
结合来模拟。
即使是类型化的Map
实例也可以自动装配,只要期望的键类型是String
。映射值包含所有期望类型的bean,键包含相应的bean名称,如下例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,如果没有匹配的候选bean可用于给定的注入点,自动装配会失败。在声明数组、集合或映射的情况下,至少需要一个匹配的元素。
默认行为是将带@Autowired
注解的方法和字段视为表示必需的依赖关系。可以像下面的例子所示那样改变这种行为,通过将其标记为非必需(例如,将@Autowired
中的required
属性设置为false
),使框架能够跳过无法满足的注入点:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果non-required 方法的依赖关系(或者在多个参数的情况下,其中一个依赖关系)不可用,那么根本不会调用该方法。在这种情况下,non-required 字段根本不会被填充,保持其默认值不变。
换句话说,将required
属性设置为false
表示相应的属性对于自动装配是可选的,如果该属性无法自动装配,那么将被忽略。这允许属性被分配默认值,可以通过依赖注入选择性地覆盖。
注入的构造函数和工厂方法参数是一个特例,因为由于Spring的构造函数解析算法可能会处理多个构造函数,@Autowired
中的required
属性有着稍微不同的含义。构造函数和工厂方法参数默认是必需的,但在单构造函数场景下有一些特殊规则,例如如果没有匹配的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
方法显式配置。
通过@Primary微调基于注解的自动装配
由于按类型自动装配可能会导致多个候选者,通常需要对选择过程有更多的控制。一种实现方式是使用Spring的@Primary
注解。@Primary
表示在多个bean成为自动装配到单一值依赖项的候选者时,应优先考虑特定的bean。如果候选者中恰好存在一个primary bean,它将成为自动装配的值。
考虑以下配置,它将firstMovieCatalog
定义为primary 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>
通过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"/>
<!-- 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>
- 有
main
限定符值的bean将与用相同值限定的构造函数参数进行绑定。 - 带有
action
限定符值的bean将与标注了相同值的构造函数参数进行绑定。
对于后备匹配,bean的名称被视为默认的限定符值。因此,可以使用id
为main
定义bean,而不是使用嵌套的qualifier
元素,这将导致相同的匹配结果。然而,尽管可以使用这种约定按名称引用特定的bean,@Autowired
本质上是关于类型驱动的注入,带有可选的语义限定符。这意味着限定符值,即使在bean名称作为后备的情况下,总是在类型匹配集合中具有缩小范围的语义。它们在语义上并不表达对唯一bean id
的引用。好的限定符值是main
或EMEA
或persistent
,它们表达了特定组件的特性,这些特性独立于bean id
,bean id
可能是自动生成的,如前面示例中的匿名bean定义。
限定符也适用于之前讨论的类型集合,例如Set<MovieCatalog>
。在这种情况下,根据声明的限定符,所有匹配的bean都会被注入为一个集合。这意味着限定符不必是唯一的。相反,它们构成了过滤条件。例如,可以定义多个具有相同限定符值“action
”的MovieCatalog
bean,所有这些bean都会被注入到一个用@Qualifier("action")
注解的Set
中。
在类型匹配的候选项中,让限定符值与目标bean名称相对应,不需要在注入点使用@Qualifier
注解。如果没有其它解析指示器(如qualifier 或primary 标记),对于非唯一的依赖情况,Spring会将注入点名称(即字段名或参数名)与目标bean名称进行匹配,并在有相同名称的候选项时选择它。
自6.1版本起,这需要加上-parameters
Java编译器标志。
话虽如此,如果打算通过名称表达基于注解的注入,即使@Autowired
能够在类型匹配的候选项中通过bean名称进行选择,也不应主要使用@Autowired
。相反,应该使用JSR-250 @Resource
注解,它在语义上被定义为通过其唯一名称识别特定的目标组件,声明的类型与匹配过程无关。@Autowired
具有相当不同的语义:在通过类型选择候选bean之后,仅在这些类型选定的候选项中考虑指定的String
限定符值(例如,将account
限定符与标记有相同限定符标签的bean进行匹配)。
对于本身被定义为集合、Map或数组类型的bean,@Resource
是一个很好的解决方案,通过唯一名称引用特定的集合或数组bean。话虽如此,自4.3版本起,只要元素类型信息保留在@Bean返
回类型签名或集合继承层次结构中,也可以通过Spring的@Autowired
类型匹配算法来匹配集合、Map和数组类型。在这种情况下,可以使用限定符值在同类型的集合中进行选择。
自4.3版本起,@Autowired
也考虑了对当前被注入bean的自身引用(即,引用回当前正在注入的bean)。请注意,自身注入是一种后备方案。对其它组件的常规依赖总是优先。在这个意义上,自身引用不参与常规的候选选择,因此永远不会是主要的。相反,它们总是作为最低优先级结束。在实践中,应该仅将自身引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其它方法)。在这种情况下,可以考虑将受影响的方法分离到一个单独的委托bean中。或者,可以使用@Resource
,它可以通过唯一名称获取回当前bean的代理。
在同一配置类中尝试注入@Bean
方法的结果实际上也是一种自身引用场景。要么在真正需要的地方(与配置类中的自动装配字段相反)的方法签名中延迟解析这些引用,要么将受影响的@Bean
方法声明为静态的,从而将它们与包含的配置类实例及其生命周期解耦。否则,这样的bean只会在后备阶段被考虑,而其它配置类上的匹配bean将作为主要候选者被选中(如果有的话)。
@Autowired
适用于字段、构造函数和多参数方法,允许通过参数级别的qualifier 注解进行缩小范围。相比之下,@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定义提供信息。可以将标签<qualifier/>
添加为标签<bean/>
的子元素,然后指定type
和value
以匹配你的自定义限定符注解。类型与注解的完全限定类名进行匹配。或者,如果没有名称冲突的风险,作为方便,可以使用短类名。以下示例展示了这两种方法:
<?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>
在某些情况下,使用没有值的注解可能就足够了。当注解用于更通用的目的并且可以应用于几种不同类型的依赖项时,这可能会很有用。例如,你可能提供了一个离线目录,在没有互联网连接时可以进行搜索。首先,定义简单的注解,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注解添加到要自动装配的字段或属性上,如下例所示:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
在bean定义只需要一个限定符type
,如下例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
还可以定义接受命名属性的自定义限定符注解,这些属性可以作为简单值属性的补充或替代。如果在要自动装配的字段或参数上指定了多个属性值,则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 meta
属性而不是<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>
使用泛型作为自动装配限定符
除了@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接口,泛型被用作限定符,如下例所示:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型限定符也适用于自动装配列表、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;
使用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
值 <beans/>
元素上可用的任何default-autowire-candidates
模式@Qualifier
注解的存在以及使用CustomAutowireConfigurer
注册的任何自定义注解
当多个bean符合自动装配候选者的条件时,“primary” bean的确定如下:如果候选者中恰好有一个bean定义的primary
属性设置为true
,则选择它。
使用@Resource进行注入
Spring还支持使用JSR-250 @Resource
注解(jakarta.annotation.Resource
)在字段或bean属性setter方法上进行注入。这是Jakarta EE中的一个常见模式:例如,在JSF管理的bean和JAX-WS端点中。Spring也支持Spring管理的对象使用这种模式。
@Resource
接受一个name
属性。默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循按名称语义,如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有明确指定名称,则默认名称是从字段名或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,并解析众所周知的可解析依赖项:BeanFactory
、ApplicationContext
、ResourceLoader、ApplicationEventPublisher
和MessageSource
接口。
因此,在下面的例子中,customerPreferenceDao
字段首先寻找名为“customerPreferenceDao
”的bean,然后回退到CustomerPreferenceDao
类型的主要类型匹配:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
context
字段基于已知的可解析依赖类型进行注入:ApplicationContext
。
使用@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
方法必须是静态的。
使用上述配置可以确保如果有任何一个${}
占位符无法解析,则Spring初始化失败。还可以使用setPlaceholderPrefix
、setPlaceholderSuffix
或setValueSeparator
等方法来自定义占位符。
Spring Boot默认配置了一个PropertySourcesPlaceholderConfigurer
bean,它会从application.properties
和application.yml
文件中获取属性。
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;
}
}
使用@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 Central获取jakarta.annotation-api
组件,就像任何其他库一样简单地添加到应用程序的类路径中。