10.类路径扫描和托管组件
本章中的大多数示例都使用XML来指定在Spring容器中生成每个BeanDefinition
的配置元数据。 上一节(基于注解的容器配置)演示了如何通过源级别的注解提供许多配置元数据。 但是,即使在这些示例中,“基本” bean定义也已在XML文件中明确定义,而注解仅驱动依赖项注入。 本节介绍了通过扫描类路径来隐式检测候选组件的选项。 候选组件是与过滤条件匹配的类,并在容器中注册了相应的Bean定义。 这消除了使用XML进行bean注册的需要。 而是可以使用注解(例如,@Component
),AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类已向容器注册了bean定义。
注意:
- 从Spring 3.0开始,Spring JavaConfig项目提供的许多功能是核心Spring Framework的一部分。 这使您可以使用Java而不是使用传统的XML文件来定义bean。
10.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
已被支持作为持久层中自动异常转换的标记。
10.2使用元注解和组合注解
Spring提供的许多注解都可以在您自己的代码中用作元注解。 元注解是可以应用于另一个注解的注解。 例如,前面提到的@Service
注解使用@Component
进行元注解,如以下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
您还可以组合元注解来创建“组合注解”。 例如,Spring MVC中的@RestController
批注由@Controller
和@ResponseBody
组成。
此外,组合注解可以选择从元注解中重新声明属性,以允许自定义。 当您只希望公开元注解属性的子集时,此功能特别有用。 例如,Spring的@SessionScope
注解将作用域名称硬编码为会话,但仍允许自定义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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
然后,您可以使用@SessionScope
而不用声明如下的proxyMode
:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
- 1
- 2
- 3
- 4
- 5
您还可以覆盖proxyMode的值,如以下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
- 1
- 2
- 3
- 4
- 5
10.3自动检测类并注册Bean定义
Spring可以自动检测原型类,并向ApplicationContext
注册相应的BeanDefinition
实例。例如,以下两个类适合进行这种自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
- 1
- 2
- 3
- 4
要自动检测这些类并注册相应的bean,需要将@ComponentScan
添加到@Configuration
类中,其中basePackages属性是这两个类的公共父包。 (或者,您可以指定一个逗号分隔,分号分隔或空格分隔的列表,其中包括每个类的父包。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
- 1
- 2
- 3
- 4
- 5
注意:
- 为简便起见,前面的示例可能使用了注解的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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
注意:
- <context:component-scan>的使用隐式启用<context:annotation-config>的功能。 使用<context:component-scan>时,通常无需包含<context:annotation-config>元素。
- 扫描类路径包需要在类路径中存在相应的目录条目。 使用Ant构建JAR时,请确保未激活JAR任务的仅文件开关。 另外,在某些环境中,基于安全策略可能不会公开类路径目录。
- 在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常可以按预期进行。 但是,请确保将组件类导出到模块信息描述符中。 如果您希望Spring调用您的类的非公共成员,请确保将它们“打开”(即,它们在模块信息描述符中使用了opens声明而不是export声明)。
此外,当您使用component-scan
元素时,将隐式包括AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
。 这意味着两个组件将被自动检测并连接在一起,而所有这些都不需要XML中提供任何bean配置元数据。
注意:
- 您可以通过将注解配置属性包括为
false
来禁用AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
的注册。
10.4使用过滤器自定义扫描
默认情况下,仅使用@Component
,@Repository
,@Service
,@Controller
,@Configuration
进行注解的类或使用@Component
进行注解的自定义注解是唯一检测到的候选组件。 但是,您可以通过应用自定义过滤器来修改和扩展此行为。 将它们添加为@ComponentScan
注解的includeFilters
或excludeFilters
属性(或作为XML配置中<context:component-scan>元素的<context:include-filter />或<context:exclude-filter />子元素)。 每个过滤器元素都需要类型和表达式属性。 下表描述了过滤选项:
过滤类型 | 示例表达式 | 描述 |
---|---|---|
annotation (default) | org.example.SomeAnnotation | 在目标组件中的类型级别上存在或元存在的注解。 |
assignable | org.example.SomeClass | 目标组件可分配给(扩展或实现)的类(或接口)。 |
aspectj | org.example…*Service+ | 目标组件要匹配的AspectJ类型表达式。 |
regex | org.example.Default.* | 与目标组件的类名匹配的正则表达式。 |
custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter接口的自定义实现。 |
以下示例显示了忽略所有@Repository
注解并改为使用“stub”存储库的配置:
@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
下面的清单显示了等效的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
注意:
- 您也可以通过在注解上设置
useDefaultFilters = false
或通过将use-default-filters =“ false”
作为元素的属性来禁用默认过滤器。 这有效地禁用了对使用@Component
,@Repository
,@Service
,@Controller
,@RestController
或@Configuration
进行注解或元注解的类的自动检测。
10.5在组件中定义Bean元数据
Spring组件还可以向容器提供bean定义元数据。您可以使用与在@Configuration
注解类中定义bean元数据相同的@Bean注释来做到这一点。下面的示例展示了如何做到这一点:
@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
- 13
上一类是Spring组件,在其doWork()
方法中具有特定于应用程序的代码。 但是,它也提供了一个具有工厂方法的bean定义,该工厂方法引用了方法publicInstance()
。 @Bean
注解标识工厂方法和其他bean定义属性,例如通过@Qualifier
批注的限定符值。 可以指定的其他方法级别注释是@Scope
,@Lazy
和自定义限定符注释。
注意:
- 除了用于组件初始化的角色外,您还可以将
@Lazy
批注放置在标有@Autowired
或@Inject
的注入点上。 在这种情况下,它导致注入了惰性解析代理。
如前所述,支持自动装配的字段和方法,并自动装配@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);
}
}
- 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
该示例将String方法参数国家/地区自动连线到另一个名为privateInstance
的bean
上age
属性的值。 Spring Expression Language元素通过符号#{}定义属性的值。 对于@Value批注,表达式解析程序已预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,您还可以声明类型为InjectionPoint
的工厂方法参数(或更具体的子类:DependencyDescriptor
),以访问触发当前bean创建的请求注入点。 注意,这仅适用于实际创建bean实例,而不适用于注入现有实例。 因此,此功能对原型范围的bean最有意义。 对于其他作用域,factory方法仅在给定作用域中看到触发创建新bean实例的注入点(例如,触发创建惰性单例bean的依赖项)。 在这种情况下,可以将提供的注入点元数据与语义一起使用。 以下示例显示如何使用InjectionPoint
:
@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
- 8
常规Spring组件中的@Bean
方法的处理方式与Spring @Configuration
类中的@Bean
方法不同。 区别在于,使用CGLIB不能增强@Component
类,以拦截方法和字段的调用。 CGLIB代理是调用@Configuration
类中@Bean
方法中的方法或字段的方法,用于创建Bean元数据引用来协作对象。 此类方法不是使用普通的Java语义调用的,而是通过容器进行的,以提供通常的Spring Bean生命周期管理和代理,即使通过@Bean
方法的编程调用引用其他Bean时也是如此。 相反,在普通@Component
类内的@Bean
方法中调用方法或字段具有标准Java语义,而无需特殊的CGLIB处理或其他约束。
注意:
- 您可以将@Bean方法声明为静态方法,从而允许在不将其包含配置类创建为实例的情况下调用它们。 在定义后处理器Bean(例如
BeanFactoryPostProcessor
或BeanPostProcessor
类型)时,这特别有意义,因为此类Bean在容器生命周期的早期进行了初始化,并且应避免在那时触发配置的其他部分。 - 由于技术限制,对静态
@Bean
方法的调用永远不会被容器拦截,即使在@Configuration
类中也是如此(如本节前面所述),这是由于技术限制:CGLIB子类只能覆盖非静态方法。 结果,直接调用另一个@Bean
方法具有标准的Java语义,从而导致直接从工厂方法本身直接返回一个独立的实例。 @Bean
方法的Java语言可见性不会对Spring容器中的最终bean定义产生直接影响。 您可以在非@Configuration
类中自由声明自己的工厂方法,也可以在任何地方声明静态方法。 但是,@Configuration
类中的常规@Bean
方法必须是可重写的 ,即不得将它们声明为private
或final
。@Bean
方法也可以在给定组件或配置类的基类中发现,也可以在Java 8中在组件或配置类实现的接口中声明的默认方法中发现。这为组合复杂配置安排提供了很大的灵活性,甚至可以通过Spring 4.2中的Java 8默认方法实现多重继承。- 最后,一个类可以为同一个bean保留多个@Bean方法,这取决于在运行时可用的依赖关系,从而可以使用多个工厂方法。 这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时将选择具有最大可满足依赖关系数量的变量,类似于容器在多个
@Autowired
构造函数之间进行选择的方式。
10.6命名自动检测的组件
在扫描过程中自动检测到组件时,其bean名称由该扫描程序已知的BeanNameGenerator
策略生成。 默认情况下,任何包含名称值的Spring构造型注释(@ Component
,@ Repository
,@ Service
和@Controller
)都会将该名称提供给相应的bean定义。
如果这样的注释不包含名称值,或者不包含任何其他检测到的组件(例如,由自定义过滤器发现的组件),则缺省bean名称生成器将返回未大写的非限定类名称。 例如,如果检测到以下组件类,则名称将为myMovieLister
和movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
- 1
- 2
- 3
- 4
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
- 1
- 2
- 3
- 4
如果不想依赖默认的Bean命名策略,则可以提供自定义Bean命名策略。 首先,实现BeanNameGenerator
接口,并确保包括默认的no-arg构造函数。 然后,在配置扫描程序时提供完全限定的类名,如以下示例注解和Bean定义所示。
注意:
- 如果由于多个自动检测到的组件具有相同的非限定类名称(即,具有相同名称但位于不同程序包中的类)而导致命名冲突,则可能需要配置一个
BeanNameGenerator
,其默认值为标准名称。 生成的bean名称。 从Spring Framework 5.2.3开始,位于org.springframework.context.annotation
包中的FullyQualifiedAnnotationBeanNameGenerator
可以用于此类目的。
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
- 1
- 2
- 3
- 4
- 5
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
- 1
- 2
- 3
- 4
通常,请考虑在其他组件可能对其进行显式引用时,使用注解指定名称。 另一方面,只要容器负责接线,自动生成的名称就足够了。
10.7提供自动检测组件的范围
通常,与Spring管理的组件一样,自动检测到的组件的默认范围也是最常见的范围是单例。 但是,有时您需要使用@Scope
批注指定的其他范围。 您可以在批注中提供范围的名称,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
- 1
- 2
- 3
- 4
- 5
注意:
@Scope
注解仅在具体的bean类(对于带注解的组件)或工厂方法(对于@Bean
方法)上进行内省。 与XML bean定义相反,没有bean定义继承的概念,并且在类级别的继承层次结构与元数据目的无关。- 要提供用于范围解析的自定义策略,而不是依赖于基于注解的方法,可以实现
ScopeMetadataResolver
接口。 确保包括默认的无参数构造函数。 然后,可以在配置扫描程序时提供完全限定的类名,如以下注释和Bean定义示例所示:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
- 1
- 2
- 3
- 4
- 5
<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动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
- 1
- 2
- 3
- 4
- 5
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
- 1
- 2
- 3
10.8提供带有注解的限定符元数据
@Qualifier
注解将在使用限定符的基于注释的自动装配中进行讨论。 该部分中的示例演示了@Qualifier
注解和自定义限定符批注的使用,以在解析自动装配候选时提供细粒度的控制。 由于这些示例基于XML bean定义,因此通过使用XML中bean元素的限定符或meta子元素,在候选bean定义上提供了限定符元数据。 当依靠类路径扫描来自动检测组件时,可以在候选类上为限定符元数据提供类型级别的注释。 下面的三个示例演示了此技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
- 1
- 2
- 3
- 4
- 5
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
- 1
- 2
- 3
- 4
- 5
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
- 1
- 2
- 3
- 4
- 5
注意:
- 与大多数基于注解的替代方法一样,请记住,注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean在其限定符元数据中提供变体,因为元数据是按实例而不是按类提供的。
10.9生成候选组件的索引
尽管类路径扫描非常快,但可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。 在这种模式下,作为组件扫描目标的所有模块都必须使用此机制。
注意:
- 您现有的
@ComponentScan
或<context:component-scan
指令必须保留原样,以请求上下文扫描某些软件包中的候选对象。 当ApplicationContext
检测到这样的索引时,它将自动使用它,而不是扫描类路径。
要生成索引,请向每个包含组件的模块添加附加依赖关系,这些组件是组件扫描指令的目标。 以下示例显示了如何使用Maven进行操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.9.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
对于Gradle 4.5和更早版本,依赖关系应在compileOnly
配置中声明,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.9.RELEASE"
}
- 1
- 2
- 3
在Gradle 4.6及更高版本中,依赖性应在注释处理器配置中声明,如以下示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
- 1
- 2
- 3
该过程将生成一个包含在jar文件中的META-INF / spring.components文件。
注意:
- 在IDE中使用此模式时,
spring-context-indexer
必须注册为注释处理器,以确保在更新候选组件时索引是最新的。 - 在类路径上找到
META-INF / spring.components
时,将自动启用索引。 如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建,则可以通过将spring.index.ignore
设置为来回退到常规的类路径安排(好像根本没有索引)。 true,可以是系统属性,也可以是classpath
根目录下的spring.properties
文件。