spring之Spring的扫描和mybatis扫描
Spring的扫描和mybatis扫描
一、概述
还是从三行代码出发:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.refresh();
这三行代码中,重点分析了AppConfig为什么要解析成一个AnnotatedGenericBeanDefinition
1、做为一个扫描类,配置扫描哪些包下的哪些类;
2、独立做为一个特殊的BeanDefinition;
3、提供给外界API,来注册一个BeanDefinition;
@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig {}
那么spring会来解析这个AppConfig类,解析阶段就可以获取得到AppConfig类的源信息,然后获取得到basePackages中配置的字符串,来进行扫描。
那么重点就在于spring是如何来进行扫描阶段的。
那么就又涉及到三行代码中的一个属性ClassPathBeanDefinitionScanner,在这个类中有scan方法会来完成扫描阶段。
二、扫描思路
现在用我们自己的思路来模拟一下Spring的扫描过程:
利用IO技术来找到对应的File即可
1、从AppConfig中获取得到value中的扫描路径:com.guang.spring
2、遍历classpath:target/classes/com/guang/spring,利用file.list得到一个String[]数组
3、遍历整个String[]数组,得到每个XX.class文件,然后得到com.guang.spring.XX类
4、利用反射:Class.forName("com.guang.spring.XX")加载得到对应的类
5、得到类之后,就可以得到类的信息,然后利用类的信息来判断。如:类上是否有@Component注解、是否是接口等信息
但是spring比我们上述理解中做的更聪明,因为有些类不需要加载到JVM内存中去,比如说加了@Conditional注解的类,不符合条件的不需要加载进去。
所以spring利用了ASM技术,对类的字节码文件进行分析,然后将其加载到JVM内存中去。
2.1、spring扫描逻辑
在第一行代码中
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
点进去可以看到
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
在第二行代码中,可以看到ClassPathBeanDefinitionScanner类,而这个类就是来实现扫描逻辑的。
具体的方法是:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
2.1.1、遍历包名下的每个文件得到BD
那么我们就需要知道spring如何来扫描对应的class文件让其称为BD的。
2.1.2、通过索引或者扫描判断
然后进入到真正的扫描逻辑
2.1.3、遍历每个源文件并读取识别
那么这里的重点就是spring是如何来判断一个类文件是否是一个候选者的呢?
2.1.4、通过过滤器判断每个源文件是否能够成为BD
那么我们需要知道的是这些过滤器是在什么时候进行初始化的?
那么还需要进入到ClassPathBeanDefinitionScanner的实例化阶段:
spring默认过滤器
那么就来看一下,spring默认注册了哪些过滤器
这里默认添加了三个过滤器:类上著有Component注解、ManagedBean、Named注解的。
2.1.5、二次判断
根据源码来看,需要进行二次判断
两个方法构成重载,只不过方法参数不同而已。上面是通过注解来进行判断,那么第二个就是针对具体的类文件判断。
具体的判断逻辑:
1、必须要是顶级类(也就是单独的类)或者是静态内部类;
2、不能是接口或者是抽象类;
3、如果是抽象类,那么在方法上需要存在LookUp注解;
如果想要称为一个候选者,那么spring中必须要满足上述条件中的①②或者是①③;
2.1.6、扫描总结
spring利用ClassPathBeanDefinitionScanner来进行扫描,扫描规则如下所示:
1、根据包找到所有的文件;
2、遍历每个文件,利用过滤器判断类上是否有满足条件的注解;
3、再次对每个类文件进行判断。如是否是接口、抽象类 或者顶级类等;
4、对每个类生成对应的ScannedGenericBeanDefinition。此时BD只有资源文件位置和对应的类名
三、模拟mybatis扫描
既然spring利用ClassPathBeanDefinitionScanner扫描器来进行扫描,那么我们写一个自己的扫描器来进行扫描。
而扫描器和spring的扫描规则有一定区别,但是又有相似的地方,那么我们可以重写ClassPathBeanDefinitionScanner中的方法来实现自己的扫描逻辑。
那么重点就是两个地方:①过滤器扫描规则;②对是否是一个候选者来选择进行实现。
自定义注解@Mapper
/**
* @Description
* @Author liguang
* @Date 2023/01/01/23:26
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mapper {
}
自定义mybatis扫描器逻辑
/**
* @Description
* @Author liguang
* @Date 2023/01/01/23:24
*/
public class MybatiScanner extends ClassPathBeanDefinitionScanner {
public MybatiScanner(BeanDefinitionRegistry registry) {
super(registry);
addIncludeFilter(null);
}
/**
* 模拟mybatis自己的扫描逻辑
*
* @param includeFilter
*/
@Override
public void addIncludeFilter(TypeFilter includeFilter) {
super.resetFilters(false);
// 但是为了封装一下,所以这里使用自己的。这里判断类上是否有@Mapper注解
super.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
}
/**
* 利用MetadataReader来判断对应的源文件是否满足对应的需求
*
* @param metadataReader
* @return
* @throws IOException
*/
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
return super.isCandidateComponent(metadataReader);
}
/**
* 对类来进行判断是否能够满足
* @param beanDefinition
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// return super.isCandidateComponent(beanDefinition);
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isInterface()));
}
}
测试代码
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.refresh();
MybatiScanner mybatiScanner = new MybatiScanner(applicationContext);
mybatiScanner.scan("com.guang.spring.mapper");
通过断点进行测试,发现是生成对应的ScannedGenericBeanDefinition的。