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

前面介绍了代理模式,讲了动态代理常见的实现方式,包含了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

package net.sf.cglib.proxy;

/**
 * General-purpose {@link Enhancer} callback which provides for "around advice".
 * @author Juozas Baliuka [email protected]
 * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
 */
public interface MethodInterceptor
extends Callback
{
    /**
     * All generated proxied methods call this method instead of the original method.
     * The original method may either be invoked by normal reflection using the Method object,
     * or by using the MethodProxy (faster).
     * @param obj "this", the enhanced object
     * @param method intercepted Method
     * @param args argument array; primitive types are wrapped
     * @param proxy used to invoke super (non-intercepted method); may be called
     * as many times as needed
     * @throws Throwable any exception may be thrown; if so, super method will not be invoked
     * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
     * @see MethodProxy
     */    
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;

}

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


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

先定义一个主题对象

/**
 * Create by zxb on 2017/4/23
 */
public class DBQuery {

    public DBQuery() {
    }

    public DBQuery(Integer i) {
        System.out.println("Here's in DBQuery Constructor");
    }

    public String getElement(String id) {
        return id + "_CGLib";
    }

    public List getAllElements() {
        return Arrays.asList("Hello_CGLib1", "Hello_CGLib2");
    }

    public String methodForNoop() {
        return "Hello_Noop";
    }

    public String methodForFixedValue(String param) {
        return "Hello_" + param;
    }

    public final String sayHello() {
        return "Hello Everyone!";
    }
}

(一)单回调

切入类:

/**
 * Create by zxb on 2017/4/22
 */
public class DBQueryProxy implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Here in interceptor !");
        return methodProxy.invokeSuper(o, objects);
    }
}

测试类:

public class TestCGLibProxy {

    public static void main(String[] args) {
        DBQueryProxy dbQueryProxy = new DBQueryProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DBQuery.class);
        enhancer.setCallback(dbQueryProxy);
//        DBQuery dbQuery = (DBQuery)enhancer.create(new Class[]{Integer.class}, new Object[]{1});
        DBQuery dbQuery = (DBQuery) enhancer.create();
        System.out.println(dbQuery.getElement("Hello"));
        System.out.println();
        System.out.println(dbQuery.getAllElements());
        System.out.println();
        System.out.println(dbQuery.sayHello());
    }
}

执行结果:

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

(二)多回调

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

/**
 * Create by zxb on 2017/4/22
 */
public class DBQueryProxy2 implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Here in interceptor 2!");
        return methodProxy.invokeSuper(o, objects);
    }
}

测试类:

/**
 * Create by zxb on 2017/4/22
 */
public class TestCGLibProxy {

    public static void main(String[] args) {
        DBQueryProxy dbQueryProxy = new DBQueryProxy();
        DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DBQuery.class);
        enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2});
        enhancer.setCallbackFilter(new CallbackFilter() {

            public int accept(Method method) {
                if (method.getName().equals("getElement")) {
                    return 0;
                } else {
                    return 1;
                }
            }
        });
        DBQuery dbQuery = (DBQuery) enhancer.create();
        System.out.println("========Inteceptor By DBQueryProxy ========");
        System.out.println(dbQuery.getElement("Hello"));
        System.out.println();
        System.out.println("========Inteceptor By DBQueryProxy2 ========");
        System.out.println(dbQuery.getAllElements());
    }
}

执行结果:

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

(三)不处理

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


测试类:

public class TestCGLibProxy {

    public static void main(String[] args) {
        DBQueryProxy dbQueryProxy = new DBQueryProxy();
        DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
        Callback noopCb = NoOp.INSTANCE;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DBQuery.class);
        enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2, noopCb});
        enhancer.setCallbackFilter(new CallbackFilter() {

            public int accept(Method method) {
                if (method.getName().equals("getElement")) {
                    return 0;
                } else if (method.getName().equals("getAllElements")) {
                    return 1;
                } else {
                    return 2;
                }
            }
        });
        DBQuery dbQuery = (DBQuery) enhancer.create();
        System.out.println("========Inteceptor By DBQueryProxy ========");
        System.out.println(dbQuery.getElement("Hello"));
        System.out.println();
        System.out.println("========Inteceptor By DBQueryProxy2 ========");
        System.out.println(dbQuery.getAllElements());
        System.out.println();
        System.out.println("========Return Original Value========");
        System.out.println(dbQuery.methodForNoop());
    }
}

执行结果:

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

(四)固定值

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

/**
 * 返回固定的值
 * Create by zxb on 2017/4/23
 */
public class DBQueryProxyFixedValue implements FixedValue {

    public Object loadObject() throws Exception {
        System.out.println("Here in DBQueryProxyFixedValue ! ");
        return "Fixed Value";
    }
}

测试类:

public class TestCGLibProxy {

    public static void main(String[] args) {
        DBQueryProxy dbQueryProxy = new DBQueryProxy();
        DBQueryProxy2 dbQueryProxy2 = new DBQueryProxy2();
        Callback noopCb = NoOp.INSTANCE;
        Callback fixedValue = new DBQueryProxyFixedValue();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DBQuery.class);
        enhancer.setCallbacks(new Callback[]{dbQueryProxy, dbQueryProxy2, noopCb, fixedValue});
        enhancer.setCallbackFilter(new CallbackFilter() {

            public int accept(Method method) {
                if (method.getName().equals("getElement")) {
                    return 0;
                } else if (method.getName().equals("getAllElements")) {
                    return 1;
                } else if (method.getName().equals("methodForNoop")) {
                    return 2;
                } else if (method.getName().equals("methodForFixedValue")) {
                    return 3;
                } else {
                    return 0;
                }
            }
        });
        DBQuery dbQuery = (DBQuery) enhancer.create();
        System.out.println("========Inteceptor By DBQueryProxy ========");
        System.out.println(dbQuery.getElement("Hello"));
        System.out.println();
        System.out.println("========Inteceptor By DBQueryProxy2 ========");
        System.out.println(dbQuery.getAllElements());
        System.out.println();
        System.out.println("========Return Original Value========");
        System.out.println(dbQuery.methodForNoop());
        System.out.println();
        System.out.println("========Return Fixed Value========");
        System.out.println(dbQuery.methodForFixedValue("myvalue"));
    }
}

执行结果:

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

(五)懒加载

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

public interface LazyLoader extends Callback {
    Object loadObject() throws Exception;
}

public interface Dispatcher extends Callback {
    Object loadObject() throws Exception;
}

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

/**
 * 课程表
 * Create by zxb on 2017/4/23
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Schedule {

    private String courseName;

    private Date courseTime;
}

EnglishSchedule属性的加载依赖于ScheduleLazyLoader

/**
 * Create by zxb on 2017/4/23
 */
public class ScheduleLazyLoader implements LazyLoader {

    public Object loadObject() throws Exception {
        System.out.println("before LazyLoader init...you can query from db...");
        Schedule schedule = new Schedule();
        schedule.setCourseName("English");
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017,3,28);
        schedule.setCourseTime(calendar.getTime());
        System.out.println("after LazyLoader init...");
        return schedule;
    }
}

MathSchedule属性的加载依赖于ScheduleDispatcher

/**
 * Create by zxb on 2017/4/23
 */
public class ScheduleDispatcher implements Dispatcher {

    public Object loadObject() throws Exception {
        System.out.println("before Dispatcher init...you can query from db...");
        Schedule schedule = new Schedule();
        schedule.setCourseName("Math");
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017,4,1);
        schedule.setCourseTime(calendar.getTime());
        System.out.println("after Dispatcher init...");
        return schedule;
    }
}

学生类:

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

package org.zheng.proxy.cglib.lazyload;

import lombok.Data;
import org.springframework.cglib.proxy.Enhancer;

/**
 * Create by zxb on 2017/4/23
 */
@Data
public class Student {

    private int id;

    private String name;

    /**
     * 英语课时间表
     */
    private Schedule EnglishSchedule;

    /**
     * 数学课时间表
     */
    private Schedule MathSchedule;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
        this.EnglishSchedule = createEnglishSchedule();
        this.MathSchedule = createMathSchedule();
    }

    private Schedule createEnglishSchedule() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Schedule.class);
        enhancer.setCallback(new ScheduleLazyLoader());
        return (Schedule) enhancer.create();
    }

    private Schedule createMathSchedule() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Schedule.class);
        enhancer.setCallback(new ScheduleDispatcher());
        return (Schedule) enhancer.create();
    }
}

测试类:

/**
 * 延迟加载属性
 * Create by zxb on 2017/4/23
 */
public class LazyLoadTest {

    public static void main(String[] args) {
        Student student = new Student(666, "XiaoMing");
        System.out.println("id=" + student.getId());
        System.out.println("name=" + student.getName());
        // LazyLoader 只有第一次,Dispatcher是每次都会进loadObject的方法
        System.out.println("========First Get  EnglishSchedule ========");
        System.out.println(student.getEnglishSchedule());
        System.out.println();
        System.out.println("========First Get  MathSchedule ========");
        System.out.println(student.getMathSchedule());
        System.out.println();
        System.out.println("========Second Get  EnglishSchedule ========");
        System.out.println(student.getEnglishSchedule());
        System.out.println();
        System.out.println("========Second Get  MathSchedule ========");
        System.out.println(student.getMathSchedule());
    }
}

执行结果:

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

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


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

项目完整代码:

https://github.com/difffate/JavaProject

你可能感兴趣的:(Java工具类)