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@SessionScopepublic class SessionScopedService { // ...}
您还能够笼罩的值proxyMode,如以下示例所示:
@Service@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)public class SessionScopedUserService implements UserService { // ...}
1.10.3。自动检测类并注册Bean Definitions
Spring能够自动检测构造型类,并应用来注册相应的BeanDefinition实例ApplicationContext。
例如,以下两个类别有资格进行这种自动检测:
@Servicepublic class SimpleMovieLister { private MovieFinder movieFinder; public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; }}
@Repositorypublic 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注解来做到这一点。
上面的例子展现了如何做到这一点:
@Componentpublic 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办法的附加反对。
以下示例显示了如何执行此操作:
@Componentpublic 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:
@Componentpublic 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 { // ...}@Repositorypublic 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")@Repositorypublic 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.Componentorg.springframework.example.Transfer=org.springframework.stereotype.Component
在IDE中应用此模式时,spring-context-indexer必须将其注册为注解处理器,以确保在更新候选组件时索引是最新的。
当在类门路中找到META-INF/spring.components时,索引会主动启用。
如果索引对于某些库(或用例)是局部可用的,但不能为整个应用程序构建,那么您能够通过设置spring.index.ignore为true返回到惯例的类门路安顿。