@ComponentScan 注解
日积月累,水滴石穿 😄
基本使用
@ComponentScan 注解的作用就是根据指定的扫描路径,把路径中符合扫描规则的类装配到 Spring 容器中。
- 加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
- 配置启动类,使用
ComponentScan
扫描指定包路径
@ComponentScan(basePackages = "com.cxyxj.componentscan.app")
public class AppMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
// 打印 bean 名称
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
System.out.println(name);
}
}
}
运行结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appMain
除了 Spring 本身注册的一些 bean 之外, AppMain
这个类也注册进了容器中。
上述的使用方式,在没什么特殊的要求下,在实际工作当中完全满足项目的开发。@ComponentScan
注解还有其他好用的功能,我们有必要了解并会使用。
@ComponentScan
注解与XML文件中的context:component-scan
标签等效。
<beans>
<context:component-scan base-package="com.cxyxj"/>
</beans>
注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
// basePackages 的别名
// 在没有其他属性的情况下可以如此写:@ComponentScan("com.cxyxj.componentscan")
// 而不需要:@ComponentScan(basePackages = "com.cxyxj.componentscan")
@AliasFor("basePackages")
String[] value() default {};
// 扫描带注解的包路径,默认是@Component
@AliasFor("value")
String[] basePackages() default {};
// 扫描指定带注解的类,同时会扫描该类所在的包,默认是@Component
//示例: @ComponentScan(basePackageClasses = {UserServiceImpl.class})
Class<?>[] basePackageClasses() default {};
// beanName 的生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 解析@Scope注解
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
// 是否为扫描到的Bean生成代理
/**
* ScopedProxyMode有四个取值
* 1.DEFAULT:默认值,默认情况下取no
* 2.NO:不开启代理
* 3.INTERFACES:使用jdk动态代理
* 4.TARGET_CLASS:使用cglib代理
* 假如有一个单例beanA,其中有一个属性B,B的Scope是session,此时,容器在启动时创建A的过程中需要注入B属性,
* 但是B的scope是session,这种情况下是注入不了的(只有用户访问时才会创建),是会报错的
* 但是如果将B的Scope的ProxyMode属性配置为INTERFACES或者TARGET_CLASS时,那么B就会生成一个ScopedProxyFactoryBean类型的BeanDefinitionHolder
* 在A注入B时,就会注入一个ScopedProxyFactoryBean类型的Bean
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
// 控制符合组件检测条件的类文件,默认是包路径下的 **/*.class
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
// 是否自动检测带有 @Component 注解的类,默认为 true
// 像 @Repository、 @Controller、@Service、@Configuration 内部都包含了 @Component
boolean useDefaultFilters() default true;
/**
* 如果扫描到的类符合 includeFilters 中的过滤条件
* 这个类会被注册到容器中
* 过滤类型与如下5种
* 1、ANNOTATION:注解类型(默认)
* 2、ASSIGNABLE_TYPE:指定类
* 3、ASPECTJ:指定表达式
* 4、REGEX:正则表达式
* 5、CUSTOM:自定义类型
*/
Filter[] includeFilters() default {};
// 如果扫描到的类符合 excludeFilters 中的过滤条件
// 这个类不会被注册
Filter[] excludeFilters() default {};
// 扫描到的类是否应延迟初始化,默认为 false
boolean lazyInit() default false;
// 声明要用作包含过滤器或排除过滤器的类型过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
// 要使用的过滤器类型,默认为 ANNOTATION
FilterType type() default FilterType.ANNOTATION;
// classes 的别名
@AliasFor("classes")
Class<?>[] value() default {};
/**
* 作用同上面的 value 相同
* 过滤器的参数,参数必须为class数组
* 只能用于 ANNOTATION 、ASSIGNABLE_TYPE 、CUSTOM 这三个类型
* ANNOTATION:参数为注解类,比如:Controller.class
* ASSIGNABLE_TYPE:参数为类,如 UserServiceImpl.class
* CUSTOM:参数为实现 TypeFilter 接口的类 ,如 MyTypeFilter.class
* 实现了TypeFilter 接口的类,还能实现 EnvironmentAware,BeanFactoryAware, BeanClassLoaderAware,ResourceLoaderAware 这四个回调接口
* 它们各自的方法将在 TypeFilter#match 之前被调用
*/
@AliasFor("value")
Class<?>[] classes() default {};
//这个属性主要用于 ASPECTJ 类型和 REGEX 类型
String[] pattern() default {};
}
}
接下来使用一下几个重要属性。
实践
basePackageClasses
创建 ProductServiceImpl
、UserServiceImpl
两个类,并放在 impl
包下。整体包结构如下:
类中内容如下:
- ProductServiceImpl类中包含
@Configuration
注解
@Configuration
public class ProductServiceImpl {
}
- UserServiceImpl则是普通类
public class UserServiceImpl {
}
- 在
ComponentScan
注解增加basePackageClasses
的属性,指定UserServiceImpl
。上面讲过basePackageClasses
会扫描指定带注解的类,同时会扫描该类所在的包。
@ComponentScan(basePackages = "com.cxyxj.componentscan.app",basePackageClasses = {UserServiceImpl.class})
- 启动结果
由于指定的 UserServiceImpl
上面没有注解,所以没被加入容器中。
useDefaultFilters
useDefaultFilters
的值默认为 true,也就是默认会往 includeFilters
中添加一个为 @Component
注解类型的过滤器。这时我们将 useDefaultFilters
的值修改为 false ,这会扫描出来几个类呢?
@ComponentScan(basePackages = "com.cxyxj.componentscan.app",basePackageClasses = {UserServiceImpl.class},useDefaultFilters = false)
- 启动结果
答案是一个都没有,请忽略
AppMain
,这相当于是我们手动调用 context.register(AppMain.class)
进行注册的。
includeFilters
我们想把 ProductServiceImpl
扫描出来,需要怎么做呢?
ANNOTATION
- 修改
@ComponentScan
的值,增加includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Configuration.class}
,指定 value 的值为@Configuration
。
这代表着只要扫描到的类中包含 @Configuration
注解,就会被注册到容器中。一般我们自定义的注解就可以结合该方式。
@ComponentScan(basePackages = "com.cxyxj.componentscan.app",
basePackageClasses = {UserServiceImpl.class},
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Configuration.class})
)
- 启动结果
ASSIGNABLE_TYPE
将 UserServiceImpl
加入到容器中,但是它是一个普普通通的类,这时可以使用 ASSIGNABLE_TYPE
。
- includeFilters 值修改如下:
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {UserServiceImpl.class})
- 启动结果
ASPECTJ
如果觉得一个一个的加入比较麻烦,可以使用 ASPECTJ
表达式的方式。
- includeFilters 值修改如下:
includeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ,pattern = {"com.cxyxj.componentscan.impl.*"})
- 启动结果
如果启动报错需要加入 aspectj
的依赖。
<!--aspectj 支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
REGEX
跟 ASPECTJ
能达到效果的是 REGEX
,使用正则表达式进行匹配。需要注意的是进行匹配的值是类的全限定名,比如:com.cxyxj.componentscan.impl.UserServiceImpl
。
- includeFilters 值修改如下:
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX,pattern = {"[A-Za-z.]+Impl$"})
- 启动结果
CUSTOM
如果觉得上述几种方式都不满足需求,我们可以进行自定义过滤规则。
- 创建
CustomTypeFilter
类,并实现TypeFilter
接口,重写其match
方法。
public class CustomTypeFilter implements TypeFilter {
/**
*
* @param metadataReader:可以获得当前正在扫描的类的信息
* @param metadataReaderFactory:可以获得 MetadataReader。metadataReaderFactory.getMetadataReader(UserServiceImpl.class.getName());
* @return match方法返回false 则表示不通过过滤规则,true 表示通过过滤规则
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//Class元数据
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//全限定名称
String className = classMetadata.getClassName();
//如果名称包含 User 则通过过滤
if(className.contains("User")){
return true;
}
return false;
}
}
- includeFilters 值修改如下:
includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM,value = {CustomTypeFilter.class})
- 启动结果
通过自定义过滤规则的扫描,只有 UserServiceImpl
符合。
excludeFilters
的使用方式与 includeFilters
一致。只不过 excludeFilters
是排除而已。
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
转载自:https://juejin.cn/post/7084883364737466376