Appearance
Spring 核心:IoC 容器 - Part3
Tip:基于 Spring Core 5.3.30 版本。
1. 基于注解的容器配置
Note:注解比 XML 在配置 Spring 方面更好吗?
引入基于注解的配置引发了一个问题,即这种方法是否比 XML “更好”。简短的回答是 “这取决于情况”。长远来看,每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注解在声明时提供了大量的上下文信息,从而导致配置更短、更简洁。然而,XML 在不触及源代码或重新编译组件的情况下优秀地完成了组件的连线工作。一些开发人员喜欢将连线配置放在源代码附近,而另一些人则认为带有注解的类不再是 POJO,并且配置变得分散且难以控制。
无论选择哪种方式,Spring 都能够适应这两种风格,甚至可以混合使用。值得一提的是,通过其 JavaConfig 选项,Spring 允许以非侵入性的方式使用注解,而不必触及目标组件的源代码。此外,就工具而言,Spring Tools for Eclipse 支持所有的配置风格。
与 XML 设置相比,注解驱动的配置提供了一种替代方案,它依赖于字节码元数据来连接组件,而不是使用尖括号声明。开发人员可以通过在相关类、方法或字段声明上使用注解,将配置移入组件类本身,而不是使用 XML 来描述 Bean 的连线。正如在示例中提到的 AutowiredAnnotationBeanPostProcessor,使用 BeanPostProcessor 结合注解是扩展 Spring IoC 容器的常见方式。例如,Spring 2.0 通过引入 @Required 注解,实现了对必需属性的强制性要求。而在 Spring 2.5 中,我们可以采用相同的通用方法来驱动 Spring 的依赖注入。基本上,@Autowired 注解提供了与自动装配协作者中描述的相同功能,但具有更精细的控制和更广泛的适用性。Spring 2.5 还增加了对 JSR-250 注解的支持,例如 @PostConstruct 和 @PreDestroy。Spring 3.0 增加了对 JSR-330(Java 的依赖注入)注解的支持,这些注解包含在 javax.inject 包中,例如 @Inject 和 @Named。有关这些注解的详细信息可以在相关部分找到。
Warning:注解注入在 XML 注入之前执行。因此,对于通过两种方式连接的属性,XML 配置会覆盖注解配置。
一如既往,你可以将后处理器注册为单独的 Bean 定义,但也可以通过在基于 XML 的 Spring 配置中包含以下标签来隐式注册(注意包含了 context 命名空间):
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
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>1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
<context:annotation-config/> 元素隐式注册了以下后处理器:
ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessorEventListenerMethodProcessor
Warning:
<context:annotation-config/>只在其定义的同一应用上下文中查找 Bean 的注解。这意味着,如果你在DispatcherServlet的WebApplicationContext中放置了<context:annotation-config/>,它只会检查你的控制器中的@AutowiredBean,而不是你的服务。有关更多信息,请参阅DispatcherServlet。
1.1. @Required
@Required 注解适用于 Bean 属性的 Setter 方法,如下例所示:
Java
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
这个注解意味着,受影响的 Bean 属性必须在配置时被填充,可以通过 Bean 定义中的显式属性值或者自动装配来实现。如果受影响的 Bean 属性没有被填充,容器会抛出一个异常。这样可以提前并明确地发现失败,避免后续出现 NullPointerException 或类似的情况。我们仍然建议你在 Bean 类本身中添加断言(例如,添加到 init 方法中)。这样做可以确保即使在容器之外使用该类时,也能强制执行那些必需的引用和值。
Tip 1:必须将
RequiredAnnotationBeanPostProcessor注册为 Bean,以启用对@Required注解的支持。
Tip 2:自 Spring Framework 5.1 起,正式弃用
@Required注解和RequiredAnnotationBeanPostProcessor,推荐使用构造函数注入来进行必需的设置(或者自定义实现InitializingBean.afterPropertiesSet()或者配合 Bean 属性 Setter 方法的自定义@PostConstruct方法)。
1.2. 使用 @Autowired
Note:在本节包含的示例中,JSR 330 的
@Inject注解可以替代 Spring 的@Autowired注解。更多详情请参见此处。
你可以将 @Autowired 注解应用到构造函数上,如下例所示:
Java
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Note:从 Spring Framework 4.3 开始,如果目标 Bean 本身只定义了一个构造函数,那么在这样的构造函数上的
@Autowired注解就不再必需。然而,如果有多个构造函数可用,并且没有主要/默认的构造函数,那么至少有一个构造函数必须用@Autowired注解,以便指示容器使用哪一个。
你也可以将 @Autowired 注解应用到传统的 Setter 方法上,如下例所示:
Java
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
你也可以将注解应用到具有任意名称和多个参数的方法上,如下例所示:
Java
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
你也可以将 @Autowired 注解应用到字段上,甚至可以与构造函数混合使用,如下例所示:
Java
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Note
确保你的目标组件(例如,
MovieCatalog或CustomerPreferenceDao)的声明类型与你用于@Autowired注解注入点的类型一致。否则,由于在运行时发现 “没有找到类型匹配” 的错误,注入可能会失败。对于通过类路径扫描找到的 XML 定义的 Bean 或组件类,容器通常提前知道具体的类型。然而,对于
@Bean工厂方法,你需要确保声明的返回类型具有足够的表达能力。对于实现了多个接口的组件,或者可能按其实现类型引用的组件,考虑在你的工厂方法上声明最具体的返回类型(至少与引用你的 Bean 的注入点所需的一样具体)。假设
CustomerPreferenceDao组件实现了PreferenceDao和CustomerDao两个接口。如果你有一个@Bean工厂方法来创建这个组件,你应该声明返回类型为CustomerPreferenceDao:Java@Configuration public class AppConfig { @Bean public CustomerPreferenceDao customerPreferenceDao() { return new CustomerPreferenceDaoImpl(); } }这样,无论你的注入点是
PreferenceDao还是CustomerDao,都可以正确地注入CustomerPreferenceDao组件。而如果你的@Bean工厂方法的返回类型声明为PreferenceDao或CustomerDao,那么当注入点的类型为CustomerPreferenceDao时,就可能会出现类型不匹配的问题。
你也可以通过在期望某种类型数组的字段或方法上添加 @Autowired 注解,指示 Spring 提供来自 ApplicationContext 的特定类型的所有 Bean,如下例所示:
Java
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}1
2
3
4
5
6
2
3
4
5
6
同样的规则也适用于类型化的集合,如下例所示:
Java
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Tip
如果你希望数组或列表中的项目按特定顺序排序,你的目标 Bean 可以实现
org.springframework.core.Ordered接口,或者使用@Order或标准的@Priority注解。否则,它们的顺序遵循容器中相应目标 Bean 定义的注册顺序。你可以在目标类级别和
@Bean方法上声明@Order注解,可能对单个 Bean 定义(如果有多个定义使用相同的 Bean 类)。@Order值可能影响注入点的优先级,但请注意,它们不影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。请注意,标准的
javax.annotation.Priority注解在@Bean级别不可用,因为它不能在方法上声明。可以通过在每种类型的单个 Bean 上使用@Order值和@Primary来模拟其语义。
只要预期的键类型是 String,甚至类型化的 Map 实例也可以进行自动装配。映射值包含所有预期类型的 Bean,键包含相应的 Bean 名称,如下例所示:
Java
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
默认情况下,当给定注入点没有匹配的候选 Bean 可用时,自动装配会失败。在声明了数组、集合或映射的情况下,至少期望有一个匹配的元素。
默认行为是将注解的方法和字段视为表示必需的依赖项。你可以改变这种行为,如下例所示,通过将其标记为非必需(即,将 @Autowired 中的 required 属性设置为 false),使框架能够跳过无法满足的注入点:
Java
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
如果非必需的方法的依赖项(或者在有多个参数的情况下,其依赖项之一)不可用,那么这个方法将不会被调用。在这种情况下,非必需的字段将不会被填充,保留其默认值。
构造函数和工厂方法参数是一个特殊情况,因为 @Autowired 中的 required 属性由于 Spring 的构造函数解析算法可能会处理多个构造函数,而有稍微的不同含义。构造函数和工厂方法参数实际上默认是必需的,但在单构造函数场景下有一些特殊规则,比如多元素注入点(数组、集合、映射)如果没有匹配的 Bean,则解析为空实例。这允许一种常见的实现模式,即所有依赖项都可以在一个独特的多参数构造函数中声明,例如,声明为一个没有 @Autowired 注解的单一公共构造函数。
Warning
任何给定的 Bean 类只能有一个构造函数声明
@Autowired,并将required属性设置为true,表示当该构造函数被用作 Spring Bean 时,它是自动装配的构造函数。因此,如果required属性保持其默认值true,则只有一个构造函数可以用@Autowired进行注解。如果多个构造函数声明了该注解,那么它们都必须声明required=false,以便被视为自动装配的候选项(类似于 XML 中的autowire=constructor)。在 Spring 容器中,能够满足最大数量依赖项的构造函数将被选择。如果没有任何候选项可以满足,那么将使用主要/默认的构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但没有一个被@Autowired注解,那么将使用主要/默认的构造函数(如果存在)。如果一个类一开始只声明了一个构造函数,那么它将始终被使用,即使没有注解。请注意,注解的构造函数不必是公开的。
@Autowired的required属性比在 Setter 方法上的已弃用的@Required注解更为推荐。将required属性设置为false表示该属性对于自动装配来说不是必需的,如果无法进行自动装配,该属性将被忽略。另一方面,@Required更强,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,将引发相应的异常。
或者,你可以通过 Java 8 的 java.util.Optional 来表达特定依赖项的非必需性,如下例所示:
Java
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}1
2
3
4
5
6
2
3
4
5
6
从 Spring Framework 5.0 开始,你也可以使用 @Nullable 注解(任何类型的任何包中的注解,例如 JSR-305 中的 javax.annotation.Nullable),或者直接利用 Kotlin 内置的空安全支持:
Java
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}1
2
3
4
5
6
2
3
4
5
6
你也可以对一些众所周知的可解析依赖接口使用 @Autowired,如:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher 和 MessageSource。这些接口及其扩展接口,如 ConfigurableApplicationContext 或 ResourcePatternResolver,都会被自动解析,无需特殊设置。以下示例自动装配了一个 ApplicationContext 对象:
Java
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Warning:
@Autowired、@Inject、@Value和@Resource注解由 Spring 的BeanPostProcessor实现处理。这意味着你不能在你自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注解。这些类型必须通过使用 XML 或 Spring 的@Bean方法显式地进行 “连线”。
1.3. 使用 @Primary 对基于注解的自动装配进行微调
因为按类型自动装配可能会导致多个候选项,因此通常需要对选择过程有更多的控制。实现这一点的一种方式是使用 Spring 的 @Primary 注解。@Primary 表示当多个 Bean 是单值依赖项的自动装配候选项时,应优先考虑特定的 Bean。如果在候选项中只存在一个主要的 Bean,那么它就会成为自动装配的值。
考虑以下配置,它将 firstMovieCatalog 定义为主要的 MovieCatalog:
Java
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
有了前面的配置,以下的 MovieRecommender 将与 firstMovieCatalog 进行自动装配:
Java
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}1
2
3
4
5
6
2
3
4
5
6
以下是相应的 Bean 定义:
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
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.4. 使用限定符对基于注解的自动装配进行微调
当使用类型自动装配处理多个实例可以确定一个主要候选项时,@Primary 是一种有效的方法。而当你需要对选择过程有更多的控制时,你可以使用 Spring 的 @Qualifier 注解。你可以将限定符值与特定的参数关联,缩小类型匹配的集合,以便为每个参数选择一个特定的 Bean。在最简单的情况下,这可以是一个简单的描述值,如下例所示:
Java
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
你也可以在单独的构造函数参数或方法参数上指定 @Qualifier 注解,如下例所示:
Java
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;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
以下示例显示了相应的 Bean 定义:
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
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
对于后备匹配,Bean 名称被视为默认的限定符值。因此,你可以定义一个 id 为 main 的 Bean,而不是嵌套的限定符元素,这将导致相同的匹配结果。然而,尽管你可以使用这种约定通过名称引用特定的 Bean,但 @Autowired 基本上是关于类型驱动的注入,并带有可选的语义限定符。这意味着,即使在 Bean 名称回退的情况下,限定符值在类型匹配集合中总是具有缩小的语义。它们并不在语义上表示对唯一 Bean id 的引用。好的限定符值是 main、EMEA 或 persistent,它们表达了特定组件的特性,这些特性独立于 Bean id,在匿名 Bean 定义(如前面的示例)的情况下,Bean id 可能是自动生成的。
限定符也适用于前面讨论过的类型集合,例如 Set<MovieCatalog>。在这种情况下,所有匹配的 Bean,根据声明的限定符,被注入为一个集合。这意味着限定符不必是唯一的。相反,它们构成了过滤条件。例如,你可以定义多个具有相同限定符值 action 的 MovieCatalog Bean,所有这些都被注入到一个用 @Qualifier("action") 注解的 Set<MovieCatalog> 中。
Note:让限定符值在类型匹配的候选对象中选择目标 Bean 名称,并不需要在注入点使用
@Qualifier注解。如果没有其他解决指示器(例如限定符或主要标记),对于非唯一依赖情况,Spring 会将注入点名称(即字段名称或参数名称)与目标 Bean 名称进行匹配,并选择同名候选对象(如果有的话)。
也就是说,如果你打算通过名称进行注解驱动的注入,即使 @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 中。或者,你可以使用 @Resource,它可能会通过其唯一名称获取到当前 Bean 的代理。
Tip:在同一配置类上尝试注入
@Bean方法的结果实际上也是一个自我引用的情况。要么在实际需要的方法签名中懒加载这样的引用(而不是在配置类中的自动装配字段),要么将受影响的@Bean方法声明为静态的,将它们从包含的配置类实例及其生命周期中解耦。否则,这样的 Bean 只会在后备阶段被考虑,而其他配置类上的匹配 Bean 将被选为主要候选对象(如果有的话)。
@Autowired 适用于字段、构造函数和多参数方法,允许通过在参数级别使用限定符注解进行缩小。相反,@Resource 只支持字段和具有单个参数的 Bean 属性 Setter 方法。因此,如果你的注入目标是构造函数或多参数方法,你应该坚持使用限定符。
你可以创建自己的自定义限定符注解。要做到这一点,定义一个注解,并在你的定义中提供 @Qualifier 注解,如下面的示例所示:
Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}1
2
3
4
5
6
2
3
4
5
6
然后,你可以在自动装配字段和参数上提供自定义限定符,如下面的示例所示:
Java
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
接下来,你可以为候选 Bean 定义提供信息。你可以添加 <qualifier/> 标签作为 <bean/> 标签的子元素,然后指定 type 和 value 来匹配你的自定义限定符注解。类型与注解的全限定类名进行匹配。或者,如果不存在冲突名称的风险,你可以使用短类名,这是一种便利的方式。以下示例演示了这两种方法:
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
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在类路径扫描和管理组件中,你可以看到一个基于注解的替代方案,用于在 XML 中提供限定符元数据。具体来说,参见使用注解提供限定符元数据。
在某些情况下,使用没有值的注解可能就足够了。当注解服务于更通用的目的并且可以应用于几种不同类型的依赖时,这可能很有用。例如,你可以提供一个离线目录,当没有网络连接可用时可以进行搜索。首先,定义简单的注解,如下面的示例所示:
Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}1
2
3
4
5
6
2
3
4
5
6
然后将注解添加到要进行自动装配的字段或属性,如下面的示例所示:
Java
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
现在,Bean 定义只需要一个限定符类型,如下面的示例所示:
XML
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>1
2
3
4
2
3
4
你还可以定义接受命名属性的自定义限定符注解,这可以作为简单 value 属性的补充或替代。如果在要进行自动装配的字段或参数上指定了多个属性值,那么 Bean 定义必须匹配所有这些属性值才被视为自动装配的候选对象。例如,考虑以下注解定义:
Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
在这种情况下,Format 是一个枚举,定义如下:
Java
public enum Format {
VHS, DVD, BLURAY
}1
2
3
2
3
对要进行自动装配的字段用自定义限定符进行注解,并包括两个属性的值:genre 和 format,如下面的示例所示:
Java
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;
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
最后,Bean 定义应包含匹配的限定符值。这个示例还演示了你可以使用 Bean meta 属性代替 <qualifier/> 元素。如果有的话,<qualifier/> 元素及其属性优先,但是如果没有这样的限定符,自动装配机制会回退到在 <meta/> 标签中提供的值,就像下面示例中的最后两个 Bean 定义那样:
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1.5. 使用泛型作为自动装配限定符
除了 @Qualifier 注解之外,你还可以使用 Java 泛型作为一种隐式的限定条件。例如,假设你有以下配置:
Java
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
假设前面的 Bean 实现了一个泛型接口(即,Store<String> 和 Store<Integer>),你可以使用 @Autowire 对 Store 接口进行自动装配,并使用泛型作为限定符,如下面的示例所示:
Java
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean1
2
3
4
5
2
3
4
5
泛型限定符也适用于自动装配列表、Map 实例和数组。以下示例自动装配了一个泛型列表:
Java
// 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;1
2
3
4
2
3
4
1.6. 使用 CustomAutowireConfigurer
CustomAutowireConfigurer 是一个 BeanFactoryPostProcessor,它允许你注册自己的自定义限定符注解类型,即使它们未使用 Spring 的 @Qualifier 注解进行标注。以下示例展示了如何使用 CustomAutowireConfigurer:
XML
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
AutowireCandidateResolver 通过以下方式确定自动装配候选项:
- 每个 Bean 定义的
autowire-candidate值; - 在
<beans/>元素上可用的任何default-autowire-candidates模式; @Qualifier注解的存在以及与CustomAutowireConfigurer注册的任何自定义注解;
当多个 Bean 作为自动装配候选项时,确定 “Primary” 的规则如下:如果候选项中恰有一个 Bean 定义的 primary 属性设为 true,那么它将被选择。
1.7. 使用 @Resource 进行注入
Spring 还支持在字段或 Bean 属性 Setter 方法上使用 JSR-250 的 @Resource 注解(javax.annotation.Resource)进行注入。这是 Java EE 中的常见模式:例如,在 JSF 管理的 Bean 和 JAX-WS 端点中。Spring 对于 Spring 管理的对象也支持这种模式。
@Resource 接受一个 name 属性。默认情况下,Spring 将该值解释为要注入的 Bean 名称。换句话说,它遵循按名称语义,如下面的示例所示:
Java
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如果没有明确指定名称,那么默认名称将从字段名称或 Setter 方法中派生。在字段的情况下,它使用字段名称。在 Setter 方法的情况下,它使用 Bean 属性名称。以下示例将会将名为 movieFinder 的 Bean 注入到其 Setter 方法中:
Java
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Tip:注解提供的名称由
CommonAnnotationBeanPostProcessor所知的ApplicationContext解析为 Bean 名称。如果你明确配置了 Spring 的SimpleJndiBeanFactory,那么可以通过 JNDI 解析这些名称。然而,我们建议你依赖默认行为,并使用 Spring 的 JNDI 查找能力来保持间接级别。
在仅使用 @Resource 且没有明确指定名称的情况下,类似于 @Autowired,@Resource 会找到一个主要类型匹配,而不是一个特定命名的 Bean,并解析众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher 和 MessageSource 接口。
因此,在下面的示例中,customerPreferenceDao 字段首先寻找一个名为 customerPreferenceDao 的 Bean,然后回退到 CustomerPreferenceDao 类型的主要类型匹配:
Java
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
1.8. 使用 @Value
@Value 通常用于注入外部化属性:
Java
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
使用以下配置:
Java
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }1
2
3
2
3
以及以下的 application.properties 文件:
Properties
catalog.name=MovieCatalog在这种情况下,catalog 参数和字段将等于 MovieCatalog 的值。
Spring 提供了一个默认的宽松的嵌入值解析器。它会尝试解析属性值,如果无法解析,属性名称(例如 ${catalog.name})将被注入为值。如果你想对不存在的值进行严格控制,你应该声明一个 PropertySourcesPlaceholderConfigurer Bean,如下面的示例所示:
Java
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
Tip:当使用 JavaConfig 配置
PropertySourcesPlaceholderConfigurer时,@Bean方法必须是静态的。
使用上述配置可以确保如果任何 ${} 占位符无法解析,Spring 初始化会失败。也可以使用 setPlaceholderPrefix、setPlaceholderSuffix 或 setValueSeparator 等方法来自定义占位符。
Tip:Spring Boot 默认配置了一个
PropertySourcesPlaceholderConfigurerBean,它将从application.properties和application.yml文件中获取属性。
Spring 提供的内置转换器支持允许自动处理简单类型转换(例如转换为 Integer 或 int)。多个逗号分隔的值可以自动转换为 String 数组,无需额外的努力。
可以提供默认值,如下所示:
Java
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Spring 的 BeanPostProcessor 在幕后使用 ConversionService 来处理将 @Value 中的 String 值转换为目标类型的过程。如果你想为你自己的自定义类型提供转换支持,你可以提供你自己的 ConversionService Bean 实例,如下面的示例所示:
Java
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
当 @Value 包含一个 SpEL 表达式时,值将在运行时动态计算,如下面的示例所示:
Java
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
SpEL 还支持使用更复杂的数据结构:
Java
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
1.9. 使用 @PostConstruct 和 @PreDestroy
CommonAnnotationBeanPostProcessor 不仅识别 @Resource 注解,还识别 JSR-250 生命周期注解:javax.annotation.PostConstruct 和 javax.annotation.PreDestroy。在 Spring 2.5 中引入的这些注解,为初始化回调和销毁回调中描述的生命周期回调机制提供了替代选择。只要在 Spring ApplicationContext 中注册了 CommonAnnotationBeanPostProcessor,带有这些注解的方法在生命周期中的调用时机与对应的 Spring 生命周期接口方法或明确声明的回调方法相同。在下面的示例中,初始化时预填充缓存,销毁时清除缓存:
Java
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制小节。
Tip:像
@Resource一样,@PostConstruct和@PreDestroy注解类型是 JDK 6 到 8 的标准 Java 库的一部分。然而,在 JDK 9 中,整个javax.annotation包从核心 Java 模块中分离出来,并最终在 JDK 11 中被移除。如果需要,现在需要通过 Maven Central 获取javax.annotation-api工件,就像添加任何其他库一样,只需将其添加到应用程序的类路径中。
2. 类路径扫描和管理组件
先前我们大部分使用 XML 来定义 Spring 容器中每个 BeanDefinition 的配置元数据。前一部分(基于注解的容器配置)展示了如何通过源代码级别的注解来提供大部分配置元数据。然而,即使在那些例子中,“基础” 的 Bean 定义也是在 XML 文件中明确定义的,注解只是用来驱动依赖注入。本节介绍了一种通过扫描类路径来隐式检测候选组件的方法。候选组件是满足过滤条件并在容器中注册了对应 Bean 定义的类。这种方法消除了使用 XML 进行 Bean 注册的需求。取而代之的是,你可以使用注解(如 @Component),AspectJ 类型表达式,或者你可以使用自定义过滤条件来决定哪些类应该在容器中注册 Bean 定义。
Note:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能已成为 Spring 框架的核心部分。这使你可以使用 Java 而不是传统的 XML 文件来定义 Bean。看看
@Configuration、@Bean、@Import和@DependsOn注解,这些都是如何使用这些新功能的示例。
2.1. @Component 和进一步的刻板印象注解
@Repository 注解是用于标记任何满足仓库角色或刻板印象(也被称为数据访问对象或 DAO)的类。这个标记的用途之一是异常的自动转换,如在异常转换部分所述。
Spring 提供了更多的刻板印象注解:@Component、@Service 和 @Controller。@Component 是对任何 Spring 管理的组件的通用刻板印象。@Repository、@Service 和 @Controller 是 @Component 的特化形式,用于更具体的使用场景(分别在持久化层、服务层和表示层)。因此,你可以用 @Component 注解你的组件类,但是,通过用 @Repository、@Service 或 @Controller 来注解它们,你的类更适合于被工具处理或与切面关联。例如,这些刻板印象注解对于切入点来说是理想的目标。@Repository、@Service 和 @Controller 在未来的 Spring 框架版本中也可能带有额外的语义。因此,如果你在选择使用 @Component 还是 @Service 来表示你的服务层,@Service 显然是更好的选择。同样,如前所述,@Repository 已经被支持作为在你的持久化层进行自动异常转换的标记。
2.2. 使用元注解和组合注解
Spring 提供的许多注解可以在你自己的代码中作为元注解使用。元注解是可以应用于另一个注解的注解。例如,前面提到的 @Service 注解被元注解 @Component 标注,如下面的示例所示:
Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
你也可以组合元注解来创建 “复合注解”。例如,Spring MVC 中的 @RestController 注解是由 @Controller 和 @ResponseBody 组成的。
此外,复合注解可以选择性地重新声明元注解的属性以允许自定义。当你只想暴露元注解的部分属性时,这可能特别有用。例如,Spring 的 @SessionScope 注解将作用域名硬编码为 session,但仍然允许自定义 proxyMode。下面的清单显示了 SessionScope 注解的定义:
Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
然后,你可以不声明 proxyMode 就使用 @SessionScope,如下所示:
Java
@Service
@SessionScope
public class SessionScopedService {
// ...
}1
2
3
4
5
2
3
4
5
你也可以覆盖 proxyMode 的值,如下面的示例所示:
Java
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}1
2
3
4
5
2
3
4
5
有关更多详细信息,请参见 Spring 注解编程模型的 Wiki 页面。
2.3. 自动检测类并注册 Bean 定义
Spring 可以自动检测刻板印象类,并在 ApplicationContext 中注册相应的 BeanDefinition 实例。例如,以下两个类都符合自动检测的条件:
Java
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Java
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}1
2
3
4
2
3
4
要自动检测这些类并注册相应的 Bean,你需要在你的 @Configuration 类中添加 @ComponentScan,其中的 basePackages 属性是这两个类的共同父包。或者,你也可以提供一个由逗号、分号或空格分隔的列表,其中包含每个类的父包。
Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}1
2
3
4
5
2
3
4
5
Note:为了简洁,前面的示例可以使用注解的
value属性(即,@ComponentScan("org.example"))。
以下是使用 XML 的替代方法:
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
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:component-scan base-package="org.example"/>
</beans>1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Tip:使用
<context:component-scan>会隐式启用<context:annotation-config>的功能。在使用<context:component-scan>时,通常无需包含<context:annotation-config>元素。
Note:类路径包的扫描需要类路径中相应目录项的存在。当你使用 Ant 构建 JARs 时,确保你没有激活 JAR 任务的仅文件开关。此外,根据一些环境的安全策略,类路径目录可能不会被暴露 —— 例如,在 JDK 1.7.0_45 及更高版本的独立应用(需要在你的清单中设置
Trusted-Library—— 参见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。然而,确保你的组件类在你的
module-info描述符中被导出。如果你希望 Spring 调用你的类的非公共成员,确保它们被 “打开”(即,在你的module-info描述符中使用opens声明,而不是exports声明)。
此外,当你使用 component-scan 元素时,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都会被隐式地包含在内。这意味着这两个组件会被自动检测和连接在一起 —— 全部都不需要在 XML 中提供任何 Bean 配置元数据。
Note:你可以通过将
annotation-config属性的值设置为false来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。
2.4. 使用过滤器自定义扫描
默认情况下,只有使用 @Component、@Repository、@Service、@Controller、@Configuration 注解,或者使用了带有 @Component 注解的自定义注解的类才会被检测为候选组件。然而,你可以通过应用自定义过滤器来修改和扩展这种行为。将它们作为 @ComponentScan 注解的 includeFilters 或 excludeFilters 属性添加(或者作为 XML 配置中的 <context:component-scan> 元素的 <context:include-filter /> 或 <context:exclude-filter /> 子元素)。每个过滤器元素都需要 type 和 expression 属性。以下表格描述了过滤选项:
| 过滤器类型 | 示例表达式 | 描述 |
|---|---|---|
| annotation (default) | org.example.SomeAnnotation | 一个注解在目标组件中的类型级别是 “present” 或 “meta-present” |
| assignable | org.example.SomeClass | 目标组件可以分配给(扩展或实现)的类(或接口)。 |
| aspectj | org.example..*Service+ | 目标组件需要匹配的 AspectJ 类型表达式 |
| regex | org\.example\.Default.* | 目标组件的类名需要匹配的正则表达式 |
| custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter 接口的自定义实现 |
以下示例显示了配置忽略所有的 @Repository 注解,并使用 “stub” 存储库代替:
Java
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
下面的清单显示了等价的 XML:
XML
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Note:你也可以通过在注解上设置
useDefaultFilters=false或者为<component-scan/>元素提供use-default-filters="false"属性来禁用默认的过滤器。这实际上会禁用对使用了@Component、@Repository、@Service、@Controller、@RestController或@Configuration注解或元注解的类的自动检测。
2.5. 在组件内定义 Bean 元数据
Spring 组件也可以向容器贡献 Bean 定义元数据。您可以通过在 @Configuration 注解的类中用于定义 Bean 元数据的同样的 @Bean 注解来实现这一点。以下示例展示了如何操作:
Java
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
前面的类是一个 Spring 组件,其 doWork() 方法中有应用特定的代码。然而,它也贡献了一个 Bean 定义,该定义有一个工厂方法指向 publicInstance() 方法。@Bean 注解标识了工厂方法和其他 Bean 定义属性,比如通过 @Qualifier 注解的限定符值。其他可以指定的方法级注解包括 @Scope、@Lazy 以及自定义的限定符注解。
Note:除了用于组件初始化的角色外,您还可以在标记有
@Autowired或@Inject的注入点上放置@Lazy注解。在这种情况下,它会引导注入一个延迟解析代理。然而,这种代理方法相当有限。对于复杂的延迟交互,特别是与可选依赖项结合的情况,我们建议使用ObjectProvider<MyTargetBean>代替。
如之前讨论过的那样,支持自动装配字段和方法,并且还针对 @Bean 方法提供额外的自动装配支持。以下示例展示了如何实现:
Java
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这个例子中,country 参数的值来源于(被自动装配到了)privateInstance Bean 的 age 属性。Spring 表达式语言(SpEL)通过 #{ <expression> } 的形式定义属性值。对于 @Value 注解,一个表达式解析器被预配置为在解析表达式文本时查找 Bean 名称。
从 Spring Framework 4.3 版本开始,你也可以声明一个工厂方法参数类型为 InjectionPoint(或者它的更为具体的子类:DependencyDescriptor)以访问触发当前 Bean 创建的请求注入点。注意,这只适用于实际创建 Bean 实例,而不适用于现有实例的注入。因此,这个特性对于原型作用域的 Bean 来说最有意义。对于其他作用域,工厂方法只能看到在给定作用域内触发新的 Bean 实例创建的注入点(例如,触发懒加载单例 Bean 创建的依赖项)。你可以在这种情况下,谨慎地使用提供的注入点元数据。以下示例展示了如何使用 InjectionPoint:
Java
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
在常规的 Spring 组件中的 @Bean 方法,其处理方式与在 Spring @Configuration 类中的对应方法不同。区别在于,@Component 类并没有使用 CGLIB 来增强(拦截)方法和字段的调用。CGLIB 代理是通过在 @Configuration 类的 @Bean 方法中调用方法或字段来创建与合作对象的 Bean 元数据引用的方式。这些方法并不是以常规的 Java 语义进行调用,而是通过容器进行调用,以便为 Spring Bean 提供常规的生命周期管理和代理,即使是通过对 @Bean 方法的程序调用来引用其他 Bean 也是如此。相反,在一个简单的 @Component 类中的 @Bean 方法中调用方法或字段有标准的 Java 语义,没有任何特殊的 CGLIB 处理或其他约束适用。
Warning
你可以将
@Bean方法声明为静态的,这样就可以在不创建其包含的配置类实例的情况下调用它们。当定义后处理器 Bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时,这种方式特别有意义,因为这些 Bean 在容器生命周期的早期就被初始化,因此应该避免在此时触发配置的其他部分。对静态
@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中(如本节前面描述的)也是如此,这是由于技术限制:CGLIB 子类只能重写非静态方法。因此,直接调用另一个@Bean方法具有标准的 Java 语义,结果是直接从工厂方法本身返回一个独立的实例。
@Bean方法的 Java 语言可见性对于 Spring 容器中生成的 Bean 定义并没有直接影响。你可以在非@Configuration类中自由地声明你的工厂方法,也可以在任何地方声明静态方法。然而,常规的@Bean方法在@Configuration类中需要能够被覆盖 —— 也就是说,它们不能声明为private的或final的。
@Bean方法也可以在给定组件或配置类的基类上被发现,以及在组件或配置类实现的接口中声明的 Java 8 默认方法上。这为构建复杂的配置排列提供了很大的灵活性,借助于 Java 8 默认方法,甚至可以实现多重继承,这从 Spring 4.2 开始就是可能的。最后,一个类可以为同一个 Bean 持有多个
@Bean方法,作为对多个工厂方法的安排,根据运行时的可用依赖来选择使用哪个工厂方法。这与在其他配置场景中选择 “最贪婪” 的构造函数或工厂方法的算法是相同的:在构建时选择具有最大数量的可满足依赖项的变体,这类似于容器在多个@Autowired构造函数之间的选择。
2.6. 命名自动检测到的组件
当组件作为扫描过程的一部分被自动检测到时,其 Bean 名称是由扫描器所知的 BeanNameGenerator 策略生成的。默认情况下,任何包含名称值的 Spring 刻板印象注解(@Component,@Repository,@Service,和 @Controller)都会向相应的 Bean 定义提供该名称。
如果这样的注解不包含名称值,或者对于任何其他检测到的组件(例如那些通过自定义过滤器发现的),默认的 Bean 名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,那么名称将是 myMovieLister 和 movieFinderImpl:
Java
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}1
2
3
4
2
3
4
Java
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}1
2
3
4
2
3
4
如果你不想依赖于默认的 Bean 命名策略,你可以提供一个自定义的 Bean 命名策略。首先,实现 BeanNameGenerator 接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描器时,提供完全限定的类名,如下面的示例注解和 Bean 定义所示。
Tip:如果你遇到由于多个自动检测的组件具有相同的非限定类名(即,类名相同但位于不同包中的类)而导致的命名冲突,你可能需要配置一个默认为生成的 Bean 名称使用完全限定类名的
BeanNameGenerator。在 Spring 框架 5.2.3 中,位于org.springframework.context.annotation包中的FullyQualifiedAnnotationBeanNameGenerator可以用于此类目的。
Java
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}1
2
3
4
5
2
3
4
5
XML
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>1
2
3
4
2
3
4
作为一般规则,当其他组件可能需要对其进行显式引用时,应考虑使用注解指定名称。另一方面,当容器负责装配时,自动生成的名称就足够了。
2.7. 为自动检测到的组件提供作用域
与 Spring 管理的组件一般情况一样,自动检测组件的默认和最常见的作用域是单例。然而,有时候你需要一个不同的可以通过 @Scope 注解来指定的作用域。你可以在注解中提供作用域的名称,如下面的示例所示:
Java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}1
2
3
4
5
2
3
4
5
Tip:
@Scope注解只在具体的 Bean 类(对于带有注解的组件)或工厂方法(对于@Bean方法)上进行内省。与 XML 的 Bean 定义相反,这里没有 Bean 定义继承的概念,而且类级别的继承层次结构与元数据无关。
有关 Spring 上下文中诸如 request 或 session 等 Web 特定作用域的详细信息,请参见 请求、会话、应用和 WebSocket 作用域。与这些作用域的预构建注解一样,你也可以使用 Spring 的元注解方法来编写自己的作用域注解:例如,一个用 @Scope("prototype") 元注解注解的自定义注解,可能还声明了一个自定义的作用域代理模式。
Note
如果你想提供自定义的作用域解析策略,而不是依赖于基于注解的方法,你可以实现
ScopeMetadataResolver接口。别忘了包含一个默认的无参数构造器。然后在配置扫描器时,你可以提供完全限定的类名称,如下注解和 Bean 定义的示例所示:Java@Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { // ... }1
2
3
4
5XML<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/> </beans>1
2
3
在使用某些非单例作用域时,可能需要为作用域对象生成代理。这个原因在作为依赖项的作用域 Bean 中有所描述。为此,component-scan 元素上提供了一个 scoped-proxy 属性。它有三种可能的值:no、interfaces 和 targetClass。例如,以下配置会导致生成标准的 JDK 动态代理:
Java
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}1
2
3
4
5
2
3
4
5
XML
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>1
2
3
2
3
2.8. 使用注解提供限定符元数据
@Qualifier 注解在使用限定符对基于注解的自动装配进行微调中进行了讨论。那一部分的例子演示了如何使用 @Qualifier 注解和自定义限定符注解来在解决自动装配候选项时进行细粒度的控制。由于这些例子是基于 XML Bean 定义的,所以限定符元数据是通过在 XML 中使用 qualifier 或 meta 子元素在候选 Bean 定义中提供的。当依赖类路径扫描来自动检测组件时,你可以在候选类上使用类型级别的注解来提供限定符元数据。下面的三个例子演示了这种技术:
Java
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}1
2
3
4
5
2
3
4
5
Java
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}1
2
3
4
5
2
3
4
5
Java
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}1
2
3
4
5
2
3
4
5
Tip:与大多数基于注解的替代方案一样,你需要记住的是,注解元数据是绑定到类定义本身,而使用 XML 允许多个相同类型的 Bean 提供他们的限定符元数据的不同变体,因为 XML 元数据是根据每个实例,而非每个类提供的。
2.9. 生成候选组件的索引
尽管类路径扫描速度非常快,但通过在编译时创建静态的候选项列表,还可以进一步提高大型应用的启动性能。在这种模式下,所有作为组件扫描目标的模块都必须使用这种机制。
Note:你现有的
@ComponentScan或者<context:component-scan/>指令必须保持不变,以便请求上下文在某些包中扫描候选项。当ApplicationContext检测到这样的索引时,它会自动使用它,而不是扫描类路径。
要生成索引,你需要在每个包含用于组件扫描指令目标组件的模块中添加额外的依赖项。以下示例展示了如何使用 Maven 进行操作:
XML
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.30</version>
<optional>true</optional>
</dependency>
</dependencies>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
对于 Gradle 4.5 和更早版本,应该在 compileOnly 配置中声明依赖,如下面的示例所示:
Gradle
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.3.30"
}1
2
3
2
3
对于 Gradle 4.6 及后续版本,应该在 annotationProcessor 配置中声明依赖,如下面的示例所示:
Gradle
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.3.30"
}1
2
3
2
3
spring-context-indexer 工件会生成一个包含在 Jar 文件中的 META-INF/spring.components 文件。
Tip 1:在 IDE 中使用此模式时,必须将
spring-context-indexer注册为注解处理器,以确保当候选组件更新时,索引能够保持最新。
Tip 2:当在类路径上找到
META-INF/spring.components文件时,索引会自动启用。如果某些库(或使用案例)的索引部分可用,但无法为整个应用程序构建索引,你可以通过将spring.index.ignore设置为true,回退到常规类路径配置(就好像根本不存在索引),这可以通过 JVM 系统属性或SpringProperties机制来设置。
3. 使用 JSR 330 标准注解
从 Spring 3.0 开始,Spring 开始支持 JSR-330 标准注解(依赖注入)。这些注解的扫描方式与 Spring 的注解一样。要使用它们,你需要在你的类路径中具有相关的 Jar 包。
Note
如果你使用 Maven,那么
javax.inject工件可以在标准的 Maven 仓库中找到。你可以在你的pom.xml文件中添加以下依赖:XML<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>1
2
3
4
5
3.1. 使用 @Inject 和 @Named 进行依赖注入
你可以使用 @javax.inject.Inject 替代 @Autowired,如下所示:
Java
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
与 @Autowired 一样,你可以在字段级别、方法级别和构造函数参数级别使用 @Inject。此外,你可以将你的注入点声明为一个 Provider,允许对较短作用域(shorter scopes)的 Bean 进行按需访问,或者通过调用 Provider.get() 对其他 Bean 进行懒加载。下面的例子提供了一个前述例子的变体:
Java
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果你想为应该被注入的依赖使用一个限定的名字,你应该使用 @Named 注解,如下面的示例所示:
Java
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
与 @Autowired 一样,@Inject 也可以和 java.util.Optional 或 @Nullable 一起使用。这在此处更加适用,因为 @Inject 并没有 required 属性。以下两个例子展示了如何使用 @Inject 和 @Nullable:
Java
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}1
2
3
4
5
6
2
3
4
5
6
Java
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}1
2
3
4
5
6
2
3
4
5
6
3.2. @Named 和 @ManagedBean:@Component 注解的标准等价物
你可以使用 @javax.inject.Named 或 javax.annotation.ManagedBean 代替 @Component,如下面的示例所示:
Java
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component 常常在不指定组件名的情况下使用,并且 @Named 也可以以类似的方式使用,如下面的示例所示:
Java
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
当你使用 @Named 或 @ManagedBean 时,你可以像使用 Spring 注解一样使用组件扫描,如下面的示例所示:
Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}1
2
3
4
5
2
3
4
5
Note:与
@Component相比,JSR-330 的@Named和 JSR-250 的@ManagedBean注解是不可组合的。你应该使用 Spring 的刻板模型来构建自定义组件注解。
3.3. JSR-330 标准注解的局限性
当你使用标准注解时,你应该知道有一些重要的功能是不可用的,如下表所示:
| Spring | javax.inject.* | javax.inject 限制 / 评论 |
|---|---|---|
@Autowired | @Inject | @Inject 没有 required 属性。可以与 Java 8 的 Optional 一起使用 |
@Component | @Named / @ManagedBean | JSR-330 不提供可组合模型,只提供一种识别命名组件的方式 |
@Scope("singleton") | @Singleton | JSR-330 的默认作用域类似于 Spring 的 prototype。然而,为了保持与 Spring 的一般默认值的一致性,在 Spring 容器中声明的 JSR-330 Bean 默认是 singleton 的。如果要使用除 singleton 之外的作用域,你应该使用 Spring 的 @Scope 注解。javax.inject 也提供了一个 @Scope 注解。然而,这个注解只是用来创建你自己的注解 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier 只是一个用于构建自定义限定符的元注解。具体的 String 限定符(如带有值的 Spring 的 @Qualifier)可以通过 javax.inject.Named 关联 |
@Value | - | 无对等项 |
@Required | - | 无对等项 |
@Lazy | - | 无对等项 |
ObjectFactory | Provider | javax.inject.Provider 是 Spring 的 ObjectFactory 的直接替代品,只是 get() 方法名更短。它也可以与 Spring 的 @Autowired 或非注解的构造函数和 Setter 方法一起使用 |