什么是代理

代理模式,目的就是为其他对象提供一个代理以控制对某个对象的访问,代理类为被代理者处理过滤消息,说白了就是对被代理者的方法进行增强。

看到这里,有没有感觉很熟悉?AOP,我们熟知的面向切面编程,不也是对方法增强,对切点进行处理过滤么。

其实AOP这种设计思想,他的精髓便是,在预编译和运行阶段使用动态代理实现的。

初体验

下面是我自己写的小例子。

  1. //代理的接口
  2. /**
  3. * @created with IDEA
  4. * @author: yonyong
  5. * @version: 1.0.0
  6. * @date: 2020/4/21
  7. * @time: 21:13
  8. **/
  9. public interface person {
  10. void doSomething();
  11. void fun1();
  12. void fun2();
  13. }
  1. //被代理者/委托人
  2. /**
  3. * @created with IDEA
  4. * @author: yonyong
  5. * @version: 1.0.0
  6. * @date: 2020/4/21
  7. * @time: 21:13
  8. **/
  9. public class Student implements person{
  10. @Override
  11. public void doSomething() {
  12. System.out.println("dosomeThing");
  13. }
  14. @Override
  15. public void fun1() {
  16. System.out.println("fun1");
  17. }
  18. @Override
  19. public void fun2() {
  20. System.out.println("fun2");
  21. }
  22. }
  1. //实现InvocationHandler接口,加入切面逻辑
  2. /**
  3. * @created with IDEA
  4. * @author: yonyong
  5. * @version: 1.0.0
  6. * @date: 2020/4/21
  7. * @time: 21:15
  8. **/
  9. public class StudentProxyHandler implements InvocationHandler {
  10. Student target;
  11. public StudentProxyHandler(Student target) {
  12. this.target = target;
  13. }
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. System.out.println("pre");
  17. Object obj = method.invoke(target,args);
  18. System.out.println("aft");
  19. return obj;
  20. }
  21. }
  1. /**
  2. * @created with IDEA
  3. * @author: yonyong
  4. * @version: 1.0.0
  5. * @date: 2020/4/21
  6. * @time: 21:19
  7. **/
  8. public class Main {
  9. public static void main(String[] args) {
  10. Student p=new Student();
  11. person person = (person) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), new StudentProxyHandler(p));
  12. person.doSomething();
  13. System.out.println("______________________");
  14. person.fun1();
  15. System.out.println("______________________");
  16. person.fun2();
  17. }
  18. }

运行代码,我们可以得到:

  1. pre
  2. dosomeThing
  3. after
  4. ______________________
  5. pre
  6. dosomeThing
  7. after
  8. ______________________
  9. pre
  10. dosomeThing
  11. after

我们来通过打印的结果来实实在在的体会代理模式的优点及aop的特性:

如果有种业务场景,需要有多个方法有重复的代码块,或者相同的实现规则,如果不用aop我们可能要每个方法写一份规则,或者自定义个方法,每个方法调用来实现。首先这就已经使代码变得侵入性,也违反了java的封装重构原则。

而使用java动态代理,无论这个委托人有多少方法,他都会执行切面逻辑里的规则,这样很便于后期的代码维护,即便是后续加入新的方法,也无须考虑其他的,因为在切面里都已经做好了,这样代码的侵入性便降了很多。其实这和AOP的原理是一样的。

动态代理与mybatis

看到这里,我便觉得mybatis和j动态代理必定有着很紧密的联系。

但我们通过上面的例子,我们知道,想要用动态代理,必须要有个接口的实现类,否则代理接口便没什么意义。而mybatis只是接口+xml的形式,他是怎么被代理的呢?

还有一个疑问,我们知道Spring的注解如果放在service的接口层,而不是放在实现类,他会找不到这个bean,那为什么mapper层可以加注解呢?

首先我们通过查询资料知道,mybatis确实是有动态代理实现的。那我们带着这个疑问,去看mybatis的源码。

为了便于查看源码,我使用sqlsession的方式获取mapper。

  1. LRoleMapper lRoleMapper = sqlSessionFactory.openSession().getMapper(LRoleMapper.class);
  2. lRoleMapper.selectAll();

点进getMapper的实现类 SqlSessionManager

发现这里有sql的各个方法

  1. Connection getConnection(){}
  2. void commit() {}
  3. rollback(){}
  4. int insert(String statement){}
  5. update(String statement){}
  6. ...

那我们在往下寻找,我看到了这个方法

  1. private class SqlSessionInterceptor implements InvocationHandler {
  2. public SqlSessionInterceptor() {
  3. }
  4. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  5. SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
  6. if (sqlSession != null) {
  7. try {
  8. return method.invoke(sqlSession, args);
  9. } catch (Throwable var12) {
  10. throw ExceptionUtil.unwrapThrowable(var12);
  11. }
  12. } else {
  13. SqlSession autoSqlSession = SqlSessionManager.this.openSession();
  14. Object var7;
  15. try {
  16. Object result = method.invoke(autoSqlSession, args);
  17. autoSqlSession.commit();
  18. var7 = result;
  19. } catch (Throwable var13) {
  20. autoSqlSession.rollback();
  21. throw ExceptionUtil.unwrapThrowable(var13);
  22. } finally {
  23. autoSqlSession.close();
  24. }
  25. return var7;
  26. }
  27. }
  28. }

看到这里,是不是眼前一亮,那我们再看这个类的构造方法

  1. private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
  2. this.sqlSessionFactory = sqlSessionFactory;
  3. this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
  4. }

那看到这里我们明白了,这个类代理的是SqlSessionFactory这个类,而它的切面逻辑,就是执行方法前,如果当前sqlsession存在sqlsession,就正常执行这个方法,如果不存在,就创建一个session,创建失败再回滚数据。

那这里的opensession,我直接贴源码了,无非就是从配置文件读取jdbc,连接验证后,获取会话之类,大家感兴趣可以一层一层往下扒。

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. DefaultSqlSession var8;
  4. try {
  5. Environment environment = this.configuration.getEnvironment();
  6. TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
  7. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  8. Executor executor = this.configuration.newExecutor(tx, execType);
  9. var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
  10. } catch (Exception var12) {
  11. this.closeTransaction(tx);
  12. throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
  13. } finally {
  14. ErrorContext.instance().reset();
  15. }
  16. return var8;
  17. }

我们继续回到getMapper方法,我们一层一层往下扒,最后我们可以看到这个类

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

再继续扒newInstance方法

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package org.apache.ibatis.binding;
  6. import java.io.Serializable;
  7. import java.lang.reflect.InvocationHandler;
  8. import java.lang.reflect.Method;
  9. import java.util.Map;
  10. import org.apache.ibatis.reflection.ExceptionUtil;
  11. import org.apache.ibatis.session.SqlSession;
  12. public class MapperProxy<T> implements InvocationHandler, Serializable {
  13. private static final long serialVersionUID = -6424540398559729838L;
  14. private final SqlSession sqlSession;
  15. private final Class<T> mapperInterface;
  16. private final Map<Method, MapperMethod> methodCache;
  17. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  18. this.sqlSession = sqlSession;
  19. this.mapperInterface = mapperInterface;
  20. this.methodCache = methodCache;
  21. }
  22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  23. if (Object.class.equals(method.getDeclaringClass())) {
  24. try {
  25. return method.invoke(this, args);
  26. } catch (Throwable var5) {
  27. throw ExceptionUtil.unwrapThrowable(var5);
  28. }
  29. } else {
  30. MapperMethod mapperMethod = this.cachedMapperMethod(method);
  31. return mapperMethod.execute(this.sqlSession, args);
  32. }
  33. }
  34. private MapperMethod cachedMapperMethod(Method method) {
  35. MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
  36. if (mapperMethod == null) {
  37. mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
  38. this.methodCache.put(method, mapperMethod);
  39. }
  40. return mapperMethod;
  41. }
  42. }

那到这里我们可以得出,mybatis中使用了大量的java动态代理。

这个getMapper呢,到这里也就结束了,可以看到,SqlSession这个对象,代理的接口就是我们的mapper层dao,而这里的切面逻辑,如果当前声明的类是Object,直接执行方法,如果不是,执行另外的excute。这里我没有细看,初步理解是区分注解sql与mapper.xml 的sql。因为获取mapper的sql是通过反射得到的Class类。有兴趣的同学可以继续扒,我精力有限就先到这儿了。

到此为止,第一个问题迎刃而解,总结来说,其实我们注入的mapper,是动态代理产生的对象

那么为什么Mapper层加注解,spring也能获取到呢。

其实这个问题已经和java动态代理没什么关系了,在这里大概解释一下。

mybatis并不是spring的产品,而作为第三方的插件,我们都知道spring被称作胶水框架,而第三方就需要将自己的产品让spring管理。

换句话说,他们和spring自身的bean生命周期并不是同步的。

  1. spring--------------------
  2. class->扫描->新建实例->交给容器
  3. mybatis ---------------------spring---
  4. class -> 扫描-> 新建对象 -> 交给spring

同理,mybatis那就需要自己创建对象,把他交给spring。而我们平时的那些注解Mapperscan之类的,其实只是mybatis在标志这些接口,使用反射,获取这些类,实现一个ImportBeanDefinitionRegistrar接口,把自己产生的对象交给Spring。

jdk动态代理:由浅入深理解mybatis底层的更多相关文章

  1. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  2. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  3. JDK动态代理[4]----ProxyGenerator生成代理类的字节码文件解析

    通过前面几篇的分析,我们知道代理类是通过Proxy类的ProxyClassFactory工厂生成的,这个工厂类会去调用ProxyGenerator类的generateProxyClass()方法来生成 ...

  4. JDK动态代理浅析

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/17.html 作者:夜月归途 出处:http://www.guitu ...

  5. 从Mybatis源码理解jdk动态代理默认调用invoke方法

    一.背景最近在工作之余,把开mybatis的源码看了下,决定自己手写个简单版的.实现核心的功能即可.写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题. ...

  6. jdk动态代理底层实现

    一.代理设计模式 代理设计模式是Java常用的设计模式之一. 特点: 01.委托类和代理类有共同的接口或者父类: 02.代理类负责为委托类处理消息,并将消息转发给委托类: 03.委托类和代理类对象通常 ...

  7. jdk动态代理和cglib动态代理底层实现原理超详细解析(jdk动态代理篇)

    代理模式是一种很常见的模式,本文主要分析jdk动态代理的过程 1.举例 public class ProxyFactory implements InvocationHandler { private ...

  8. MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析

    我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...

  9. AOP的底层实现:JDK动态代理与Cglib动态代理

    转载自 https://www.cnblogs.com/ltfxy/p/9872870.html SpringAOP底层的实现原理: JDK动态代理:只能对实现了接口的类产生代理.(实现接口默认JDK ...

随机推荐

  1. python ndarray与pandas series相互转换,ndarray与dataframe相互转换

    https://blog.csdn.net/qq_33873431/article/details/98077676

  2. sql-lib闯关21-30

    第二十一关 base64编码,单引号,报错型,cookie型注入. 本关和less-20相似,只是cookie的uname值经过base64编码了,下图为我们输入万能密码显示 uname = YWRt ...

  3. Python第三章-输入输出和运算符

    输入输出和运算符 一.输入和输出 1.1 输出 `print()`函数用来向终端输出数据(其实也可以向文件输出数据,后面再讲) 可以传递多个参数,则输出的时候 python 会把多个参数的值用空格隔开 ...

  4. Springcloud 整合Hystrix 断路器,支持Feign客户端调用

    1,在这篇博文中,已经大致说过了Springcloud服务保护框架 Hystrix在服务隔离,服务降级,以及服务熔断中的使用 https://www.cnblogs.com/pickKnow/p/11 ...

  5. 不可被忽视的操作系统( FreeRTOS )【1】

    把大多数人每个星期的双休过过成了奢侈的节假日放假,把每天23点后定义为自己的自由时间,应该如何去思考这个问题 ? 双休的两天里,不!是放假的两天里,终于有较长的时间好好的学习一下一直断断续续的Free ...

  6. 2288: 【基础】小X转进制

    2288: [基础]小X转进制 时间限制: 1 Sec 内存限制: 256 MB 提交: 1316 解决: 576 [提交] [状态] [讨论版] [命题人:ghost79] 题目描述 小X喜欢研究进 ...

  7. 新建基于STM32F103ZET6的工程-HAL库版本

    1.STM32F103ZET6简介 STM32F103ZET6的FLASH容量为512K,64K的SRAM.按照STM32芯片的容量产品划分,STM32F103ZET6属于大容量的芯片. 2.下载HA ...

  8. 【tensorflow2.0】AutoGraph的机制原理

    有三种计算图的构建方式:静态计算图,动态计算图,以及Autograph. TensorFlow 2.0主要使用的是动态计算图和Autograph. 动态计算图易于调试,编码效率较高,但执行效率偏低. ...

  9. PTA数据结构与算法题目集(中文) 7-38寻找大富翁 (25 分)

    PTA数据结构与算法题目集(中文)  7-38寻找大富翁 (25 分) 7-38 寻找大富翁 (25 分)   胡润研究院的调查显示,截至2017年底,中国个人资产超过1亿元的高净值人群达15万人.假 ...

  10. Shell脚本的编写及测试

                                                      Shell脚本的编写及测试 1.1问题 本例要求两个简单的Shell脚本程序,任务目标如下: 编写一 ...