20191226 Spring官方文档(Core 1.10)
1.10。类路径扫描和托管组件
1.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在持久层中已经支持作为自动异常转换的标记。
组合注释可以选择从元注释中重新声明属性,以允许自定义。当您只想公开元注释属性的子集时,此功能特别有用。例如,Spring的 @SessionScope
注释将作用域名称硬编码为session
,但仍允许自定义proxyMode
。
@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;
}
然后,您无需声明proxyMode
以下即可使用@SessionScope
:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
您还可以覆盖的值proxyMode,如以下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
1.10.3。自动检测类并注册Bean定义
要自动检测这些类并注册相应的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:annotation-config>
使用时通常不需要包含<context:component-scan>
元素。
此外,当您使用component-scan元素时,AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
都隐式包括在内。这意味着将自动检测这两个组件并装配它们,而这一切都不需要XML中提供的任何bean配置元数据。
您可以禁用注册AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
,通过设置annotation-config
属性值为false
。
1.10.4。使用过滤器自定义扫描
默认情况下,类注解有@Component,@Repository,@Service,@Controller, @Configuration,或自定义的自身带有@Component的注释,是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan
的includeFilters
或excludeFilters
属性(或XML配置中<context:component-scan>
元素的<context:include-filter />
或 <context:exclude-filter />
子元素)。每个过滤器元素都需要type
和expression
属性。下表描述了过滤选项(type属性的值域):
过滤器类型 | 示例 | 描述 |
---|---|---|
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 {
...
}
<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
,@Repository
,@Service
,@Controller
,@RestController
或@Configuration
进行注释或元注释的类的自动检测。
1.10.5。在组件中定义Bean元数据
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@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);
}
}
从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());
}
}
将@Bean在普通的Spring组件方法与Spring里的同行@Configuration类处理方式不同。不同之处在于,CGLIB并未增强@Component类来拦截方法和字段的调用。CGLIB代理是一种方法,通过该方法可以调用@Configuration类中的方法或@Bean方法中的字段来创建Bean元数据引用以协作对象。此类方法不是用普通的Java语义调用的,而是通过容器进行的,以提供通常的生命周期管理和Spring Bean的代理,即使通过程序调用@Bean方法引用其他Bean时也是如此。相反,在普通区域内调用@Component类中的@Bean方法或方法中的字段具有标准的Java语义,没有特殊的CGLIB处理或其他限制。
您可以将@Bean方法声明为static
,从而允许在不将其包含的配置类创建为实例的情况下调用它们。在定义后处理器Bean(例如BeanFactoryPostProcessor
或 BeanPostProcessor
类型)时,这特别有意义,因为此类Bean在容器生命周期的早期进行了初始化,并且应避免在那时触发配置的其他部分。
由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类内也不会(如本节前面所述),因为技术限制:CGLIB子类只能覆盖非静态方法。结果,直接调用另一个@Bean方法具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。
@Bean方法的Java语言可见性不会对Spring容器中的结果bean定义产生直接影响。您可以随意声明自己的工厂方法,以适合非@Configuration类,也可以在任何地方使用静态方法。但是,@Configuration类中的常规@Bean方法必须是可重写的,也就是说,不得将其声明为private
或final
。
还可以在给定组件或配置类的基类上以及在由组件或配置类实现的接口中声明的Java 8默认方法上发现@Bean方法。这为组合复杂的配置安排提供了很大的灵活性,从Spring 4.2开始,通过Java 8默认方法甚至可以进行多重继承。
最后,单个类可以为同一个bean 保留多个@Bean方法,这取决于在运行时可用的依赖项,以安排使用多个工厂方法。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时将选择具有最大可满足依赖关系数量的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。
1.10.6。命名自动检测的组件
当组件被自动检测为扫描过程的一部分时,其bean名称由扫描器已知的BeanNameGenerator
策略生成。默认情况下,任何Spring构造型注解(@Component,@Repository,@Service和 @Controller),其中包含一个名称value,从而提供了相应的bean定义的名称。
如果这样的注释不包含value值,或者不包含任何其他检测到的组件(例如,由自定义过滤器发现的组件),则默认bean名称生成器将返回不使用大写字母的非限定类名称。例如,如果检测到以下组件类,则名称为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>
作为一般规则,每当其他组件可能对其进行显式引用时,请考虑使用注释指定名称。另一方面,只要容器负责装载,自动生成的名称就足够了。
1.10.7。为自动检测的组件提供作用域
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
注释仅在具体的bean类(对于带注释的组件)或工厂方法(对于@Bean方法)上进行内省。 与XML bean定义相反,没有bean定义继承的概念,并且在类级别的继承层次结构与元数据目的无关。
要提供用于范围解析的自定义策略,而不是依赖于基于注释的方法,可以实现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>
使用某些非单作用域时,可能有必要为作用域对象生成代理。为此,在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。提供带有注释的限定符元数据
@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提供其限定符元数据的变体,因为该元数据是按instance而不是按类。
1.10.9。生成候选组件的索引
尽管类路径扫描非常快,但可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用此机制。
您现有的@ComponentScan
或<context:component-scan>
指令必须保持原样,以请求上下文扫描某些软件包中的候选对象。当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。
要生成索引,请向每个包含组件的模块添加附加依赖关系,这些组件是组件扫描指令的目标。以下示例显示了如何使用Maven进行操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.1.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
对于Gradle 4.5和更早版本,应在compileOnly 配置中声明依赖项,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.1.RELEASE"
}
对于Gradle 4.6及更高版本,应在annotationProcessor 配置中声明依赖项,如以下示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
该过程将生成一个包含在jar文件中的META-INF/spring.components
文件。
在IDE中使用此模式时,必须将spring-context-indexer
注册为注释处理器,以确保在更新候选组件时索引是最新的。
在类路径上找到 META-INF/spring.components
时,索引将自动启用。如果某个索引对于某些库(或用例)部分可用,但无法为整个应用程序构建,您可以通过将spring.index.ignore
设置为true
来回退到常规的类路径安排(好像根本没有索引),可以将其设置为系统属性,也可以将其设置为类路径根目录下的spring.properties
文件。