spring 类路径扫描和托管组件

​ 通过扫描类路径来隐式检测候选组件的选项。候选组件是与过滤条件匹配的类,并具有在容器中注册的相应bean定义。这消除了使用XML进行bean注册的需要。而是可以使用批注(例如,@ Component),AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类已向容器注册了bean定义。

​ 从Spring 3.0开始,Spring JavaConfig项目提供的许多功能是核心Spring Framework的一部分。这使您可以使用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来注释组件类,您的类更适合通过工具进行处理或与方面相关联。例如,这些构造型注释成为切入点的理想目标。 @ Repository,@ Service和@Controller在Spring框架的将来版本中也可以带有其他语义。因此,如果在服务层使用@Component或@Service之间进行选择,则@Service显然是更好的选择。同样,如前所述,@ Repository已被支持作为持久层中自动异常转换的标记。

1.10.2 使用元注解和组合注解

​ Spring提供的许多注释都可以在您自己的代码中用作元注释。元注释是可以应用于另一个注释的注释。例如,前面提到的@Service注释使用@Component进行元注释,如以下示例所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}

​ 您还可以结合使用元注释来创建“组合注释”。例如,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 {
    // ...
}

您还可以覆盖proxyMode的值,如以下示例所示:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
1.10.3 自动检测类并注册Bean definitions

​ 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:

<?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>元素。

​ 此外,当您使用component-scan元素时,将隐式包括AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor。这意味着两个组件将被自动检测并连接在一起,而所有这些都不需要XML中提供任何bean配置元数据。

​ 您可以通过将annotation-config属性包括为false来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

1.10.4 使用过滤器自定义扫描

​ 默认情况下,仅使用@ Component,@ Repository,@ Service,@ Controller,@ Configuration进行注释的类或使用@Component进行注释的自定义注释是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan批注的includeFilters或excludeFilters属性(或作为XML配置中<context:component-scan>元素的<context:include-filter />或<context:exclude-filter />子元素)。每个过滤器元素都需要类型和表达式属性。下表描述了过滤选项:

Filter TypeExample ExpressionDescription
annotation (default)org.example.SomeAnnotation在目标组件中的类型级别上要存在或元存在的注释。
assignableorg.example.SomeClass目标组件可分配给(扩展或实现)的类(或接口)。
aspectjorg.example…*Service+目标组件要匹配的AspectJ类型表达式。
regexorg.example.Default.*要与目标组件的类名匹配的正则表达式。
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter接口的自定义实现。

以下示例显示了忽略所有@Repository批注并改为使用“存根”存储库的配置:

@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或通过将use-default-filters =“ false”作为元素的属性来禁用默认过滤器。这有效地禁用了对使用@ Component,@ Repository,@ Service,@ Controller,@ RestController或@Configuration进行注释或元注释的类的自动检测。

1.10.5 在组件中定义Bean元数据

​ Spring组件还可以将bean定义元数据贡献给容器。您可以使用与@Bean注解相同的@Bean注解来定义@Configuration带注释的类中的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的注入点上。在这种情况下,它导致注入了惰性解析代理。

​ 如前所述,支持自动装配的字段和方法,并自动装配@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);
    }
}

​ 该示例将String方法参数国家/地区自动连线到另一个名为privateInstance的bean上age属性的值。 Spring Expression Language元素通过符号#{}定义属性的值。对于@Value批注,表达式解析程序已预先配置为在解析表达式文本时查找bean名称。

​ 从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());
    }
}

​ 常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类中的@Bean方法不同。区别在于@Component类没有使用CGLIB增强,无法拦截方法和字段的调用。 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定义产生直接影响。您可以在非@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)都会将该名称提供给相应的bean定义。

​ 如果这样的注释不包含名称值,或者不包含任何其他检测到的组件(例如,由自定义过滤器发现的组件),则缺省bean名称生成器将返回不使用大写字母的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}

@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

​ 如果不想依赖默认的Bean命名策略,则可以提供自定义Bean命名策略。首先,实现BeanNameGenerator接口,并确保包括默认的no-arg构造函数。然后,在配置扫描程序时提供完全限定的类名,如以下示例注释和Bean定义所示。

如果由于多个自动检测到的组件具有相同的非限定类名称(即,具有相同名称但位于不同程序包中的类)而导致命名冲突,则可能需要配置一个BeanNameGenerator,其默认值为标准名称。生成的bean名称。从Spring Framework 5.2.3开始,位于org.springframework.context.annotation包中的FullyQualifiedAnnotationBeanNameGenerator可以用于此目的。

@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上下文中的“请求”或“会话”与这些范围的预构建批注一样,您也可以使用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>

使用某些非单作用域时,可能有必要为作用域对象生成代理。在作用域Bean中将推理描述为依赖项。为此,在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 提供带有注释的Qualifier元数据

​ 该部分中的示例演示了@Qualifier批注和自定义限定符批注的使用,以在解析自动装配候选时提供细粒度的控制。因为这些示例基于XML bean定义,所以通过使用XML中bean元素的限定符或meta子元素,在候选bean定义上提供了限定符元数据。当依靠类路径扫描来自动检测组件时,可以在候选类上为限定符元数据提供类型级别的注释。下面的三个示例演示了此技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog 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.3.1</version>
        <optional>true</optional>
    </dependency>
</dependencies>

该过程将生成一个包含在jar文件中的META-INF / spring.components文件。

在您的IDE中使用此模式时,spring-context-indexer必须注册为注释处理器,以确保在更新候选组件时索引是最新的。

在类路径上找到META-INF / spring.components时,会自动启用索引。如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建,则可以通过将spring.index.ignore设置为来回退到常规的类路径安排(好像根本没有索引)。 true,可以是系统属性,也可以是classpath根目录下的spring.properties文件。

参考文献

【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】【1.10. Classpath Scanning and Managed Components】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值