什么是代理

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

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

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

初体验

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

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

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

pre
dosomeThing
after
______________________
pre
dosomeThing
after
______________________
pre
dosomeThing
after

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

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

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

动态代理与mybatis

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

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

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

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

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

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

点进getMapper的实现类 SqlSessionManager

发现这里有sql的各个方法

Connection getConnection(){}
void commit() {}
rollback(){}
int insert(String statement){}
update(String statement){}
...

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

 private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var12) {
throw ExceptionUtil.unwrapThrowable(var12);
}
} else {
SqlSession autoSqlSession = SqlSessionManager.this.openSession(); Object var7;
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var7 = result;
} catch (Throwable var13) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var13);
} finally {
autoSqlSession.close();
} return var7;
}
}
}

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

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

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

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

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null; DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
} return var8;
}

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

  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);
}
}
}

再继续扒newInstance方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.apache.ibatis.binding; import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession; public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
} private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
} return mapperMethod;
}
}

那到这里我们可以得出,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生命周期并不是同步的。

spring--------------------
class->扫描->新建实例->交给容器 mybatis ---------------------spring---
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. [最短路,floyd] Codeforces 1202B You Are Given a Decimal String...

    题目:http://codeforces.com/contest/1202/problem/B B. You Are Given a Decimal String... time limit per ...

  2. 干货 | Python进阶之学习笔记(一)

    认识Python Python应用场景 Python基础语法 一.认识Python Python 是一种计算机程序设计语言.是一种动态的.面向对象的脚本语言,最初被设计用于编写自动化脚本(shell) ...

  3. Flutter Weekly Issue 49

    插件/Librarys flutter_date_pickers Allows to use date pickers without dialog. Provides some customizab ...

  4. Linux上的软件安装有哪些方式?

    Linux上的软件安装有以下几种常见方式介绍 1.二进制发布包 软件已经针对具体平台编译打包发布,只要解压,修改配置即可 2.RPM包 软件已经按照redhat的包管理工具规范RPM进行打包发布,需要 ...

  5. Material Design 组件之 CollapsingToolbarLayout

    CollapsingToolbarLayout 主要用于实现一个可折叠的标题栏,一般作为 AppBarLayout 的子 View 来使用,下面总结一下 CollapsingToolbarLayout ...

  6. captcha-killer burp验证码识别插件体验

    0x01 使用背景 在渗透测试和src挖洞碰到验证码不可绕过时,就会需要对存在验证码的登录表单进行爆破,以前一直使用PKav HTTP Fuzzer和伏羲验证码识别来爆破,但是两者都有缺点PKav H ...

  7. CodeForces 6C(贪心 + 模拟)

    题目链接 思路如下 贪心的思想,⚠️女士优先的策略,当它们吃掉之前的物品所用的时间相同的时候,此时女士先开始 继续吃 题解如下 #include<iostream> using names ...

  8. PTA | 1010 一元多项式求导 (25分)

    设计函数求一元多项式的导数.(注:xn(n为整数)的一阶导数为n*xn-1.) 输入格式: 以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过1000的整数).数字间以空格分隔. 输出格式: ...

  9. 30.2 案例:ArrayList本身数据可以重复,写出去重的方法

    package day30_HashSet; /* * ArrayList特点(实现List接口) 有序.可以重复.可以使用索引 *使用ArrayList实现数据去重 * */ import java ...

  10. String 对象-->概念和创建

    1.String 对象 String 对象用于处理文本(字符串). String 对象创建方法: new String(). 语法: var txt = new String("string ...