详解Spring系列之@ComponentScan批量注册bean

目录
  • 回顾
  • 本文内容
  • @ComponentScan基本原理和使用
    • 基本原理
    • 使用案例
    • 定义配置类
    • 容器扫描和使用
  • @ComponentScan进阶使用
    • 源码简析
    • 案例1:使用Filters过滤
    • 案例2:使用自定义的bean名称生成策略
    • 案例3:自定义bean的作用域策略
  • @Componet及其衍生注解使用
    • 使用元注解和组合注解
  • 总结

回顾

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

本文内容

@ComponentScan基本原理和使用

@ComponentScan进阶使用

@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
知识分享,转载请注明出处。学无先后,达者为先!

到此这篇关于详解Spring系列之@ComponentScan批量注册bean的文章就介绍到这了,更多相关Spring @ComponentScan批量注册bean内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • @ComponentScan在spring中无效的原因分析及解决方案

    目录 @ComponentScan在spring中无效 查了大量资料之后,找到了原因 @Component和@ComponentScan常规理解 @Component和@ComponentScan的联系 @SpringBootApplication和@ComponentScan,扫描包的区别 @ComponentScan在spring中无效 在我实现第一个spring AOP程序的时候,我按照主流的推荐,采用注解@ComponentScan @Aspect @Before 来实现一个切面. 让我

  • SpringBoot中@ComponentScan的使用详解

    目录 SpringBoot @ComponentScan的使用 SpringBoot @ComponentScan 作用 SpringBoot @ComponentScan的使用 SpringBoot的启动类中有一个@ComponentScan,之前项目由于这个注解造成打包失败,这里对于这个注解进行总结,防止下次遇到这个问题再被难住. 其实这个注解主要是针对于第三方jar包中注解的应用. 如果第三方包中没有使用注解那么就完全不需要使用这个注解 使用方式如图所示,这里扫描的是 maven项目的依赖

  • SpringBoot默认包扫描机制及@ComponentScan指定扫描路径详解

    目录 SpringBoot默认包扫描机制 @ComponentScan的使用 常用参数含义 @Component与@ComponentScan SpringBoot默认包扫描机制 标注了@Component和@Component的衍生注解如@Controller,@Service,@Repository就可以把当前的Bean加入到IOC容器中.那么SpringBoot是如何知道要去扫描@Component注解的呢.@ComponentScan做的事情就是告诉Spring从哪里找到bean Spr

  • 详解Spring系列之@ComponentScan批量注册bean

    目录 回顾 本文内容 @ComponentScan基本原理和使用 基本原理 使用案例 定义配置类 容器扫描和使用 @ComponentScan进阶使用 源码简析 案例1:使用Filters过滤 案例2:使用自定义的bean名称生成策略 案例3:自定义bean的作用域策略 @Componet及其衍生注解使用 使用元注解和组合注解 总结 回顾 在前面的章节,我们介绍了@Comfiguration和@Bean结合AnnotationConfigApplicationContext零xml配置文件使用S

  • 详解Spring系列之@ComponentScan自动扫描组件

    目录 无注解方式component-scan使用 注解方式@ComponentScan使用 @ComponentScan的扫描规则 无注解方式component-scan使用 之前,我们需要扫描工程下一些类上所标注的注解,这些常用注解有: @Controller,@Service,@Component,@Repository 通过在Spring的配置文件中配置<context:component-scan>扫描对应包下扫描这些注解的方式: <beans xmlns="http:

  • 详解Spring Boot 使用Java代码创建Bean并注册到Spring中

    从 Spring3.0 开始,增加了一种新的途经来配置Bean Definition,这就是通过 Java Code 配置 Bean Definition. 与Xml和Annotation两种配置方式不同点在于: 前两种Xml和Annotation的配置方式为预定义方式,即开发人员通过 XML 文件或者 Annotation 预定义配置 bean 的各种属性后,启动 spring 容器,Spring 容器会首先解析这些配置属性,生成对应都?Bean Definition,装入到 DefaultL

  • 详解Spring Cloud Consul 实现服务注册和发现

    Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中涉及的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式.通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂.易部署和易维护的分布式系统开发工具包. Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Sprin

  • 详解Spring 中如何控制2个bean中的初始化顺序

    开发过程中有这样一个场景,2个 bean 初始化逻辑中有依赖关系,需要控制二者的初始化顺序.实现方式可以有多种,本文结合目前对 Spring 的理解,尝试列出几种思路. 场景 假设A,B两个 bean 都需要在初始化的时候从本地磁盘读取文件,其中B加载的文件,依赖A中加载的全局配置文件中配置的路径,所以需要A先于B初始化,此外A中的配置改变后也需要触发B的重新加载逻辑,所以A,B需要注入彼此. 对于下面的模型,问题简化为:我们需要initA()先于initB()得到执行. @Service pu

  • 详解spring如何使用注解开发

    在Spring4之后,要使用注解开发,必须要保证aop的包导入了. 使用注解需要导入context约束,增加注解的支持. <?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance

  • 详解Spring容器的使用流程

    前言 Spring容器的API有 BeanFactory 和 ApplicationContext 两大类,他们都是顶级接口.其中ApplicationContext 是 BeanFactory 的子接口.对于两者的说明请参考面试题讲解Spring容器部分.我们主要使用 ApplicationContext 应用上下文接口. 一.主要流程 二.开发步骤 2.1 准备Maven项目及环境 首先创建一个Maven项目,名称为 spring-study ,以下是项目的maven配置文件 pom.xml

  • 详解Spring bean的注解注入之@Autowired的原理及使用

    一.@Autowired 概念: @Autowired 注释,它可以对类成员变量.方法及构造函数进行标注,完成自动装配的工作. 通过 @Autowired的使用来消除 set ,get方法. 在使用@Autowired之前,我们对一个bean配置起属性时,用的是 <property name="属性名" value=" 属性值"/> 使用@Autowired之后,我们只需要在需要使用的地方使用一个@Autowired 就可以了. 代码使用: public

  • 详解Spring如何解析占位符

    目录 什么是Spring的占位符? Spring什么时候去解析并占位符 什么是Spring的占位符? 在以前的Spring Xml配置中我们可能会有如下配置: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001

  • 详解Spring Security中权限注解的使用

    目录 1. 具体用法 2. SpEL 3. @PreAuthorize 最近有个小伙伴在微信群里问 Spring Security 权限注解的问题: 很多时候事情就是这么巧,松哥最近在做的 tienchin 也是基于注解来处理权限问题的,所以既然大家有这个问题,咱们就一块来聊聊这个话题. 当然一些基础的知识我就不讲了,对于 Spring Security 基本用法尚不熟悉的小伙伴,可在公众号后台回复 ss,有原创的系列教程. 1. 具体用法 先来看看 Spring Security 权限注解的具

随机推荐