8.ClassPathBeanDefinitionScanner的作用
八.ClassPathBeanDefinitionScanner
spring包扫描是通过ClassPathBeanDefinitionScanner类来完成的,它主要工作有两个:
功能1:扫描BeanDefinition
扫描类路径下的候选Component,构造BeanDefinition对象即ScannedGenericBeanDefinition
功能2:注册BeanDefinition
利用BeanDefinitionRegister注册BeanDefinition到bean工厂中,BeanDefinitionRegister是spring默认bean工厂DefaultListableBeanFactory的一个接口,用于注册BeanDefinition。
初始化
ClassPathBeanDefinitionScanner在spring启动的时候完成初始化。
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.refresh();
}
}
public AnnotationConfigApplicationContext() {
//在IOC容器中初始化一个 注解bean读取器AnnotatedBeanDefinitionReader
this.reader = new AnnotatedBeanDefinitionReader(this);
//在IOC容器中初始化一个 按类路径扫描注解bean的 扫描器
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
构造函数
//spring将bean工厂传递进去
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this(registry, true);
}
//useDefaultFilters表示是否开启默认过滤器,默认是true
//用户可以提供过自定义滤器让spring忽略或者添加自己定义的业务类。
public ClassPathBeanDefinitionScanner
(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
设置默认的扫描过滤器Filter
ClassPathBeanDefinitionScanner的构造函数有多个,真正完成构造函数初始化的是
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//bean工厂,IOC容器,注册BeanDefinition
this.registry = registry;
//
if (useDefaultFilters) {
/**
* 如果开启默认过滤器会注册spring的扫描类过滤器加了特定注解的类会被扫描到
* @Component、@Repository、@Service、@Controller、@ManagedBean、@Named等
*/
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
我们主要关注registerDefaultFilters方法,这个方法是注册默认的过滤器,默认的过滤器用来过滤从指定包下面查找到的 Class ,如果能通过滤器,那么这个class 就会被转换成BeanDefinition 注册到容器。
如果在实例化ClassPathBeanDefinitionScanner时,没有说明要使用用户自定义的过滤器的话,那么就会采用默认的过滤器规则。
registerDefaultFilters()是父类ClassPathScanningCandidateComponentProvider的方法。
protected void registerDefaultFilters() {
/***
*注册了@Component过滤器到includeFiters
*相当于所有加了@Component的注解即继承@Component或者说是派生于@Component的注解
*包括@Component @Configuration @Controller @Service @Repository 等注解
*只要加了这些注解的bean,Spring都能扫描到
*/
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
//类加载器,这个没啥好解释的
ClassLoader cl =
ClassPathScanningCandidateComponentProvider.class.getClassLoader();
/****
* 同时也支持javaEE6的javax.annotation.ManagedBean和JSR-330的@Named注解
*/
try {
// 添加ManagedBean 注解过滤器
this.includeFilters.add(
new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)),false));
}catch (ClassNotFoundException ex) {
}
try {
// 添加Named 注解过滤器
this.includeFilters.add(
new AnnotationTypeFilter(((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Named", cl)), false));
}catch (ClassNotFoundException ex) {
}
}
首先这里的includeFilters大家熟悉吗,还有个excludeFilters,先看一下属性。
这里提前往includeFilters里面添加需要扫描的特定注解。
//包含的Filters 如果类上的注解在includeFilters存在 那么就需要被扫描
private final List<TypeFilter> includeFilters = new LinkedList<>();
//排除的Filters 如果类上的注解在Filters存在 那么就不需要被扫描
private final List<TypeFilter> excludeFilters = new LinkedList<>();
1.添加@Component对应的TypeFilter
添加元注解@Component,需要注意的是@Configuration @Repository、@Service、@Controller里面都标注了@Component。很好理解,扫描的时候用includeFilters 去过滤时,会找到并处理这些注解的类。
2.添加@ManagedBean、@Named的TypeFilter
上面源码中的两个注解@ManagedBean、@Named需要有对应的jar包,否则includeFilters里面只会有一个元素@Component对应的TypeFilter。
为什么呢?
因为这2个注解都是用反射去加载到类过滤器,如果没有引用相应的依赖,必然加载不到对应的类,那么就会抛出异常进入try catch。
其实按照spring的加载流程,ClassPathBeanDefinitionScanner到这里的作用就结束,里面的很多重要方法是在流程加载后面用到的,但是既然都是一个类里面的方法,就在这里先讲一下吧。
scan
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext();
//注册配置类
context.register(Config.class);
context.scan("com");
context.refresh();
}
}
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.scanner.scan(basePackages);
}
scan方法是开始扫描的入口:context.scan("com")->this.scanner.scan(basePackages);
这个scanner就是AnnotationConfigApplicationContext的默认构造函数初始化的的时候创建的ClassPathBeanDefinitionScanner。
只有手动调用context.scan("com");
这个初始化的scanner才有发挥的作用,这个类就是为程序员手动扫描用的。
言外之意,如果你不手动调用context.scan("com");
这个初始化的scanner就没有用武之地。
而spring内部的自动扫描不会使用初始化的scanner,spring会重新生成新的ClassPathBeanDefinitionScanner完成扫描。
从代码角度来讲完成的功能一模一样。
/**
* 扫描给定的包路径,生成BeanDefinition并注册到注册器
*/
public int scan(String... basePackages) {
////获取注册器中已注册的bean数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//通过doScan给定包路径并生成BeanDefinition注册到registry中
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
// 这个方法很重要,注册了内置的ConfigurationClassPostProcessor
AnnotationConfigUtils.
registerAnnotationConfigProcessors(this.registry);
}
//返回本次扫描并注册到IOC容器中的类
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
doScan
看源码可知,真正完成扫描的是doScan方法:
/**
* 扫描给定的包路径,生成BeanDefinition并注册到注册器
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
/**
* 扫描basePackage路径下的java文件
* 先全部转为Resource,然后再判断拿出符合条件的bd
*/
for (String basePackage : basePackages) {
//调用findCandidateComponents扫描包组装BeanDefinition集合
//findCandidateComponents方法是从父类继承的方法
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//遍历BeanDefinition,根据条件将BeanDefinition注册到注册中心
for (BeanDefinition candidate : candidates) {
//解析scope属性
ScopeMetadata scopeMetadata = this.scopeMetadataResolver
.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//获取beanName,先判断注解上有没有显示设置beanName
//没有的话,就以类名小写为beanName
String beanName = this.beanNameGenerator.
generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
/**
* 如果这个类是AbstractBeanDefinition类型
* 则为他设置默认值,比如lazy/init/destroy
* 通过扫描出来的bd是ScannedGenericBeanDefinition
* 它实现了AbstractBeanDefinition
*/
postProcessBeanDefinition
((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
/**
* 处理加了注解的类
* 把常用注解设置到AnnotationBeanDefinition中
*/
AnnotationConfigUtils
.processCommonDefinitionAnnotations
((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new
BeanDefinitionHolder(candidate, beanName);
definitionHolder =AnnotationConfigUtils.
applyScopedProxyMode
(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
findCandidateComponents
先关注findCandidateComponents(basePackage);
进入到父类ClassPathScanningCandidateComponentProvider。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex
(this.componentsIndex, basePackage);
}
else {
//完成真正的扫描
return scanCandidateComponents(basePackage);
}
}
scanCandidateComponents(basePackage);
方法名顾名思义在指定的包中找到候选组件,进入scanCandidateComponents(basePackage);
中
1.拼接路径:classpath*:com*.class
2.扫描路径:classpath*:com*.class
3.解析类的注解信息
4.判断excludeFilters中是否包含类上的注解,包含返回false
false代表非候选,true代表候选
5.判断includeFilters中是否包含类上的注解,包含继续判断条件满足
false代表非候选,true代表候选,如果excludeFilters和includeFilters都包含,返回false代表非候选。
6.创建bd并将bd加入set
/**
* 扫描包,生成BeanDefinition集合
*/
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// //根据包名组装包扫描路径
// 如classpath*:com/**/*.class
String packageSearchPath =
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) +
'/' +
this.resourcePattern;
/*
resourcePatternResolver即资源加载器,资源加载器其实就是容器本身
资源加载器根据匹配规则获取Resource[]
可以理解为资源加载器根据包扫描路径扫描指定路径下的所有class文件。
Resource数组中每一个对象都是对应一个Class文件
Spring用Resource定位资源,封装了资源的IO操作。
这里的Resource实际类型是FileSystemResource。
*/
Resource[] resources = getResourcePatternResolver()
.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
//循环处理每一个resource,相当于循环处理每一个class文件
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
/*
*读取类的注解信息和类信息,信息储存到MetadataReader。
*meteDataFactory根据Resouce获取到MetadataReader对象
*MetadataReader提供了获取Class文件的
*ClassMetadata和AnnotationMetadata的操作。
*/
MetadataReader metadataReader
= getMetadataReaderFactory().getMetadataReader(resource);
/*
*判断元数据是否需要组装成BeanDefinition
*此处判断当前class是否需要注册到spring的IOC容器中通过IOC容器管理。
*spring默认对Component注解的类进行动态注册到IOC容器
*通过includeFilters与excludeFilters来判定匹配。
*/
if (isCandidateComponent(metadataReader)) {
//把符合条件的类转换成ScannedGenericBeanDefinition
//现在知道ClassPathBeanDefinitionScanner扫描出来的bd
//为什么是ScannedGenericBeanDefinition了吧
//1.从metadataReader中取出metadata设置到bd中
//2.从metadata中取出ClassName设置到bd中
//3.从metadataReader中取出resource设置到bd中
ScannedGenericBeanDefinition sbd
= new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
//再次判断 如果是实体类 返回true
//如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class");
}
//添加到候选集合
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored,not a concrete top-level-class");
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching anyfilter");
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to read candidate component class:");
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
isCandidateComponent:是否是候选组件
上面有一个组件判断if (isCandidateComponent(metadataReader)),就是判断当前class文件符不符合扫描过滤器includeFilters与excludeFilters中的定义,最后返回一个符合条件的Set
是否满足候选通过includeFilters与excludeFilters来判定匹配。 1.判断excludeFilters中是否包含类上的注解,包含直接返回false,否则继续往下走。 2.判断includeFilters中是否包含类上的注解,包含返回isConditionMatch(metadataReader);,否则继续往下走。 3.直接返回false。
false代表非候选,true代表候选,如果excludeFilters和includeFilters都包含返回false代表非候选。
也就是excludeFilters的优先级大于includeFilters。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//看下是否在排除的filter内 如果是返回false
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
//看下是否在包含的filter内 如果是继续判断条件是否匹配
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
//判断条件是否匹配
return isConditionMatch(metadataReader);
}
}
return false;
}
isCandidateComponent
/**
* metadata.isIndependent():不是内部类或者是静态内部类
* metadata.isConcrete():不能是接口或抽象类
* metadata.isAbstract():是抽象类
* metadata.hasAnnotatedMethods(Lookup.class.getName()):有lookUp注解标注
* 总结就是需要满足以下两个条件:
* 1. 不能是内部类或者是静态内部类
* 2. 不能是接口或者抽象类,如果是抽象类,则需要LookUp注解标注。
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
//1.普通的实体类
//2.带@Lookup注解的方法的抽象类
//上面2种情况返回true
return (
metadata.isIndependent() && (metadata.isConcrete()
||
(metadata.isAbstract() &&
metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
registerBeanDefinition
回到之前的doScan(String... basePackages)中的findCandidateComponents(basePackage)下面的for循环 for (BeanDefinition candidate : candidates),开始循环,处理注解,设置beanDefinition属性 最后执行 registerBeanDefinition(definitionHolder, this.registry);注册beanDefinition
registerBeanDefinition(definitionHolder, this.registry);
跟进此方法:
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
/**
* 这里的registerBeanDefinition是由父类GenericApplicationContext实现的
* 跟踪源码可知,是在父类中调用
* this.beanFactory.registerBeanDefinition(beanName, beanDefinition)
* 而这个beanFactory是AnnotationConfigApplicationContext
* 在执行自己的构造方法this()时
* 先去执行了父类GenericApplicationContext的构造方法
* 完成了this.beanFactory = new DefaultListableBeanFactory()
* 所以,最终将beanDefinition注册到了DefaultListableBeanFactory中
* */
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
//this.aliasMap.put(alias, name);
//实际是别名作为key
//获取到实际名称后在获取对应的对象
registry.registerAlias(beanName, alias);
}
}
}
我们对以上流程做个梳理:
转载自:https://juejin.cn/post/7236028062872846396