一起来读官方文档-----SpringIOC(09)

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-filtercontext:exclude-filter子元素)。

下表描述了筛选选项:

表5.过滤器类型

过滤器类型举例描述
annotation (default)org.example.SomeAnnotation目标组件的注解或元注解中存在的当前注解
assignableorg.example.SomeClass目标组件继承或实现自某个类(或接口)
aspectjorg.example..*Service+目标组件要匹配的AspectJ类型表达式。
regexorg.example.Default.*要与目标组件的类名匹配的正则表达式。
customorg.example.MyTypeFilterorg.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返回到常规的类路径安排。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值