文章目录
- 背景
- 方案
- 详解
- 场景
- 自定义注解扫描
- 自定义扫描过滤规则
背景
默认是扫描当前application启动类所在的包及其子包
例如我们的LakerApplication
代码如下:
package io.gitee.lakernote;
@SpringBootApplication
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
扫描的包为
io.gitee.lakernote
包及其子包
其@SpringBootApplication
内部原理,其实用的就是@ComponentScan
注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
excludeFilters ?FilterType?你有疑问?下面会详细介绍的,这里你先别管。
如果我们想扫描其他的多个包目录应该怎么做呢?例如,扫描下面2个包。
- io.gitee.lakernote
- io.gitee.lakernote2
方案
方式一 @ComponentScan
@SpringBootApplication
@ComponentScan(basePackages = {"io.gitee.lakernote2","io.gitee.lakernote"})
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
或者
@SpringBootApplication
@ComponentScan("io.gitee")
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
或者
@SpringBootApplication
@ComponentScan("io.gitee.lakernote")
@ComponentScan("io.gitee.lakernote2")
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
或者
@SpringBootApplication
@ComponentScan(basePackageClasses = HelloWorldController2.class,basePackages = "io.gitee.lakernote")
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
注意
@ComponentScan
会使默认application的包失效。
方式二 @SpringBootApplication(scanBasePackages = {"xxx"})
原理跟方式一相同
@SpringBootApplication(scanBasePackages = {"io.gitee.lakernote","io.gitee.lakernote2"})
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
方式三 @ComponentScans
原理跟方式一相同
@SpringBootApplication
@ComponentScans({
@ComponentScan(basePackageClasses = HelloWorldController2.class),
@ComponentScan("io.gitee.lakernote")}
)
public class LakerApplication {
public static void main(String[] args) {
SpringApplication.run(LakerApplication.class, args);
}
}
详解
由上可知都是基于@ComponentScan
注解做的,那么我们来看下它是如何使用的。
@ComponentScan
注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。
@ComponentScan
注解代码如下:
注意看里面的注释,非常详细。
package org.springframework.context.annotation;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(ComponentScans.class)// 这个注解可以同时使用多个
public @interface ComponentScan {
// 用于指定包的路径,进行扫描io.gitee.lakernote
@AliasFor("basePackages")
String[] value() default {};
// 同value用于指定包的路径,进行扫描 io.gitee.lakernote
@AliasFor("value")
String[] basePackages() default {};
// 用于指定某个类的包的路径进行扫描 HelloService.class
Class<?>[] basePackageClasses() default {};
// bean的名称的生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 用于解析@Scope注解
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
// 用来设置类的代理模式
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
// 扫描路径 如 resourcePattern = "**/*.class"
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
// 默认的过滤规则是开启的,默认装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。
boolean useDefaultFilters() default true;
// 对被扫描的包或类进行过滤,若符合条件,不论组件上是否有注解,Bean对象都将被创建,需要借助@ComponentScan.Filter来完成
// @ComponentScan(value = "io.laker",includeFilters = {
// @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Laker.class}),
// @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {LakerTypeFilter.class}),
// @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$")
// })
Filter[] includeFilters() default {};
// 指定哪些类型不进行组件扫描。 用法和includeFilters一样
Filter[] excludeFilters() default {};
// 指定注册扫描的Bean延迟初始化。
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
// 指定过滤的规则,有以下几种
// FilterType.ANNOTATION:按照注解过滤
// FilterType.ASSIGNABLE_TYPE:按照给定的类型
// FilterType.ASPECTJ:使用ASPECTJ表达式
// FilterType.REGEX:正则
// FilterType.CUSTOM:自定义规则
FilterType type() default FilterType.ANNOTATION;
// 过滤器的参数,参数必须为class数组,单个参数可以不加大括号
// 只能用于 ANNOTATION 、ASSIGNABLE_TYPE 、CUSTOM 这三个类型
// @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class, Service.class})
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Laker.class})
// @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {LakerTypeFilter.class})
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
// 主要用于 ASPECTJ 类型和 REGEX 类型
// ASPECTJ 参数为 ASPECTJ 表达式
// @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "spring.annotation..*")
// REGEX 参数为 正则表达式
// @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$")
String[] pattern() default {};
}
}
场景
以下是项目中可能遇到的应用场景。
自定义注解扫描
扫描业务自定义注解
@Laker
标识的类到Spring容器中。
第一步,自定义注解上加上@Component
,参考@Controller注解
其上加上@Component
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Laker {
@AliasFor(annotation = Component.class)
String value() default "";
}
第二步,在@ComponentScan注解中进行配置,如下:
@Configuration
@ComponentScan("io.gitee")
public class LakerConfig {
}
如果第一步中没有加上@Component
注解,或者设置了useDefaultFilters = false
,则应该设置includeFilters
。
@Configuration
@ComponentScan(value = "io.gitee",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Laker.class})
})
public class LakerConfig {
}
第三步,在类上加上自定义注解
@Laker("lakerService")
public class LakerService {
}
LakerService就会被加入到Spring容器中。
自定义扫描过滤规则
这个就是自由度非常高了,想怎么玩就怎么玩。
第一步,自定义一个类LakerTypeFilter
实现TypeFilter
接口
public class LakerTypeFilter implements TypeFilter {
/**
* 两个参数的含义:
* metadataReader:包含读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到当前正在扫描的类的其他类信息(如父类和接口)
* match方法返回false即不通过过滤规则,true通过过滤规则
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//可以通过这个读想获取类的一些元数据信息,如类的class对象、是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、内部包含的之类列表等等
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
Class curClass = null;
try {
//当前被扫描的类
curClass = Class.forName(metadataReader.getClassMetadata().getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//判断curClass是否是IService类型
boolean result = IService.class.isAssignableFrom(curClass);
return result;
}
}
第二步,在@ComponentScan注解中进行配置,如下:
@Configuration
@ComponentScan(value = "io.gitee",
useDefaultFilters = true, // 装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {LakerTypeFilter.class})
}
)
public class LakerConfig {
}
这个需求也可以这样做,使用FilterType.ASSIGNABLE_TYPE
。
@Configuration
@ComponentScan(value = "io.gitee",
useDefaultFilters = true, // 装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
}
)
public class LakerConfig {
}