本篇我们介绍一下Cglib是如何实现动态代理的。

Cglib是什么?

Cglib是一个强大的代码生成包,广泛地被许多AOP框架使用,用来提供方法的拦截,下图展示了Cglib和一些语言、框架的关系:


根据图总结一下:

(1)最底层是字节码,Java中就是.class文件;

(2)字节码上面是ASM,一种可以直接操作字节码的框架;

(3)ASM上面是CGLIB、Groovy、BeanShell,后两个是脚本语言非Java体系中的内容,它们都通过ASM来操作字节码;

(4)再往上就是我们比较熟悉的Hibernate、Spring AOP等框架;

(5)最上层是具体应用,比如Web项目;

Cglib安装

使用Cglib需要导入以下两个jar包:

cglib包

链接:https://pan.baidu.com/s/1gsavHrvFCqFiw8gGTtxuBA

提取码:k5y6

asm包

链接:https://pan.baidu.com/s/1C2ciBA9tcKTBQ5vS7BKXlA

提取码:v1d1

Cglib实现动态代理流程

1.创建被代理类Person,其中eat()方法被final修饰。

/**
 * @author codeZhao
 * @date 2021/1/14  14:22
 * @Description 被代理类Person
 */
public class Person{
    final public void eat() {
        System.out.println("I'm eating...");
    }
    public void run() {
        System.out.println("I'm running...");
    }
}

2.创建方法拦截器,需要继承MethodInterceptor接口,重写intercept()方法,这里在方法执行前后打印日志。

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

import java.lang.reflect.Method;

/**
 * @author codeZhao
 * @date 2021/1/14  16:22
 * @Description 方法拦截器,实现日志打印
 */
public class LoggerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Logger: enter method " + method.getName() + "() ...");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("Logger: quit method " + method.getName() + "() ...");
        return result;
    }
}

3.Cglib代理测试

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

/**
 * @author codeZhao
 * @date 2021/1/14  14:23
 * @Description Cglib代理测试
 */
public class CglibTest {
    public static void main(String[] args) {
        //在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaProject");

        //创建Enhancer对象,类似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置被代理类类型
        enhancer.setSuperclass(Person.class);
        //设置回调函数,即方法拦截器
        enhancer.setCallback(new LoggerMethodInterceptor());
        //创建代理类
        Person personProxy = (Person) enhancer.create();
        //调用代理类方法
        personProxy.eat();
        personProxy.run();
    }
}
I'm eating...
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...

可以看到,调用run()方法时被拦截了,但eat()并没有被拦截,这是因为代理类要继承被代理类,重写代理方法,而eat()被修饰为final了,所以不能被代理类重写。

使用Cglib实现不同的拦截策略

在Spring AOP经常遇到这样的场景,我要对类A的B方法实现一种拦截策略,对类A的C方法实现另一种拦截策略或者是不进行拦截,下面我们看下如何实现。

为更好地看到效果,在Person类中又加了两个方法:

/**
 * @author codeZhao
 * @date 2021/1/14  14:22
 * @Description 被代理类Person
 */
public class Person{
    public void run() {
        System.out.println("I'm running...");
    }
    public void say() {
        System.out.println("I'm saying...");
    }
    public void sleep() {
        System.out.println("I'm sleeping...");
    }
}

我们首先再定义一个不同的方法拦截器TimeMethodInterceptor,在方法前后输出一下当前时间。

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

import java.lang.reflect.Method;

/**
 * @author codeZhao
 * @date 2021/1/15  8:15
 * @Description
 */
public class TimeMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("StartTime=" + System.currentTimeMillis());
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("EndTime=" + System.currentTimeMillis());
        return result;
    }
}

然后还需要定义一个Filter,继承CallbackFilter接口,重写accept()方法即可,accept()返回int数值,具体有什么用到下面测试时再看。

import net.sf.cglib.proxy.CallbackFilter;

import java.lang.reflect.Method;

/**
 * @author codeZhao
 * @date 2021/1/15  8:25
 * @Description
 */
public class PersonFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("run".equals(method.getName())) {
            return 0;
        }else if ("say".equals(method.getName())) {
            return 1;
        }
        return 2;
    }
}

最后修改Cglib测试类,这次回调函数传入一个集合,并且设置CallbackFilter为上一步创建的PersonFilter对象。

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

/**
 * @author codeZhao
 * @date 2021/1/14  14:23
 * @Description Cglib代理测试
 */
public class CglibTest {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaProject");

        LoggerMethodInterceptor loggerMethodInterceptor = new LoggerMethodInterceptor();
        TimeMethodInterceptor timeMethodInterceptor = new TimeMethodInterceptor();

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallbacks(new Callback[]{loggerMethodInterceptor, timeMethodInterceptor, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new PersonFilter());

        Person personProxy = (Person) enhancer.create();

        personProxy.eat();
        personProxy.run();
        personProxy.say();
        personProxy.sleep();
    }
}

这样设置的意思是,根据PersonFilter的返回值确定每个方法使用回调函数集合中的哪一个,具体来说,当PersonFilter返回0时调用集合中的第0个,返回1时调用集合中的第1个...。所以执行结果如下:

Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
StartTime=1610672867924
I'm saying...
EndTime=1610672867924
I'm sleeping...

可以看到对不同方法实现了不同的拦截方法,其中sleep()方法没有做任何的拦截,因为它对应回调函数集合的最后一个NoOp.INSTANCE,这是一个空Callback,所以如果不想对某个方法做拦截的话,可以通过NoOp.INSTANCE实现。

设置构造函数不拦截

另外如果在构造函数中调用了某方法,那在创建类的时候也会对该方法拦截。比如我在Perison中加一个构造器,调用run()方法:

/**
 * @author codeZhao
 * @date 2021/1/14  14:22
 * @Description 被代理类Person
 */
public class Person{
    public Person() {
        run();
    }
    public void run() {
        System.out.println("I'm running...");
    }
}

然后Cglib代理测试

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

/**
 * @author codeZhao
 * @date 2021/1/14  14:23
 * @Description Cglib代理测试
 */
public class CglibTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new LoggerMethodInterceptor());
        
        Person personProxy = (Person) enhancer.create();
        System.out.println("-----------------");
        personProxy.run();
    }
}
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
-----------------
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...

结果在构造器中调用run()时也做了拦截,如果不想在构造器调用时被拦截,可以通过EnhancersetInterceptDuringConstruction(boolean interceptDuringConstruction)方法,传入false即可,默认是true。

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

/**
 * @author codeZhao
 * @date 2021/1/14  14:23
 * @Description Cglib代理测试
 */
public class CglibTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new LoggerMethodInterceptor());
        //设置构造器调用方法时不拦截
        enhancer.setInterceptDuringConstruction(false);
        
        Person personProxy = (Person) enhancer.create();
        System.out.println("-----------------");
        personProxy.run();
    }
}
I'm running...
-----------------
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...