mybatis整合spring代码模拟和源码分析

目录

1.代码结构图

2.依赖

2.1基于spring源码调试

2.2基于springboot调试

3.代码

3.1BatisConfig

3.2Student

3.3UserDao

3.4UserService

3.5UserServiceImpl

3.6MySqlSession

3.7MyFactoryBean

3.8MyImportBeanDefinitionRegistrar

3.9MyMapperScan

4.测试

4.1测试类

4.2结果

5.源码分析

5.0简介

5.1mybatis扩展的spring的扫描

5.2mybatis把自己的代理对象交给spring

5.3代理类是怎么生成及功能

6.总结


1.代码结构图

框红的类不需要,可以基于spring源码添加mybatis的依赖包调试,也可以建个springBoot项目,添加基本的依赖包

2.依赖

2.1基于spring源码调试

dependencies {
    //log 输出
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
    implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.1.7'
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7'
    implementation group: 'org.projectlombok', name: 'lombok', version: '1.18.12'

    //添加spring的依赖
    compile(project(":spring-context"))
    //数据源
    compile(project(":spring-jdbc"))
    //支持aop
//    compile(project(":spring-aspects"))
//    implementation group: 'org-aspectj', name: 'aspectjweaver', version: '1.9.6'
    implementation group: 'org.springframework', name: 'spring-aspects', version: '5.3.24'
    //连接池
    implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.23'
    //mybatis
    implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.5'
    //mybatis 和 spring 的 插件包
    implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.5'

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

2.2基于springboot调试

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.4</version>
        </dependency>        
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>

3.代码

3.1BatisConfig

package com.spring.mybatis.config;

import com.spring.mybatis.util.MyMapperScan;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**
 * @Description:
 * @Author: tangHC
 * @Date: 2023/6/21 0:32
 */

@Configuration
//@MapperScan("com.spring.mybatis.dao")
@ComponentScan("com.spring.mybatis")
@MyMapperScan("com.spring.mybatis.dao")
public class BatisConfig {

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource source = new DriverManagerDataSource();
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setPassword("root");
        source.setUsername("root");
        source.setUrl("jdbc:mysql://localhost:3306/stu?serverTimezone=Asia/Shanghai&useAffectedRows=true&useSSL=false&allowPublicKeyRetrieval=true");
        return source;
    }

    @Bean
    @Autowired
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        //如果有配置xml就引入
//		factoryBean.setConfigLocation(new ClassPathResource("mysql-config.xml"));
        return factoryBean.getObject();
    }

    @Bean
	public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
    	return new DataSourceTransactionManager(dataSource);
	}
}

3.2Student

@Data
public class Student implements Serializable {

    private Integer uid;
    private String name;

}

3.3UserDao

@Repository
public interface UserDao {

    @Select("select * FROM student where uid = 1")
	Student select();

}

3.4UserService

public interface UserService {
    void select();
}

3.5UserServiceImpl

package com.spring.mybatis.service.impl;

import com.spring.mybatis.dao.UserDao;
import com.spring.mybatis.entity.Student;
import com.spring.mybatis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j(topic = "e")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void select() {
        Student student = userDao.select();
		log.info("结果: " + student.toString());
    }



}

3.6MySqlSession

package com.spring.mybatis.util;

import com.spring.mybatis.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Description:
 * @Author: tangHC
 * @Date: 2023/6/21 1:15
 */

@Slf4j(topic = "e")
public class MySqlSession {

    public Object getMapper(Class<?> type) {
        //动态代理会重写toString 默认空实现
        return  Proxy.newProxyInstance(MySqlSession.class.getClassLoader(),
                new Class[]{type},
                new MyMapperProxy());
    }

    class MyMapperProxy implements InvocationHandler{

        /**
         * 完成所有代理功能  得到sql  执行sql
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.isAnnotationPresent(Select.class)){
                Select annotation = method.getAnnotation(Select.class);
                String sql = annotation.value()[0];
                log.info("sql: {}",sql);
                log.info("jdbc conn ...");
                log.info("execute query ...");
            }
            if (method.getName().equals("toString")){
                return proxy.getClass().getName();
            }
            Student student = new Student();
            student.setUid(1);
            student.setName("代理模拟返回查询结果");
            return student;
        }
    }

}

3.7MyFactoryBean

package com.spring.mybatis.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;

/**
 * @Description:
 * @Author: tangHC
 * @Date: 2023/6/21 14:17
 */

/**
 * FactoryBean 本身是一个bean,即生效需要把自己 MyFactoryBean 交给spring
 * 可以返回一个bean
 */
@Slf4j(topic = "e")
public class MyFactoryBean implements FactoryBean {

    Class<?> mapperInterface;

    public MyFactoryBean(){
        log.info("MyFactoryBean 无参构造");
    }

    /**
     * 使用构造函数注入
     * beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass);
     */
    public MyFactoryBean(Class<?> mapperInterface){
        log.info("MyFactoryBean 有参构造");
        this.mapperInterface = mapperInterface;
    }

    /**
     * 使用set方法注入
     * beanDefinition.getPropertyValues().add("mapperInterface", aClass);这种方式添加参数
     * 初始化时PropertyValues的参数赋值,需要set方法
     */
    public void setMapperInterface(Class<?> mapperInterface){
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object getObject() throws Exception {
        MySqlSession sqlSession = new MySqlSession();
        //FactoryBean 的 getObject() 方法 return 的对象,会交给spring容器管理
        return sqlSession.getMapper(mapperInterface);
    }

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

3.8MyImportBeanDefinitionRegistrar

package com.spring.mybatis.util;

import com.spring.mybatis.dao.UserDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description:
 * @Author: tangHC
 * @Date: 2023/6/21 16:13
 */

/**
 * ImportBeanDefinitionRegistrar
 * 此时BeanDefinition已经完成了扫描,用来注册一个beanDefinition到beanDefinitionMap
 */
@Slf4j(topic = "e")
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //假设完成了扫描
        getScanPackage(importingClassMetadata);
        /**
         * 测试用,直接是Class,而且 beanDefinition 直接设置为 FactoryBean 类型的
         * mybatis的 doScan,把包下的dao都扫描完成,形成 beanDefinition,然后把每个bd设置为mapperFactBean,
         * 所以 list 里是一个个 dao 的 BeanDefinition,把 FactoryBean代理的对象 aClass 设置到构造函数参数里,
         * 然后 通过 beanDefinition.setBeanClass(MyFactoryBean.class) 把当前 dao 的BeanClass类型,设置为
         * FactoryBean 类型。
         */
        List<Class<?>> list = new ArrayList<>();
        list.add(UserDao.class);

        for (Class<?> aClass : list) {
            //把 MyFactoryBean 加工成一个 beanDefinition 放到 beanDefinitionMap
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            /**
             * mybatis 真实使用的方式
             * beanDefinition.setBeanClass(MyFactoryBean.class);
             */

            /**
             * PropertyValues在beanDefinition中存这个类 MyFactoryBean 的属性和值,
             * 添加属性值需要有提供的set方法即MyFactoryBean.setMapperInterface方法
             */
//            beanDefinition.getPropertyValues().add("mapperInterface", aClass);
            /**
             * 设置构造函数的参数,MyFactoryBean需要提供对应的有参构造函数
             * ConstructorArgumentValues里面没值,走无参构造
             */
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass);
            String name = aClass.getSimpleName();
            String beanName = toLowerCaseFirstChar(name);
            registry.registerBeanDefinition(beanName, beanDefinition);
        }
    }

    public static void getScanPackage(AnnotationMetadata importingClassMetadata){
        MergedAnnotations annotations = importingClassMetadata.getAnnotations();
        MergedAnnotation<MyMapperScan> annotation = annotations.get(MyMapperScan.class);
        if (annotation.isPresent()) {
            Map<String, Object> attributes = annotation.asAnnotationAttributes();
            String[] value = (String[])attributes.get("value");
            if (value != null && value.length > 0) {
                String basePackage = value[0];
                //要扫描的包名
                log.info(basePackage);
            }
        }
    }

    public static String toLowerCaseFirstChar(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        char firstChar = Character.toLowerCase(str.charAt(0));
        if (str.length() == 1) {
            return String.valueOf(firstChar);
        } else {
            return firstChar + str.substring(1);
        }
    }
}

3.9MyMapperScan

package com.spring.mybatis.util;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * @Description:
 * @Author: tangHC
 * @Date: 2023/6/21 16:55
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface MyMapperScan {
    String[] value() default {};
}

4.测试

4.1测试类

package com.spring.mybatis.main;

import com.spring.mybatis.config.BatisConfig;
import com.spring.mybatis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


/**
 * @Description: 测试 mybatis 整合到 spring
 * @Author: tangHC
 * @Date: 2023/6/21 0:40
 */
@Slf4j(topic = "e")
public class MybatisMain {

    @Test
    public void MybatisMain03(){
        //执行mybatis操作  注释掉:BatisConfig 上的 @MapperScan 关闭了mybatis的代理
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(BatisConfig.class);
        context.refresh();
        UserService bean = context.getBean(UserService.class);
        bean.select();
    }


}

4.2结果

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\software\idea2019\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=61135:D:\software\idea2019\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\software\idea2019\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar;D:\software\idea2019\IntelliJ IDEA 2019.2.4\plugins\junit\lib\junit-rt.jar;D:\software\idea2019\IntelliJ IDEA 2019.2.4\plugins\junit\lib\junit5-rt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-context\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-context\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-jdbc\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-jdbc\out\production\resources;D:\gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-classic\1.1.7\9865cf6994f9ff13fce0bf93f2054ef6c65bb462\logback-classic-1.1.7.jar;D:\gradle\caches\modules-2\files-2.1\org.slf4j\slf4j-api\1.7.21\139535a69a4239db087de9bab0bee568bf8e0b70\slf4j-api-1.7.21.jar;D:\gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-core\1.1.7\7873092d39ef741575ca91378a6a21c388363ac8\logback-core-1.1.7.jar;D:\gradle\caches\modules-2\files-2.1\org.projectlombok\lombok\1.18.12\48e4e5d60309ebd833bc528dcf77668eab3cd72c\lombok-1.18.12.jar;D:\gradle\caches\modules-2\files-2.1\org.springframework\spring-aspects\5.3.24\e97d36c3e516d3dd0579437428b6e26902da0c88\spring-aspects-5.3.24.jar;D:\gradle\caches\modules-2\files-2.1\mysql\mysql-connector-java\8.0.23\14d64ac509adccaff444513c12646d93e4ed8be8\mysql-connector-java-8.0.23.jar;D:\gradle\caches\modules-2\files-2.1\org.mybatis\mybatis\3.5.5\1c8974cdec88c9259b6fa21147ea4538216e447\mybatis-3.5.5.jar;D:\gradle\caches\modules-2\files-2.1\org.mybatis\mybatis-spring\2.0.5\3bfeffacf579b7f607486c1cd32224643102c316\mybatis-spring-2.0.5.jar;D:\java_code\spring-framework-5.2.x\spring-aop\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-aop\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-tx\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-tx\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-beans\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-beans\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-expression\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-expression\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-core\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-core\out\production\resources;D:\java_code\spring-framework-5.2.x\spring-core\build\libs\spring-cglib-repack-3.3.0.jar;D:\java_code\spring-framework-5.2.x\spring-core\build\libs\spring-objenesis-repack-3.1.jar;D:\gradle\caches\modules-2\files-2.1\org.aspectj\aspectjweaver\1.9.6\ee3b73aa16df35179255f17354d9dfd8e7822835\aspectjweaver-1.9.6.jar;D:\gradle\caches\modules-2\files-2.1\com.google.code.findbugs\jsr305\3.0.2\25ea2e8b0c338a877313bd4672d3fe056ea78f0d\jsr305-3.0.2.jar;D:\gradle\caches\modules-2\files-2.1\com.google.protobuf\protobuf-java\3.11.4\7ec0925cc3aef0335bbc7d57edfd42b0f86f8267\protobuf-java-3.11.4.jar;D:\java_code\spring-framework-5.2.x\spring-jcl\out\production\classes;D:\java_code\spring-framework-5.2.x\spring-jcl\out\production\resources;D:\java_code\spring-framework-5.2.x\lib\junit-4.12.jar;D:\java_code\spring-framework-5.2.x\lib\hamcrest-core-1.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.spring.mybatis.main.MybatisMain,MybatisMain03

2023-11-30 11:16:38  DEBUG 219  --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1810399e
2023-11-30 11:16:38  DEBUG 234  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
2023-11-30 11:16:38  DEBUG 297  --- [           main] o.s.c.a.ClassPathBeanDefinitionScanner   : Ignored because not a concrete top-level class: file [D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes\com\spring\mybatis\dao\UserDao.class]
2023-11-30 11:16:38  DEBUG 297  --- [           main] o.s.c.a.ClassPathBeanDefinitionScanner   : Identified candidate component class: file [D:\java_code\spring-framework-5.2.x\spring-aaMy\out\production\classes\com\spring\mybatis\service\impl\UserServiceImpl.class]
MyImportBeanDefinitionRegistrar.java l=77 11:16:38.803 [main] INFO  e - com.spring.mybatis.dao
2023-11-30 11:16:38  DEBUG 391  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
2023-11-30 11:16:38  DEBUG 391  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
2023-11-30 11:16:38  DEBUG 391  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
2023-11-30 11:16:38  DEBUG 391  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
2023-11-30 11:16:38  DEBUG 406  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'batisConfig'
2023-11-30 11:16:38  DEBUG 422  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'userServiceImpl'
MyFactoryBean.java l=31 11:16:38.944 [main] INFO  e - MyFactoryBean 有参构造
2023-11-30 11:16:38  DEBUG 453  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'userDao'
2023-11-30 11:16:38  DEBUG 453  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'dataSource'
2023-11-30 11:16:38  DEBUG 469  --- [           main] o.s.j.d.DriverManagerDataSource          : Loaded JDBC driver: com.mysql.cj.jdbc.Driver
2023-11-30 11:16:38  DEBUG 469  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'sqlSessionFactoryBean'
2023-11-30 11:16:38  DEBUG 469  --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
2023-11-30 11:16:38  DEBUG 484  --- [           main] org.apache.ibatis.logging.LogFactory     : Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-11-30 11:16:38  DEBUG 484  --- [           main] o.mybatis.spring.SqlSessionFactoryBean   : Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
2023-11-30 11:16:39  DEBUG 532  --- [           main] o.mybatis.spring.SqlSessionFactoryBean   : Property 'mapperLocations' was not specified.
MySqlSession.java l=37 11:16:39.038 [main] INFO  e - sql: select * FROM student where uid = 1
MySqlSession.java l=38 11:16:39.038 [main] INFO  e - jdbc conn ...
MySqlSession.java l=39 11:16:39.038 [main] INFO  e - execute query ...
UserServiceImpl.java l=22 11:16:39.038 [main] INFO  e - 结果: Student(uid=1, name=代理模拟返回查询结果)



Process finished with exit code 0

5.源码分析

对于mybatis整合spring,主要问题是解决以下几点:

1.在扫描时,即@MyMapperScan("com.spring.mybatis.dao")扫描包,spring本身不会扫接口,所以需要扩展扫描

2.扫描后的dao层的接口是不能用的,需要代理成对象,而spring只提供接口,实现就得mybatis自己来

3.代理对象只能由mybatis完成,但是注入的对象是spring提供的,所以需要把代理对象交给spring管理

5.0简介

1.后文 beanDefinitionMap 简称bdMap;beanDefinition简称bd,beanDefinition是类信息的抽象,存储是否单例、是否懒加载、是否抽象、类属性值等等,就是用来描述这个类用于后面实例化和初始化。

2.ImportBeanDefinitionRegistrar接口,spring提供的回调方法,可以用户自己提供的BeanDefinition到bdMap,此时还处于整体开始实例化bean之前。

3.FactoryBean(工厂bean)本身是一个bean,同样他的getObject()方法,可以返回一个bean

4.BeanDefinitionRegistryPostProcessor是bean工厂后置处理器BeanFactoryPostProcessor的子类接口,执行时,先执行实现了子类的回调,在执行实现了父类的回调。

5.还是4.1的测试类,我们把BatisConfig类的上的MapperScan注释放开,把我们自己扩展的MyMapperScan注释掉,开始后面的调试。

5.1mybatis扩展的spring的扫描

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

1.这是我们常用的@MapperScan("com.xxx"),其中@Import注解,在spring解析配置类时,会拿到Import的这个类

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
  @Deprecated
  public void setResourceLoader(ResourceLoader resourceLoader) {
    // NOP
  }

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

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    //这里直接封装bd,类是MapperScannerConfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    //省略部分代码

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

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

2.ImportBeanDefinitionRegistrar接口的回调registerBeanDefinitions方法执行时,又包装了MapperScannerConfigurer.class为beanDefinition,放到bdMap里

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

3. MapperScannerConfigurer类继承了BeanDefinitionRegistryPostProcessor类,这是bean工厂后置处理器BeanFactoryPostProcessor的子类,所以在spring执行的invokeBeanFactoryPostProcessors(beanFactory)方法(bean工厂后置处理器执行方法)时,MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor类的回调方法将被执行

 @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      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(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

 4.自己实例化扫描器类ClassPathMapperScanner,继承了spring的扫描器ClassPathBeanDefinitionScanner,随后 scanner.registerFilters();

public void registerFilters() {
    //通过所有接口
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      //如果配了annotationClass = Mapper.class,则只有dao层加了Mapper的才被扫描
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }
    //省略部分代码
    if (acceptAllInterfaces) {
      //扫描规则默认返回true
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

  }

 5.添加过滤规则,如果@MapperScan(value = "com.spring.mybatis.dao", annotationClass = Mapper.class)则进入第一个if条件,如果没加 annotationClass = Mapper.class 则进入第二个if条件,全返回true,即可以扫描到接口

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

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

6.调用super.doScan(basePackages);意味着使用spring的扫描方法来扫描,那么这些接口将被包装为一个个beanDefinition,放入bdMap,至此扫描结束。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      //1.此时mybatis扫描完毕,@MapperScan注解里提供的包名就是扫描的包路径
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      //2.循环这些返回的bd,得到类名,也就是接口名称
      String beanClassName = definition.getBeanClassName();
      //3.给构造函数的参数设置值
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      //4.设置beanClass即当前bd描述的类的类型被改为mapperFactoryBeanClass->MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.setLazyInit(lazyInitialization);
    }
  }

7.上面这段代码,是不是类似我们的模拟代码 3.8MyImportBeanDefinitionRegistrar,通过这4步,在实例化时,将采用有参构造,参数是我们的接口名(这里是字符串,但是有全类名,对spring来说,全类名的字符串和类class一样),此时类类型是FactoryBean,除了名字是userDao,内里已经基本全部替换。

5.2mybatis把自己的代理对象交给spring

    @Test
    public void MybatisMain03(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(BatisConfig.class);
        context.refresh();
        UserService bean = context.getBean(UserService.class);
        bean.select();
    }

1.还是4.1的测试类,在context.refresh();之后,开始了整个容器的刷新过程。

/**
* 此时校验是从最新的 mergedBeanDefinitions 中拿的,冻结也不好使
* 在次判断是否单例
*/
if (mbd.isSingleton()) {
    /**
	* getSingleton方法
	* 0.在singletonsCurrentlyInCreation添加beanName,表示当前bean已经开始创建
	* 1.创建bean 2.放入单例池
 	*/
	sharedInstance = getSingleton(beanName, () -> {
		try {
			// 创建bean
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			destroySingleton(beanName);
			throw ex;
		}
	});
	//如果是工厂bean,返回自定义的,不是则直接返回
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

 2.这段代码是我们的createBean方法,在返回创建完的sharedInstance对象后,bean的生命周期基本已经完成,但是最后一行代码就是我们把代理类交给spring返回的入口。

 

3.记住此时的sharedInstance对象,他的类型是MapperFactoryBean,这个类继承了FactoryBean,还记的我们的 3.7章节的 MyFactoryBean吗?程序接着往下走

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
		Object object;
		try {
			if (System.getSecurityManager() != null) {
				AccessControlContext acc = getAccessControlContext();
				try {
					object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				//调用factoryBean的getObject方法,返回bean
				object = factory.getObject();
			}
		}

 4.终于看到了熟悉的factory.getObject();FactoryBean(工厂bean)可以返回一个bean交给spring管理。getObject方法在往下走一步

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

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }


  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

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

  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

5.对比我们 3.7MyFactoryBean,是不是极为相似,调用到MapperFactoryBean的getObject()方法,通过sqlSession.getMapper()返回代理对象,bean工厂的功能,返回一个bean给spring容器,至此容器中的userDao已经是一个正经的类了。

5.3代理类是怎么生成及功能

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

1.顺着return getSqlSession().getMapper(this.mapperInterface);方法接着往下走,最终调用newInstance方法完成动态代理,此时代理类返回。

需要注意的是参数mapperProxy是继承了InvocationHandler的,所以,代理的逻辑就在mapperProxy的invoke方法里。在3.5UserServiceImpl类中,执行userDao.select();方法时,执行的就是代理方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

2. 此时的SqlSession是SqlSessionTemplate,目前只是SqlSessionFactory和数据源DataSource已经创建,真正的openSession还没调用,所以目前SqlSession只是个引用对象。

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //getSqlSession将调用mybatis的
          //session = sessionFactory.openSession(executorType);方法,
          SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        //method.invoke方法和纯mybatis的方法还是有点不一样的,这一步就是userDao.select();的代理
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
      
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        
      } finally {
        if (sqlSession != null) {
          //这里关闭了sqlSession,放入连接池,即一级缓存不生效,因为每次查询都是一个新的session
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

 3.上面的getSqlSession方法,后续调用的openSession方法这里就不展开了,还有method.invoke后面具体调用的mybatis相关的代码也是一样。本篇主要讲mybatis整合spring,下面通过两个示例代码的注释大家对比下纯mybatis和mybatis整合spring后的区别。

代码片段1,spring和mybatis

@Service
@Slf4j(topic = "e")
public class UserServiceImpl implements UserService {

	/**
	 * 在初始化UserServiceImpl时,需要填充属性userDao
	 */
    @Autowired
    private UserDao userDao;

    @Override
    public void select() {
		/**
		 * 1.获取工厂对象创建sqlSession
		 * 2.调用插件代理Executor的方法
		 * 3.建立被spring管理的事务对象和连接
		 * 4.执行真实的sql语句
		 * 5.提交commit,spring整合mybatis,默认事务手动提交
		 * 6.sqlSession.close关闭sqlSession,所以一级缓存不生效,每次查询都是一个全新的sqlSession
		 */
        List<Student> student = userDao.select();
		log.info("结果: " + student.toString());
    }

}

 代码片段2,纯mybatis

@Slf4j
public class MybatisApplication {

    public static void main(String[] args) throws Exception {

        /**
         * 第一步,通过classLoader读取 mybatis-config.xml 配置文件
         */
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        /**
         * 第二步
         * 1.读取 .dtd文件(mybatis文件的标签规范),把流文件解析为document,再下一步解析成xNode对象
         * 2.开始进行Configuration对象的初始化,从xNode里把标签值拿出来封装要用的对象,比如数据源、事务工厂
         * 3.构建 sqlSessionFactory 框架初始化 底层等于 new DefaultSqlSessionFactory(config)
         * 相当于创建了一个 DefaultSqlSessionFactory对象,通过构造持有 Configuration(包含整个xml解析后的标签信息 + 事务工厂 + 数据源对象)的引用
         */
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        /**
         * 第三步,打开 sqlSession,返回了 DefaultSqlSession对象
         * 此对象持有Configuration、
         * 和执行器,包含 事务对象 + datasource数据源,和操作sql的 Executor执行器接口的实现类
         */
        SqlSession sqlSession = sqlSessionFactory.openSession();

        /**
         * 第四步,获取第二步解析xml得到的接口,通过动态代理生成代理对象
         * 代理的业务逻辑即增删改查等在 MapperProxy对象的invoke里
         */
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        /**
         * 第五步,调用接口执行操作数据库的方法,调用代理类的invoke方法
         * 1.找到要执行的sql类型(select、update等),以及确定是否有返回值及返回一个还是多个,通过 MapperMethod来执行方法
         * 2.预编译sql语句,其中首先加载驱动、获取连接、代理增强(连接池)、代理增强(日志打印)、在通过conn获取statement
         *  (封装了下面jdbc的1、2、3、4步)
         * 3.执行sql,封装结果集返回,根据映射关系 resultType 或 resultMap拿到实体属性和数据库字段的对应关系
         *   对返回结果进行逐行逐循环处理,通过反射创建要返回的对象,然后反射调用set方法给对象中的字段赋值
         */
        Student select = mapper.select();

        log.info(select.toString());

        /**
         * 第六步,关闭,close方法被代理,连接放入空闲连接池
         */
        sqlSession.close();

    }

 4,通过上面两个代码可以看出,和spring整合后,spring容器在初始化后,sqlSessionFactory和dataSource和userDao对象已经在容器中,即第二个代码的第一和第二步和第四步已经完成。而在第一个代码执行select方法时,内部完成了第二个代码的第三步和第五步和第六步。

6.总结

1.我们在dao层写的使用mybatis查询数据库的方法,都是一个个接口,在使用查询方法时,通过@Autowired 注入比如userDao,然后使用userDao的方法,此时的userDao已经不是一个接口,而是一个bean,所以mybatis实现对spring的整合,需要生成接口的代理对象,并且把代理对象交给spring容器管理。

2.mybatis生成代理对象是在sqlSession.getMapper(UserDao.class)这一步,
通过动态代理为传入的接口生成代理对象,其中完成了从xml中解析sql语句,通过SqlSessionFactory产生sqlSession,并最终执行sql语句的方法。

3.代理对象生成后,需要做的就是把代理对象交给spring容器管理,此时需要用到spring中的FactoryBean接口和ImportBeanDefinitionRegistrar接口,mybatis实现FactoryBean的类叫做mapperFactoryBean,可以通过getObject方法返回一个对象,会被交给spring管理,mybatis实现ImportBeanDefinitionRegistrar的类叫做MapperScannerRegistrar,顾名思义,可以注册BeanDefinition到BeanDefinitionMap中,mybatis通过doScan方法解析出MapperScan注解中要扫描的包,并且把包下相关的接口全部生成对应的BeanDefinition,然后设置BeanDefinition的BeanClass为mapperFactoryBean类型,生成代理对象需要传入代理的接口,把接口也设置进BeanDefinition中,在执行实现了mapperFactoryBean的方法时,在BeanDefinition里拿到要代理的接口,在getObject方法里完成动态代理。

4.最后一步,就是把MapperScannerRegistrar类通过@Import注解导入,作为入口,让spring开始解析。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值