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的。

posted @ 2023-01-02 17:04  写的代码很烂  阅读(220)  评论(0编辑  收藏  举报