本章中的大多数示例使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。但是,即使在这些示例中,“基本”bean定义也显式地定义在XML文件中,而注释仅驱动依赖项注入。本节描述通过扫描类路径隐式检测候选组件的选项。候选组件是根据筛选条件匹配的类,并且在容器中注册了相应的bean定义。这样就不需要使用XML来执行bean注册。相反,您可以使用注释(例如@Component)、AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类拥有向容器注册的bean定义。
从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是Spring框架核心的一部分。这允许您使用Java而不是使用传统的XML文件来定义bean。查看@Configuration、@Bean、@Import和@DependsOn注解,了解如何使用这些新特性。
1.10.1. @Component和进一步的原型注解
@Repository注释是满足存储库(也称为数据访问对象或DAO)角色或原型的任何类的标记。此标记的用途之一是异常的自动转换,如异常转换中所述。
Spring提供了进一步的原型注解:@Component、@Service和@Controller。@Component是任何spring管理组件的通用原型。@Repository、@Service和@Controller是@Component的专门化,用于更具体的用例(分别在持久层、服务层和表示层)。因此,您可以使用@Component来注释组件类,但是如果使用@Repository、@Service或@Controller来注释,则类更适合由工具处理或与方面关联。例如,这些构造型注释是切入点的理想目标。在Spring框架的未来版本中,@Repository、@Service和@Controller还可以携带额外的语义。因此,如果要在服务层使用@Component或@Service之间进行选择,@Service显然是更好的选择。类似地,如前所述,@Repository已经被支持作为持久层中自动异常转换的标记。
1.10.2. 使用元注解和组合注解
Spring提供的许多注解都可以在您自己的代码中用作元注解。元注释是可以应用于另一个注解的注解。例如,前面提到的@Service注解是用@Component进行元注解的,如下面的例子所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
@Component导致@Service被以与@Component相同的方式对待。
您还可以组合元注释来创建“组合注释”。例如,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;
}
然后你可以使用@SessionScope而不声明proxyMode,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
有关更多细节,请参阅Spring注释编程模型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 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及更高版本上的独立应用程序(这需要在清单中设置“可信库”-请参阅stackoverflow.com/questions/1…
在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在模块信息描述符中导出组件类。如果您希望Spring调用类的非公共成员,请确保它们是“打开的”(也就是说,它们在模块信息描述符中使用了开放声明,而不是exports声明)。
此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都被隐式包含。这意味着这两个组件是自动检测并连接在一起的—所有这些都不需要以XML提供任何bean配置元数据。
您可以禁用
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册,方法是使用值为false的annotation-config属性。
1.10.4. 使用过滤器自定义扫描
默认情况下,使用@Component、@Repository、@Service、@Controller、@Configuration标注的类,或者本身使用@Component标注的自定义标注,是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展这种行为。将它们作为@ComponentScan注释的includeFilters或excludeFilters属性添加(或作为XML配置中<context:component-scan>元素的<context:include-filter />或<context:exclude-filter />子元素)。每个筛选器元素都需要类型和表达式属性。过滤选项说明如下:
| Filter Type | Example Expression | Description |
|---|---|---|
| annotation (default) | org.example.SomeAnnotation | An annotation to be present or meta-present at the type level in target components. |
| assignable | org.example.SomeClass | A class (or interface) that the target components are assignable to (extend or implement). |
| aspectj | org.example..*Service+ | An AspectJ type expression to be matched by the target components. |
| regex | org.example.Default.* | A regex expression to be matched by the target components' class names. |
| custom | org.example.MyTypeFilter | A custom implementation of the org.springframework.core.type.TypeFilterinterface. |
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public enum FilterType {
/**
* Filter candidates marked with a given annotation.
* @see org.springframework.core.type.filter.AnnotationTypeFilter
*/
ANNOTATION,
/**
* Filter candidates assignable to a given type.
* @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
/**
* Filter candidates matching a given AspectJ type pattern expression.
* @see org.springframework.core.type.filter.AspectJTypeFilter
*/
ASPECTJ,
/**
* Filter candidates matching a given regex pattern.
* @see org.springframework.core.type.filter.RegexPatternTypeFilter
*/
REGEX,
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
}
下面的例子显示了忽略所有@Repository注释而使用“存根”存储库的配置:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
<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或在<component-scan/>元素的属性中提供use-default-filters="false"来禁用默认过滤器。这有效地禁用了使用@Component、@Repository、@Service、@Controller、@RestController或@Configuration注释或元注释的类的自动检测。
1.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
}
}
前面的类是一个Spring组件,它的doWork()方法中有特定于应用程序的代码。但是,它还提供了一个bean定义,其中有一个引用方法publicInstance()的工厂方法。@Bean注释标识工厂方法和其他bean定义属性,例如通过@Qualifier注释标识一个限定符值。其他可以指定的方法级注释有@Scope、@Lazy和自定义限定符注释。
除了它在组件初始化中的角色,您还可以将@Lazy注释放在标记为@Autowired或@Inject的注入点上。在这种情况下,它会导致惰性解析代理的注入。然而,这种代理方法是相当有限的。对于复杂的惰性交互,特别是与可选依赖项结合使用时,我们建议改用
ObjectProvider<MyTargetBean>。
如前所述,支持自动连接字段和方法,还支持自动装配@Bean方法。下面的例子展示了如何这样做:
参考文档:@RequestScope注解的用法 www.jianshu.com/p/9b1aff532…
@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 Language元素通过符号#{< 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组件中的@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定义产生直接影响。你可以自由地在non-@Configuration类中声明你的工厂方法,也可以在任何地方声明静态方法。但是,@Configuration类中的常规@Bean方法需要被重写——也就是说,它们不能被声明为private或final。
@Bean方法也在给定组件或配置类的基类中发现,以及在组件或配置类实现的接口中声明的Java 8默认方法中发现。这允许在组合复杂的配置安排时具有很大的灵活性,甚至可以通过Spring 4.2的Java 8默认方法实现多重继承。
最后,对于同一个bean,单个类可以包含多个@Bean方法,作为多个工厂方法的安排,根据运行时可用的依赖关系来使用。这与在其他配置场景中选择“最贪婪”构造函数或工厂方法的算法相同:具有最大数量可满足依赖项的变体在构造时被选择,类似于容器如何在多个@Autowired构造函数之间选择。
1.10.6. 命名自动检测组件
当一个组件作为扫描过程的一部分被自动检测时,它的bean名称将由该扫描器所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring原型注释(@Component, @Repository, @Service,和@Controller)都将该名称提供给相应的bean定义。
如果这样的注释不包含任何名称值,或者对于任何其他检测到的组件(例如由自定义过滤器发现的组件),默认bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,名称将是myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果您不想依赖于默认的bean命名策略,您可以提供一个自定义的bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名,如下面的示例注释和bean定义所示。
如果由于多个自动检测到的组件具有相同的非限定类名(即具有相同名称但位于不同包中的类)而遇到命名冲突,则可能需要配置一个BeanNameGenerator,该BeanNameGenerator默认为生成的bean名的全限定类名。从Spring Framework 5.2.3开始,位于org.springframework.context.annotation包中的FullyQualifiedAnnotationBeanNameGenerator可以用于这样的目的。
BeanNameGenerator如何实现bean注册,参考文档blog.csdn.net/qq_31900497…
@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管理的组件一般一样,自动检测组件的默认和最常见的范围是单例。然而,有时您需要一个可以由@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>
Spring ComponentScan注解默认的AnnotationScopeMetadataResolver实现了ScopeMetadataResolver,获取了scope的注解,源码如下:
参考连接:www.jianshu.com/p/130d7b7f1…
public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
private final ScopedProxyMode defaultProxyMode;
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;
/**
* Construct a new {@code AnnotationScopeMetadataResolver}.
* @see #AnnotationScopeMetadataResolver(ScopedProxyMode)
* @see ScopedProxyMode#NO
*/
public AnnotationScopeMetadataResolver() {
this.defaultProxyMode = ScopedProxyMode.NO;
}
/**
* Construct a new {@code AnnotationScopeMetadataResolver} using the
* supplied default {@link ScopedProxyMode}.
* @param defaultProxyMode the default scoped-proxy mode
*/
public AnnotationScopeMetadataResolver(ScopedProxyMode defaultProxyMode) {
Assert.notNull(defaultProxyMode, "'defaultProxyMode' must not be null");
this.defaultProxyMode = defaultProxyMode;
}
/**
* Set the type of annotation that is checked for by this
* {@code AnnotationScopeMetadataResolver}.
* @param scopeAnnotationType the target annotation type
*/
public void setScopeAnnotationType(Class<? extends Annotation> scopeAnnotationType) {
Assert.notNull(scopeAnnotationType, "'scopeAnnotationType' must not be null");
this.scopeAnnotationType = scopeAnnotationType;
}
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
}
当使用某些非单例作用域时,可能需要为作用域对象生成代理。其推理在作用域bean中描述为依赖项。为此,可以在组件扫描元素上使用scope -proxy属性。取值为no、interfaces和targetClass。例如,以下配置会生成标准JDK动态代理:
暂时没看懂。@Scope注解的proxyMode的作用以及如何影响IoC容器的依赖查找:blog.csdn.net/m0_43448868…
@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注释将在微调基于注释的与限定符的自动装配中讨论。该部分中的示例演示了在解析自动装配候选对象时使用@Qualifier注释和自定义限定符注释来提供细粒度控制。因为这些示例基于XML bean定义,所以通过使用XML中bean元素的限定符或元元素,可以在候选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>6.0.3</version>
<optional>true</optional>
</dependency>
</dependencies>
在Gradle 4.5及更早的版本中,依赖项应该在compileOnly配置中声明,如下例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:6.0.3"
}
在Gradle 4.6及以后版本中,依赖关系应该在annotationProcessor配置中声明,如下例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:6.0.3"
}
spring-context-indexer构件生成一个包含在jar文件中的META-INF/spring.components文件。
在IDE中使用这种模式时,
spring-context-indexer必须注册为注释处理器,以确保在候选组件更新时索引是最新的。当在类路径中找到META-INF/spring.components文件时,索引将自动启用。如果某个索引对某些库(或用例)部分可用,但不能为整个应用程序构建,则可以通过将spring.index.ignore设置为true,或者作为JVM系统属性,或者通过SpringProperties机制,返回到常规的类路径安排(就好像根本没有索引一样)。