什么是代理

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

看到这里,有没有感觉很熟悉?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. Java 访问修饰符大全详解

    鉴于笔试面试总会遇到,决心仔细认真梳理一下: 1:涉及的关键字:public,default(表示缺省),protected,private,static,final,abstract. 2:关键字含 ...

  2. 学霸双胞胎开源斯坦福CS 221人工智能备忘录,图文并茂看懂反射、状态、变量、逻辑...

    一份斯坦福CS 221人工智能备忘录最近登上了GitHub Trending. 这份备忘录解释了课程中的许多名词.公式和原理,动图.文字.表格并茂,作者之一还是官方助教,堪称CS 221最佳学习笔记. ...

  3. 详解Redis持久化(RDB和AOF)

    详解Redis持久化(RDB和AOF) 什么是Redis持久化? Redis读写速度快.性能优越是因为它将所有数据存在了内存中,然而,当Redis进程退出或重启后,所有数据就会丢失.所以我们希望Red ...

  4. 纯干货 C# 通过 RFC_READ_TABLE 读取 SAP TABLE

    SAP系统又称企业管理解决方案,是全球企业管理软件与解决方案的技术领袖,同时也是市场领导者.通过其应用软件.服务与支持,SAP持续不断向全球各行业企业提供全面的企业级管理软件解决方案. 在实际开发过程 ...

  5. python pdb 转载:https://www.linuxidc.com/Linux/2017-11/148329.htm

    最近在为一个监控系统开发agent,需要支持Linux.FreeBSD及Windows等操作系统.复杂的线上环境,带来了一系列诡异的问题,尽管代码上线前在为数不少的测试机器验证过. Python程序吐 ...

  6. NSObject常用方法

    类 @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; } // 初始化加载 + (void)load; / ...

  7. [leetcode] 树 -Ⅰ

    均为 Simple 难度的水题. 二叉树的中序遍历 题目[94]:给定一个二叉树,返回它的中序 遍历. 解题思路:Too simple. class Solution { public: vector ...

  8. Docker基础修炼1--Docker简介及快速入门体验

    本文作为Docker基础系列第一篇文章,将详细阐述和分析三个问题:Docker是什么?为什么要用Docker?如何快速掌握Docker技术? 本系列文章中Docker的用法演示是基于CentOS7进行 ...

  9. Vertica的这些事(十二)——-vertica备份与恢复

    最近在使用vertica,上网找了很多资料都没有,只有自己看官方文档动手搞一下了,今天搞了vertica的备份与恢复 以下是整理的过程,分享给大家,如有问题欢迎大家指正~ 可加QQ群交流:412191 ...

  10. 搭建单区域DNS服务器

                                                          搭建单区域DNS服务器 案例1:搭建单区域DNS服务器 案例2:特殊DNS解析 案例3:配置 ...