Spring和Mybatis主流框架的部分源码分析

Mybatis的简单查询

首先 我们按照官方文档 新建一个查询
配置数据源

@SpringBootApplication
@MapperScan("com.springmybatisexample.demo.Dao")
public class test implements ApplicationRunner {
    @Autowired
    UserService userService;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource source = new DriverManagerDataSource();
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");//一定要加上时区
        source.setUsername("root");
        source.setPassword("123456");
        return source;
    }

    @Bean
    @Autowired
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean;
    }

    public static void main(String[] args) {
        SpringApplication.run(test.class, args);

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        userService.list();
    }
}

写一个mapper

@Mapper
public interface UserDao {
    @Select("select * from test1")
    public List<Map<String,String>> FindUser();
}

实现mapper的一个service

@Service
public class UserService {

    @Autowired
    UserDao userDao;

    public void list(){
        List<Map<String, String>> users = userDao.FindUser();
        System.out.println(users);
    }

}

一个简单的通过mybaits查询数据库的方法 就写完了

接下来 执行操作

在这里插入图片描述

mybaits的核心功能

在我们的UserService类中 注入的 userDao 这个userDao一定是个对象 不然 调用不了方法 但是 我们发现 这个 userDao 是一个接口 如果说 userDao 是个对象的话 它一定是实现了userDao的某个类的对象 所以 我们就发现了一个 问题 mapper类的实现 是哪里来的?
通过 官方文档 mybaits的流程 大致如下:

 DataSource dataSource=null;//数据源
        Environment environment=null;//环境参数
        Configuration configuration = new Configuration(null);//配置类
        configuration.addMapper(UserDao.class);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(configuration);
        //以上为初始化 SqlSessionFactory

        SqlSession sqlSession = build.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);//通过动态代理  我们返回了一个我们需要的对象
        mapper.FindUser();

我们看getMapper()方法中 动态代理的具体实现:
使用 ctrl+alt+b 查看这个接口的实现类 在 DefaultSqlSession 类 中 进行的实现 最后 我们找到这个

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

我们 可以看到 它有一个 返回值 return mapperProxyFactory.newInstance(sqlSession);
所以 这一行代码的意义 UserDao mapper = sqlSession.getMapper(UserDao.class)
就是 返回了一个代理对象 底层 是通过jdk包下的类实现的

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

最终 我们可以得到一个大概的结论:mybaits的核心功能 就是通过JDK的动态代理来产生了一个代理对象 代理对象实现了我们的dao/mapper

模拟mybatis的核心功能

因为 我们可以看到 spring的动态代理 使用的是sqlSession.getMapper实现的 所以 我们也写一个
叫 EnjoySessions
需要 注意的是 我们在这个地方产生的类是没有对象的 它的对象在内存中 大概长这个样子

public class EnjoySessions  {
    public static Object GetMapper(Class clazz){

        Class[] classes =new Class[] {clazz};//这个对象 需要实现的接口
        Object o=Proxy.newProxyInstance(EnjoySessions.class.getClassLoader(),classes,new EnjoyHandler());//mybatis中的方法
                //第一个参数 类加载器 基本上 就是这个类的类加载器  
                //第三个参数 也就是说  当我们调用mapper.FindUser(); 这个方法的时候 会执行到handler中的invoke中
        return null;
    }
}

public class A implements UserDao {


    @Override
    public List<Map<String, String>> FindUser() {
        //去调用这个接口对象的invoke()方法;
        //invoke();
        return null;
    }
}

特别是 第三个参数 其意义是 当我们调用FindUser方法的时候 它会去调用EnjoyHandler中的invoke方法 进行数据库的连接查询

public class EnjoyHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("conn");//数据库的连接
        String s = method.getAnnotation(Select.class).value()[0];//读取到selec上写的SQL语句 多个select的话 用循环处理
        System.out.println(s);
        return null;
    }
}

结果如下:
在这里插入图片描述

将模拟出的Mybatis和Spring配合工作

需求:将我们产生的对象 放到spring的容器当中

如何把一个对象交给spring管理?
1、xml配置?<bean>?
2、注解方式 @Controller?
3、@bean 方式?
4、通过spring ApplicationContext的api方式?
5、扩展spring动态加入BeanDefinition方式?
6、通过FactoryBean的方式?

把一个对象 交个给pring管理 只有3和6可以
其他的方法 都是将类交给spring管理

他们的区别
把类交给spring管理 在spring扫描到它之后 会对它进行实例化
把对象交给spring管理 我们可以自己控制对象的产生过程

比如说 我们之前使用的newProxyInstance 它是存在于mybatis中的方法 spring中不存在

使用@bean

public class test implements ApplicationRunner {
  
    @Autowired
    UserDao dao;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource source = new DriverManagerDataSource();
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");//一定要加上时区
        source.setUsername("root");
        source.setPassword("123456");
        return source;
    }

    @Bean
    @Autowired
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean;
    }

    @Bean
    public UserDao userDao(){
        UserDao o = (UserDao) EnjoySessions.GetMapper(UserDao.class);
        return o;

    }

    public static void main(String[] args) {
        SpringApplication.run(test.class, args);

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
      
        dao.FindUser();

       
    }
}

在这里插入图片描述

使用FactoryBean
因为 我们不可能每一个bean都去自己写 所以 mybatis使用的是这个方法

@Component
public class EnjoyFactoryBean implements FactoryBean {
    //FactoryBean  自己是一个bean  里面的getObject返回的对象也是个bean   只能返回一个mapper
 
    @Override
    public Object getObject() throws Exception {
        UserDao o = (UserDao) EnjoySessions.GetMapper(UserDao.class);
        return o;
    }

    @Override
    //告诉上面 返回对象的类型
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

运行的时候 把UserDao的注释去掉
在这里插入图片描述
当然 这种处理还是很简陋的 无法处理多个bean 所以 为了处理多个bean 进行改进:

@Component
public class EnjoyFactoryBean implements FactoryBean {
    //FactoryBean  自己是一个bean  里面的getObject返回的对象也是个bean   只能返回一个mapper
    Class mapperInterface;


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

    @Override
    public Object getObject() throws Exception {
        Object o = EnjoySessions.GetMapper(mapperInterface);
        return o;
    }

    @Override
    //告诉上面 返回对象的类型
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

每次调方法的时候 传入 class即可

当然 在bean创建生成之后 一般来说 会保存在一个全局变量list中 是不能修改的

bean生成的具体流程
首先 通过类加载器 将被注解的类和为注解的类 通过 javac 变成 .class文件
run 之后 当 spring容器 扫描到 被注解的类之后 将这个类 变成BeanDefinition对象
伪代码:

  Map map=null;//spring的全局变量
        List<Class> list = null;//保存的被扫描出来的信息
        for (Class aClass : list) {
            GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
            //通过这个对象 将扫描出来的类的信息 保存到这个对象里面
            genericBeanDefinition.setBeanClass(aClass);//设置bean的名字
            genericBeanDefinition.setScope("pro");//设置作用域
            genericBeanDefinition.setAutowireMode(1);//设置类的注入模型  是通过byname还是bytype 还是通过构造方法
            genericBeanDefinition.setLazyInit(true);//是否懒加载
            ...
            ...
            map.put("x",genericBeanDefinition);//将信息对象放到map中
        }
        map.forEach(
                //取出里面的信息 然后new出来
        );

我们程序员通过写的实现BeanFactoryPostProcessor接口的方法
即 通过我们写的这个方法 对于list 进行 操作
比如说 我们现在有A B 两个类 一个有注解 一个无注解 我们可以通过这个方法 取出名为x的bean 然后将y注入

例子

@Component
public class X {
}

public class Y {
    public void test(){
        System.out.println("aaa");
    }
}

@Component
public class EnjoyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory BeanFactory) throws BeansException {
        GenericBeanDefinition x = (GenericBeanDefinition) BeanFactory.getBeanDefinition("x");//通过全局变量map 去取出我们需要的信息对象 不过 这个map我们只有间接权限
        x.setBeanClass(Y.class);

    }
}

@SpringBootApplication
//@MapperScan("com.springmybatisexample.demo.Dao")
public class test implements ApplicationRunner {
    //@Autowired
    //UserService userService;

    //@Autowired
    //UserDao dao;

    @Autowired
    Y y;

    public static void main(String[] args) {
        SpringApplication.run(test.class, args);

    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        y.test();
    }
    }

在这里插入图片描述
这个时候 我们取x 是取不到的
由此 可知 在spring中 一个bean的产生 与提供的类 关系不大 因为中间有可能被篡改 只有和其对应的BeanDefinition 有关系
即 我们创建一个bean 可以不通过扫描 直接通过创建相应的 BeanDefinition 加入到Map中 就行
这个 接口 只通供了 我们对map的访问 权限

如果 要对这个mapper进行 操作 我们就需要实现 ImportBeanDefinitionRegistrar 接口
这个方法 spring自己会回调 在spring容器扫描完了之后 放到这个map之后 就会调用 程序员提供的这个类

public class EnjoyBeanDefinitionRegistart implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        //这个方法 spring自己会回调 在spring容器扫描完了之后 放到这个map之后 就会调用 程序员提供的这个类
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(EnjoyFactoryBean.class);
        //通过BeanDefinitionBuilder的genericBeanDefinition方法 创建一个 BeanDefinitionBuilder 并取出它的beanDefinition
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.getPropertyValues().add("mapperInterface", UserDao.class);
        //这个一个集合
        registry.registerBeanDefinition(String.valueOf(EnjoyFactoryBean.class),beanDefinition );//bean的名字 BeanDefinition
    }
}

在这里插入图片描述
输出 正常

当然 目前 是写死的 我们需要对他进行 一个 循环操作

    List<Class> list=null;//保存 需要装配的bean
        for(class clazz : list){
            //这个方法 spring自己会回调 在spring容器扫描完了之后 放到这个map之后 就会调用 程序员提供的这个类
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(EnjoyFactoryBean.class);
            //通过BeanDefinitionBuilder的genericBeanDefinition方法 创建一个 BeanDefinitionBuilder 并取出它的beanDefinition
            AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
            beanDefinition.getPropertyValues().add("mapperInterface", UserDao.class);
            //这个一个集合
            registry.registerBeanDefinition(String.valueOf(EnjoyFactoryBean.class), beanDefinition);//bean的名字 BeanDefinition
        }

importingClassMetadata 这个是传进来的包名 通过这个包名 将其下的所有dao扫描出来

同样 @MapperScan(“com.springmybatisexample.demo.Dao”) 等价于
我们自己建的 @HuiScan(“com.springmybatisexample.demo.Dao”)

@Retention(RetentionPolicy.RUNTIME)
@Import(EnjoyBeanDefinitionRegistart.class)
public @interface HuiScan {
}

总结

说来说去 我们也就用了Spring中的3个扩展点 分别为:

FactoryBean
将多个dao 初始化为bean 通过GetMapper( )方法 这里面 又通过了Proxy.newProxyInstance(EnjoySessions.class.getClassLoader(), classes, new EnjoyHandler());
这个jdk的方法 其中的 EnjoyHandler( ) 是 我们调对象方法的时候 会掉这个对象中的 invoke( )方法,在里面 实现了数据库的连接 jdbc的实现 返回我们需要的数据
当然 多个bean的话 要使用 ImportBeanDefinitionRegistrar 进行辅助

BeanFactoryPostProcessor
可以 将全局变量map中 创建出的bean 根据名字 取出来 然后 放新的class

ImportBeanDefinitionRegistrar
可以对于全局变量map 继续 增加操作 通过注解 将importingClassMetadata(需要扫描的包名)下的所有类 通过循环 使用mapperInterface注入进去 完成扫描
这个方法 spring自己会回调 在spring容器扫描完了之后 放到这个map之后 就会调用 程序员提供的这个类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值