2.10 Spring Framework 5.x 之类路径扫描和组件管理

1.10. Classpath 扫描 和 Components 管理

本章中的大多数示例都使用XML来指定BeanDefinition在Spring容器中生成每个元素的配置元数据。上一节(基于注释的容器配置)演示了如何通过源级注解提供大量配置元数据。但是,即使在这些示例中,“基本”bean定义也在XML文件中显式定义,而注解仅驱动依赖项注入。本节介绍通过扫描类路径隐式检测候选组件的选项。候选组件是与过滤条件匹配的类,并且具有向容器注册的相应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注解是针对满足的存储库(也被称为Data Access Object————数据访问对象或DAO)的作用或者固定型的任何类的标记。此标记的用途之一是异常的自动转换,如 异常转换中所述。

Spring提供进一步典型化注解:@Component,@Service,和 @Controller。@Component是任何Spring管理组件的通用构造型。 @Repository,@Service和,@Controller是@Component更具体的用例的专业化(分别在持久性,服务和表示层)。因此,您可以来注解你的组件类有 @Component,但是,通过与注解它们@Repository,@Service或者@Controller ,你的类能更好地被工具处理,或与切面进行关联。例如,这些刻板印象注释成为切入点的理想目标。@Repository,@Service并且@Controller还可以在Spring Framework的未来版本中携带其他语义。因此,如果您在使用之间进行选择@Component或者@Service对于您的服务层,@Service显然是更好的选择。同样,如前所述,@Repository已经支持将其作为持久层中自动异常转换的标记。

1.10.2 使用元注释和组合注释

Spring提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如,@Service提及的注解前面是间注解有 @Component,如下面的示例所示:

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

    // ....
}

要以同样的方式对待的Component原因。@Service@Component
您还可以组合元注解以创建“组合注解”。例如,@RestControllerSpring MVC 的注解由@Controller和 组成@ResponseBody。

此外,组合注解可以选择从元注解重新声明属性以允许自定义。当您只想公开元注解属性的子集时,这可能特别有用。例如,Spring的 @SessionScope注解将范围名称硬编码为session但仍允许自定义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 {
    // ...
}

有关更多详细信息,请参阅 Spring Annotation Programming Model wiki页面。

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

Spring可以自动检测构造型类并使用它注册相应的 BeanDefinition实例ApplicationContext。例如,以下两个类符合此类自动检测的条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

使用context:component-scan隐式启用功能 context:annotation-configcontext:annotation-config使用时通常不需要包含 元素context:component-scan

扫描类路径包需要在类路径中存在相应的目录条目。使用Ant构建JAR时,请确保不要激活JAR任务的仅文件开关。此外,在某些环境中,可能不会基于安全策略公开类路径目录 - 例如,JDK 1.7.0_45及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library”) - 请参阅 http://stackoverflow.com/ questions / 19394570 / java-jre-7u45-breaks-classloader-getresources)。

在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在module-info 描述符中导出组件类。如果您希望Spring调用类的非公共成员,请确保它们已“打开”(即,它们 在描述符中使用opens声明而不是exports声明module-info)。

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

您可以禁用注册,AutowiredAnnotationBeanPostProcessor并将 CommonAnnotationBeanPostProcessorannotation-config属性包含在值中false。

1.10.4。使用过滤器自定义扫描

默认情况下,类注有@Component,@Repository,@Service, @Controller,或者本身都标注有一个自定义的注释@Component是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。他们加为includeFilters或excludeFilters 的参数@ComponentScan注解(或include-filter或exclude-filter 在的子元素component-scan元素)。每个过滤器元素都需要type 和expression属性。下表介绍了筛选选项:

表5.过滤器类型
过滤器类型

过滤器类型 示例表达 描述
注释(默认) org.example.SomeAnnotation 要在目标组件中的类型级别出现的注释
分配 org.example.SomeClass 目标组件可分配给(扩展或实现)的类(或接口
AspectJ的 org.example…*Service+ 要由目标组件匹配的AspectJ类型表达式。
正则表达式 org.example.Default.* 要由目标组件类名匹配的正则表达式。
习惯 org.example.MyTypeFilter org.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,或@Configuration。

1.10.5 在组件中定义Bean元数据

Spring组件还可以向容器提供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);
    }
}

该示例将Stringmethod参数自动装配country到age 另一个名为的bean 的属性值privateInstance。Spring Expression Language元素通过符号定义属性的值#{ }。对于@Value 注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。

从Spring Framework 4.3开始,您还可以声明类型的工厂方法参数 InjectionPoint(或其更具体的子类:) DependencyDescriptor来访问触发创建当前bean的请求注入点。请注意,这仅适用于实例创建bean实例,而不适用于注入现有实例。因此,此功能对原型范围的bean最有意义。对于其他作用域,工厂方法只能看到触发在给定作用域中创建新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类。不同之处在于@Component ,CGLIB不会增强类来拦截方法和字段的调用。CGLIB代理是调用类中@Bean方法中的方法或字段@Configuration创建对协作对象的bean元数据引用的方法。这些方法不是使用普通的Java语义调用的,而是通过容器来提供通常的生命周期管理和Spring bean的代理,即使通过对@Bean方法的编程调用引用其他bean也是如此。相反,@Bean在plain 中的方法中调用方法或字段@Component class具有标准的Java语义,没有特殊的CGLIB处理或其他约束应用。

您可以将@Bean方法声明为static,允许在不创建包含配置类作为实例的情况下调用它们。这在定义后处理器bean(例如,类型BeanFactoryPostProcessor或 类型BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期初始化,并且应避免在此时触发配置的其他部分。

由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,甚至在@Configuration类中也不会被拦截(如本节前面所述):CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。

方法的Java语言可见性@Bean不会立即影响Spring容器中的结果bean定义。您可以根据需要在非@Configuration类中自由声明工厂方法,也可以在任何地方自由声明静态方法。但是,类中的常规@Bean方法@Configuration需要可覆盖 - 也就是说,它们不能声明为private或final。

@Bean还可以在给定组件或配置类的基类上以及在由组件或配置类实现的接口中声明的Java 8缺省方法上发现方法。这使得组成复杂配置安排具有很大的灵活性,从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 接口,并确保包含默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名,如以下示例注解和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 为自动检测组件提供范围

与Spring管理的组件一样,自动检测组件的默认和最常见的范围是singleton。但是,有时您需要一个可以由@Scope注释指定的不同范围。您可以在注释中提供范围的名称,如以下示例所示:

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

@Scope注释仅在具体bean类(用于注释组件)或工厂方法(用于@Bean方法)上进行内省。与XML bean定义相比,没有bean定义继承的概念,类级别的继承层次结构与元数据目的无关。

有关特定于Web的范围(如Spring上下文中的“request”或“session”)的详细信息,请参阅请求,会话,应用程序和WebSocket范围。与这些范围的预构建注释一样,您也可以使用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>

使用某些非单例范围时,可能需要为范围对象生成代理。这种推理在Scoped Beans中描述为依赖关系。为此,组件扫描元素上提供了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中 元素的qualifier或meta元素bean元素在候选bean定义上提供限定符元数据。当依靠类路径扫描来自动检测组件时,您可以在候选类上为类型级注释提供限定符元数据。以下三个示例演示了此技术:

@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在其限定符元数据中提供变体,因为每个元数据都是按照 - 实例而不是每班。

1.10.9 生成候选组件索引

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

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

要生成索引,请为包含作为组件扫描指令目标的组件的每个模块添加其他依赖项。以下示例显示了如何使用Maven执行此操作:

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

以下示例显示了如何使用Gradle执行此操作:

dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.1.4.RELEASE")
}

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

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

META-INF/spring.components在类路径中找到 a时,将自动启用索引。如果索引部分可用一些库(或用例),但整个应用程序无法建立,可以通过设置回退到普通类路径安排(好像没有索引存在的话)spring.index.ignore来 true,无论是作为一个系统属性或spring.properties类路径根目录下的文件。

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86439709