Spring系列11:@ComponentScan批量注册bean
2022-02-14 23:47
996 查看
回顾
在前面的章节,我们介绍了
@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系列之二,bean标签的解析和BeanDefinition的注册
- 读懂Spring核心系列1(注册与获取bean)
- Spring系列(一):Spring MVC bean 解析、注册、实例化流程源码剖析
- Spring系列之-Spring IOC容器设计:IOC容器初始化-Bean注册
- Spring系列【11】配置Bean的初始化行为
- Spring整理系列(01)——spring概念简介、bean扫描与注册实现方式
- 死磕Spring系列之二,bean标签的解析和BeanDefinition的注册
- 通过DefaultListableBeanFactory加载.xml配置文件学习Spring-IoC容器注册/加载bean的机制(源码走读)
- Spring-11 , bean的生命周期
- Spring(六)核心容器 - 注册单例 Bean 实例、SingletonBeanRegistry 简介
- SpringBoot系列教程11--SpringBoot其他常用配置
- springboot源码解析-管中窥豹系列之BeanDefine如何加载(十三)
- Spring应用、原理以及粗读源码系列(一)--框架总述、以Bean为核心的机制(IoC容器初始化以及依赖注入)
- spring源码分析系列2:Bean与BeanDefinition关系
- 用注解来向Spring容器注册Bean
- spring中注册bean(通过代码动态注册)
- Spring源码解读3——Bean资源的注册
- SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
- Bean定义并注册到spring
- 【Spring注解驱动开发】在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean