文章摘要(AI生成)
1.10. 类路径扫描和托管组件本章中的大多数示例都使用 XML 来指定BeanDefinition在 Spring 容器中生成的配置元数据。上一节(基于注解的容器配置) 演示如何通过源级注释提供大量配置元数据。然而,即使在这些示例中,“基本”bean 定义也在 XML 文件中明确定义,而注释仅驱动
1.10. 类路径扫描和管理组件
本章中的大多数示例都使用 XML 来指定在 Spring 容器内生成每个 BeanDefinition
的配置元数据。上一节(基于注解的容器配置) 演示如何通过source级注解提供大量配置元数据。然而,即使在这些示例中,“基本”bean 定义也在 XML 文件中明确定义,而注解仅驱动依赖注入。本节描述了通过扫描类路径隐式检测候选组件的选项。候选组件是与过滤条件匹配的类,并在容器中注册了相应的 bean 定义。这消除了使用 XML 来执行 bean 注册的需要。相反,您可以使用注解(例如@Component
)、AspectJ 类型表达式或您自己的自定义过滤条件来选择哪些类具有向容器注册的 bean 定义。
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多特性都是核心 Spring Framework 的一部分。这允许您使用 Java 而不是使用传统的 XML 文件来定义 bean。查看@Configuration
、@Bean
、 @Import
和@DependsOn
注解,了解如何使用这些新功能的示例。
1.10.1. @Component
和进一步的原型注解
注解@Repository
是满足存储库(也称为数据访问对象或 DAO)角色或原型的任何类的标记。此标记的用途之一是异常的自动翻译,如 Exception Translation中所述。
Spring 提供了更多的原型注解:@Component
、@Service
和 @Controller
. @Component
是任何 Spring 管理的组件的通用构造型。 @Repository
, @Service
和@Controller
是@Component
针对更具体用例(分别在持久层、服务层和表示层)的特化。因此,您可以使用 @Component
注解组件类,但是通过使用 @Repository
、@Service
或者@Controller
注解它们,您的类更适合工具处理或与切面关联。例如,这些原型注解是切入点的理想目标。@Repository
, @Service
, 和@Controller
还可以在 Spring 框架的未来版本中携带额外的语义。因此,如果您在对于你的服务层使用@Component
或者@Service
,@Service
显然是更好的选择。同样,如前所述,@Repository
已经支持作为持久层中自动异常转换的标记。
1.10.2. 使用元注解和组合注解
Spring 提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如,前面提到的注解@Service
是 用@Component
元注解的,如以下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
您还可以组合元注解来创建“组合注解”。例如,Spring MVC 的@RestController
注解由@Controller
和@ResponseBody
组成。
此外,组合注解可以选择从元注解中重新声明属性以允许自定义。当您只想公开元注解属性的子集时,这可能特别有用。例如,Spring 的 @SessionScope
注解将作用域名称硬编码为session
,但仍允许自定义proxyMode
. 以下清单显示了 SessionScope
注解的定义:
@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;
}
然后,您可以在@SessionScope
不声明proxyMode
如下的情况下使用:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
您还可以覆盖proxyMode
的值,如以下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
1.10.3. 自动检测类和注册 Bean 定义
Spring可以自动检测构造型类并向ApplicationContext
注册相应的BeanDefinition
实例。 例如,以下两个类可以进行此类自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的 bean,您需要添加 @ComponentScan
到您的@Configuration
类中,其中basePackages
属性是两个类的公共父包。(或者,您可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
为简洁起见,前面的示例可能使用了注解的value
属性(即@ComponentScan("org.example")
)。
以下替代方法使用 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>
使用<context:component-scan>
会隐式启用 <context:annotation-config>
. 使用<context:component-scan>
时通常不需要包含该 <context:annotation-config>
元素。
类路径包的扫描需要类路径中存在相应的目录条目。当您使用 Ant 构建 JAR 时,请确保您没有激活 JAR 任务的仅文件开关。此外,在某些环境中,类路径目录可能不会根据安全策略公开——例如,JDK 1.7.0_45 及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library”——请参阅 https://stackoverflow.com/问题/19394570/java-jre-7u45-breaks-classloader-getresources)。
在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。但是,请确保您的组件类在您的module-info
描述符中导出。如果您希望 Spring 调用类的非公共成员,请确保它们是“开放的”(即,它们使用opens
声明而不是module-info
描述符中的 exports
声明)。
此外,当您使用 component-scan 元素时, AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都被隐式包含在内。这意味着这两个组件会被自动检测并连接在一起——所有这些都不需要 XML 中提供任何 bean 配置元数据。
您可以禁用注册AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
通过设置属性annotation-config
值为false
。
1.10.4. 使用过滤器自定义扫描
默认情况下,使用@Component
、@Repository
、@Service
、@Controller
、 @Configuration
注解的类或本身带有@Component
注解的自定义注解是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为 @ComponentScan
注释的 includeFilters
或 exceptFilters
属性(或者作为 XML 配置中<context:component-scan>
元素的<context:include-filter />
或 <context:exclude-filter />
子元素)。每个过滤器元素都需要类型和表达式属性。下表描述了过滤选项:
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
注解(默认) | org.example.SomeAnnotation |
在目标组件的类型级别存在或元存在的注解。 |
可分配的 | org.example.SomeClass |
目标组件可分配(扩展或实现)的类(或接口)。 |
aspectj | org.example..*Service+ |
要由目标组件匹配的 AspectJ 类型表达式。 |
正则表达式 | org\.example\.Default.* |
与目标组件的类名匹配的正则表达式。 |
自定义 | org.example.MyTypeFilter |
接口的自定义实现org.springframework.core.type.TypeFilter 。 |
以下示例显示了忽略所有@Repository
注解并使用“存根”存储库的配置:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
以下清单显示了等效的 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>
您还可以通过在注解上设置useDefaultFilters=false
或use-default-filters="false"
作为 <component-scan/>
元素的属性提供来禁用默认过滤器。这有效地禁用了用@Component
, @Repository
, @Service
, @Controller
, @RestController
, 或@Configuration
注解或元注解的类的自动检测。
1.10.5. 在组件中定义 Bean 元数据
Spring 组件还可以将 bean 定义元数据贡献给容器。您可以使用@Bean
用于在带 @Configuration
注解的类中定义 bean 元数据的相同注解来执行此操作。以下示例显示了如何执行此操作:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
前面的类是一个 Spring 组件,它的
doWork()
方法中包含特定于应用程序的代码。但是,它还提供了一个 bean 定义,该定义具有引用方法的工厂方法publicInstance()
。@Bean
注解标识工厂方法和其他 bean 定义属性,例如通过注解@Qualifier
的限定符值。可以指定的其他方法级注解是@Scope
,@Lazy
和自定义限定符注解。除了用于组件初始化之外,您还可以将
@Lazy
注解放置在标有@Autowired
或@Inject
的注入点上。在这种情况下,它会导致注入惰性解析代理。然而,这种代理方法相当有限。对于复杂的惰性交互,特别是与可选依赖项结合使用,我们建议改为使用ObjectProvider<MyTargetBean>
。
如前所述,支持自动装配的字段和方法,并额外支持@Bean
方法的自动装配。以下示例显示了如何执行此操作:
@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);
}
}
该示例将方法String
类型的参数 country
自动连接到 另一个名为privateInstance
的 bean 上的age
属性值。Spring 表达式语言元素通过#{ <expression> }
表示法定义属性的值。对于@Value
注解,表达式解析器被预先配置为在解析表达式文本时查找 bean 名称。
从 Spring Framework 4.3 开始,您还可以声明类型 InjectionPoint
(或其更具体的子类:DependencyDescriptor
)的工厂方法参数来访问触发当前 bean 创建的请求注入点。请注意,这仅适用于 bean 实例的实际创建,不适用于现有实例的注入。因此,此功能对于原型范围的 bean 最有意义。对于其他范围,工厂方法只看到在给定范围内触发创建新 bean 实例的注入点(例如,触发创建惰性单例 bean 的依赖项)。在这种情况下,您可以使用提供的带有语义关怀的注入点元数据。下面的例子展示了如何使用InjectionPoint
:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规 Spring 组件中的方法的处理方式与 Spring @Configuration
类中@Bean
的对应方法不同。不同之处在于@Component
类没有通过 CGLIB 增强来拦截方法和字段的调用。CGLIB 代理是调用@Configuration
类@Bean
方法中的方法或字段创建协作对象的 bean 元数据引用的方法。这样的方法不是用普通的 Java 语义调用的,而是通过容器来提供 Spring bean 的通常的生命周期管理和代理,即使通过对@Bean
方法的编程调用来引用其他 bean 也是如此。相比之下,在普通 @Component
类中调用@Bean
方法中的方法或字段具有标准 Java 语义,无需特殊的 CGLIB 处理或其他约束。
您可以将@Bean
方法声明为static
,允许在不创建包含它们的配置类作为实例的情况下调用它们。 这在定义后处理器 bean(例如,类型BeanFactoryPostProcessor
或 BeanPostProcessor
)时特别有意义,因为这些 bean 在容器生命周期的早期就被初始化,并且应该避免在那个时候触发配置的其他部分。
由于技术限制,对静态@Bean
方法的调用永远不会被容器拦截,甚至在 @Configuration
类中也不会(如本节前面所述):CGLIB 子类化只能覆盖非静态方法。因此,直接调用另一个具有标准的 Java 语义的@Bean
方法,从而导致直接从工厂方法本身返回一个独立的实例。
@Bean
方法的 Java 语言可见性不会立即影响 Spring 容器中生成的 bean 定义。您可以自由地声明您认为适合非@Configuration
类的工厂方法,也可以在任何地方声明静态方法。但是,类中的常规@Bean
方法@Configuration
需要是可覆盖的——也就是说,它们不能被声明为private
或 final
。
@Bean
方法也在给定组件或配置类的基类上发现,以及在组件或配置类实现的接口中声明的 Java 8 默认方法上发现。这为组合复杂的配置安排提供了很大的灵活性,甚至可以通过 Spring 4.2 的 Java 8 默认方法实现多重继承。
最后,单个类可以为同一个 bean 创建@Bean
多个方法,作为多个工厂方法的排列,根据运行时可用的依赖关系使用。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变体,类似于容器如何在多个@Autowired
构造函数之间进行选择。
1.10.6. 命名自动检测到的组件
当一个组件作为扫描过程的一部分被自动检测到时,它的 Bean 名称由该扫描器已知的 BeanNameGenerator
策略生成。默认情况下,任何包含名称值的 Spring 构造型注释(@Component
、@Repository
、@Service
和 @Controller
)都会将该名称提供给相应的 bean 定义。
如果这样的注解不包含名称value
或任何其他检测到的组件(例如由自定义过滤器发现的组件),则默认 bean 名称生成器将返回未大写的非限定类名称。例如,如果检测到以下组件类,则名称为myMovieLister
和 movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果您不想依赖默认的 bean 命名策略,可以提供自定义 bean 命名策略。首先,实现 BeanNameGenerator
接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名,如以下示例注解和 bean 定义所示。
如果由于多个自动检测到的组件具有相同的非限定类名(即,具有相同名称但位于不同包中的类)而遇到命名冲突,您可能需要配置BeanNameGenerator
默认为生成的完全限定类名的Bean名。从 Spring Framework 5.2.3 开始, 位于org.springframework.context.annotation
包中的FullyQualifiedAnnotationBeanNameGenerator
可用于此类目的。
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,只要其他组件可能显式引用它,请考虑使用注解指定名称。另一方面,只要容器负责接线,自动生成的名称就足够了。
1.10.7. 为自动检测的组件提供作用域
与一般 Spring 管理的组件一样,自动检测组件的默认和最常见作用域是singleton
. 但是,有时您需要可以由@Scope
注解指定的不同作用域。您可以在注解中提供作用域的名称,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
注解仅在具体 bean 类(用于注解组件)或工厂方法(用于@Bean
方法)上进行自省。与 XML bean 定义相比,没有 bean 定义继承的概念,并且类级别的继承层次结构与元数据无关。
有关 Web 特定范围的详细信息,例如 Spring 上下文中的“请求”或“会话”,请参阅请求、会话、应用程序和 WebSocket 范围。与这些范围的预构建注解一样,您也可以使用 Spring 的元注解方法来编写自己的范围注解:例如,使用元注解@Scope("prototype")
的自定义注解,也可能声明自定义范围代理模式。
要为范围解析提供自定义策略而不是依赖基于注解的方法,您可以实现该 ScopeMetadataResolver
接口。确保包含一个默认的无参数构造函数。然后,您可以在配置扫描器时提供完全限定的类名,如以下注解和 bean 定义示例所示:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用某些非单例作用域时,可能需要为作用域对象生成代理。原因在Scoped Beans as Dependencies中进行了描述。为此,component-scan 元素上提供了 scoped-proxy 属性。三个可能的值是:no
、interfaces
和targetClass
。例如,以下配置会生成标准 JDK 动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8. 提供带有注解的限定符元数据
@Qualifier
注解在 Fine-tuning Annotation-based Autowiring with Qualifiers中讨论。该部分中的示例演示了使用@Qualifier
注解和自定义限定符注解在解析自动装配候选时提供细粒度控制。因为这些示例基于 XML bean 定义,所以通过使用 XML 中bean
元素的qualifier
或meta
子元素在候选 bean 定义上提供限定符元数据。当依赖类路径扫描来自动检测组件时,您可以在候选类上为限定符元数据提供类型级别的注解。以下三个示例演示了这种技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注解的替代方案一样,请记住注解元数据绑定到类定义本身,而 XML 的使用允许相同类型的多个 bean 提供其限定符元数据的变体,因为元数据是根据每个-实例而不是每个类。
1.10.9. 生成候选组件的索引
虽然类路径扫描非常快,但可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用这种机制。
您现有的@ComponentScan
或<context:component-scan/>
指令必须保持不变,才能请求上下文以扫描某些包中的候选人。当 ApplicationContext
检测到这样的下标时,它会自动使用它而不是扫描类路径。
要生成索引,请向每个包含作为组件扫描指令目标的组件的模块添加一个附加依赖项。以下示例显示了如何使用 Maven 执行此操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.22</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle 4.5 及更早版本,应在compileOnly
配置中声明依赖项,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.3.22"
}
对于 Gradle 4.6 及更高版本,应在annotationProcessor
配置中声明依赖项,如下例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.3.22"
}
spring-context-indexer
artifact 会生成一个包含在 jar 文件中的META-INF/spring.components
文件。
在 IDE 中使用此模式时,spring-context-indexer
必须将其注册为注解处理器,以确保更新候选组件时索引是最新的。
当在类路径中找到META-INF/spring.components
文件时,索引会自动启用。如果索引对某些库(或用例)部分可用,但无法为整个应用程序构建,您可以通过设置spring.index.ignore
为true
(作为JVM 系统属性或通过 SpringProperties
机制)来回退到常规类路径安排(好像根本不存在索引)
1.11. 使用 JSR 330 标准注解
从 Spring 3.0 开始,Spring 提供对 JSR-330 标准注解(依赖注入)的支持。这些注解的扫描方式与 Spring 注解相同。要使用它们,您需要在类路径中有相关的 jar。
如果您使用 Maven,则javax.inject
工件在标准 Maven 存储库 ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/ ) 中可用。您可以将以下依赖项添加到文件 pom.xml 中:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
1.11.1. @Inject
和@Named
依赖注入
相对于@Autowired
,您可以使用@javax.inject.Inject
如下:
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(...);
// ...
}
}
与 @Autowired
一样,您可以在字段级别、方法级别和构造函数参数级别使用@Inject
。此外,您可以将注入点声明为 Provider
,从而允许按需访问范围更短的 bean 或通过Provider.get()
调用延迟访问其他 bean。以下示例提供了前面示例的变体:
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(...);
// ...
}
}
如果您想为应该注入的依赖项使用限定名称,则应使用@Named
注解,如以下示例所示:
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;
}
// ...
}
与@Autowired
相同,@Inject
也可以与java.util.Optional
或 @Nullable
一起使用。@Inject
在这里更适用,因为没有required
属性。以下一对示例展示了如何使用@Inject
和 @Nullable
:
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
1.11.2.@Named
和@ManagedBean
注解:@Component
的标准等效项
您可以使用@javax.inject.Named
或javax.annotation.ManagedBean
代替@Component
,如以下示例所示:
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;
}
// ...
}
在不指定组件名称的情况下 使用@Component
是很常见的。可以以类似的方式使用@Named
,如以下示例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当您使用@Named
或@ManagedBean
时,您可以使用与使用 Spring 注解时完全相同的方式使用组件扫描,如以下示例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
与 @Component
相比,JSR-330@Named
和 JSR-250@ManagedBean
注解是不可组合的。您应该使用 Spring 的原型模型来构建自定义组件注解。
1.11.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 的限定符(如@Qualifier 带有值的 Spring)可以通过javax.inject.Named . |
@Value | - | 没有等价物 |
@Lazy | - | 没有等价物 |
ObjectFactory | Provider | javax.inject.Provider 是 Spring ObjectFactory 的直接替代品,只是get() 方法名称更短。它还可以与 Spring@Autowired 或未注解的构造函数和 setter 方法结合使用。 |
1.12. 基于 Java 的容器配置
本节介绍如何在 Java 代码中使用注解来配置 Spring 容器。它包括以下主题:
- 基本概念:
@Bean
和@Configuration
- 通过使用实例化 Spring 容器
AnnotationConfigApplicationContext
- 使用
@Bean
注解 - 使用
@Configuration
注解 - 组合基于 Java 的配置
- Bean 定义配置文件
PropertySource
抽象- 使用
@PropertySource
- 语句中的占位符解析
1.12.1. 基本概念:@Bean
和@Configuration
Spring 新的 Java 配置支持中的核心工件是带 @Configuration
注解的类和带@Bean
注解的方法。
@Bean
注解用于表示一个方法实例化、配置和初始化一个由 Spring IoC 容器管理的新对象。对于熟悉 Spring 的XML <beans/>
配置的人来说,注解@Bean
与元素<bean/>
的作用相同。您可以将@Bean
-annotated 方法与任何 Spring @Component
一起 使用。但是,它们最常与@Configuration
Bean类一起使用。
用@Configuration
注解一个类表明它的主要目的是作为 bean 定义的来源。此外,@Configuration
类允许通过调用@Bean
同一类中的其他方法来定义 bean 间的依赖关系。最简单的@Configuration
类如下所示:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的AppConfig
类等价于下面的 Spring <beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
完整的@Configuration 与“精简”@Bean 模式?
当
@Bean
方法在没有用@Configuration
注解的类中声明时 ,它们被称为以“精简”模式处理。在一个或什至在一个普通的旧类中声明的 Bean 方法@Component
被认为是“精简版”,包含类的不同主要目的和一种@Bean
方法在那里是一种奖励。例如,服务组件可以通过@Bean
每个适用组件类上的附加方法向容器公开管理视图。在这种情况下,@Bean
方法是一种通用的工厂方法机制。与全配置
@Configuration
不同,轻量的@Bean
方法不能声明 bean 间的依赖关系。相反,它们对其包含组件的内部状态进行操作,并且可以选择对它们可能声明的参数进行操作。因此,@Bean
方法不应调用其他@Bean
方法。每个这样的方法实际上只是特定 bean 引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是在运行时不必应用 CGLIB 子类化,因此在类设计方面没有限制(即包含类可能是final
等等)。在常见情况下,
@Bean
方法将在@Configuration
类中声明,确保始终使用“完整”模式,并且跨方法引用因此被重定向到容器的生命周期管理。这可以防止@Bean
通过常规 Java 调用意外调用相同的方法,这有助于减少在“精简”模式下操作时难以追踪的细微错误。
以下部分将深入讨论@Bean
和@Configuration
注解。然而,首先,我们介绍了使用基于 Java 的配置创建 Spring 容器的各种方法。
1.12.2. 通过使用AnnotationConfigApplicationContext
实例化 Spring 容器
以下部分记录了 Spring 3.0 中引入的 Spring AnnotationConfigApplicationContext
。这种通用ApplicationContext
的实现不仅能够接受 @Configuration
类作为输入,还能够接受普通@Component
类和使用 JSR-330 元数据注解的类。
当@Configuration
类作为输入提供时,@Configuration
类本身被注册为 bean 定义,并且@Bean
类中所有声明的方法也被注册为 bean 定义。
当@Component
和 JSR-330 类被提供时,它们被注册为 bean 定义,并且假定 DI 元数据,如@Autowired
或@Inject
在必要时在这些类中使用。
简单的构造
与实例化 ClassPathXmlApplicationContext
时使用 Spring XML 文件作为输入的方式大致相同,您可以在实例化AnnotationConfigApplicationContext
时使用 @Configuration 类作为输入如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
如前所述,AnnotationConfigApplicationContext
不仅限于使用@Configuration
类。任何@Component
或 JSR-330 注解类都可以作为输入提供给构造函数,如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前面的示例假定MyServiceImpl
、Dependency1
和Dependency2
使用 Spring 依赖注入注解,例如@Autowired
.
通过使用register(Class<?>…)
以编程方式构建容器
您可以使用无参数构造函数实例化一个AnnotationConfigApplicationContext
,然后使用register()
方法对其进行配置。这种方法在以编程方式构建AnnotationConfigApplicationContext
. 以下示例显示了如何执行此操作:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
通过scan(String…)
启用组件扫描
要启用组件扫描,您可以如下注解您的@Configuration
类:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
// ...
}
有经验的 Spring 用户可能熟悉 Spring 的 context:namespace
中的等效 XML 声明,如下例所示::
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
在前面的示例中,扫描包com.acme
以查找任何 带@Component
注解的类,并且这些类在容器中注册为 Spring bean 定义。AnnotationConfigApplicationContext
暴露了scan(String…)
方法以允许相同的组件扫描功能,如以下示例所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
请记住,@Configuration
类是用元注解 @Component
的,因此它们是组件扫描的候选对象。在前面的示例中,假设AppConfig
在com.acme
包(或下面的任何包)中声明了 ,在调用scan()
. 在 refresh()
之后,它的所有@Bean
方法都被处理并注册为容器中的 bean 定义。
使用AnnotationConfigWebApplicationContext
支持 Web 应用程序
WebApplicationContext
的变体AnnotationConfigApplicationContext
可用于AnnotationConfigWebApplicationContext
. 您可以在配置 Spring ContextLoaderListener
servlet 侦听器、Spring MVC DispatcherServlet
等时使用此实现。以下web.xml
代码片段配置了一个典型的 Spring MVC Web 应用程序(注意使用contextClass
的context-param 和 init-param):
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
对于编程用例, GenericWebApplicationContext
可以用作AnnotationConfigWebApplicationContext
. 有关详细信息,请参阅 GenericWebApplicationContext
javadoc。
1.12.3. 使用@Bean
注解
@Bean
是方法级别的注解,是 XML<bean/>
元素的直接模拟。注解支持 提供的一些属性<bean/>
,例如:
您可以在@Configuration
-annotated 或 @Component
-annotated 类中使用@Bean
注解。
声明一个 Bean
要声明一个 bean,你可以用注解来注解一个方法@Bean
。您可以使用此方法在指定为方法返回值的类型中向ApplicationContext
注册 bean 定义。默认情况下,bean 名称与方法名称相同。以下示例显示了一个@Bean
方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
前面的配置完全等价于下面的 Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都使ApplicationContext
bean中的可用transferService
bean绑定到TransferServiceImpl
type 的对象实例,如以下文本图像所示:
transferService -> com.acme.TransferServiceImpl
您还可以使用默认方法来定义 bean。这允许通过在默认方法上实现带有 bean 定义的接口来组合 bean 配置。
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
您还可以使用接口(或基类)返回类型声明您的@Bean
方法,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,这会将高级类型预测的可见性限制为指定的接口类型 ( TransferService
)。然后,只有在实例化受影响的单例 bean 后,容器才知道完整类型 ( TransferServiceImpl
)。非惰性单例 bean 会根据它们的声明顺序进行实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试通过未声明的类型进行匹配(例如@Autowired TransferServiceImpl
,仅在transferService
bean 被实例化后才解析)。
如果您始终通过声明的服务接口引用您的类型,则您的 @Bean
返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明最具体的返回类型可能更安全(至少与引用您的 bean 的注入点所要求的一样具体)。
Bean 依赖项
@Bean
-annotated 方法可以具有任意数量的参数,这些参数描述了构建该 bean 所需的依赖项。例如,如果我们TransferService
需要一个AccountRepository
,我们可以使用方法参数实现该依赖项,如下例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入几乎相同。有关详细信息,请参阅相关部分。
接收生命周期回调
使用@Bean
注解定义的任何类都支持常规生命周期回调,并且可以使用 JSR-250 中的@PostConstruct
和@PreDestroy
注解。有关详细信息,请参阅 JSR-250 注解。
也完全支持常规的 Spring生命周期回调。如果 bean 实现InitializingBean
、DisposableBean
或Lifecycle
,则容器调用它们各自的方法。
还完全支持标准的*Aware
接口集(例如BeanFactoryAware、 BeanNameAware、 MessageSourceAware、 ApplicationContextAware等)。
@Bean
注解支持指定任意初始化和销毁回调方法,很像 Spring XMLinit-method
和bean
元素上的destroy-method
属性,如以下示例所示:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
默认情况下,使用 Java 配置定义的具有公共close
或shutdown
方法的 bean 会自动加入销毁回调。如果您有一个公共 close
或shutdown
方法并且您不希望在容器关闭时调用它,您可以添加@Bean(destroyMethod="")
到您的 bean 定义以禁用默认(inferred)
模式。默认情况下,您可能希望对使用 JNDI 获取的资源执行此操作,因为它的生命周期在应用程序之外进行管理。特别是,请确保始终为DataSource
.以下示例显示了如何防止 a 的自动销毁回调 DataSource
:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
此外,对于@Bean
方法,您通常使用程序化 JNDI 查找,通过使用 SpringJndiTemplate
或JndiLocatorDelegate
帮助程序或直接使用 JNDI InitialContext
但不使用JndiObjectFactoryBean
变体(这将迫使您将返回类型声明为FactoryBean
类型而不是实际的目标类型,从而更难用于其他@Bean
方法中的交叉引用调用,这些方法旨在引用此处提供的资源)。
在上述示例BeanOne
的情况下,在构造过程中直接调用该init()
方法同样有效,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
当您直接在 Java 中工作时,您可以对您的对象做任何您喜欢的事情,而不必总是依赖容器生命周期。
指定 Bean 范围
Spring 包含@Scope
注解,以便您可以指定 bean 的范围。
使用@Scope
注解
您可以指定使用@Bean
注解定义的 bean 应具有特定范围。您可以使用 Bean Scopes部分中指定的任何标准范围。
默认范围是singleton
,但您可以使用@Scope
注解覆盖它,如以下示例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope
和scoped-proxy
Spring 提供了一种通过 作用域代理处理作用域依赖的便捷方式。使用 XML 配置时创建此类代理的最简单方法是<aop:scoped-proxy/>
元素。使用注解在 Java 中配置您的 bean提供了对属性@Scope
的等效支持。proxyMode
默认值为ScopedProxyMode.DEFAULT
,这通常表示不应创建作用域代理,除非在组件扫描指令级别配置了不同的默认值。您可以 指定ScopedProxyMode.TARGET_CLASS
、ScopedProxyMode.INTERFACES
或ScopedProxyMode.NO
。
如果您将 XML 参考文档中的作用域代理示例(请参阅 作用域代理)移植到我们使用Java的@Bean
,它类似于以下内容:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
自定义 Bean 命名
默认情况下,配置类使用@Bean
方法的名称作为生成的 bean 的名称。但是,可以使用name
属性覆盖此功能,如以下示例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
Bean 别名
正如命名 Bean中所讨论的,有时需要为单个 bean 提供多个名称,也称为 bean 别名。注解@Bean
的name
属性 为此目的接受一个字符串数组。以下示例显示了如何为 bean 设置多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean描述
有时,提供更详细的 bean 文本描述会很有帮助。当 bean 被暴露(可能通过 JMX)用于监视目的时,这可能特别有用。
要向@Bean
添加描述,您可以使用 @Description
注解,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4. 使用@Configuration
注解
@Configuration
是一个类级别的注解,表明一个对象是 bean 定义的来源。@Configuration
类通过@Configuration
-annotated 方法声明 bean 。对@Configuration
类上的方法的@Bean
调用也可用于定义 bean 间的依赖关系。请参阅基本概念:@Bean
和@Configuration
一般介绍。
注入 内部bean 依赖
当 bean 相互依赖时,表达这种依赖关系就像让一个 bean 方法调用另一个方法一样简单,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的示例中,通过构造函数注入beanOne
接收对beanTwo
的引用。
这种声明 bean 间依赖关系的方法仅在@Bean
方法在@Configuration
类中声明时才有效。您不能使用普通@Component
类来声明 bean 间的依赖关系。
查找方法注入
如前所述,查找方法注入是您应该很少使用的高级功能。在单例范围的 bean 依赖于原型范围的 bean 的情况下,它很有用。使用 Java 进行这种类型的配置为实现这种模式提供了一种自然的方式。下面的例子展示了如何使用查找方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
通过使用 Java 配置,您可以创建一个子类,CommandManager
中抽象createCommand()
方法被覆盖,从而查找新的(原型)命令对象。以下示例显示了如何执行此操作:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
有关基于 Java 的配置如何在内部工作的更多信息
考虑下面的例子,它显示了一个被@Bean
注解的方法被调用了两次:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()
已被clientService1()
调用一次和clientService2()
调用一次。由于此方法会创建一个新ClientDaoImpl
实例并返回它,因此您通常会期望有两个实例(每个服务一个实例)。那肯定会有问题:在 Spring 中,实例化的 bean默认有一个singleton
作用域。这就是神奇之处:所有@Configuration
类在启动时都使用CGLIB
. 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)bean。
根据 bean 的范围,行为可能会有所不同。我们在这里谈论单例。
从 Spring 3.2 开始,不再需要将 CGLIB 添加到类路径中,因为 CGLIB 类已被重新打包org.springframework.cglib
并直接包含在 spring-core JAR 中。
由于 CGLIB 在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从 4.3 开始,配置类上允许使用任何构造函数,包括使用 @Autowired
或使用单个非默认构造函数声明进行默认注入。如果您希望避免任何 CGLIB 强加的限制,请考虑 在非@Configuration
类上声明您的@Bean
方法(例如,改为在普通@Component
类上)。方法之间的跨方法调用@Bean
不会被拦截,因此您必须完全依赖构造函数或方法级别的依赖注入。
1.12.5. 组合基于 Java 的配置
Spring 的基于 Java 的配置功能允许您编写注解,这可以降低配置的复杂性。
使用@Import
注解
就像<import/>
在 Spring XML 文件中使用该元素来帮助模块化配置一样,@Import
注解允许@Bean
从另一个配置类加载定义,如以下示例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
现在,不需要同时指定ConfigA.class
和ConfigB.class
. 在实例化上下文时,只需要显式提供ConfigB
,如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这种方法简化了容器的实例化,因为只需要处理一个类,而不是要求您 @Configuration
在构造过程中记住大量潜在的类。
从 Spring Framework 4.2 开始,@Import
还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register
方法。如果您想通过使用一些配置类作为入口点来显式定义所有组件来避免组件扫描,这将特别有用。
@Bean
注入对导入定义的依赖
前面的示例有效,但过于简单。在大多数实际场景中,bean 跨配置类相互依赖。使用 XML 时,这不是问题,因为不涉及编译器,您可以声明 ref="someBean"
并信任 Spring 在容器初始化期间解决它。使用@Configuration
类时,Java 编译器对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的,一个@Bean
方法可以有任意数量的参数来描述 bean 的依赖关系。考虑以下具有多个@Configuration
类的更真实的场景,每个类都依赖于其他类中声明的 bean:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
还有另一种方法可以达到相同的结果。请记住,@Configuration
类最终只是容器中的另一个 bean:这意味着它们可以像任何其他 bean 一样利用@Autowired
和@Value
注入其他特性。
确保您以这种方式注入的依赖项只是最简单的类型。@Configuration
类在上下文初始化期间很早就被处理,并且强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能使用基于参数的注入,如前面的示例所示。
此外,请特别注意BeanPostProcessor
和的BeanFactoryPostProcessor
定义@Bean
。这些通常应该被声明为static @Bean
方法,而不是触发它们包含的配置类的实例化。否则,@Autowired
和@Value
可能无法在配置类本身上工作,因为可以将其创建为早于 AutowiredAnnotationBeanPostProcessor
.
以下示例显示了如何将一个 bean 自动装配到另一个 bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
仅从 Spring Framework 4.3 开始支持@Configuration
类中的 构造函数注入。另请注意,如果目标 bean 仅定义一个构造函数 ,则无需指定@Autowired
。
完全合格的导入Bean,便于导航
在前面的场景中,使用@Autowired
运行良好并提供了所需的模块化,但确定自动装配 bean 定义的确切声明位置仍然有些模棱两可。例如,作为开发人员,查找ServiceConfig
,您如何知道@Autowired AccountRepository
bean 的确切声明位置?它在代码中并不明确,这可能还好。请记住, Eclipse 的 Spring Tools提供的工具可以渲染显示所有连接方式的图形,这可能就是您所需要的。此外,您的 Java IDE 可以轻松找到AccountRepository
类型的所有声明和使用,并快速向您显示返回该类型的@Bean
方法的位置。
如果这种歧义是不可接受的,并且您希望在 IDE 中从一个@Configuration
类直接导航到另一个类,请考虑自动装配配置类本身。以下示例显示了如何执行此操作:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在上述情况下,where AccountRepository
is defined 是完全明确的。但是,ServiceConfig
现在与RepositoryConfig
. 这就是权衡。通过使用基于接口或基于抽象类的类可以在一定程度上缓解这种紧密耦合@Configuration
。考虑以下示例:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
相对于具体的DefaultRepositoryConfig
, ServiceConfig
是松散耦合的 ,并且内置的 IDE 工具仍然有用:您可以轻松获得RepositoryConfig
实现的类型层次结构。通过这种方式,导航@Configuration
类及其依赖项与导航基于接口的代码的通常过程没有什么不同。
如果您想影响某些 bean 的启动创建顺序,请考虑将其中一些声明为@Lazy
(用于在首次访问时创建而不是在启动时创建)或@DependsOn
某些其他 bean(确保在当前 bean 之前创建特定的其他 bean,超出后者的直接依赖意味着什么)。
有条件地包含@Configuration
类或@Bean
方法
基于某些任意系统状态,有条件地启用或禁用完整的@Configuration
类甚至单个@Bean
方法通常很有用。一个常见的例子是,只有在 Spring 中启用了特定配置文件时才使用@Profile
注解来激活 bean Environment
(有关详细信息,请参阅Bean 定义配置文件 )。
@Profile
注解实际上是通过使用更灵活的注解来实现的,称为@Conditional
. @Conditional
注解指示 在注册@Bean
之前应参考org.springframework.context.annotation.Condition
的具体实现。
接口的实现Condition
提供了一个matches(…)
返回true
或的方法false
。例如,以下清单显示了 Condition
用于@Profile
的实际实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
有关更多详细信息,请参阅@Conditional
javadoc。
结合 Java 和 XML 配置
Spring 的@Configuration
类支持并非旨在 100% 完全替代 Spring XML。一些工具,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,您可以选择:或者以“以 XML 为中心”的方式实例化容器,例如,ClassPathXmlApplicationContext
,或者通过使用 AnnotationConfigApplicationContext
和 @ImportResource
注释以“以 Java 为中心”的方式实例化它,以根据需要导入 XML。
以 XML 为中心的@Configuration
类的使用
最好从 XML 引导 Spring 容器并 以特别的方式包含@Configuration
类。例如,在使用 Spring XML 的大型现有代码库中,更容易根据需要创建@Configuration
类并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍在这种“以 XML 为中心”的情况下使用@Configuration
类的选项。
将类声明@Configuration
为普通 Spring<bean/>
元素
请记住,@Configuration
类最终是容器中的 bean 定义。在本系列示例中,我们创建了一个名为AppConfig
的@Configuration
类,并将其system-test-config.xml
作为<bean/>
定义包含在其中。因为 <context:annotation-config/>
是开启的,所以容器会识别 @Configuration
注解并正确处理AppConfig
其中@Bean
声明的方法 。
以下示例显示了 Java 中的一个普通配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
以下示例显示了示例system-test-config.xml
文件的一部分:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
以下示例显示了一个可能的jdbc.properties
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在system-test-config.xml
文件中,AppConfig
<bean/>
不声明id
元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他 bean 曾经引用过它,并且不太可能通过名称从容器中显式获取。类似地,DataSource
bean 仅按类型自动装配,因此并不严格要求显式 bean id
。
使用 <context:component-scan/>
拾取@Configuration
类
因为@Configuration
是用 @Component
元注解的,带@Configuration
注解的类自动成为组件扫描的候选对象。使用与前面示例中描述的相同场景,我们可以重新定义system-test-config.xml
以利用组件扫描。请注意,在这种情况下,我们不需要显式声明 <context:annotation-config/>
,因为<context:component-scan/>
启用了相同的功能。
以下示例显示了修改后的system-test-config.xml
文件:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
以类为中心使用 XML@ImportResource
在@Configuration
类是配置容器的主要机制的应用程序中,仍然可能至少需要使用一些 XML。在这些场景中,您可以根据@ImportResource
需要使用和定义尽可能多的 XML。这样做实现了一种“以 Java 为中心”的方法来配置容器并将 XML 保持在最低限度。以下示例(包括配置类、定义 bean 的 XML 文件、属性文件和main
类)显示了如何使用@ImportResource
注解来实现“以 Java 为中心”的配置,该配置根据需要使用 XML:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.密码=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
1.13. 抽象环境
Environment
接口是集成在容器中的抽象,它对应用程序环境的两个关键方面进行建模:配置文件 和属性。
配置文件是一个命名的、逻辑的 bean 定义组,仅当给定的配置文件处于活动状态时才向容器注册。可以将 Bean 分配给配置文件,无论是在 XML 中定义还是使用注解定义。与配置文件相关的Environment
对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及哪些配置文件(如果有)默认情况下应该是活动的。
属性在几乎所有应用程序中都发挥着重要作用,并且可能源自多种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、ad-hocProperties
对象、Map
对象等。与属性相关的Environment
对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,允许在不同环境中注册不同的 bean。“环境”这个词对不同的用户可能意味着不同的东西,这个功能可以帮助许多用例,包括:
- 在开发中处理内存中的数据源,而不是在 QA 或生产中从 JNDI 中查找相同的数据源。
- 仅在将应用程序部署到性能环境时才注册监控基础架构。
- 为客户 A 和客户 B 部署注册定制的 bean 实现。
考虑实际应用程序中需要 DataSource
. 在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在考虑如何将此应用程序部署到 QA 或生产环境中,假设应用程序的数据源已在生产应用程序服务器的 JNDI 目录中注册。我们的dataSource
bean 现在看起来像下面的清单:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的推移,Spring 用户设计了许多方法来完成此任务,通常依赖于系统环境变量和包含<import/>
标签的 XML 语句的组合,${placeholder}
根据环境变量的值解析为正确的配置文件路径。Bean 定义概要文件是一个核心容器特性,它为这个问题提供了解决方案。
如果我们概括前面环境特定 bean 定义示例中所示的用例,我们最终需要在某些上下文中注册某些 bean 定义,但在其他上下文中不需要。您可以说您想在情况 A 中注册特定的 bean 定义配置文件,在情况 B 中注册不同的配置文件。我们首先更新配置以反映这种需求。
使用@Profile
当一个或多个指定的配置文件处于活动状态时,@Profile
注解可让您指示组件有资格注册。使用我们前面的示例,我们可以重写dataSource
配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
如前所述,对于@Bean
方法,您通常选择使用程序化 JNDI 查找,通过使用 Spring 的JndiTemplate
/JndiLocatorDelegate
助手或InitialContext
前面显示的直接 JNDI 用法,而不是JndiObjectFactoryBean
变体,这将迫使您将返回类型声明为FactoryBean
类型。
配置文件字符串可能包含一个简单的配置文件名称(例如,production
)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,production & us-east
)。配置文件表达式中支持以下运算符:
!
:配置文件的逻辑“非”&
:配置文件的逻辑“与”|
:配置文件的逻辑“或”
不能在不使用括号的情况下混合使用&
和|
运算符。例如, production & us-east | eu-central
不是一个有效的表达式。它必须表示为 production & (us-east | eu-central)
。
您可以将@Profile
其用作元注解以创建自定义组合注解。以下示例定义了一个自定义 @Production
注解,您可以将其用作 @Profile("production")
的替代品 :
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果一个@Configuration
类用@Profile
标记,则与该类关联的所有@Bean
方法和 @Import
注解都将被绕过,除非一个或多个指定的配置文件处于活动状态。如果一个@Component
或@Configuration
类标有@Profile({"p1", "p2"})
,则除非已激活配置文件“p1”或“p2”,否则不会注册或处理该类。如果给定配置文件以 NOT 运算符 (!
) 为前缀,则仅当配置文件不活动时才注册带注解的元素。例如,@Profile({"p1", "!p2"})
如果配置文件“p1”处于活动状态或配置文件“p2”未处于活动状态,则会发生注册。
@Profile
也可以在方法级别声明为仅包含配置类的一个特定 bean(例如,对于特定 bean 的替代变体),如以下示例所示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Bean
方法使用@Profile
,可能会应用一种特殊情况:在相同 Java 方法名称的重载@Bean
方法的情况下(类似于构造函数重载),需要在所有重载方法上一致地声明一个@Profile
条件。如果条件不一致,则仅重载方法中第一个声明的条件。因此,@Profile
不能用于选择具有特定参数签名的重载方法而不选择另一个。同一 bean 的所有工厂方法之间的解析在创建时遵循 Spring 的构造函数解析算法。如果要定义具有不同配置文件条件的替代 bean,请使用不同的 Java 方法名称,这些@Bean
方法名称通过使用name 属性指向相同的 bean 名称,如前面的示例所示。如果参数签名都相同(例如,所有变体都有无参数工厂方法),这是首先在有效 Java 类中表示这种安排的唯一方法(因为只能有一个特定名称和参数签名的方法)。
XML Bean 定义配置文件
XML 对应物是<beans>
元素的profile
属性。我们前面的示例配置可以重写为两个 XML 文件,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免在同一文件中拆分和嵌套<beans/>
元素,如以下示例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
XML 对应项不支持前面描述的配置文件表达式。但是,可以使用!
运算符来否定配置文件。也可以通过嵌套配置文件来应用逻辑“和”,如以下示例所示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions -->
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>
</beans>
</beans>
在前面的示例中,如果production
和 us-east
配置文件都处于活动状态,则公开 dataSource
bean 。
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个配置文件处于活动状态。如果我们现在启动示例应用程序,我们会看到抛出NoSuchBeanDefinitionException
异常,因为容器找不到名为dataSource
的 Spring bean 。
激活配置文件可以通过多种方式完成,但最直接的方法是以编程方式针对通过 ApplicationContext
提供的Environment
API 来执行此操作。. 以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,您还可以通过 spring.profiles.active
属性以声明方式激活配置文件,可以通过系统环境变量、JVM 系统属性、web.xml
servlet 上下文参数来指定,甚至可以作为 JNDI 中的条目(参见PropertySource
Abstraction)。在集成测试中,可以使用spring-test
模块中的@ActiveProfiles
注解来声明活动配置文件(请参阅环境配置文件的上下文配置)。
请注意,配置文件不是“非此即彼”的命题。您可以一次激活多个配置文件。以编程方式,您可以为 setActiveProfiles()
接受String…
可变参数的方法提供多个配置文件名称。以下示例激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
以声明方式,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示默认启用的配置文件。考虑以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果没有激活的配置文件,dataSource
则创建。您可以将此视为一种为一个或多个 bean 提供默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。
您可以使用Environment
的setDefaultProfiles()
方法或以声明方式使用spring.profiles.default
属性来更改默认配置文件的名称。
1.13.2.PropertySource
抽象
Spring 的Environment
抽象提供了对属性源的可配置层次结构的搜索操作。考虑以下清单:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在前面的my-property
代码片段中,我们看到了一种询问 Spring 是否为当前环境定义属性的高级方法。为了回答这个问题,Environment
对象对一组对象执行搜索PropertySource
。PropertySource
是对任何键值对源的简单抽象,SpringStandardEnvironment
配置了两个 PropertySource 对象——一个代表 JVM 系统属性集(System.getProperties()
),一个代表系统环境变量集(System.getenv()
)。
这些默认属性源用于StandardEnvironment
, 用于独立应用程序。StandardServletEnvironment
填充了其他默认属性源,包括 servlet 配置和 servlet 上下文参数。它可以选择启用JndiPropertySource
. 有关详细信息,请参阅 javadoc。
具体来说,当您使用my-property
时,如果系统属性或环境变量在运行时存在my-property
,则调用StandardEnvironment
的env.containsProperty("my-property")
返回 true 。
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用env.getProperty("my-property")
期间恰好在两个位置都设置了my-property
属性,则系统属性值“获胜”并返回。请注意,属性值不会合并,而是完全被前面的条目覆盖。对于 common StandardServletEnvironment
,完整的层次结构如下,最高优先级的条目位于顶部:
- ServletConfig 参数(如果适用——例如,在
DispatcherServlet
上下文的情况下) - ServletContext 参数(web.xml 上下文参数条目)
- JNDI 环境变量(
java:comp/env/
条目) - JVM 系统属性(
-D
命令行参数) - JVM系统环境(操作系统环境变量)
最重要的是,整个机制是可配置的。也许您有一个想要集成到此搜索中的自定义属性源。为此,请实现并实例化您自己的PropertySource
并将其添加到当前Environment
的PropertySources
集合中. 以下示例显示了如何执行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在前面的代码中,MyPropertySource
已在搜索中以最高优先级添加。如果它包含一个my-property
属性,则检测并返回该属性,以支持my-property
任何其他 PropertySource
中的任何属性。MutablePropertySources
API 公开了许多允许精确操作属性源集的方法。
1.13.3. 使用@PropertySource
@PropertySource
注解提供了一种方便且声明性的机制,用于将 PropertySource
添加到Spring 的Environment
.
给定一个包含键值对的名为testbean.name=myTestBean
的app.properties
文件,以下@Configuration
类使用@PropertySource
以调用testBean.getName()
返回myTestBean
:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
资源位置中存在的任何${…}
占位符都会@PropertySource
针对已针对环境注册的属性源集进行解析,如以下示例所示:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
假设my.placeholder
存在于已注册的属性源之一(例如,系统属性或环境变量)中,则占位符被解析为相应的值。如果不是,则将default/path
其用作默认值。如果未指定默认值且无法解析属性, 则抛出 IllegalArgumentException
。
根据Java 8 约定,@PropertySource
注解是可重复的。但是,所有此类@PropertySource
注解都需要在同一级别声明,或者直接在配置类上声明,或者作为同一自定义注解中的元注解。不建议混合直接注解和元注解,因为直接注解有效地覆盖了元注解。
1.13.4. 语句中的占位符解析
从历史上看,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。这已不再是这种情况。因为抽象Environment
是在整个容器中集成的,所以很容易通过它来路由占位符的解析。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级或完全删除它们。您还可以根据需要将自己的属性源添加到组合中。
具体来说,无论属性在何处定义customer
,只要它在 Environment
中可用,以下语句都有效:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. 注册一个LoadTimeWeaver
当LoadTimeWeaver
类加载到 Java 虚拟机 (JVM) 中时,Spring 使用它来动态转换类。
要启用加载时编织,您可以将@EnableLoadTimeWeaving
添加到您的 @Configuration
类之一,如以下示例所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或者,对于 XML 配置,您可以使用以下context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
</beans>
一旦为ApplicationContext
配置 ,ApplicationContext
中的任何 bean 都 可以实现LoadTimeWeaverAware
,从而接收对加载时编织器实例的引用。这在与Spring 的 JPA 支持结合使用时特别有用, 其中 JPA 类转换可能需要加载时编织。有关更多详细信息,请参阅 LocalContainerEntityManagerFactoryBean
javadoc。有关 AspectJ 加载时编织的更多信息,请参阅Spring Framework 中使用 AspectJ 进行加载时编织。
1.15.ApplicationContext
的附加功能
正如在介绍章节中所讨论的,该org.springframework.beans.factory
包提供了管理和操作 bean 的基本功能,包括以编程方式。该org.springframework.context
包添加了 ApplicationContext
接口,它扩展了BeanFactory
接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。许多人以完全声明ApplicationContext
的方式使用它,甚至没有以编程方式创建它,而是依赖于支持类,例如在Java EE Web 应用程序的正常启动过程中ContextLoader
自动实例化一个 ApplicationContext
。
BeanFactory
为了以更加面向框架的风格增强功能,上下文包还提供了以下功能:
- 通过
MessageSource
访问 i18n 风格的消息。 - 通过
ResourceLoader
接口访问资源,例如 URL 和文件。 ApplicationListener
事件发布,即通过使用ApplicationEventPublisher接口发布给实现接口的bean
。- 加载多个(分层)上下文,让每个上下文都通过
HierarchicalBeanFactory
接口专注于一个特定的层,例如应用程序的 Web 层 。
1.15.1. 国际化使用MessageSource
ApplicationContext
接口扩展了一个名为MessageSource
的接口,因此提供了国际化(“i18n”)功能。Spring 还提供了 HierarchicalMessageSource
接口,可以分层解析消息。这些接口共同提供了 Spring 影响消息解析的基础。这些接口上定义的方法包括:
String getMessage(String code, Object[] args, String default, Locale loc)
: 用于从MessageSource
获取消息. 如果未找到指定语言环境的消息,则使用默认消息。使用标准库MessageFormat
提供的功能,传入的任何参数都将成为替换值。String getMessage(String code, Object[] args, Locale loc)
:与上一种方法基本相同,但有一个区别:不能指定默认消息。如果找不到消息,NoSuchMessageException
则抛出 a。String getMessage(MessageSourceResolvable resolvable, Locale locale)
: 上述方法中使用的所有属性也都包装在一个名为 的类MessageSourceResolvable
中,您可以在此方法中使用该类。
加载ApplicationContext
时,它会自动搜索上下文中定义的MessageSource
bean。bean 必须具有messageSource
名称。如果找到这样的 bean,则对前面方法的所有调用都委托给消息源。如果未找到消息源,则ApplicationContext
尝试查找包含同名 bean 的父级。如果是这样,它将使用该 bean 作为MessageSource
. 如果 ApplicationContext
找不到任何消息源,则实例化一个空 DelegatingMessageSource
,以便能够接受对上述方法的调用。
Spring 提供了三个MessageSource
实现ResourceBundleMessageSource
,ReloadableResourceBundleMessageSource
和StaticMessageSource
。所有这些HierarchicalMessageSource
都是为了进行嵌套消息传递而实现的。StaticMessageSource
很少使用,但ResourceBundleMessageSource
提供了将消息添加到源的编程方式。以下示例显示:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假定您有三个名为 format
,exceptions
和windows
的资源包在您的类路径中定义。任何解析消息的请求都以通过ResourceBundle
对象解析消息的 JDK 标准方式处理。出于示例的目的,假设上述两个资源包文件的内容如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下一个示例显示了一个运行该MessageSource
功能的程序。请记住,所有ApplicationContext
实现也是MessageSource
实现,因此可以转换为MessageSource
接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
上述程序的结果输出如下:
Alligators rock!
总而言之,MessageSource
是在一个名为 beans.xml
的文件中定义的,该文件位于类路径的根目录中。messageSource
bean 定义通过其属性引用了许多资源包。basenames
列表中传递给basenames
属性的三个文件作为文件存在于类路径的根目录中,分别称为format.properties
、exceptions.properties
和 windows.properties
。
下一个示例显示传递给消息查找的参数。这些参数被转换为String
对象并插入到查找消息中的占位符中。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
调用该execute()
方法的结果输出如下:
The userDao argument is required.
关于国际化(“i18n”),Spring 的各种MessageSource
实现遵循与标准 JDK 相同的语言环境解析和回退规则 ResourceBundle
。简而言之,继续前面定义的messageSource
示例,如果您想根据英国 ( en-GB
) 语言环境解析消息,您将分别创建名为format_en_GB.properties
、exceptions_en_GB.properties
和 windows_en_GB.properties
的文件。
通常,区域设置解析由应用程序的周围环境管理。在以下示例中,手动指定解析(英国)消息的语言环境:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
上述程序运行的结果如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用该MessageSourceAware
接口来获取对 已定义的任何MessageSource
内容的引用。在创建和配置 bean 时,在实现 MessageSourceAware 接口的 ApplicationContext 中定义的任何 bean 都会被注入应用程序上下文的 MessageSource。
因为 SpringMessageSource
是基于 Java 的ResourceBundle
,所以它不会合并具有相同基本名称的包,而只会使用找到的第一个包。具有相同基本名称的后续消息包将被忽略。
作为 ResourceBundleMessageSource
的替代方案,Spring 提供了一个 ReloadableResourceBundleMessageSource
类。此变体支持相同的捆绑文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource
实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅从类路径)读取文件,并支持捆绑属性文件的热重载(同时在它们之间有效地缓存它们)。有关详细信息,请参阅ReloadableResourceBundleMessageSource
javadoc。
1.15.2. 标准和自定义事件
中的事件处理ApplicationContext
是通过ApplicationEvent
类和ApplicationListener
接口提供的。如果将实现 ApplicationListener
接口的 bean 部署到上下文中,则每次 ApplicationEvent
发布到 ApplicationContext
时,都会通知该 bean。本质上,这是标准的观察者设计模式。
从 Spring 4.2 开始,事件基础结构得到了显着改进,并提供了基于注解的模型以及发布任意事件的能力(即,不一定从 扩展的对象ApplicationEvent
)。当这样的对象发布时,我们会为您将其包装在一个事件中。
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
ContextRefreshedEvent |
在初始化或刷新时发布ApplicationContext (例如,通过使用接口refresh() 上的方法ConfigurableApplicationContext )。这里,“初始化”意味着所有 bean 都已加载,后处理器 bean 被检测并激活,单例被预实例化,并且ApplicationContext 对象已准备好使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选择的ApplicationContext 实际支持这种“热”刷新。例如,XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。 |
ContextStartedEvent |
使用接口上的方法 ApplicationContext 启动时发布。在这里,“已启动”意味着所有 bean 都接收到一个明确的启动信号。通常,此信号用于在显式停止后重新启动 bean,但它也可用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。start()``ConfigurableApplicationContext``Lifecycle |
ContextStoppedEvent |
使用接口上的方法 ApplicationContext 停止时发布。在这里,“停止”意味着所有 的 bean 都会收到一个明确的停止信号。可以通过 调用重新启动已停止的上下文。stop()``ConfigurableApplicationContext``Lifecycle``start() |
ContextClosedEvent |
在ApplicationContext 使用接口close() 上的方法ConfigurableApplicationContext 或通过 JVM 关闭挂钩关闭时发布。在这里,“关闭”意味着所有的单例 bean 都将被销毁。一旦上下文关闭,它就到了生命的尽头,无法刷新或重新启动。 |
RequestHandledEvent |
一个特定于 Web 的事件,告诉所有 bean 一个 HTTP 请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 Web 应用程序DispatcherServlet 。 |
ServletRequestHandledEvent |
它的子类RequestHandledEvent 添加了 Servlet 特定的上下文信息。 |
您还可以创建和发布自己的自定义事件。以下示例显示了一个扩展 SpringApplicationEvent
基类的简单类:
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
要发布自定义ApplicationEvent
,请调用 ApplicationEventPublisher
的publishEvent()
. 通常,这是通过创建一个实现 ApplicationEventPublisherAware
并将其注册为 Spring bean 的类来完成的。下面的例子展示了这样一个类:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
在配置时,Spring 容器检测到EmailService
实现 ApplicationEventPublisherAware
并自动调用 setApplicationEventPublisher()
. 实际上,传入的参数是Spring容器本身。您正在通过其 ApplicationEventPublisher
接口与应用程序上下文进行交互。
要接收自定义的ApplicationEvent
,您可以创建一个实现 ApplicationListener
并将其注册为 Spring bean 的类。下面的例子展示了这样一个类:
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
请注意,ApplicationListener
通常使用自定义事件的类型进行参数化(在前面的BlockedListEvent
示例中)。这意味着该 onApplicationEvent()
方法可以保持类型安全,避免任何向下转换的需要。您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,事件侦听器会同步接收事件。这意味着该publishEvent()
方法会阻塞,直到所有侦听器都完成了对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它会在发布者的事务上下文中运行。如果需要另一种事件发布策略,请参阅 javadoc 了解 Spring 的 ApplicationEventMulticaster
接口和SimpleApplicationEventMulticaster
配置选项的实现。
以下示例显示了用于注册和配置上述每个类的 bean 定义:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
总而言之,当调用emailService
bean 的sendEmail()
方法时,如果有任何电子邮件消息应该被阻止, 则会发布一个自定义BlockedListEvent
类型的事件。blockedListNotifier
bean 注册为 an ApplicationListener
并接收BlockedListEvent
,此时它可以通知适当的各方。
Spring 的事件机制是为同一应用程序上下文中的 Spring bean 之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration项目为 构建基于众所周知的 Spring 编程模型的 轻量级、面向模式、事件驱动的架构提供了完整的支持。
基于注解的事件监听器
@EventListener
您可以使用注解在托管 bean 的任何方法上注册事件侦听器 。BlockedListNotifier
可以改写如下:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明了它所侦听的事件类型,但是这一次使用了一个灵活的名称并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析您的泛型参数,也可以通过泛型来缩小事件类型。
如果您的方法应该监听多个事件,或者如果您想在没有参数的情况下定义它,也可以在注解本身上指定事件类型。以下示例显示了如何执行此操作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
还可以通过使用定义SpEL
表达式的注解condition
属性添加额外的运行时过滤,表达式应该匹配以实际调用特定事件的方法。
以下示例显示了如何重写我们的通知器以仅在事件的属性content
等于my-event
时才被调用 :
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL
表达式都针对专用上下文进行评估。下表列出了对上下文可用的项目,以便您可以将它们用于条件事件处理:
名称 | 位置 | 描述 | 例子 |
---|---|---|---|
事件 | 根对象 | 实际的ApplicationEvent . |
#root.event 或者event |
参数数组 | 根对象 | 用于调用方法的参数(作为对象数组)。 | #root.args 或args ;args[0] 访问第一个参数等。 |
参数名称 | 评估上下文 | 任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为编译的字节码中没有调试信息),也可以使用代表参数索引的#a<#arg> 语法<#arg> (从 0 开始)使用单独的参数。 |
#blEvent 或#a0 (您也可以使用#p0 或#p<#arg> 参数表示法作为别名) |
请注意,#root.event
使您可以访问底层事件,即使您的方法签名实际上是指已发布的任意对象。
如果您需要发布一个事件作为处理另一个事件的结果,您可以更改方法签名以返回应该发布的事件,如以下示例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听 器不支持此功能 。
handleBlockedListEvent()
方法为它处理的每一个ListUpdateEvent
发布一个新的BlockedListEvent
。如果您需要发布多个事件,则可以改为返回一个Collection
或一组事件。
异步侦听器
如果您希望特定侦听器异步处理事件,则可以重用 常规@Async
支持。以下示例显示了如何执行此操作:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
- 如果异步事件侦听器抛出
Exception
,它不会传播给调用者。有关AsyncUncaughtExceptionHandler
更多详细信息,请参阅。 - 异步事件侦听器方法不能通过返回值来发布后续事件。如果您需要发布另一个事件作为处理的结果,请
ApplicationEventPublisher
手动注入一个来发布该事件。
订购听众
如果您需要在另一个侦听器之前调用一个侦听器,您可以@Order
在方法声明中添加注解,如以下示例所示:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
通用事件
您还可以使用泛型来进一步定义事件的结构。考虑使用 EntityCreatedEvent<T>
,T
是创建的实际实体的类型。例如,您可以创建以下侦听器定义以仅接收Person
类型的EntityCreatedEvent
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
由于类型擦除,这仅在触发的事件解析了事件侦听器过滤的通用参数(即类似的东西 class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
)时才有效。
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得非常乏味(就像前面示例中的事件一样)。在这种情况下,您可以实施ResolvableTypeProvider
以引导框架超出运行时环境提供的范围。以下事件显示了如何执行此操作:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
这不仅适用于 ApplicationEvent,还适用于作为事件发送的任何任意对象。
1.15.3. 方便访问底层资源
为了优化使用和理解应用程序上下文,您应该熟悉 Spring 的Resource
抽象,如参考资料中所述。
应用程序上下文是ResourceLoader
,可用于加载Resource
对象。Resource
本质上是 JDKjava.net.URL
类的功能更丰富的版本。事实上,在适当的地方包装一个Resource
实例的实现。java.net.URL
AResource
可以以透明的方式从几乎任何位置获取低级资源,包括从类路径、文件系统位置、可使用标准 URL 描述的任何位置以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的并且适合于实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的 bean 来实现特殊的回调接口,ResourceLoaderAware
在初始化时自动回调,应用程序上下文本身作为ResourceLoader
. 您还可以公开 type 的属性,Resource
用于访问静态资源。它们像任何其他属性一样被注入其中。您可以将这些Resource
属性指定为简单路径,并在部署 bean 时String
依赖从这些文本字符串到实际对象的自动转换。Resource
提供给构造函数的一个或多个位置路径ApplicationContext
实际上是资源字符串,并且以简单的形式,根据特定的上下文实现进行适当的处理。例如ClassPathXmlApplicationContext
,将简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或 URL 加载定义,而不管实际的上下文类型如何。
1.15.4. 应用程序启动跟踪
ApplicationContext
管理 Spring 应用程序的生命周期并围绕组件提供丰富的编程模型。因此,复杂的应用程序可以具有同样复杂的组件图和启动阶段。
使用特定指标跟踪应用程序启动步骤可以帮助了解在启动阶段花费的时间,但它也可以用作更好地了解整个上下文生命周期的一种方式。
(AbstractApplicationContext
及其子类)使用 ApplicationStartup
进行检测 ,它收集StartupStep
有关各种启动阶段的数据:
- 应用程序上下文生命周期(基础包扫描、配置类管理)
- bean 生命周期(实例化、智能初始化、后处理)
- 应用事件处理
以下是AnnotationConfigApplicationContext
仪器仪表的示例:
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
应用程序上下文已经配备了多个步骤。记录后,可以使用特定工具收集、显示和分析这些启动步骤。有关现有启动步骤的完整列表,您可以查看 专用的附录部分。
默认ApplicationStartup
实现是无操作变体,以最小化开销。这意味着默认情况下在应用程序启动期间不会收集任何指标。Spring Framework 附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现: FlightRecorderApplicationStartup
. 要使用此变体,您必须在创建它后立即配置它的实例ApplicationContext
。
如果开发人员提供自己的 AbstractApplicationContext
子类,或者希望收集更精确的数据,那么他们还可以使用 ApplicationStartup
基础设施
ApplicationStartup
仅在应用程序启动期间和核心容器中使用;这绝不是 Java 分析器或Micrometer等指标库的替代品。
要开始收集自定义 StartupStep
,组件可以 直接从应用程序上下文中获取ApplicationStartup
实例,使它们的组件实现ApplicationStartupAware
,或者在任何注入点请求ApplicationStartup
类型。
开发人员在创建自定义启动步骤时 不应使用"spring.*"
命名空间。这个命名空间是为内部 Spring 使用而保留的,并且可能会发生变化。
1.15.5. 方便的 Web 应用程序 ApplicationContext 实例化
您可以使用例如ApplicationContext
以声明方式创建实例 ContextLoader
。当然,您也可以使用其中一种ApplicationContext
实现以编程方式创建ApplicationContext
实例。
您可以使用ContextLoaderListener
注册一个ApplicationContext
,如以下示例所示:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
侦听器检查contextConfigLocation
参数。如果该参数不存在,则侦听器/WEB-INF/applicationContext.xml
用作默认值。当参数确实存在时,侦听String
器使用预定义的分隔符(逗号、分号和空格)分隔 ,并将这些值用作搜索应用程序上下文的位置。也支持 Ant 样式的路径模式。示例是/WEB-INF/*Context.xml
(对于名称以 Context.xml
结尾 且驻留在WEB-INF
目录中的所有文件)和/WEB-INF/**/*Context.xml
(对于WEB-INF
的任何子目录中的所有此类文件)。
1.15.6. 将 Spring 部署ApplicationContext
为 Java EE RAR 文件
可以将 Spring 部署ApplicationContext
为 RAR 文件,将上下文及其所有必需的 bean 类和库 JAR 封装在 Java EE RAR 部署单元中。ApplicationContext
这相当于引导一个能够访问 Java EE 服务器设施的独立设备(仅托管在 Java EE 环境中)。RAR 部署是部署无头 WAR 文件的一种更自然的替代方案——实际上,一个没有任何 HTTP 入口点的 WAR 文件,仅用于 ApplicationContext
在 Java EE 环境中引导 Spring。
RAR 部署非常适合不需要 HTTP 入口点而是仅包含消息端点和计划作业的应用程序上下文。这种上下文中的 Bean 可以使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBC DataSource
实例和 JMSConnectionFactory
实例,还可以向平台的 JMX 服务器注册——所有这些都通过 Spring 的标准事务管理和 JNDI 和 JMX 支持工具。应用程序组件还可以WorkManager
通过 Spring 的TaskExecutor
抽象与应用程序服务器的 JCA 交互。
SpringContextResourceAdapter
有关RAR 部署中涉及的配置详细信息,请参阅该类的 javadoc 。
对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
- 将所有应用程序类打包成一个 RAR 文件(这是一个具有不同文件扩展名的标准 JAR 文件)。
- 将所有必需的库 JAR 添加到 RAR 存档的根目录中。
- 添加
META-INF/ra.xml
部署描述符(如javadoc 中SpringContextResourceAdapter
所示)和相应的 Spring XML bean 定义文件(通常META-INF/applicationContext.xml
)。 - 将生成的 RAR 文件拖放到应用程序服务器的部署目录中。
这种 RAR 部署单元通常是独立的。它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于 RAR 的交互ApplicationContext
通常通过它与其他模块共享的 JMS 目标发生。例如,基于 RAR 的程序ApplicationContext
还可以安排一些作业或对文件系统中的新文件(或类似文件)做出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出 RMI 端点,这些端点可以被同一台机器上的其他应用程序模块使用。
1.16. BeanFactory
API
API 为 Spring的BeanFactory
IoC 功能提供了底层基础。它的具体契约多用于与 Spring 的其他部分和相关的第三方框架的集成,它的DefaultListableBeanFactory
实现是上层GenericApplicationContext
容器内的关键委托。
和BeanFactory
相关的接口(例如BeanFactoryAware
、InitializingBean
、 DisposableBean
)是其他框架组件的重要集成点。通过不需要任何注解甚至反射,它们允许容器与其组件之间非常有效的交互。应用程序级别的 bean 可以使用相同的回调接口,但通常更喜欢声明性依赖注入,或者通过注解或通过编程配置。
请注意,核心BeanFactory
API 级别及其DefaultListableBeanFactory
实现不会对要使用的配置格式或任何组件注解做出假设。所有这些风格都通过扩展(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)出现,并将共享BeanDefinition
对象作为核心元数据表示进行操作。这就是使 Spring 的容器如此灵活和可扩展的本质。
1.16.1.BeanFactory
还是ApplicationContext
?
BeanFactory
本节解释了容器级别和 容器级别之间的差异ApplicationContext
以及对引导的影响。
ApplicationContext
除非您有充分的理由不这样做,否则 您应该使用 anGenericApplicationContext
及其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现。这些是 Spring 核心容器的主要入口点,用于所有常见目的:加载配置文件、触发类路径扫描、以编程方式注册 bean 定义和带注解的类,以及(从 5.0 开始)注册功能 bean 定义。
因为 anApplicationContext
包含 a 的所有功能BeanFactory
,所以通常建议在 plain 上使用BeanFactory
,除了需要完全控制 bean 处理的场景。在一个ApplicationContext
(例如 GenericApplicationContext
实现)中,按照约定(即按 bean 名称或按 bean 类型——特别是后处理器)检测几种 bean,而 plainDefaultListableBeanFactory
对任何特殊 bean 是不可知的。
对于许多扩展容器特性,例如注解处理和 AOP 代理,BeanPostProcessor
扩展点是必不可少的。如果您仅使用普通DefaultListableBeanFactory
的,则默认情况下不会检测和激活此类后处理器。这种情况可能会令人困惑,因为您的 bean 配置实际上没有任何问题。相反,在这种情况下,需要通过额外的设置来完全引导容器。
下表列出了BeanFactory
和 ApplicationContext
接口和实现提供的功能。
特征 | BeanFactory |
ApplicationContext |
---|---|---|
Bean实例化/织入 | 是的 | 是的 |
集成的生命周期管理 | 不 | 是的 |
自动BeanPostProcessor 注册 |
不 | 是的 |
自动BeanFactoryPostProcessor 注册 |
不 | 是的 |
方便MessageSource 的访问(国际化) |
不 | 是的 |
内置ApplicationEvent 发布机制 |
不 | 是的 |
要使用DefaultListableBeanFactory
显式注册 bean 后处理器,您需要以编程方式调用addBeanPostProcessor
,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
要将 BeanFactoryPostProcessor
应用于DefaultListableBeanFactory
,您需要调用其postProcessBeanFactory
方法,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都很不方便,这就是为什么在 Spring 支持的应用程序中,各种ApplicationContext
变体比普通的更受青睐 ,尤其是在典型企业设置中依赖实例来扩展容器功能时DefaultListableBeanFactory
。BeanFactoryPostProcessor``BeanPostProcessor
AnAnnotationConfigApplicationContext
已注册所有常见的注解后处理器,并且可以通过配置注解引入额外的处理器,例如@EnableTransactionManagement
. 在 Spring 的基于注解的配置模型的抽象级别上,bean 后处理器的概念变成了单纯的内部容器细节。
评论区