10、IOC 之类路径扫描和托管组件

10、IOC 之类路径扫描和托管组件

本章中的大多数示例使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。然而,即使在这些示例中,“base” Bean 定义也在XML文件中显式定义,而注释仅驱动依赖注入。本节描述了通过扫描类路径隐式检测候选组件的选项。候选组件是与过滤条件匹配的类,并且具有向容器注册的相应 Bean 定义。这消除了使用XML执行 Bean 注册的需要。相反,你可以使用注释(例如,@Component)、AspectJ类型表达式或你自己的自定义筛选条件来选择哪些类具有注册到容器的Bean定义。

从Spring3.0开始,SpringJavaConfig项目提供的许多特性都是核心Spring框架的一部分。这允许你使用 Java定义Bean,而不是使用传统的 XML文件。查看 @Configuration@Bean@Import@DependsOn注释,了解如何使用这些新功能的示例。

10.1、@Component 和进一步的原型注释

@Repository注释是满足存储库的角色或构造型的任何类的标记(也称为数据访问对象或 DAO)。此标记的用途之一是异常的自动转换,如异常转换中所述。

Spring提供了更多的原型注释:@Component@Service@Controller

@Component是任何Spring托管组件的通用原型,@Repository@Service@Controller@Component 的专门化,用于更具体的用例(分别在持久层、服务层和表示层)。因此,可以使用 @Component 来注释任意组件类,但是,最好不要这样做,这些注解在未来的更新过程中可能会有不同的差异功能。类似地,如前所述,@Repository已经支持作为持久层中自动异常转换的标记。

10.2、 使用元注释和组合注释

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component 导致 @Service 被以与 @Component 相同的方式对待
public @interface Service {
    // ...
}

还可以组合元注释来创建“组合注释”。例如,Spring MVC中的 @ResponseBody 注释由 @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 {
    // ...
}

10.3、自动检测类并注册Bean定义

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

此外,当使用组件扫描元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 都隐式包含在内。这意味着这两个组件是自动检测并连接在一起的,即所有这些都不需要XML提供的任何bean配置元数据。

可以通过包含值为 falseannotation-config 属性来禁用 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 的注册。

10.4、使用过滤器自定义扫描

默认情况下,被 @Component@Repository@Service@Controller@Configuration 标注的类,或者本身被 @Component 标注的自定义标注是唯一检测到的候选组件。但是,可以通过应用自定义筛选器来修改和扩展此行为。将它们添加为 @ComponentScan 注释的 includeFiltersexcludeFilters 属性(或作为XML配置中 <context:component-scan> 元素的 <context:include-filter /><context:exclude-filter /> 子元素)。每个筛选器元素都需要 typeexpression 属性。过滤选项说明如下表所示:

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

下面的例子展示了忽略所有 @Repository 注释而使用“stub”存储库的配置:

@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注解的类。

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() {
        // 省略组件方法实现
    }
}

前面的类是一个Spring组件,它的 doWork() 方法中有特定于应用程序的代码。但是,它还提供了一个 Bean 定义,该定义有一个引用方法 publicInstance() 的工厂方法。@Bean 注释标识工厂方法和其他 Bean定义属性,例如通过 @Qualifier注释的限定符值。其他可以指定的方法级注释有@Scope@Lazy和自定义限定符注释。

除了用于组件初始化的作用之外,你还可以把 @Lazy 注释放在标记为 @Autowired@Inject 的注入点上。在此上下文中,它会导致惰性解析代理的注入。然而,这种代理方法相当有限。对于复杂的惰性交互,特别是与可选依赖项结合使用时,我们建议改用 ObjectProvider<MyTargetBean>

如前所述,支持自动装配字段和方法,并支持 @Bean 方法的自动装配。下面的例子展示了如何这样做:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // 自定义限定符的使用和方法参数的自动连接
    @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实例的实际创建,而不适用于现有实例的注入。因此,该特性对原型范围的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 类中的对应方法不同。区别在于 @Component 类没有使用 CGLIB 来拦截方法和字段的调用。CGLIB代理是通过调用 @Configuration 类中的 @Bean 方法中的方法或字段来创建对协作对象的Bean元数据引用的方法。这样的方法不是用普通的Java语义调用的,而是通过容器来提供常见的Spring Bean的生命周期管理和代理,甚至在通过对 @Bean 方法的编程调用引用其他Bean时也是如此。相比之下,在普通的 @Component 类中调用 @Bean 方法中的方法或字段具有标准的Java语义,不需要特殊的 CGLIB 处理或应用其他约束。

10.6、自动检测命名组件

当组件作为扫描过程的一部分被自动检测时,它的Bean名是由该扫描程序已知的 BeanNameGenerator 策略生成的。默认情况下,任何包含名称 value 的Spring原型注释(@Component@Repository@Service@Controller)都会将该名称提供给相应的bean定义。

如果这样的注释不包含名称 value 或任何其他检测到的组件(如自定义过滤器发现的组件),默认Bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,则名称为 myMovieListermovieFinderImpl

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

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

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

作为一般规则,每当其他组件可能显式引用注释时,请考虑使用注释指定名称。另一方面,每当容器负责装配时,自动生成的名称就足够了。

10.7、为自动检测组件提供作用域

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

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

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

关于特定于web的作用域的详细信息,比如Spring上下文中的" request “或” session ",请参见request, session, Application和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>

使用某些非单例作用域时,可能需要为作用域内对象生成代理。推理在作用域 Bean 中描述为依赖关系。为此,组件扫描元素上提供了作用域代理属性。三个可能的值是:nointerfacestargetClass。例如,以下配置将生成标准 JDK 动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

10.8、提供带有注释的限定符元数据

@Qualifier注释在使用限定符微调基于注释的自动装配中进行了讨论。该部分中的示例演示了在解析自动装配候选项时使用 @Qualifier 注释和自定义限定符注释来提供细粒度控制。因为这些例子是基于XML Bean定义的,所以通过使用XML中bean元素的 qualifiermeta 子元素,在候选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 在其限定符元数据中提供变体,因为该元数据是按实例而不是按类提供的。

10.9、生成候选组件的索引

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

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

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

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

在 Gradle 4.5 及更早版本中,应在 compileOnly 配置中声明依赖项,如以下示例所示:

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

在 Gradle 4.6 及更高版本中,应在 annotationProcessor 配置中声明依赖项,如以下示例所示:

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

spring-context-indexer工件将生成一个包含在 jar 文件中的 META-INF/spring.components 文件。

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

当在类路径中找到 META-INF/spring.components文件时,将自动启用索引。如果索引对于某些库(或用例)部分可用,但不能为整个应用程序构建索引,那么可以通过将 spring.index.ignore设置为 true(作为 JVM系统属性或通过 SpringProperties机制),回到常规的类路径安排(就好像根本没有索引一样)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值