看Spring是怎样整合MyBatis

引言

如果让自己着手开发一个框架整合到Spring当中,你会怎么做?
与其说我们怎么做,不如说Spring提供了哪些可扩展的地方允许你对接?
那我们就从最熟悉的MaBatis入手,看Spring如何整合MyBatis?

Let’s do this

如果说自己写一个mybatis整合到Spring当中,怎么做?

1. 暴露一个注解,通过spring让mybatis去扫描所有Mapper。
2. 将所有Mapper交给Spring去管理,也就是添加到 容器
3. 同时创建SqlSessionFactory 对象,包括mybatis一切所需对象。
4. 获取Mapper对象,执行mapper方法。

第一个问题:
mybatis是怎么通过spring去扫描Mapper的?

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.2</version>
</dependency>

官网最新是2.0.4版本,我用2.0.2版本,2.0以后mybatis实现了一个Spring中一个重要的接口

BeanDefinitionRegistryPostProcessor
Spring整合MyBatis主动权在于Spring,做事的规则还是交给Spring来定,ORM那一套怎么做Spring管不到,但是对象创建、对象管理,什么时候创建,怎么管理都是Spring说了算。

Spring整合MyBatis唯一的入口就在 @MapperScan 这个注解,他到底做了什么文章?

看Spring是怎样整合MyBatis_第1张图片

看Spring是怎样整合MyBatis_第2张图片
MapperScannerRegistrar 实现了Spring当中一个重要的扩展接口:
在这里插入图片描述
同时也实现了 ImportBeanDefinitionRegistrarregisterBeanDefinitions 方法:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
            this.registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
        }

    }

    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            builder.addPropertyValue("annotationClass", annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            builder.addPropertyValue("markerInterface", markerInterface);
        }

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
        }

        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
        }

        String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
        if (StringUtils.hasText(sqlSessionTemplateRef)) {
            builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
        }

        String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
        if (StringUtils.hasText(sqlSessionFactoryRef)) {
            builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
        }

        List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        String lazyInitialization = annoAttrs.getString("lazyInitialization");
        if (StringUtils.hasText(lazyInitialization)) {
            builder.addPropertyValue("lazyInitialization", lazyInitialization);
        }

        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

registerBeanDefinitions方法
上面代码大家有兴趣可以看,主线最重要,registerBeanDefinitions 他的作用主要是将 MapperScannerConfigurer new成BeanDefinition , 并且注册到Spring上下文环境(AnnotationConfigApplicationContext)当中。

描述: 在这里我简单提一下,AnnotationConfigApplicationContext 继承了GenericApplicationContext , 在GenericApplicationContext 这个类当中维护了一个beanFactory
看Spring是怎样整合MyBatis_第3张图片
DefaultListableBeanFactory 当中维护了一个Map,这个Map就是存放所有BeanDefinition的容器:
看Spring是怎样整合MyBatis_第4张图片
这里留一个小问题: MapperScannerRegistrar 这个对象能调用registerBeanDefinitions 这个方法,它必然已经实例化完成,那么他是在什么时机,又是在什么地点实例化完成的?

第二个问题:
既然MyBatis将MapperScannerConfigurer添加到Spring 容器 当中,那么他的目的是什么,或者说他的作用是什么?

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {}

它实现了Spring中又一重要扩展接口:BeanDefinitionRegistryPostProcessor,同时实现了postProcessBeanDefinitionRegistry这个方法:

 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(this.lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
        }

        scanner.registerFilters();
        //扫描包路径下的mapper
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

这个方法也就是完成了我们的第一步假设,创建了一个扫描器,扫描所有的Mapper,将Mapper new成BeanDefinition。
看Spring是怎样整合MyBatis_第5张图片

具体细节在这里不再解释了,大家可以自行断点。

第三个问题(重要):
Mapper是接口啊,他怎么会被实例化呢?
看Spring是怎样整合MyBatis_第6张图片

描述: 在扫描过程,也就是MyBatis ClassPathMapperScanner scan过程中,会设置所有Mapper BeanDefinition的属性,其中就包括设置类型。
看Spring是怎样整合MyBatis_第7张图片

所以我们要在MapperFactoryBean 这个类下功夫。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}

哇塞,MapperFactoryBean 实现了FactoryBean,那么这就说的通了,Spring不能去实例化Mapper,MyBatis就把所有的Mapper的BeanDefinition属性beanClass设置为MapperFactoryBean.class,让Spring去实例化这个FactoryBean 。让实例化Mapper的过程交给FactoryBean去做。

public static void main(String[] args) {

        AnnotationConfigApplicationContext app
                = new AnnotationConfigApplicationContext(AppConfig.class);
                
        ProductMapper productMapper = app.getBean(ProductMapper.class);

    }

我们从getBean一路断点过去:
看Spring是怎样整合MyBatis_第8张图片

描述: 整个过程是根据类型去getBean,发现一级缓存也就是SingletonObjects单例池中有一个实例MapperFactoryBean,而这个MapperFactoryBean恰好是一个FactoryBean,一个FactoryBean会走getObject 这个方法,具体实现是由MyBatis通过JDK动态代理完成。

本人自己简单实现了一下MyBatis连接Spring完成Mapper逻辑实现:
看Spring是怎样整合MyBatis_第9张图片

public class DoMybatisProcessor implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //将MapperProxyFactory new 成BeanDefinition
        BeanDefinitionBuilder beanDefinitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(MapperProxyFactory.class);
        //设置MapperProxyFactory的mapperInterface属性值
        beanDefinitionBuilder.getBeanDefinition().getPropertyValues().add("mapperInterface", UserMapper.class);
        //将BeanDefinition注册到容器当中
        beanDefinitionRegistry.registerBeanDefinition(((Class)beanDefinitionBuilder.getBeanDefinition().getPropertyValues().get("mapperInterface")).getSimpleName(),beanDefinitionBuilder.getBeanDefinition());
    }
}
public class MapperProxyFactory implements FactoryBean {

    Class mapperInterface;

    @Override
    public Object getObject() throws Exception {
        return MapperCustomProxy.getMapper(mapperInterface);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public Class getMapperInterface() {
        return mapperInterface;
    }

    public void setMapperInterface(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
}
public class MapperCustomProxy {

    //基于动态代理的Mapper
    public static Object getMapper(Class mapperInterface) {
        return Proxy.newProxyInstance(MapperCustomProxy.class.getClassLoader(),
                new Class[]{mapperInterface},new MyInvocationHandler());
    }
}
public class MyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String sql=null;
        Select annotation = method.getAnnotation(Select.class);
        if (annotation!=null) {
            sql = annotation.value()[0];
            System.out.println("创建链接");
            System.out.println("获取statement对象");
            System.out.println("执行sql: "+sql);
            System.out.println("处理并返回执行结果");
        }
        return null;
    }
}

大家可以照我的自行去测试,笔者不在这里做详细解释了。

补充

针对MyBatis中实现的其中两个重要接口:
ImportBeanDefinitionRegistrar
BeanDefinitionRegistryPostProcessor
执行时机做一个简单描述。
看Spring是怎样整合MyBatis_第10张图片
看Spring是怎样整合MyBatis_第11张图片
看Spring是怎样整合MyBatis_第12张图片

invokeBeanFactoryPostProcessors方法主要处理BeanDefinitionRegistryPostProcessor以及BeanFactoryPostProcessor这两个后置处理器。这么说有点大,因为这两个后置处理器作用范围太广。
Spring最先处理的是ConfigurationClassPostProcessor这个类,它属于Spring开天辟地的五大类之一,而这个类就实现了BeanDefinitionRegistryPostProcessor这个接口。
在这里插入图片描述
看Spring是怎样整合MyBatis_第13张图片
ConfigurationClassPostProcessor最先执行BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry 方法。

  1. 获取所有的内置BeanDefinition,其中包括AppConfig,也是就是我们的配置类。
    看Spring是怎样整合MyBatis_第14张图片
  2. 解析AppConfig,包括解析注解、扫描配置等等。
    看Spring是怎样整合MyBatis_第15张图片
    看Spring是怎样整合MyBatis_第16张图片
    看Spring是怎样整合MyBatis_第17张图片
    看Spring是怎样整合MyBatis_第18张图片
    点进去,继续往下走。
    看Spring是怎样整合MyBatis_第19张图片
    描述: 所有实现ImportBeanDefinitionRegistrar接口的类在这里都会被实例化出来,实例化的时机可能会有些差别。最重要的是MyBatis中MapperScannerRegistrar在这里已经实例化完成。

问题:MapperScannerRegistrar实现的ImportBeanDefinitionRegistrar中registerBeanDefinitions方法又是何时执行的呢?

我们回到ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法
看Spring是怎样整合MyBatis_第20张图片
点进去
看Spring是怎样整合MyBatis_第21张图片
看Spring是怎样整合MyBatis_第22张图片
看Spring是怎样整合MyBatis_第23张图片

MapperScannerRegistrarregisterBeanDefinitions方法执行完成之后:
看Spring是怎样整合MyBatis_第24张图片
看Spring是怎样整合MyBatis_第25张图片
看图:看Spring是怎样整合MyBatis_第26张图片
走到断点
看Spring是怎样整合MyBatis_第27张图片
看Spring是怎样整合MyBatis_第28张图片
让我们点进去,见证奇迹的时刻:

看Spring是怎样整合MyBatis_第29张图片
此时此刻Spring整合MyBatis主要步骤及源码解析已经完成,大家可以跟着我的断点自己走一遍,相信肯定有意想不到的收获。

总结:只有自己尝试才会感叹Spring是如此的强大,并不是说Spring用多少代码量来证明,而是在他独有的IOC功能基础上还能让其它框架来无缝扩展。

你可能感兴趣的:(Spring整合MyBatis)