Spring整合Mybatis源码分析(mybatis-spring-2.0.6)-阿里云开发者社区

开发者社区> gbYu> 正文

Spring整合Mybatis源码分析(mybatis-spring-2.0.6)

简介: 本篇文章从以下几个方面分析Spring整合Mybatis的原理 1.例模拟Spring整合Mybatis 2.mybatis-spring.jar包的整合原理
+关注继续查看
示例
  • 首先引入Mybatis的使用示例,其中两个关键变量:SqlSessionFactory ,SqlSession
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper1 mapper = sqlSession.getMapper(Mapper1.class);
String result = mapper.select1();
  • 问题引入,以下代码中(系统中存在Mapper1,Mapper2,Mapper3接口),如果直接运行main函数,后台会报错,mapper注入错误,此时可以引入FactoryBean机制,生成mapper的代理对象:
@Service
public class MybatisAndSpringService {

    @Autowired
    private Mapper1 mapper1;
    
    // ......多个Mapper接口(省略)

    @Autowired
    private Mappern mappern;

    public void test() {
        String result1 = mapper1.select1();
        System.out.println("result1 = " + result1);
        String result2 = mapper2.select2();
        System.out.println("result2 = " + result2);
        //……
    }

}

// 主函数
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 引入FactoryBean接口,此时如果直接运行main函数依然会报错,因为自定义FactoryBean不在Spring容器中,所以需要采用声明式编程的方式,修改main函数:
public class ArtistFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        // JDK动态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{Mapper1.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("当前调用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理逻辑
        // return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return Mapper1.class;
    }
}

// main函数
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 到了这一步,直接运行是没有问题的,继续思考:FactoryBean的getObject方法,生成代理对象的接口是在代码中写死的,所以还需要通过构造函数传参的方式修改
// 修改FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }

    @Override
    public Object getObject() throws Exception {
        // JDK动态代理
        Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{mapperClazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("当前调用的方法名:" + method.getName());
                return null;
            }
        });
        return obj;

        // Mybatis的代理逻辑
        // return sqlSession.getMapper(mapperClazz);
    }

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

// 同时在main函数中也要修改
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 经过上述修改之后,FactoryBean中mapper接口的传入方式得到了解决,但是,如果此时要添加其他的mapper,比如:Mapper2,Mapper3……,就会出现下面的编码:
public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 注册主配置类
    ac.register(MyConfig.class);
    
    // Mapper1
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mapper1.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);
    
    // ……很多Mapper(省略)
    
    // Mappern
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(ArtistFactoryBean.class);
    // 新增逻辑
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Mappern.class);
    ac.registerBeanDefinition("facotrybean", beanDefinition);

    // 刷新容器
    ac.refresh();
    MybatisAndSpringService service = ac.getBean("mybatisAndSpringService", MybatisAndSpringService.class);
    service.test();
}
  • 显然这种编码方式非常不优雅,那么接下来可以考虑包扫描的方式,把Mapper接口扫描出来保存到一个集合中,然后遍历集合,就可以替换掉这么一大片冗余的代码,可以利用Spring提供的ImportBeanDefinitionRegistrar接口,实现它的registerBeanDefinitions方法
  • 同时结合Spring的类扫描器ClassPathBeanDefinitionScanner,但是Spring只扫描类,而这里我们只扫描接口,所以需要自定义一个类扫描器,需要修改isCandidateComponent的逻辑,只返回接口,否则Mapper接口无法扫描到
  • 重写doScan逻辑,因为我们最终需要的是Mapper接口的代理对象,所以需要把之前定义的FactoryBean设置进去
  • 需要注意,在配置类上需要加入注解:@Import(ArtistImportBeanDefinitionRegistrar.class),使Bean定义注册器成为Spring容器中的Bean
//自定义类扫描器
public class ArtistScanner extends ClassPathBeanDefinitionScanner {

    public ArtistScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    // 需要扫描到接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);

        for (BeanDefinitionHolder holder : holders) {
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(holder.getBeanDefinition().getBeanClassName());
            // 为了获取被扫描到的Mapper接口的代理对象
            beanDefinition.setBeanClass(ArtistFactoryBean.class);
        }
        return holders;
    }
}


// 批量注册Bean定义
public class ArtistImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String scanPath = "com.xxxx.mapper";
        // 此时要自定义包扫描器,因为Spring的包扫描器只扫描类,不扫描接口;而这里要扫描接口
        ArtistScanner scanner = new ArtistScanner(registry);
        // match方法返回true,保证接口能被扫描到
        scanner.addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
        scanner.scan(scanPath);
    }
}
  • 经过上述的修改,就可以把main方法中冗余的代码替换掉,到此还有一个最大的问题,getObject中生成代理对象是手工操作的,而代理对象应该由Mybatis框架生成,对应到文章开头提到的SqlSessionFactory
// 修改后的FactoryBean
public class ArtistFactoryBean implements FactoryBean {

    private Class<?> mapperClazz;
    public ArtistFactoryBean(Class<?> mapperClazz) {
        this.mapperClazz = mapperClazz;
    }
    
    @Autowired
    public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = sqlSessionFactory.openSession();
    }

    @Override
    public Object getObject() throws Exception {
        // Mybatis的代理逻辑
        return sqlSession.getMapper(mapperClazz);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperClazz;
    }
}
  • 可以看到,通过setter注入SqlSessionFactory对象,获取到SqlSession
  • 在配置类中也要添加SqlSessionFactory的Bean对象
@Configuration
@ComponentScan("com.mybatisandspring")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}
  • 继续看,我们的扫描路径是在代码中写死的,可以进一步作修改
  • 自定义一个注解,把mapper接口所在的包路径作为value,传入注解,然后在registerBeanDefinitions方法中,通过参数AnnotationMetadata获取该注解的value值,这样就可以灵活地配置了。
  • 首先自定义注解(这里把配置类中的@Import移到此处,为了代码的可读性,这里不影响逻辑的描述)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}
  • 接着在配置类上加上自定义注解,最终的配置类如下:
@Configuration
@ComponentScan("com.mybatisandspring")
@ArtistMapperScan("com.mybatisandspring.mapper")
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-spring-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
}
  • 到此为止,基本上mybatis-spring框架的核心原理基本都体现出来了,概括地说:就是把Mybatis生成的代理对象注入到Spring容器中,mybatis-spring.jar包起到了整合的桥梁的作用
  • 下面对比自己写的类和mybatis-spring.jar中的核心类
mybatis-spring包的整合源码分析
  • 对比Mapper扫描注解:
// @MapperScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
}

// 自定义扫描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ArtistImportBeanDefinitionRegistrar.class)
public @interface ArtistMapperScan {
    String value();
}
  • MapperScannerRegistrar的registerBeanDefinitions方法会先解析MapperScan注解,然后再注册Bean定义
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    // MapperScannerConfigurer执行扫描逻辑
    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(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

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

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // 注册Bean定义
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}
  • 在MapperScannerConfigurer中有一个属性mapperFactoryBeanClass,类型是MapperFactoryBean,它的getObject方法就是返回Mapper接口的代理对象,mapperInterface也是作为被扫描的属性传入
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}
  • 总结:

    • MapperFactoryBean完成的功能是生成Mapper接口的代理对象
    • MapperScannerConfigurer扫描Mapper接口
    • MapperScannerRegistrar完成的功能是把扫描到的Mapper接口的代理对象转为Bean定义,存入Spring容器
    • SqlSessionFactory负责解析配置的mybatis配置文件,包括数据源、类型别名、映射文件的扫描路径等等信息

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Docker入门实战 (二) - Docker环境的搭建方法(中)
Docker入门实战 (二) - Docker环境的搭建方法
3 0
Spring之Bean的生命周期详解
通过前面多个接口的介绍了解了Bean对象生命周期相关的方法,本文就将这些接口的方法串起来,来了解Bean的完整的生命周期。而介绍Bean的生命周期也是面试过程中经常会碰到的一个问题,如果不注意就跳坑里啦~~
4 0
Docker入门实战 (二) - Docker环境的搭建方法(下)
Docker入门实战 (二) - Docker环境的搭建方法
3 0
Spring之InitializingBean接口和DisposableBean接口介绍
本文来介绍下InitializingBean接口和DisposableBean接口的作用
5 0
Spring之Aware接口介绍
在Bean对象的生命周期的方法中有好几个接口是Aware接口的子接口,所以弄清楚Aware接口对于理解Spring框架还是很有帮助的。
3 0
Spring之Bean对象的初始化和销毁方法
在Bean对象的完整的生命周期前我们还需要给大家介绍下Bean对象自身初始化及销毁的相关方法。
3 0
Docker入门实战 (二) - Docker环境的搭建方法(上)
Docker入门实战 (二) - Docker环境的搭建方法
3 0
Java模板模式(template)
java23中设计模式中的模板模式是我们经常在框架源码中能看到的设计模式,所以本文就给大家来介绍下模板模式
4 0
教妹学Java(五):Java程序在编译和运行时发生了什么
教妹学Java(五):Java程序在编译和运行时发生了什么
3 0
Java装饰者模式(decorator)
文章目录 装饰者模式(decorator) 1.实现细节 2.案例演示 3.IO流实现细节 4.实际使用场景 5.总结
3 0
+关注
gbYu
极客coder
8
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载