一、@ComponentScan源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
-
@Target({ElementType.TYPE}) 表示只可以声明在类上
-
value 表示用法如 @ComponentScan(value="")
也可以简写为 @ComponentScan("") ,省略value= -
basePackages,表示用法如@ComponentScan(basePackages=""),由于和value等价(@AliasFor("value")),也可简写为@ComponentScan("")
-
nameGenerator: bean的名称的生成器
-
useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测
-
includeFilters: 包含的过滤条件
- FilterType.ANNOTATION:按照注解过滤
- FilterType.ASSIGNABLE_TYPE:按照给定的类型
- FilterType.ASPECTJ:使用ASPECTJ表达式
- FilterType.REGEX:正则
- FilterType.CUSTOM:自定义规则
-
excludeFilters: 排除的过滤条件,用法和includeFilters一样
一个稍完整的示例:
// com.jiaobuchong.business 和 com.jiaobuchong.user.servic 下的类都不会被扫描
@ComponentScan(basePackages = {"com.jiaobuchong.order.service"},
excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com.jiaobuchong.business\\..*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.jiaobuchong.user.service\\..*")})
二、@SpringBootApplication源码
// 指定@SpringBootConfiguration注解的使用范围,可以用在类、接口、枚举上
@Target(ElementType.TYPE)
// 指定@SpringBootConfiguration注解的生命周期,不仅被保存到Class文件中,JVM加载Class文件之后,仍然存在。
@Retention(RetentionPolicy.RUNTIME)
// 生成JavaDoc时,会把@SpringBootConfiguration注解给显示出来。
@Documented
// 表明@SpringBootConfiguration注解可以被子类继承
@Inherited
// 表明被@SpringBootConfiguration注解标注的类是一个配置类,
// 并会将当前类内部声明的一个或多个以@Bean注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。
@SpringBootConfiguration
// 表明被@SpringBootConfiguration注解标注的类会开启自动配置
@EnableAutoConfiguration
// 扫描当前包及其子包下被@Component,@Controller,@Service,@Repository等注解标记的类并纳入到Spring容器中进行管理。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 排除掉不需要自动装配的类,传入的参数是Class类型
// 例如 @SpringBootApplication(exclude = {MybatisAutoConfiguration.class})
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 排除掉不需要自动装配的类,传入的参数是类的全限定名字符串
// 例如 @SpringBootApplication(excludeName = {"org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration"})
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 以字符串的方式配置需要扫描包的路径
// 例如 @SpringBootApplication(scanBasePackages = {"com.panda.map.struct.demo"})
// @ComponentScan注解的basePackages属性的别名
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 指定某些类所在包下的所有组件都会被扫描为Bean被Spring容器加载
// 例如 @SpringBootApplication(scanBasePackageClasses = User.class)
// @ComponentScan注解的basePackageClasses属性的别名
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
// 指定Bean的Name的生成策略
// @ComponentScan注解的nameGenerator属性的别名
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 指定被@SpringBootConfiguration注解标注的类中被@Bean注解标注的方法是否可以被代理,默认可以被代理
// @Configuration注解的proxyBeanMethods方法的别名
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@Target、@Retention、@Documented和@Inherited注解是 Java 提供的元注解,本文不作过多的讲解。
从@SpringBootApplication注解的源码可以看出,它是一个复合注解,其核心注解有三个,分别是:标注某个类是配置类的@SpringBootConfiguration注解,启动自动配置的@EnableAutoConfiguration注解,以及指定需要扫描的包路径的@ComponentScan注解。
而@EnableAutoConfiguration注解是SpringBoot 自动化配置的核心所在。
scanBasePackages属性
本质上,SpringBootApplicatioscan中的scanBasePackages属性底层原理正是复用了@ComponentScan,因此语法和意义基本一致
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor标签表示别名的意义,可以理解为等价于
因此,下面2种用法相同:
@SpringBootApplication (scanBasePackages="com.test")
public class MyClass {
package com.test;
@ComponentScan("com.test")
@SpringBootApplication
public class MyClass {
三、注意事项
当使用Spring Boot项目时,可以不指定加载路径,即不使用@ComponentScan
或@SpringBootApplication (scanBasePackages="com.test"
),默认会加载MyClass所在的包
举个例子,看下面定义的类:
package com.test;
@SpringBootApplication
public class MyClass {}
MyClass 的package为com.test,这个类使用了@SpringBootApplication注解,该注解定义了Spring将自动扫描包com.test及其子包下的bean。
如果你项目中所有的类都定义在com.test包及其子包下,那你不需要做任何事。
但假如你一个类定义在包com.test2下,则你需要将这个新包也纳入扫描的范围,有两个方案可以达到这个目的:
方案1, 定义@SpringBootApplication扩大范围或者定义分别扫描两个包
@SpringBootApplication(scanBasePackages = {"com"})或者@SpringBootApplication(scanBasePackages = {"com.test","com.test2"})
方案2, 使用@CoponentScan扩大范围或者定义分别扫描两个包
@CoponentScan(“com”)或者@ComponentScan({“com.test”,”com.test2”})
注解失效
- 如果
ComponentScan
只包括一个值且就是默认启动类目录,SpringBootApplication
生效,ComponentScan
注解失效,同时ComponentScan报错:
package com.test; @ComponentScan("com.test") @SpringBootApplication public class MyClassApplication {}
ComponentScan注解上报错,提示:Redundant declaration: @SpringBootApplication already applies given @ComponentScan
- 如果
ComponentScan
指定多个具体子目录,此时SpringBootApplication
会失效,Spring 只会扫描ComponentScan
指定目录下的注解。如果恰好有ComponentScan指定目录外的 Controller 类,很遗憾,这些控制器将无法访问。
原因:在
org.springframework.context.annotation.ComponentScanAnnotationParser#parse
方法内有着获取basePackages
的业务逻辑,源码如下所示Set<String> basePackages = new LinkedHashSet<>(); // 获取@ComponentScan注解配置的basePackages属性值 String[] basePackagesArray = componentScan.getStringArray("basePackages"); // 将basePackages属性值加入Set集合内 for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } // 获取@ComponentScan注解的basePackageClasses属性值 for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { // 获取basePackageClasses所在的package并加入Set集合内 basePackages.add(ClassUtils.getPackageName(clazz)); } // 如果并没有配置@ComponentScan的basePackages、basePackageClasses属性值 if (basePackages.isEmpty()) { // 使用Application入口类的package作为basePackage basePackages.add(ClassUtils.getPackageName(declaringClass)); }
获取
basePackages
分为了那么三个步骤,分别是:
- 获取
@ComponentScan
注解basePackages
属性值- 获取
@ComponentScan
注解basePackageClasses
属性值- 将
Application
入口类所在的package
作为默认的basePackages
根据源码也就证实了,为什么我们配置了
basePackages
、basePackageClasses
后会把默认值覆盖掉,这里其实也不算是覆盖,是根本不会去获取Application
入口类的package
。扫描Packages下的Bean
获取到全部的
Packages
后,通过org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
方法来扫描每一个Package
下使用注册注解(@Component
、@Service
、@RestController
...)标注的类,源码如下所示:protected Set<BeanDefinitionHolder> doScan(String... basePackages) { // 当basePackages为空时抛出IllegalArgumentException异常 Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // 遍历每一个basePackage,扫描package下的全部Bean for (String basePackage : basePackages) { // 获取扫描到的全部Bean Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // 遍历每一个Bean进行处理注册相关事宜 for (BeanDefinition candidate : candidates) { // 获取作用域的元数据 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); // 获取Bean的Name String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } // 如果是注解方式注册的Bean if (candidate instanceof AnnotatedBeanDefinition) { // 处理Bean上的注解属性,相应的设置到BeanDefinition(AnnotatedBeanDefinition)类内字段 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 检查是否满足注册的条件 if (checkCandidate(beanName, candidate)) { // 声明Bean具备的基本属性 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // 应用作用域代理模式 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // 写入返回的集合 beanDefinitions.add(definitionHolder); // 注册Bean registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
在上面源码中会扫描每一个
basePackage
下通过注解定义的Bean
,获取Bean
注册定义对象后并设置一些基本属性。注册Bean
扫描到
basePackage
下的Bean
后会直接通过org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition
方法进行注册,源码如下所示:public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 注册Bean的唯一名称 String beanName = definitionHolder.getBeanName(); // 通过BeanDefinitionRegistry注册器进行注册Bean registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果存在别名,进行注册Bean的别名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
非 Spring Boot项目
在非Spring Boot项目中,我们必须显式地使用@ComponentScan
注解定义被扫描的包,可以通过XML文件在应用上下文中定义或在Java代码中对应用上下文定义,否则会加载不到预期的bean。
站在巨人肩膀上摘苹果
https://blog.csdn.net/m0_45406092/article/details/115700557
http://www.manongjc.com/detail/55-nqqyzwasdxnhrmu.html
https://zhuanlan.zhihu.com/p/498969192
标签:basePackages,default,SpringBootApplication,ComponentScan,注解,com,scanBasePackages From: https://www.cnblogs.com/eternityz/p/17039617.html