【Mybatis】二、MyBatis用到的代理模式

上一篇:【Mybatis】一、SQL映射框架MyBatis源码分析

一、 代理模式

  • 代理模式是23种设计模式中的一种;
    代理模式就是给一个对象提供一个代理,就像我们生活中的中介(租房、留学、相亲),由代理对象控制对原目标对象的访问,所以在代理对象中就可以实现对目标对象功能的增强、扩展、甚至改写;
  • 为什么需要代理?
    因为一个良好的设计不应该轻易的修改,这正是开闭原则的体现:一个良好的设计应该对修改关闭,对扩展开放。而代理正是为了扩展类而存在的,它可以控制对现有类(也就是被代理的类)的访问,通俗来说就是可以拦截对现有类方法的调用并做相应处理;
    代理模式应用场景:aop、权限控制、服务监控、缓存、日志、限流、事务、拦截过滤 等;
    在这里插入图片描述

代理模式分为 静态代理 和 动态代理;

二、静态代理

静态代理就是我们自己手写代理类; aspectJ静态代理(编译期生成代理类)
静态代理可以实现在不修改目标对象代码的前提下,对目标对象的功能进行扩展;

静态代理:(目标接口、目标接口实现、代理类)
在这里插入图片描述

  • 优点:可以实现不对目标对象进行修改的前提下,对目标对象进行功能的扩展和增强,也就是扩展原功能,不污染原代码。
  • 缺点:因为代理对象,需要实现与目标对象一样的接口,如果目标接口类繁多,也会导致代理类繁多,另外一旦目标接口增加新方法,则代理类也需要维护;

三、动态代理

动态代理四种方案:JDK、CGLIB、Javassist、ASM(编码特别复杂,比较少用);

1、JDK动态代理

动态代理,是指在运行期动态的为指定的类生成其代理类;
JDK动态代理会在程序运行时生成一个$Proxy0.Class,该class类在代码中看不到,在磁盘中也没有保存,是程序运行时候在jvm内存中生成的,所以我们感觉动态代理很抽象,要想保存该class文件,我们可以在代码中设置:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这样就会在运行时候将该class文件保存到我们磁盘上;
动态代理在运行时将接口中声明的所有方法都转移到一个集中的**InvocationHandler.invoke()**方法进行处理,这样在接口方法数量比较多的时候,我们可以进行灵活处理,而静态代理需要在每一个方法进行中转;

  • 动态代理是AOP思想的底层实现,当然JDK的动态代理,目标类必须实现某个接口,如果某个类没有实现接口则不能生成代理对象;
/**
 * 实现jdk提供的InvocationHandler接口
 *
 * 实现该接口是为了实现jdk的动态代理
 *
 * 此类不是真正的代理类,真正的代理的类在jvm内存中,我们看不见摸不着的,这个真正的代理类名字一般是以$Proxy.
 *
 */
public class TargetProxy implements InvocationHandler {

    //持有目标接口的引用,动态代理为了适配各种目标类型,把引用使用Object
    private Object target;

    /**
     * 使用构造方法对目标接口的引用实现初始化
     *
     * @param target
     */
    public TargetProxy(Object target) {
        this.target = target;
    }

    /**
     * 获取真正的代理类
     *
     * @param interfaces
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class interfaces) {
        //1、jvm内存中生成一个class类;
        //2、根据该class类反射创建一个代理对象 $Proxy@564546548
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {interfaces},
                this);
    }

    /**
     * 覆盖InvocationHandler接口的方法
     * 该方法会对目标接口的方法进行拦截
     *
     * @param proxy 这个就是我们那个代理类,就是jdk生成的那个叫$Proxy.代理类
     * @param method 就是目标接口的方法,比如 sayhi(), work()的反射对象Method;
     * @param args 就是目标接口的方法,比如 sayhi(), work()的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("前置增强(通知)......");

        //中间是调用目标接口的方法
        Object result = method.invoke(target, args);

        System.out.println("后置增强(通知)......");

        return result;
    }
}

2、Cglib动态代理

官方Github:https://github.com/cglib

  • CGLib (Code Generation Library) 是一个强大、高性能、高质量的代码生成库,它可以在运行时扩展JAVA类并实现接口;
    字节码生成库是生成和转换Java字节码的高级API,它被AOP、测试、数据访问框架用来生成动态代理对象和拦截字段访问;
    在这里插入图片描述
    Rafael Winterhalter
    来自德国,软件顾问,居住挪威的奥斯陆,也是hibernate的成员之一

CGLib 比 Java 的 java.lang.reflect.Proxy 类更强大的地方在于它不仅可以接管接口类的方法,还可以接管普通类的方法,为JDK的动态代理提供了很好的补充,通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口时,那么CGLIB是一个更好的选择;
在一些开源框架中都采用了cglib:
Hibernate
Spring
Guice
CGLib 的底层是Java字节码操作框架 —— ASM https://asm.ow2.io

3.1 、CGLIB类库结构
  • net.sf.cglib.core: 底层字节码处理类,他们大部分与ASM有关;
  • net.sf.cglib.transform: 编译期或运行期类和类文件的转换
  • net.sf.cglib.proxy: 实现创建代理和方法拦截器的类
  • net.sf.cglib.reflect: 实现快速反射类
  • net.sf.cglib.util: 集合排序等工具类
  • net.sf.cglib.beans: javabean相关的工具类
3.2 、CGLIB实现动态代理

使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK8之前CGLib动态比JDK的动态代理(使用Java反射)效率要高。
唯一需要注意的是,如果被代理的类被final修饰,那么它不可被继承,即不可被代理,同样,如果被代理的类中存在final修饰的方法,那么该方法也不可被代理;
因为CGLib原理是动态生成被代理类的子类;
final类不能被继承,final方法不能被复写;
使用CGLIB,需要两个jar包:
cglib-3.3.0.jar
asm-7.1.jar
cglib-nodep-3.3.0.jar:使用nodep包不需要关联asm的jar包,jar包内部已经包含了asm的类.
cglib-3.3.0.jar:使用此jar包需要关联asm的jar包,否则运行时报错;

package com.xiaojialin.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * TargetProxy类还不是一个真正的代理类,它是代理类的一部分
 *
 */
public class TargetProxy implements MethodInterceptor {

    /**
     * 获取真正的代理类
     *
     *   //1、jvm内存中生成一个class类;
     *   //2、根据该class类反射创建一个代理对象 $Proxy@564546548
     *   return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
     *                 new Class<?>[] {interfaces},
     *                 this);
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T getProxy(Class<T> clazz) {
        //字节码增强的一个类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(clazz);
        //enhancer.setInterfaces(new Class[] {clazz});
        //设置回调类
        enhancer.setCallback(this);
        //创建代理类
        return (T)enhancer.create();
    }

    /**
     * 既可以 sayHello,也可以拦截 sayThanks
     *
     * @param obj
     * @param method
     * @param args
     * @param proxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(method.getName() + "数据缓存start..........");

        //调用目标方法
        Object result = proxy.invokeSuper(obj, args);
        //就像mybatis一样, 需要自己实现接口
        //System.out.println("sayHello...................");
        System.out.println(method.getName() + "数据缓存end..........");

        return result;
    }
}
3.3 、CGLIB实现动态代理CGLib动态代理原理是什么?
  • CGLIB原理: 动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑;
    通俗点讲就是,自己生成一个类继承目标类,然后重写要代理的类不是final的方法,自己要代理的逻辑就是在cglib中新写的,这样及就完成了代理。

  • CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。但不推荐直接使用ASM编码开发,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉,编码比较复杂繁琐;

  • CGLIB缺点:对于final类和final方法,无法进行代理;

四、JDK动态代理与CGLib代理的区别?

1、原理区别:

JDK动态代理是利用反射机制生成一个实现代理接口的类(这个类看不见摸不着,在jvm内存中有这个类),在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的拦截和处理;
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理,核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的拦截和处理;

2、 各自局限:
  • 1、JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理。
  • 2、CGLib的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
3、JDK Proxy 的优势:

最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠;
可以平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。

4、 CGLib 的优势:

从某种角度看,必须要求调用者实现接口是有些侵入性的实践,CGLib 动态代理就没有这种限制,只操作我们关心的类,而不必为其他相关类增加工作量,一个普通的类也可以实现代理;

五、Javassist动态代理

自1999年以来的Java字节码工程工具包,Javassis使Java字节码操作变得简单,它是用于在Java中编辑字节码的类库,由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的,它加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架;
官网:http://www.javassist.org/
Github:https://github.com/jboss-javassist/javassist


/**
 * 不是真正的代理类,是生成代理类的其中一个环节
 *
 */
public class TargetProxy implements MethodHandler {

    /**
     * 获取代理对象
     *
     * @param clazz
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public Object getProxy(Class<?> clazz) throws InstantiationException, IllegalAccessException {
        // 代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();

        // 设置需要创建子类的父类
        proxyFactory.setSuperclass(clazz);

        // 通过字节码技术动态创建子类实例
        proxyFactory.writeDirectory = "D:/aaa";
        Object proxy = proxyFactory.createClass().newInstance();

         //在调用目标方法时,Javassist会回调MethodHandler接口方法拦截,
         //来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
        ((ProxyObject)proxy).setHandler(this);

        // 返回代理类对象
        return proxy;
    }

    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {

        System.out.println("开启事务-------");

        //调用目标类的方法
        Object result = proceed.invoke(self, args);

        System.out.println("提交事务-------");

        return result;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值