Spring基础教程—Classpath扫描和管理的组件

简介: Spring基础教程—Classpath扫描和管理的组件

本章中的大多数例子都使用XML来指定配置元数据,在Spring容器中产生每个 BeanDefinition。上一节(基于注解的容器配置)演示了如何通过源级注解提供大量的配置元数据。然而,即使在这些例子中,"基础" Bean定义也是在XML文件中明确定义的,而注解只驱动依赖性注入。本节描述了一个通过扫描classpath来隐式检测候选组件的选项。候选组件是与过滤器标准相匹配的类,并且有一个在容器中注册的相应的bean定义。这样就不需要使用 XML 来执行 bean 注册了。相反,你可以使用注解(例如 @Component)、AspectJ类型表达式或你自己的自定义过滤标准来选择哪些类在容器中注册了Bean定义。

你可以使用Java来定义 Bean,而不是使用XML文件。看看 @Configuration、@Bean、@Import 和 @DependsOn 注解,看看如何使用这些功能的例子。

1.10.1.@Component和进一步的 Stereotype 注解

@Repository 注解是任何满足 repository(也被称为数据访问对象或 DAO)角色或 stereotype 的类的标记。这个标记的用途包括异常的自动翻译,如 异常翻译 中所述。

Spring提供了更多的 stereotype 注解。@Component, @Service, 和 @Controller。 @Component 是一个通用的stereotype,适用于任何Spring管理的组件。@Repository、 @Service 和 @Controller 是 @Component 的特殊化,用于更具体的使用情况(分别在持久层、服务层和表现层)。因此,你可以用 @Component 来注解你的组件类,但是,通过用 @Repository、@Service 或 @Controller 来注解它们,你的类更适合于被工具处理或与切面关联。例如,这些stereotype注解是指向性的理想目标。在Spring框架的未来版本中,@Repository、@Service 和 @Controller 还可以携带额外的语义。因此,如果你要在服务层使用 @Component 或 @Service 之间进行选择,@Service 显然是更好的选择。同样地,如前所述,@Repository 已经被支持作为持久层中自动异常翻译的标记。

1.10.2. 使用元注解和组合注解

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

Java

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

@Component 使 @Service 与 @Component 的处理方式相同。

你也可以结合元注解来创建 “composed annotations”(组合注解)。例如,Spring MVC的 @RestController 注解是由 @Controller 和 @ResponseBody 组成。

此外,组合注解可以选择性地重新声明来自元注解的属性以允许定制。当你想只暴露元注解的一个子集的属性时,这可能特别有用。例如,Spring的 @SessionScope 注解将 scope 名称硬编码为 session,但仍然允许自定义 proxyMode。下面的列表显示了 SessionScope 注解的定义。

Java

@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,如下所示。

Java

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

你也可以覆盖 proxyMode 的值,如下例所示。

Java

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

更多细节,请参见 Spring 注解编程模型 维基页面。

1.10.3. 自动检测类和注册Bean定义

Spring可以自动检测 stereotype 的类,并在 ApplicationContext 中注册相应的 BeanDefinition 实例。例如,以下两个类符合这种自动检测的条件。

Java

@Service
public class SimpleMovieLister {
    private MovieFinder movieFinder;
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

Java

@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

为了自动检测这些类并注册相应的Bean,你需要在你的 @Configuration 类中添加 @ComponentScan,其中 basePackages 属性是这两个类的共同父包。(或者,你可以指定一个用逗号或分号或空格分隔的列表,其中包括每个类的父包。)

Java

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

扫描classpath包需要classpath中存在相应的目录项。当你用Ant构建JAR时,确保你没有激活JAR任务的 files-only 开关。另外,在某些环境中,根据安全策略,classpath目录可能不会被暴露—例如,JDK 1.7.0_45及以上版本的独立应用程序(这需要在清单中设置 'Trusted-Library',见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。在JDK 9的模块路径(Jigsaw)上,Spring的classpath扫描一般都能按预期工作。但是,请确保你的组件类在你的 module-info 描述符中被导出。如果你希望Spring调用你的类中的非public成员,请确保它们是 "开放的"(也就是说,它们在你的 module-info 描述符中使用 opens 声明而不是 exports 声明)。

此外,当你使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都被隐含地包括在内。这意味着这两个组件被自动检测并连接在一起—所有这些都不需要在XML中提供任何bean配置元数据。

你可以通过包含值为 false 的 annotation-config 属性来禁用 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 的注册。

1.10.4. 使用Filter来自定义扫描

默认情况下,用 @Component、@Repository、@Service、@Controller、 @Configuration 注解的类,或者本身用 @Component 注解的自定义注解是唯一被检测到的候选组件。然而,你可以通过应用自定义 filter 来修改和扩展这种行为。将它们作为 @ComponentScan 注解的 includeFilters 或 excludeFilters 属性(或作为XML配置中 <context:component-scan> 元素的 <context:include-filter /> 或 <context:exclud-filter /> 子元素)。每个 filter 元素都需要 type 和 expression 属性。下表描述了过滤选项。

Table 5. Filter Type

Filter Type

示例表达式

说明

注解 (默认)

org.example.SomeAnnotation

一个注解在目标组件中的类型级别是 presentmeta-present

可指定

org.example.SomeClass

目标组件可分配给(继承或实现)的一个类(或接口)。

aspectj

org.example..*Service+

要被目标组件匹配的 AspectJ type 表达式。

regex

org\.example\.Default.*

一个与目标组件的类名相匹配的 regex expression。

自定义

org.example.MyTypeFilter

org.springframework.core.type.TypeFilter 接口的自定义实现。

下面的例子显示了配置忽略了所有 @Repository 注解,而使用 “stub” repository。

Java

@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-scan/> 元素的属性来禁用默认过滤器。这将有效地禁止对用 @Component、@Repository、@Service、@Controller、 @RestController 或 @Configuration 注解或元注解的类进行自动检测。

1.10.5. 在组件中定义Bean元数据

Spring组件也可以向容器贡献Bean定义元数据。你可以用用于在 @Configuration 注解的类中定义Bean元数据的相同的 @Bean 注解来做到这一点。下面的例子展示了如何做到这一点。

Java

@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 注解标识了 qualifier 值。其他可以指定的方法级注解有 @Scope、@Lazy 和自定义 qualifier 注解。

除了对组件初始化的作用,你还可以将 @Lazy 注解放在标有 @Autowired 或 @Inject 的注入点上。在这种情况下,它导致了一个延迟解析的代理的注入。然而,这种代理方法是相当有限的。对于复杂的懒加载交互,特别是与可选的依赖关系相结合,我们推荐使用 ObjectProvider<MyTargetBean> 来代替。

如前所述,支持自动注入的字段和方法,并额外支持 @Bean 方法的自动注入。下面的例子展示了如何做到这一点。

Java

@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 方法的参数 country 自动注入到另一个名为 privateInstance 的bean上的 age 属性值。一个Spring表达式语言元素通过符号 #{ <expression> } 定义了该属性的值。对于 @Value 注解,表达式解析器被预设为在解析表达式文本时寻找Bean名称。

从Spring Framework 4.3开始,你也可以声明一个 InjectionPoint(或其更具体的子类: DependencyDescriptor)类型的工厂方法参数来访问触发创建当前Bean的请求注入点。请注意,这只适用于Bean实例的实际创建,而不适用于现有实例的注入。因此,这个功能对 prototype scope 的Bean最有意义。对于其它scope,工厂方法只看到在给定scope中触发创建新 bean 实例的注入点(例如,触发创建 lazy singleton bean 的依赖关系)。在这种情况下,你可以在语义上注意使用所提供的注入点元数据。下面的例子显示了如何使用 InjectionPoint。

Java

@Component
public class FactoryMethodComponent {
    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

普通Spring组件中的 @Bean 方法与Spring @Configuration 类中的对应方法的处理方式不同。区别在于,@Component 类没有用CGLIB来拦截方法和字段的调用。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。

@Bean 方法也可以在特定组件或配置类的基类上发现,也可以在组件或配置类实现的接口中声明的Java 8默认方法上发现。这使得组成复杂的配置安排有了很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8默认方法进行多重继承。

最后,一个类可以为同一个Bean持有多个 @Bean 方法,作为对多个工厂方法的安排,根据运行时的可用依赖关系来使用。这与在其他配置情况下选择 "最环保" 的构造函数或工厂方法的算法相同。在构造时选择具有最大数量的可满足的依赖关系的变量,类似于容器在多个 @Autowired 构造函数之间进行选择。

1.10.6. 命名自动检测的组件

当一个组件作为扫描过程的一部分被自动检测到时,它的Bean名称由该扫描器已知的 BeanNameGenerator 策略生成。默认情况下,任何包含 name value 的Spring stereotype 注解(@Component、@Repository、@Service 和 @Controller)都会向相应的Bean定义提供该名称。

如果这样的注解不包含 name value,或者对于任何其他检测到的组件(比如那些由自定义过滤器发现的组件),默认的bean类名称生成器会返回未加大写的非限定类名称。例如,如果检测到以下组件类,其名称将是 myMovieLister 和 movieFinderImpl。

Java

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

Java

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

如果你不想依赖默认的 Bean 命名策略,你可以提供一个自定义的 Bean 命名策略。首先,实现 BeanNameGenerator 接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描器时提供全路径类名,正如下面的注解和 Bean 定义示例所示。

如果你由于多个自动检测的组件具有相同的非限定类名称(即具有相同名称的类,但驻留在不同的包中)而遇到命名冲突,你可能需要配置一个 BeanNameGenerator,它默认为生成的Bean名称的完全限定类名称。从Spring Framework 5.2.3开始,位于包 org.springframework.context.annotation 中的 FullyQualifiedAnnotationBeanNameGenerator 可以用于此类目的。

Java

@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

与一般的Spring管理的组件一样,自动检测的组件的默认和最常见的scope是 singleton。然而,有时你需要一个不同的scope,可以通过 @Scope 注解来指定。你可以在注解中提供scope的名称,如下面的例子所示。

Java

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

@Scope 注解只对具体的Bean类(对于注解的组件)或工厂方法(对于 @Bean 方法)进行内省。与XML bean定义相比,没有bean定义继承的概念,而且类级别的继承层次与元数据的目的无关。

有关Spring context 中 “request” 或 “session” 等Web特定 scope 的详细信息,请参阅 Request、 Session、 Application 和 WebSocket Scope。与这些 scope 的预制注解一样,你也可以通过使用Spring的元注解方法来组成你自己的 scope 注解:例如,用 @Scope("prototype") 元注解的自定义注解,可能还会声明一个自定义 scope 代理(scoped-proxy)模式。

为了给 scope 解析提供一个自定义的策略,而不是依赖基于注解的方法,你可以实现 ScopeMetadataResolver 接口。请确保包含一个默认的无参数构造函数。然后你可以在配置扫描器时提供全路径的类名,正如下面这个注解和 bean 定义的例子所示。

Java

@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>

当使用某些非 singleton scope 时,可能需要为 scope 对象生成代理。这在 “作为依赖的 Scope Bean”中有所描述。为了这个目的,在组件扫描元素上有一个 scoped-proxy 属性。三个可能的值是:no、interfaces 和 targetClass。例如,以下配置的结果是标准的JDK动态代理。

Java

@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 注解在 “用 Qualifiers 微调基于注解的自动注入” 中讨论过。那一节中的例子演示了如何使用 @Qualifier 注解和自定义 qualifier 注解来提供细粒度的控制,当你解决自动注入候选对象时。因为这些例子是基于XML Bean定义的,qualifier 元数据是通过使用XML中 bean 元素的 qualifier 或 meta 子元素提供给候选Bean定义的。当依靠classpath扫描来自动检测组件时,你可以在候选类上用类型级注解来提供 qualifier 元数据。下面的三个例子演示了这种技术。

Java

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

Java

@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

Java

@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

与大多数基于注解的替代方案一样,请记住,注解元数据是与类定义本身绑定的,而XML的使用允许同一类型的多个Bean在其限定符元数据中提供变化,因为该元数据是按实例而不是按类提供的。

1.10.9. 生成一个候选组件的索引

虽然classpath扫描非常快,但通过在编译时创建一个静态的候选列表,可以提高大型应用程序的启动性能。在这种模式下,所有作为组件扫描目标的模块都必须使用这种机制。

你现有的 @ComponentScan 或 <context:component-scan/> 指令必须保持不变,以请求上下文扫描某些包中的候选者。当 ApplicationContext 检测到这样的索引时,它会自动使用它而不是扫描classpath。

要生成索引,请为每个包含组件的模块添加一个额外的依赖,这些组件是组件扫描指令的目标。下面的例子说明了如何用Maven做到这一点。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>6.0.8-SNAPSHOT</version>
        <optional>true</optional>
    </dependency>
</dependencies>

对于Gradle 4.5和更早的版本,应该在 compileOnly 配置中声明该依赖关系,如下面的例子所示。

dependencies {
    compileOnly "org.springframework:spring-context-indexer:6.0.8-SNAPSHOT"
}

在Gradle 4.6及以后的版本中,应该在 annotationProcessor 配置中声明该依赖,如以下例子所示。

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:6.0.8-SNAPSHOT"
}

spring-context-indexer 工件会生成一个 META-INF/spring.component 文件,并包含在jar文件中。

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

当在classpath上发现 META-INF/spring.component 文件时,索引会自动启用。如果索引对某些库(或用例)是部分可用的,但不能为整个应用程序建立索引,你可以通过将 spring.index.ignore 设置为 true,或者通过 SpringProperties 机制,退回到常规的 classpath 安排(就像根本没有索引存在一样)。


目录
相关文章
|
2天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
37 0
|
2天前
|
监控 Java API
掌握 Spring Boot AOP:使用教程
Spring Boot 中的面向切面编程(AOP)为软件开发提供了一种创新方法,允许开发者将横切关注点与业务逻辑相分离。这不仅提高了代码的复用性和可维护性,而且还降低了程序内部组件之间的耦合度。下面,我们深入探讨如何在 Spring Boot 应用程序中实践 AOP,以及它为项目带来的种种益处。
|
2天前
|
Java Spring 容器
【Java】Spring如何扫描自定义的注解?
【Java】Spring如何扫描自定义的注解?
41 0
|
2天前
|
Kubernetes Java 容器
部署 Spring Boot 应用到 K8S 教程
部署 Spring Boot 应用到 K8S 教程
70 0
|
2天前
|
开发框架 监控 Java
深入探索Spring Boot的监控、管理和测试功能及实战应用
【5月更文挑战第14天】Spring Boot是一个快速开发框架,提供了一系列的功能模块,包括监控、管理和测试等。本文将深入探讨Spring Boot中监控、管理和测试功能的原理与应用,并提供实际应用场景的示例。
15 2
|
2天前
|
Java 测试技术 API
Spring Boot 单元测试 0基础教程
Spring Boot 单元测试 0基础教程
11 0
|
2天前
|
Java 关系型数据库 MySQL
优质全套Spring全套教程三
优质全套Spring全套教程三
|
2天前
|
XML Java 数据格式
优质全套Spring全套教程二
优质全套Spring全套教程二
|
2天前
|
XML Java 数据格式
优质全套Spring全套教程一
优质全套Spring全套教程一
|
2天前
|
安全 Java 数据库
第4章 Spring Security 的授权与角色管理(2024 最新版)(下)
第4章 Spring Security 的授权与角色管理(2024 最新版)
13 0