• Spring系列11:@ComponentScan批量注册bean


    回顾

    在前面的章节,我们介绍了@Comfiguration@Bean结合AnnotationConfigApplicationContext零xml配置文件使用Spring容器的方式,也介绍了通过<context:component-scan base-package="org.example"/>扫描包路径下的bean的方式。如果忘了可以看下前面几篇。这篇我们来结合这2种方式来理解@ComponentScan

    本文内容

    1. @ComponentScan基本原理和使用

    2. @ComponentScan进阶使用

    3. @Componet及其衍生注解使用

    @ComponentScan基本原理和使用

    基本原理

    源码中解析为配置组件扫描指令与@Configuration类一起使用提供与 Spring XML 的 <context:component-scan> 元素同样的作用支持。简单点说,就是可以扫描特定包下的bean定义信息,将其注册到容器中,并自动提供依赖注入。

    @ComponentScan可以对应一下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> 的功能,也就是扫描批量注册并自动DI。

    默认情况下,使用@Component@Repository@Service@Controller@Configuration 注释的类或本身使用@Component 注释的自定义注释是会作为组件被@ComponentScan指定批量扫描到容器中自动注册。

    使用案例

    定义组件

    @Component
    public class RepositoryA implements RepositoryBase {
    }
    
    @Component
    public class Service1 {
        @Autowired
        private RepositoryBase repository;
    }
    

    定义配置类

    @Configuration
    @ComponentScan(basePackages = "com.crab.spring.ioc.demo08")
    public class AppConfig {
    }
    

    容器扫描和使用

        @org.junit.Test
        public void test_component_scan1() {
            AnnotationConfigApplicationContext context =
                    new AnnotationConfigApplicationContext(AppConfig.class);
            Service1 service1 = context.getBean(Service1.class);
            System.out.println(service1);
            context.close();
        }
    

    @ComponentScan进阶使用

    源码简析

    @ComponentScan源码和解析如下

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
    
    	// 见 basePackages
    	@AliasFor("basePackages")
    	String[] value() default {};
    
    	// 指定扫描组件的包路径,为空则默认是扫描当前类所在包及其子包
    	@AliasFor("value")
    	String[] basePackages() default {};
    
    	// 指定要扫描带注释的组件的包 可替换basePackages
    	Class<?>[] basePackageClasses() default {};
    
    	// 用于命名 Spring 容器中检测到的组件的类
    	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
    	// 指定解析bean作用域的类
    	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    
    	// 指示为检测到的组件生成代理的模式
    	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    
    	// 控制符合组件检测条件的类文件;建议使用下面的 includeFilters excludeFilters
    	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
    
    	// 指示是否应启用使用 @Component @Repository @Service  @Controller 注释的类的自动检测。
    	boolean useDefaultFilters() default true;
    
        // 指定哪些类型适合组件扫描。进一步将候选组件集从basePackages中的所有内容缩小到与给定过滤器或多个过滤			器匹配的基本包中的所有内容。
        // <p>请注意,除了默认过滤器(如果指定)之外,还将应用这些过滤器。将包含指定基本包下与给定过滤器匹配的任		何类型,即使它与默认过滤器不匹配
    	Filter[] includeFilters() default {};
    
    	//	定哪些类型不适合组件扫描
    	Filter[] excludeFilters() default {};
    
    	// 指定是否应为延迟初始化注册扫描的 bean
    	boolean lazyInit() default false;
    }
    

    其中用到的Filter类型过滤器的源码和解析如下

    @Retention(RetentionPolicy.RUNTIME)
    	@Target({})
    	@interface Filter {
    
    		// 过滤的类型  支持注解、类、正则、自定义等
    		FilterType type() default FilterType.ANNOTATION;
    
    		@AliasFor("classes")
    		Class<?>[] value() default {};
    
    		// 指定匹配的类型,多个时是OR关系
    		@AliasFor("value")
    		Class<?>[] classes() default {};
    
    		// 用于过滤器的匹配模式,valua没有配置时的替代方法,根据type变化
    		String[] pattern() default {};
    
    	}
    

    FilterType的支持类型如下

    过滤类型 样例表达式 描述
    annotation (default) org.example.SomeAnnotation 在目标组件的类型级别存在的注释。
    assignable org.example.SomeClass 目标组件可分配(扩展或实现)的类(或接口)
    aspectj org.example..*Service+ 要由目标组件匹配的 AspectJ 类型表达式。
    regex org\.example\.Default.* 与目标组件的类名匹配的正则表达式。
    custom org.example.MyTypeFilter org.springframework.core.type.TypeFilter 接口的自定义实现。

    案例1:使用Filters过滤

    忽略所有@Repository 注释并使用特定包下正则表达式来匹配*Repository

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

    案例2:使用自定义的bean名称生成策略

    自定义一个生成策略实现BeanNameGenerator 接口

    /**
     * 自定义的bean名称生成策略
     * @author zfd
     * @version v1.0
     * @date 2022/1/19 9:07
     * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
     */
    public class MyNameGenerator implements BeanNameGenerator {
        public MyNameGenerator() {
        }
    
        @Override
        public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
            // bean命名统一采用固定前缀+类名
            return "crab$$" + definition.getBeanClassName();
        }
    }
    

    @ComponentScan中指定生成名称策略

    @Configuration
    @ComponentScan(basePackages = "com.crab.spring.ioc.demo08",
    nameGenerator = MyNameGenerator.class)
    public class AppConfig {
    }
    

    从 Spring Framework 5.2.3 开始,位于包 org.springframework.context.annotation 中的 FullyQualifiedAnnotationBeanNameGenerator 可用于默认为生成的 bean 名称的完全限定类名称

    测试输出

    @org.junit.Test
    public void test_name_generator() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
        Service1 service1 = context.getBean(Service1.class);
        Arrays.stream(context.getBeanNamesForType(service1.getClass())).forEach(System.out::println);
        System.out.println(service1);
        context.close();
    }
    // bean名称中存在我们自定义的命名了
    crab$$com.crab.spring.ioc.demo08.Service1
    com.crab.spring.ioc.demo08.Service1@769f71a9
    

    案例3:自定义bean的作用域策略

    与一般 Spring 管理的组件一样,自动检测组件的默认和最常见的范围是单例。可以使用@Scope 注解中提供范围的名称,针对单个组件。

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

    针对全部扫描组件,可以提供自定义作用域策略。

    自定义策略实现ScopeMetadataResolver接口

    /**
     * 自定义作用域策略
     * @author zfd
     * @version v1.0
     * @date 2022/1/19 9:32
     * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
     */
    public class MyMetadataResolver implements ScopeMetadataResolver {
        @Override
        public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
            ScopeMetadata metadata = new ScopeMetadata();
            // 指定原型作用域
            metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
            // 代理模式为接口
            metadata.setScopedProxyMode(ScopedProxyMode.INTERFACES);
            return metadata;
        }
    }
    

    @ComponentScan中指定作用域策略

    @Configuration
    @ComponentScan(basePackages = "com.crab.spring.ioc.demo08",
    // nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
    nameGenerator = MyNameGenerator.class,
    scopeResolver = MyMetadataResolver.class
    )
    public class AppConfig {
    }
    

    @Componet及其衍生注解使用

    @Component 是任何 Spring 管理的组件的通用原型注解。在使用基于注释的配置和类路径扫描时,此类类被视为自动检测的候选对象

    @Repository@Service@Controller @Component 针对更具体的用例(分别在持久层、服务层和表示层)的特化。

    简单看一下的注解@Component@Repository定义,其它的类似

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Indexed
    public @interface Component {
    
    	// 指定组件名
    	String value() default "";
    
    }
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component // 元注解@Component
    public @interface Repository {
       @AliasFor(annotation = Component.class)
       String value() default "";
    
    }
    

    使用元注解和组合注解

    Spring 提供的许多注解都可以在您自己的代码中用作元注解。元注释是可以应用于另一个注释的注释。

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

    可以组合元注释来创建“组合注释”,例如@RestController就是@ResponseBody@Controller的组合。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
    	@AliasFor(annotation = Controller.class)
    	String value() default "";
    
    }
    

    组合注释可以选择从元注释中重新声明属性以允许自定义。这在只想公开元注释属性的子集时可能特别有用。

    例如,Spring 的 @SessionScope 注解将作用域名称硬编码为 session,但仍允许自定义 proxyMode。

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Scope(WebApplicationContext.SCOPE_SESSION)
    public @interface SessionScope {
    
        // 重新声明了元注解的属性并赋予了默认值
        @AliasFor(annotation = Scope.class)
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
    
    }
    

    Spring 中对java的注解增强之一: 通过@AliasFor声明注解属性的别名,此机制实现了通过当前注解内的属性给元注解属性赋值。

    总结

    本文介绍各种@ComponentScan批量扫描注册bean的基本使用以及进阶用法和@Componet及其衍生注解使用。

    本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo08
    知识分享,转载请注明出处。学无先后,达者为先!

  • 相关阅读:
    价值观
    周记 改
    周记
    C语言问卷调查
    icon踩坑记录
    console.log(a)和console.log(window.a)的区别?
    记录一次微信二次分享的优化过程
    在jQuery中使用自定义属性
    10个JS技巧
    日常工作总结 2019/10/10
  • 原文地址:https://www.cnblogs.com/kongbubihai/p/15894719.html
Copyright © 2020-2023  润新知