IoC容器篇(十)——类路径扫描与组件管理

目录

类路径扫描与组件管理

1.@Component与构造型注解

2.元注解

3.自动检测类与注册bean定义

4.使用过滤器定制扫描

5.通过组件定义bean元数据

6.命名被自动检测的组件

7.为被自动检测的组件提供范围

8.通过注解提供qualifier元数据

9.生成候选组件目录

学习源:Spring官方文档


类路径扫描与组件管理

除了将基础的bean定义显式定义在xml文件中,还可以选择通过扫描类路径隐式查找候选组件。

候选组件是针对过滤条件匹配的类,并且在容器中注册有一致的bean定义。

通过这种方法,就可以不使用xml完成bean的注册,而是通过使用注解(eg:@Component),AspectJ类型表达式或自定义的过滤条件来选择那些类将作为bean注册在容器中。

1.@Component与构造型注解

Spring提供了构造型注解。eg:@Component,@Service,@Controller,@Repository

@Component是一个可以用于任意Spring管理对象的通用构造型。

@Service、@Controller、@Repository是@Component的特殊化,用于特定的使用情况。

使用特殊化标签取代@Component可以令类更适合通过工具运行或关联切面。

eg:部分构造型注解提供了理想目标的切入点,而@Service、@Controller、@Repository在未来发布的Spring框架中,可能会携带新的额外语义。

2.元注解

Spring框架提供的注解许多可以用作源代码的元注解。

元注解是可以应用于其他注解的简单注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

元注解可以进行结合用于创建组合注解。

eg:SpringMVC的@RestController注解由@Controller与@ResponseBody组成。

组合注解可以任意重新声明来自元注解的属性,以允许用户定制。这种方法用于允许开发者只露出注解的部分属性。

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

}

3.自动检测类与注册bean定义

Spring可以自动检查被构造型注解的类,并且将它们的BeanDefinition注册进ApplicationContext。

@Component
public class Example {

}
@Service
public class ExampleService {

}

note:@Component构造型注解Example类,@Service构造型注解ExampleService。

@Configuration
@ComponentScan(basePackages = "examples")
public class AppConfig {

}

note:在java配置类AppConfig中设置,应用启动自动检测,检测包examples下的类。

ps:@ComponentScan的属性basePackages可以设置为一个由逗号/分号/空格分隔的包列表。

ps:@ComponentScan("examples")也可以。

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

note:通过基于xml的配置,启动自动搜索。

ps:使用<context:component-scan>元素会隐式启动<context:annotation-config>的功能。所以,可以省略<context:annotation-config>的使用。

ps:对类路径的扫描要求classpath中存在一致完整的目录。

ps:当启用组件扫描元素之后,AutowiredAnnotationBeanPostProcessor与CommonAnnotationBeanPostProcessor将会被隐式包含。

ps:可以通过将annotation-config属性的值设置为false,阻止AutowiredAnnotationBeanPostProcessor与CommonAnnotationBeanPostProcessor的注册。

4.使用过滤器定制扫描

默认情况下,由@Component,@Service,@Controller,@Repository或被@Component注解的定制注解注解的类才会成为被检测的候选组件。

通过应用定制过滤器可以修改与扩展以上行为。

基于java的配置,通过应用@ComponentScan的includeFilters与exculdeFilters参数可以添加过滤器。

基于xml的配置,通过应用component-scan元素的include-filter子元素与exculde-filter子元素可以添加过滤器。

每一个过滤器元素要求type属性与expression属性。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

note:基于java配置设置过滤器,忽略所有的@Repository注解,扫描所有"stub"的repository。

ps:设置注解的useDefaultFilters=false关闭默认过滤器。

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

note:基于xml配置设置过滤器,忽略所有的@Repository注解,扫描所有"stub"的repository。

ps:设置<component-scan>元素的属性use-default-filters="false"关闭默认过滤器。

5.通过组件定义bean元数据

Spring组件可以给容器提供bean定义元数据。

@Component
public class FactoryMethodComponent {
    
    @Bean
    public Example getInstance() {
        return new Example();
    }
}

note:FactoryMethodComponent组件通过@Bean注解提供一个Example bean。

ps:@Qualifier可以被应用于@Bean注解的方法。

ps:@Scope、@Lazy等方法层次的注解也可以被应用。

ps:@Lazy通过应用于@Autowired、@Inject标识的注入点,可以导致怠惰解析代理的注入。

@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);
    }
}

note:@Value注解将名为privateInstance的bean的age属性注入country参数。

ps:@Bean注解的方法将会被应用自动注入。

@Component
public class FactoryMethodComponent {

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

ps:Spring框架4.3之后,可以在工厂方法之中声明InjectionPoint类型的参数(或它的特定子类DependencyDescriptor),用于访问触发了当前bean创建的注入点。注意,是触发了创建而不是已存在实例的注入。

Spring组件中的@Bean方法运行与@Configure类中的@Bean的类不同。

组件类不能通过CGLIB拦截对方法与域的调用。

CGLIB代理是一种通过@Configuration类中@Bean方法的调用方法与域创建协作对象的bean元数据的方法。

方法不是通过通常java语义,而是通过容器来调用的,用来提供通常的生命周期管理域beans代理,即使当通过编程方法调用@Bean方法来引用其他beans。

可以将@Bean方法声明为static,使得它们被调用时,不用创建包含这些配置的类的实例。

这方法对于需要初始化早于容器生命周期的并且避免触发此阶段其他配置的post-processor beans很有意义。

对静态@Bean方法的调用不会被容器拦截,即使配置在@Configure类中。结果,直接对其他@Bean方法调用将具有标准Java语义,即将会直接从工厂方法返回一个独立实例。

@Bean方法的Java语言可见性不会直接影响容器中生成的bean定义,@Configuration类中的@Bean方法由于可能需要被重载,所以不要声明为private或final。

@Bean方法除了可以在给定的组件或配置类中被发现,还可以为在由组件或配置类实现的接口中声明的default方法。

一个class可以有多个相同bean的@Bean方法,具体选择哪一个方法取决于运行时有效的依赖。与选择"greediest"构造器或工厂方法的算法相同,选择能满足最多数量依赖的@Bean方法。

6.命名被自动检测的组件

当一个组件在扫描过程中被自动探测到时,它的名字由BeanNameGenerator策略生成,然后告知给扫描仪。

默认情况下,任何包含名称值的Spring构造型注解(eg:@Component、@Service、@Controller、@Repository)将会把传入的值作为bean名称送入bean定义。 

默认情况下,注解在没有设置值的情况下,bean名为首字母小写的非限定类名。(即,首字母小写的不含包名的非完整类名。)

@Service("exa")
public class Example {
 
}
@Component
public class Example {

}

note:第一的Example的bean名为exa,第二个Example的bean名为example。

自定义命名策略

  1. 实现一个BeanNameGenerator接口并包含一个无参的默认构造器
  2. 将这个命名策略的完整限定类名传入配置扫描器

 

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

note:java注解方式配置自定义命名策略,xml注解方法配置自定义命名策略。

通常情况下

  • 每当其他组件可能显式引用当前组件时,考虑使用注解指定bean名称
  • 每当容器负责注入时,自动生成的名字便足以使用

7.为被自动检测的组件提供范围

通常情况下,被自动检测的组件的默认最通用范围为singleton。

@Scope("prototype")
@Component
public class Example {

}

note:将Example组件的范围设置为prototype。

ps:@Scope注解只在具体类(被注解组件)或工厂方法(@Bean方法)上有效,不像xml配置,继承不会继承注解设置的范围。

ps:可以通过Spring元注解组合出自定义的范围注解,定制的组合注解也可以设置代理模式。

自定义范围解析策略

  1. 实现一个ScopeMetadataResolver接口并包含一个无参的默认构造器
  2. 将这个范围解析策略的完整限定类名传入配置扫描器
@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>
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

note:通过scoped-proxy属性设置JDK动态代理。

8.通过注解提供qualifier元数据

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

note:第一个使用@Qualifier注解设置qualifier,第二个使用定制qualifier注解设置qualifier,第三个使用定制无值qualifier注解设置qualifier。

ps:注解元数据绑定在类定义,与之相对,xml允许同一类型的多个beans提供不同的qualifier元数据,因为qualifier元数据绑定在实例上,而不是类上。

9.生成候选组件目录

可以通过在编辑阶段为大型应用创建静态候选组件列表的方式提升启动性能。

在这种模式下,应用的所有组件都必需使用这一机制,这样,ApplicationContext将会自动检测生成的目录,而不是搜索类路径。

每一个包含被扫描组件的模块中需要包含依赖:

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

这一机制将会在jar文件中生成META-INF/spring.components文件。

在IDE中开发时,为了保证候选组件更新时,index同时更新数据,spring-context-indexer需要被作为注解处理器注册。

将系统属性或classpath下的spring.properties文件中的spring.index.ignore设置为true,这样,当部分库可以构建目录但完整应用无法构建目录时,可以自动启动通常的组建扫描。


学习源:Spring官方文档

猜你喜欢

转载自blog.csdn.net/qq_32165041/article/details/82531303