CGLib动态代理的介绍及用法(单回调、多回调、不处理、固定值、懒加载)

参照: https://blog.csdn.net/difffate/article/details/70552056

前面介绍了代理模式,讲了动态代理常见的实现方式,包含了JDK的动态代理和CGLib的动态代理。本文将介绍下CGLib动态代理及几种用法。CGLib(Code Generation Library)是一个高效的代码生成库,底层实现是使用asm来转换字节码生成类。在生成代理类的场景中,由于JDK动态代理必须要求源对象有实现接口,而实际场景中,并不是所有类都有实现接口,因此使用CGLib可以用在未实现接口的类上。

值得注意的几点是:

1)使用CGLib代理的类不能是final修饰的,因为代理类需要继承主题类;

2)final修饰的方法不会被切入;

3)如果主题类的构造函数不是默认空参数的,那么在使用Enhancer类create的时候,选择create(java.lang.Class[] argumentTypes, java.lang.Object[] arguments) 方法。

接下来认识实现动态代理最重要的一个接口 MethodInteceptor

  1.  
    package net.sf.cglib.proxy;
  2.  
     
  3.  
    /**
  4.  
    * General-purpose {@link Enhancer} callback which provides for "around advice".
  5.  
    * @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
  6.  
    * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
  7.  
    */
  8.  
    public interface MethodInterceptor
  9.  
    extends Callback
  10.  
    {
  11.  
    /**
  12.  
    * All generated proxied methods call this method instead of the original method.
  13.  
    * The original method may either be invoked by normal reflection using the Method object,
  14.  
    * or by using the MethodProxy (faster).
  15.  
    * @param obj "this", the enhanced object
  16.  
    * @param method intercepted Method
  17.  
    * @param args argument array; primitive types are wrapped
  18.  
    * @param proxy used to invoke super (non-intercepted method); may be called
  19.  
    * as many times as needed
  20.  
    * @throws Throwable any exception may be thrown; if so, super method will not be invoked
  21.  
    * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
  22.  
    * @see MethodProxy
  23.  
    */
  24.  
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
  25.  
    MethodProxy proxy) throws Throwable;
  26.  
     
  27.  
    }

MethodInterceptor,从名字上方法拦截器,就是对方法做切入的。intercept方式的4个参数分别对应增强对象、调用方法、方法参数以及调用父类方法的代理。使用MethodProxy速度会更快,所以后面将用

下面介绍几种用法,这里使用spring包中cglib,其实和引单独的cglib包是一样,只不过spring为了版本不冲突,将cglib包含在自己的包中。

先定义一个主题对象

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/23
  3.  
    */
  4.  
    public class DBQuery {
  5.  
     
  6.  
    public DBQuery() {
  7.  
    }
  8.  
     
  9.  
    public DBQuery(Integer i) {
  10.  
    System.out.println("Here's in DBQuery Constructor");
  11.  
    }
  12.  
     
  13.  
    public String getElement(String id) {
  14.  
    return id + "_CGLib";
  15.  
    }
  16.  
     
  17.  
    public List<String> getAllElements() {
  18.  
    return Arrays.asList("Hello_CGLib1", "Hello_CGLib2");
  19.  
    }
  20.  
     
  21.  
    public String methodForNoop() {
  22.  
    return "Hello_Noop";
  23.  
    }
  24.  
     
  25.  
    public String methodForFixedValue(String param) {
  26.  
    return "Hello_" + param;
  27.  
    }
  28.  
     
  29.  
    public final String sayHello() {
  30.  
    return "Hello Everyone!";
  31.  
    }
  32.  
    }

(一)单回调

切入类:

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/22
  3.  
    */
  4.  
    public class DBQueryProxy implements MethodInterceptor {
  5.  
     
  6.  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  7.  
    System.out.println("Here in interceptor !");
  8.  
    return methodProxy.invokeSuper(o, objects);
  9.  
    }
  10.  
    }

测试类:

  1.  
    public class TestCGLibProxy {
  2.  
     
  3.  
    public static void main(String[] args) {
  4.  
    DBQueryProxy dbQueryProxy = new DBQueryProxy();
  5.  
    Enhancer enhancer = new Enhancer();
  6.  
    enhancer.setSuperclass(DBQuery.class);
  7.  
    enhancer.setCallback(dbQueryProxy);
  8.  
    // DBQuery dbQuery = (DBQuery)enhancer.create(new Class[]{Integer.class}, new Object[]{1});
  9.  
    DBQuery dbQuery = (DBQuery) enhancer.create();
  10.  
    System.out.println(dbQuery.getElement("Hello"));
  11.  
    System.out.println();
  12.  
    System.out.println(dbQuery.getAllElements());
  13.  
    System.out.println();
  14.  
    System.out.println(dbQuery.sayHello());
  15.  
    }
  16.  
    }

执行结果:

(二)多回调

在前面的基础上,加个切入类,并通过CallbackFilter来决定是使用哪个切入类

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/22
  3.  
    */
  4.  
    public class DBQueryProxy2 implements MethodInterceptor {
  5.  
     
  6.  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  7.  
    System.out.println("Here in interceptor 2!");
  8.  
    return methodProxy.invokeSuper(o, objects);
  9.  
    }
  10.  
    }

测试类:

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/22
  3.  
    */
  4.  
    public class TestCGLibProxy {
  5.  
     
  6.  
    public static void main(String[] args) {
  7.  
    DBQueryProxy dbQueryProxy = new DBQueryProxy();
  8.  
    DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
  9.  
    Enhancer enhancer = new Enhancer();
  10.  
    enhancer.setSuperclass(DBQuery.class);
  11.  
    enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2});
  12.  
    enhancer.setCallbackFilter(new CallbackFilter() {
  13.  
     
  14.  
    public int accept(Method method) {
  15.  
    if (method.getName().equals("getElement")) {
  16.  
    return 0;
  17.  
    } else {
  18.  
    return 1;
  19.  
    }
  20.  
    }
  21.  
    });
  22.  
    DBQuery dbQuery = (DBQuery) enhancer.create();
  23.  
    System.out.println("========Inteceptor By DBQueryProxy ========");
  24.  
    System.out.println(dbQuery.getElement("Hello"));
  25.  
    System.out.println();
  26.  
    System.out.println("========Inteceptor By DBQueryProxy2 ========");
  27.  
    System.out.println(dbQuery.getAllElements());
  28.  
    }
  29.  
    }

执行结果:

(三)不处理

利用枚举常量 Callback noopCb = NoOp.INSTANCE;

测试类:

  1.  
    public class TestCGLibProxy {
  2.  
     
  3.  
    public static void main(String[] args) {
  4.  
    DBQueryProxy dbQueryProxy = new DBQueryProxy();
  5.  
    DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
  6.  
    Callback noopCb = NoOp.INSTANCE;
  7.  
    Enhancer enhancer = new Enhancer();
  8.  
    enhancer.setSuperclass(DBQuery.class);
  9.  
    enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2, noopCb});
  10.  
    enhancer.setCallbackFilter(new CallbackFilter() {
  11.  
     
  12.  
    public int accept(Method method) {
  13.  
    if (method.getName().equals("getElement")) {
  14.  
    return 0;
  15.  
    } else if (method.getName().equals("getAllElements")) {
  16.  
    return 1;
  17.  
    } else {
  18.  
    return 2;
  19.  
    }
  20.  
    }
  21.  
    });
  22.  
    DBQuery dbQuery = (DBQuery) enhancer.create();
  23.  
    System.out.println("========Inteceptor By DBQueryProxy ========");
  24.  
    System.out.println(dbQuery.getElement("Hello"));
  25.  
    System.out.println();
  26.  
    System.out.println("========Inteceptor By DBQueryProxy2 ========");
  27.  
    System.out.println(dbQuery.getAllElements());
  28.  
    System.out.println();
  29.  
    System.out.println("========Return Original Value========");
  30.  
    System.out.println(dbQuery.methodForNoop());
  31.  
    }
  32.  
    }

执行结果:

(四)固定值

需要实现FixedValue接口,会忽略原来函数的返回值,使用固定值来替换。

  1.  
    /**
  2.  
    * 返回固定的值
  3.  
    * Create by zxb on 2017/4/23
  4.  
    */
  5.  
    public class DBQueryProxyFixedValue implements FixedValue {
  6.  
     
  7.  
    public Object loadObject() throws Exception {
  8.  
    System.out.println("Here in DBQueryProxyFixedValue ! ");
  9.  
    return "Fixed Value";
  10.  
    }
  11.  
    }

测试类:

  1.  
    public class TestCGLibProxy {
  2.  
     
  3.  
    public static void main(String[] args) {
  4.  
    DBQueryProxy dbQueryProxy = new DBQueryProxy();
  5.  
    DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
  6.  
    Callback noopCb = NoOp.INSTANCE;
  7.  
    Callback fixedValue = new DBQueryProxyFixedValue();
  8.  
    Enhancer enhancer = new Enhancer();
  9.  
    enhancer.setSuperclass(DBQuery.class);
  10.  
    enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2, noopCb, fixedValue});
  11.  
    enhancer.setCallbackFilter(new CallbackFilter() {
  12.  
     
  13.  
    public int accept(Method method) {
  14.  
    if (method.getName().equals("getElement")) {
  15.  
    return 0;
  16.  
    } else if (method.getName().equals("getAllElements")) {
  17.  
    return 1;
  18.  
    } else if (method.getName().equals("methodForNoop")) {
  19.  
    return 2;
  20.  
    } else if (method.getName().equals("methodForFixedValue")) {
  21.  
    return 3;
  22.  
    } else {
  23.  
    return 0;
  24.  
    }
  25.  
    }
  26.  
    });
  27.  
    DBQuery dbQuery = (DBQuery) enhancer.create();
  28.  
    System.out.println("========Inteceptor By DBQueryProxy ========");
  29.  
    System.out.println(dbQuery.getElement("Hello"));
  30.  
    System.out.println();
  31.  
    System.out.println("========Inteceptor By DBQueryProxy2 ========");
  32.  
    System.out.println(dbQuery.getAllElements());
  33.  
    System.out.println();
  34.  
    System.out.println("========Return Original Value========");
  35.  
    System.out.println(dbQuery.methodForNoop());
  36.  
    System.out.println();
  37.  
    System.out.println("========Return Fixed Value========");
  38.  
    System.out.println(dbQuery.methodForFixedValue("myvalue"));
  39.  
    }
  40.  
    }

执行结果:

(五)懒加载

CGLib的懒加载,可以用在一些不需要立即加载完整对象实例的场景,比如说Hibernate中的查询对象,如果这个对象有关联其他对象,这个时候不会马上将关联对象一起查询出来关联,要等到调用到这个关联对象时才去做查询。利用CGLib的懒加载机制,可以很好的实现这个需求。需要了解2个接口,LazyLoader和Dispatcher。这两个接口的定义如下:

  1.  
    public interface LazyLoader extends Callback {
  2.  
    Object loadObject() throws Exception;
  3.  
    }
  4.  
     
  5.  
    public interface Dispatcher extends Callback {
  6.  
    Object loadObject() throws Exception;
  7.  
    }

它们都继承了Callback接口,都有一个loadObject的方法,区别在于LazyLoader只有在第一次调用时,会执行loadObject获取对象,而Dispatcher会在每次调用时都触发loadObject方法,不理解?没关系,后面代码示例上可以看到明显的区别。假定有个学生类(Student),学术类包含2门课的课程表对象,分别是英语课程表(EnglishSchedule)和数学课程表(MathSchedule),它们都是课程表类的实例

  1.  
    /**
  2.  
    * 课程表
  3.  
    * Create by zxb on 2017/4/23
  4.  
    */
  5.  
    @Data
  6.  
    @NoArgsConstructor
  7.  
    @AllArgsConstructor
  8.  
    public class Schedule {
  9.  
     
  10.  
    private String courseName;
  11.  
     
  12.  
    private Date courseTime;
  13.  
    }

EnglishSchedule属性的加载依赖于ScheduleLazyLoader

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/23
  3.  
    */
  4.  
    public class ScheduleLazyLoader implements LazyLoader {
  5.  
     
  6.  
    public Object loadObject() throws Exception {
  7.  
    System.out.println("before LazyLoader init...you can query from db...");
  8.  
    Schedule schedule = new Schedule();
  9.  
    schedule.setCourseName("English");
  10.  
    Calendar calendar = Calendar.getInstance();
  11.  
    calendar.set(2017,3,28);
  12.  
    schedule.setCourseTime(calendar.getTime());
  13.  
    System.out.println("after LazyLoader init...");
  14.  
    return schedule;
  15.  
    }
  16.  
    }

MathSchedule属性的加载依赖于ScheduleDispatcher

  1.  
    /**
  2.  
    * Create by zxb on 2017/4/23
  3.  
    */
  4.  
    public class ScheduleDispatcher implements Dispatcher {
  5.  
     
  6.  
    public Object loadObject() throws Exception {
  7.  
    System.out.println("before Dispatcher init...you can query from db...");
  8.  
    Schedule schedule = new Schedule();
  9.  
    schedule.setCourseName("Math");
  10.  
    Calendar calendar = Calendar.getInstance();
  11.  
    calendar.set(2017,4,1);
  12.  
    schedule.setCourseTime(calendar.getTime());
  13.  
    System.out.println("after Dispatcher init...");
  14.  
    return schedule;
  15.  
    }
  16.  
    }

学生类:

定义时,需要对EnglishSchedule和MathSchedule先初始为动态代理的对象

  1.  
    package org.zheng.proxy.cglib.lazyload;
  2.  
     
  3.  
    import lombok.Data;
  4.  
    import org.springframework.cglib.proxy.Enhancer;
  5.  
     
  6.  
    /**
  7.  
    * Create by zxb on 2017/4/23
  8.  
    */
  9.  
    @Data
  10.  
    public class Student {
  11.  
     
  12.  
    private int id;
  13.  
     
  14.  
    private String name;
  15.  
     
  16.  
    /**
  17.  
    * 英语课时间表
  18.  
    */
  19.  
    private Schedule EnglishSchedule;
  20.  
     
  21.  
    /**
  22.  
    * 数学课时间表
  23.  
    */
  24.  
    private Schedule MathSchedule;
  25.  
     
  26.  
    public Student(int id, String name) {
  27.  
    this.id = id;
  28.  
    this.name = name;
  29.  
    this.EnglishSchedule = createEnglishSchedule();
  30.  
    this.MathSchedule = createMathSchedule();
  31.  
    }
  32.  
     
  33.  
    private Schedule createEnglishSchedule() {
  34.  
    Enhancer enhancer = new Enhancer();
  35.  
    enhancer.setSuperclass(Schedule.class);
  36.  
    enhancer.setCallback(new ScheduleLazyLoader());
  37.  
    return (Schedule) enhancer.create();
  38.  
    }
  39.  
     
  40.  
    private Schedule createMathSchedule() {
  41.  
    Enhancer enhancer = new Enhancer();
  42.  
    enhancer.setSuperclass(Schedule.class);
  43.  
    enhancer.setCallback(new ScheduleDispatcher());
  44.  
    return (Schedule) enhancer.create();
  45.  
    }
  46.  
    }

测试类:

  1.  
    /**
  2.  
    * 延迟加载属性
  3.  
    * Create by zxb on 2017/4/23
  4.  
    */
  5.  
    public class LazyLoadTest {
  6.  
     
  7.  
    public static void main(String[] args) {
  8.  
    Student student = new Student(666, "XiaoMing");
  9.  
    System.out.println("id=" + student.getId());
  10.  
    System.out.println("name=" + student.getName());
  11.  
    // LazyLoader 只有第一次,Dispatcher是每次都会进loadObject的方法
  12.  
    System.out.println("========First Get EnglishSchedule ========");
  13.  
    System.out.println(student.getEnglishSchedule());
  14.  
    System.out.println();
  15.  
    System.out.println("========First Get MathSchedule ========");
  16.  
    System.out.println(student.getMathSchedule());
  17.  
    System.out.println();
  18.  
    System.out.println("========Second Get EnglishSchedule ========");
  19.  
    System.out.println(student.getEnglishSchedule());
  20.  
    System.out.println();
  21.  
    System.out.println("========Second Get MathSchedule ========");
  22.  
    System.out.println(student.getMathSchedule());
  23.  
    }
  24.  
    }

执行结果:

可以看到第二次取懒加载对象的时候,实现LoadLazy接口不会重新执行loadObject,而实现Dispatcher的会重新执行LoadObject方法:)

以上,就是使用CGLib的几种常见用法。

项目完整代码:

https://github.com/difffate/JavaProject

cglib用法的更多相关文章

  1. Spring-core中的cglib小用法

    对象复制听说用这个更高效 /** * 拷贝对象 * @param src 源对象 * @param dist 需要赋值的对象 */ public static void copy(Object src ...

  2. Spring AOP 实现原理与 CGLIB 应用

    https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/ AOP(Aspect Orient Programming),也就是面向 ...

  3. Spring AOP 实现原理与 CGLIB 应用--转

    AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理.安全检查.缓存.对象池管理等.AOP 实现的关键就在于 ...

  4. Spring-AOP用法总结

    前言     Spring AOP的实现方法很多,在项目开发中具体采用什么方式,需要按实际情况来选择,每一种的用法,有其一定的实用价值,所以本文将各种使用方法进行了具体实现.主要包括Advice的be ...

  5. Cglib及其基本使用

    前言 最近一直在看Spring源码,其实我之前一直知道AOP的基本实现原理: 如果针对接口做代理默认使用的是JDK自带的Proxy+InvocationHandler 如果针对类做代理使用的是Cgli ...

  6. 使用CGlib实现Bean拷贝(BeanCopier)

    在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来.大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1( ...

  7. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  8. cglib之Enhancer

    1. 背景 cglib库的Enhancer在Spring AOP中作为一种生成代理的方式被广泛使用.本文针对Enhancer的用法以实际代码为例作一些介绍. 2. Enhancer是啥 Enhance ...

  9. Spring AOP切点表达式用法总结

    1. 简介        面向对象编程,也称为OOP(即Object Oriented Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的.通过面向对象编程,不同的模 ...

随机推荐

  1. CAD 批量提取点坐标,实现坐标的快速提取

    原创 CAD 批量提取点坐标,实现坐标的快速提取 2018-08-07 20:36:13 caohongji 阅读数 13678   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议 ...

  2. eli和字符串 (牛客假期训练)

    链接:https://ac.nowcoder.com/acm/contest/3002/G来源:牛客网 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262144K,其他语言5242 ...

  3. 2019牛客多校第三场H Magic Line 思维

    Magic Line 题意 给出n(偶)个整点 整点范围1000,找出一条直线,把n个点分成均等的两部分 分析 因为都是整数,并且范围比较小,所以直接按x排序找到在中间那一部分,并且把中间那一部分的点 ...

  4. FIR滤波器工作原理(算法)以及verilog算法实现(包含与IIR的一些对比)

    滤波器在2017年IC前端的笔试中,出现频率十分的高.不论今后是否会涉及,还是要记住一些会比较好.接下来就将从这四个方面来讲解,FIR数字滤波器的工作原理(算法)与verilog实现. ·什么是FIR ...

  5. jquery获取select多选框选中的文本值

    $("#select option:selected").text();

  6. 密码学笔记——eval(function(p,a,c,k,e,d) 加密破解

    密码学笔记——eval(function(p,a,c,k,e,d) 的加密破解 例题: 小明某天在看js的时候,突然看到了这么一段代码,发现怎么也理不出代码逻辑,你能帮帮他吗? 格式:SimCTF{} ...

  7. opencv:形态学操作-腐蚀与膨胀

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace st ...

  8. 威佐夫博奕(Wythoff Game)poj 1067

    有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜. 这种情况下是颇为复杂的.我们用(ak,bk)(ak ≤ bk ,k=0,1,2,…, ...

  9. 自己常用的Linux命令和Hadoop命令

    记录自己常用的Linux命令: ss的启动命令:ssserver -c /etc/shadowsocks.json jupyter notebook的启动命令:jupyter notebook --a ...

  10. promise封装ajax

    promise的含义(本身不是异步,是封装异步操作的容器,统一异步的标准) promise对象的特点:对象的状态不受外界影响:一旦状态改变,就不会再变,任何时候都可以得到这个结果. function ...