共计 11467 个字符,预计需要花费 29 分钟才能阅读完成。
1.10。Classpath Scanning and Managed Components
本章中的大多数示例应用 XML 配置。
上一节 (基于注解的容器配置) 演示了如何通过注解配置 bean。虽说是注解配置,但根本”bean”定义也显式地定义在 XML 文件中,而注解仅驱动依赖项注入。
本节形容通过扫描 classpath 隐式检测候选 Spring 组件。候选组件是指满足筛选规范的类,并有相应的 bean definition 注册到容器中。
这样就不须要应用 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 的数据拜访异样类型。
Spring 提供了进一步的构造型注解:@Component,@Service 和 @Controller。
@Component 是任何 spring 治理组件的通用原型。
@Repository、@Service 和 @Controller 则是针对更具体用例(别离在持久性、服务和表示层中),它们是 @Component 的细化。
因而,您能够应用 @Component 来注解组件类,然而,通过应用 @Repository、@Service 或 @Controller 来注解它们,您的类更适宜通过工具进行解决或与方面关联。
例如,这些构造型注解是 AOP 切点的现实指标。
@Repository、@Service 和 @Controller 还能够在 Spring 框架的将来版本中附带额定的语义,就像 @Repository 的异样翻译一样。
因而,如果您要在您的服务层应用 @Component 或 @Service 之间进行抉择,那么 @Service 显然是更好的抉择。
相似地,如前所述,@Repository 曾经被反对作为长久化层中主动异样转换的标记。
1.10.2。应用元注解和组合注解
Spring 提供的许多注解都能够在您本人的代码中用作元注解。
元注解是能够利用于另一个注解的注解。
例如,@Service 注解上存在元注解 @Component,如上面的示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {// ...}
//@Component 的存在导致 Spring 以 @Component 的形式来看待 @Service。
您还能够联合应用元注解来创立“组合注解”。
例如,Spring MVC 中的注解 @RestController 由 @Controller 和 组成 @ResponseBody。
此外,组合注解能够抉择从元注解中从新申明属性,以容许自定义。
当您只心愿公开元注解属性的子集时,这特地有用。
例如,Spring 的 @SessionScope 注解将 @Scope 的 value 属性硬编码为 session,但仍容许自定义 @Scope 的 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 {// ...}
1.10.3。自动检测类并注册 Bean Definitions
Spring 能够自动检测构造型类,并应用来注册相应的 BeanDefinition 实例 ApplicationContext。
例如,以下两个类别有资格进行这种自动检测:
@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 {// ...}
以下应用 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:annotation-config> 应用时通常不须要蕴含 元素 <context:component-scan>。
此外,当您应用 component-scan 元素时,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都隐式包含在内。
这意味着将自动检测这两个组件并将它们连贯在一起,而这所有都不须要 XML 中提供的任何 bean 配置元数据。
您能够通过包含 annotation-config 与属性的值 false 禁用注册 AutowiredAnnotationBeanPostProcessor 并 CommonAnnotationBeanPostProcessor。
如下所示:
<context:component-scan base-package="org.springframework.example" annotation-config="false"/>
1.10.4。应用过滤器自定义扫描
默认状况下,仅检测到用 @Component、@Repository、@Service、@Controller、@Configuration 正文的类或自身用 @Component 正文的自定义正文。
然而,您能够通过利用自定义过滤器来批改和扩大此行为。
比方批改 @ComponentScan 注解的 includeFilters 或 excludeFilters 属性(或 <context:component-scan> 元素中 <context:include-filter> 或 <context:exclude-filter> 子元素)。
下表形容了筛选选项:
表 5. 过滤器类型
过滤器类型 | 举例 | 形容 |
---|---|---|
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 接口的自定义实现。 |
@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 注解
或通过批改 <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 和自定义 Qualifier 正文。
@Lazy 除了用于组件初始化的角色外,您还能够将 @Lazy 注解搁置在标有 @Autowired 或的注入点上 @Inject。在这种状况下,它导致注入了惰性解析代理。
如前所述,反对主动注入的字段和办法,并主动反对 @Bean 办法的附加反对。
以下示例显示了如何执行此操作:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {return new TestBean("publicInstance");
}
//privateInstance.age 的值会被主动注入
//Qualifier 值为 public 的 bean 会被注入
@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);
}
}
在 Spring Framework 4.3 中,您还能够申明一个类型为 InjectionPoint 的工厂办法参数 (或者它更具体的子类:DependencyDescriptor) 来拜访触发以后 bean 创立的申请注入,能看到以后 bean 是作为哪个 bean 的属性被实例化的(比方:实例化时的属性名,实例化所处的类等)。
留神,这只实用于 bean 实例的理论创立时候,现有实例被注入的时候不会触发。
因而,这个个性对 prototype 范畴的 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 类中的对应办法解决形式不同。
区别在于 @Configuration 类会被 CGLIB 加强过,@Component 类没有通过 CGLIB 加强来拦挡办法和字段的调用。
您能够将 @Bean 办法申明为动态的,容许在不将蕴含它的配置类创立为实例的状况下调用它们。这在定义后处理器 bean(例如,BeanFactoryPostProcessor 或 BeanPostProcessor 类型)时特地有意义,因为这样的 bean 在容器生命周期的晚期被初始化,应该防止在此时触发配置的其余局部。容器永远不会拦挡对动态 @Bean 办法的调用,甚至在 @Configuration 类中也不会(如本节后面所述),因为技术限度:CGLIB 子类化只能笼罩非静态方法。Java 语言中 @Bean 办法的可见性对 Spring 容器中生成的 bean 定义没有间接影响。您能够自在地在 non-@Configuration 类中申明您的工厂办法,也能够在任何中央申明静态方法。然而,@Configuration 类中的惯例 @Bean 办法须要被重写——也就是说,它们不能被申明为 private 或 final。@Bean 办法也能够在给定组件或配置类的基类中发现,也能够在 Java 8 中在组件或配置类实现的接口中申明的默认办法中发现。这为组合简单配置安顿提供了很大的灵活性,甚至多重继承在 java8 的默认办法下也成为了可能。interface OneClass{default void printOne(){System.out.println("print one");
}
}
interface TwoClass{default void printTwo(){System.out.println("print two");
}
}
public class SonClass implements OneClass, TwoClass {public static void main(String[] args) {SonClass son = new SonClass();
son.printOne();
son.printTwo();}
}
每个接口都定义了一个默认办法。因而 SonClass 类能够从这两个接口调用办法。这就像多重继承。最初,一个类可能为同一个 bean 保留多个 @Bean 办法,作为多个工厂办法的安顿,在运行时依据可用的依赖项应用。这与在其余配置场景中抉择构造函数或工厂办法是雷同的算法: 在构建时抉择可满足依赖关系最多的变量,相似于容器在多个 @Autowired 构造函数之间进行抉择。如下:@Bean
public ServiceTwo serviceTwo(InjectionPoint injectionPoint){return new ServiceTwo();
}
@Bean
public ServiceTwo serviceTwo(InjectionPoint injectionPoint, CusService cusService){return new ServiceTwo();
}
这两个 bean 只会实例化一个来应用
1.10.6。命名自动检测的组件
当一个组件作为扫描过程的一部分被自动检测时,
它的 bean 名称是由扫描器晓得的 BeanNameGenerator 策略生成的。
默认状况下,一些 Spring 注解 (@Component,@Repository,@Service,和 @Controller) 蕴含一个名为 value 的属性,能够提供名称给相应的 bean 定义。
如果 value 值不蕴含任何名称值,那么默认的 bean 名称生成器将返回类名称首字母小写的字符串作为 beanName。
例如,如果检测到以下组件类,名称将是 myMovieLister 和 movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {// ...}
@Repository
public class MovieFinderImpl implements MovieFinder {// ...}
如果不想依赖默认的 Bean 命名策略,则能够提供自定义 Bean 命名策略。
首先,实现 BeanNameGenerator 接口,并确保包含默认的 no-arg 构造函数。
而后,在配置扫描程序时提供齐全限定的类名,如以下示例注解和 Bean 定义所示。
@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>
如果因为自动检测到的多个组件具备雷同的非限定类名 (即具备雷同名称但位于不同包中的类) 而遇到命名抵触,您可能须要配置一个以类的全门路作为 beanName 的 BeanNameGenerator。在 Spring Framework 5.2.3 中,位于 package org.springframework.context 中的 FullyQualifiedAnnotationBeanNameGenerator 可用于此。public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
return beanClassName;
}
}
作为个别规定,当其余组件可能显式地援用该注解时,请思考应用该正文指定名称。
另一方面,只有容器负责连贯,主动生成的名称就足够了。
1.10.7。提供自动检测组件的范畴
一般而言,与 Spring 治理的组件一样,自动检测到的组件的默认范畴也是最常见的范畴是 singleton。
然而,有时您须要 @Scope 注解能够指定的其余范畴。
您能够在批注中提供范畴的名称,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {// ...}
@Scope 注解仅在具体的 bean 类(对于任何的 @Component 注解)或工厂办法(对于 @Bean 办法)上进行应用。
与 XML bean 定义相同,没有 bean 定义继承的概念,并且在类级别的继承层次结构与元数据目标无关。
无关特定于 web 的作用域的详细信息,看本系列的第五篇文章。
要为 Scope 解析提供自定义策略,而不是依赖于基于注解的办法,您能够实现 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>
在应用某些非单例作用域时,可能须要为作用域对象生成代理。
其起因还是因为作用域的问题,比方作用域为单例 bean 的援用 prototype 的 bean,则须要应用代理,否则单例 bean 将始终持有 prototype 的一个实例。
为此,component-scan 元素上有一个作用域代理属性。三个可能的值是:no、interface 和 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。生成候选组件的索引
尽管类门路扫描十分快,然而能够通过在编译时创立一个动态候选列表来进步大型应用程序的启动性能。
其实就是把所有须要注册成 bean 的类在编译时就筛选进去了,启动时间接用,不须要全副遍历了。
在这种模式下,所有作为组件扫描指标的模块都必须应用这种机制。
您现有的 @ComponentScan 或当 ApplicationContext 检测到这样一个索引时,它会主动应用它,而不是扫描类门路。
要生成索引,请向蕴含组件扫描指令指标组件的每个模块增加额定的依赖项。
上面的例子展现了如何应用 Maven 来实现这一点:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.9.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle 4.5 和更早版本,应在 compileOnly 配置中申明依赖项,如以下示例所示:
dependencies {compileOnly "org.springframework:spring-context-indexer:5.2.9.RELEASE"}
对于 Gradle 4.6 和更高版本,应在 annotationProcessor 配置中申明依赖项,如以下示例所示:
dependencies {annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
该过程将生成一个 META-INF/spring.components 的文件蕴含在 jar 文件中。
内容如下:
org.springframework.example.Cancel=org.springframework.stereotype.Component
org.springframework.example.Transfer=org.springframework.stereotype.Component
在 IDE 中应用此模式时,spring-context-indexer 必须将其注册为注解处理器,以确保在更新候选组件时索引是最新的。
当在类门路中找到 META-INF/spring.components 时,索引会主动启用。
如果索引对于某些库 (或用例) 是局部可用的,但不能为整个应用程序构建,那么您能够通过设置 spring.index.ignore 为 true 返回到惯例的类门路安顿。