Java动态代理:基于JDK的动态代理和基于CGLIB的动态代理

前言

学习Spring aop的时候,一直都知道是基于动态代理实现的,那么到底什么是动态代理,又该如何自己实现一个动态代理呢,对此做一个记录。
所谓动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。

基于JDK的动态代理

两个核心的类Proxy、InvocationHandler(接口);基于JDK的动态代理是需要被代理的类实现某个接口。下面贴一下代码应用:
首先要有一个接口和实现类,实现类就是需要被代理的类;业务很简单,就是打印一句话。

public interface Subject {

    void hello(String param);

    void coding();

    void debug();
}
public class SubjectImpl implements Subject {
    @Override
    public void hello(String param) {
        System.out.println("hello " + param);
    }

    @Override
    public void coding() {
        System.out.println("I am is coding");
    }

    @Override
    public void debug() {
        System.out.println("I am is debugging");
    }
}

还需要一个代理类,实现InvocationHandler接口,重写invoke方法;
说明一下invoke方法:method.invoke(target, args);就是利用发射调用被代理类中的方法(了解反射的话,这个方法就很好理解)

public class SubjectProxy implements InvocationHandler {

    // 目标类,也就是被代理对象
    private Object target;

    public SubjectProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------我是前置处理------");
        // 执行原本方法
        Object invoke = method.invoke(target, args);
        System.out.println("------我是后置处理------");
        return invoke;
    }
}

最后进入测试,利用Proxy这个类来帮助我们创建代理之后的Subject类。
说明下newProxyInstance方法:第一个参数是类加载器;第二个参数是被代理类的接口,如果有多个就以数组形式传入;第三个就是代理对象的实例。这里要注意实际代理的对象是SubjectImpl而不是Subject 这个接口,这个一定要清晰。

SubjectImpl subject = new SubjectImpl();

Subject s = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), new SubjectProxy(subject));

s.hello("world");

输出结果:

------我是前置处理------
hello world
------我是后置处理------

基于CGLIB的动态代理

因为基于JDK的动态代理一定要继承一个接口,而绝大部分情况是基于POJO类的动态代理,那么CGLIB就是一个很好的选择,而且CGLIB可以对类中的方法有选择性的代理。
ps:这里需要添加的依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

两个核心的类:MethodInterceptor(接口)、Enhancer
下面贴一下代码应用:
首先有一个需要被代理的类:

public class SubjectClass {

    public void hello(String param) {
        System.out.println("hello " + param);
    }

    public void coding() {
        System.out.println("I am is coding");
    }

    public void debug() {
        System.out.println("I am is debugging");
    }
}

然后需要一个拦截器,实现MethodInterceptor接口,重写intercept方法。
说明下intercept方法,第一个参数是被代理对象的示例;第四个参数是代理的方法。这里要注意用methodProxy去调用invokeSuper方法,不能调用invoke方法;也不能用method去调用。

public class MyInterceptor implements MethodInterceptor {
    /*** 
    * Object obj:这是代理对象,也就是[目标对象]的子类 
    * Method method:[目标对象]的方法 
    * Object[] arg:参数 
    * MethodProxy methodProxy:代理对象的方法 
    */
	@Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    	// 因为代理对象是目标对象的子类 
    	// 该行代码,实际调用的是父类目标对象的方法
        System.out.println("------我是前置处理------");
		
		// 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
		// 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的 invoke方法完成目标对象的调用
        return methodProxy.invokeSuper(obj, objects);
    }
}

最后测试:这里就是通过Enhancer来创建被代理之后的类,拦截到类中的方法,添加处理的代码。

public class main {

    public static void main(String[] args) {
    	// 创建增强器
        Enhancer enhancer = new Enhancer();
        // 设置需要增强的类的类对象
        enhancer.setSuperclass(SubjectClass.class);
        // 设置回调函数
        enhancer.setCallback(new MyInterceptor());
		
		// 获取增强之后的代理对象
        SubjectClass subject = (SubjectClass) enhancer.create();

		// 调用方法
        subject.hello("world");
        subject.coding();
        subject.debug();
    }
}

最后扩展下:如果我想要对SubjectClass类中的三个方法执行不同的额外处理业务,该如果实现呢?CGLIB也提供了相关api,核心类是CallbackFilter(接口),贴下代码:

/***
 * @Description: 回调的过滤函数,返回的数字是Callback[]数组的下标
 *              下面的意思是:通过方法名来判断,如果方法名是'hello',则在数组第一个位置;
 *              如果方法名是'coding',则在数组第二个位置;
 *              如果方法名是'debug',则在数组的第三个位置
 * @Date: 2020/6/28 9:18
 * @version: v1.0
 */
public class MyCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        String name = method.getName();
        int num = 0;
        switch (name) {
            case "hello":
                num = 0;
                break;
            case "coding":
                num = 1;
                break;
            case "debug":
                num = 2;
                break;
            default:
                break;
        }
        return num;
    }
}

重新写下测试代码:

public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SubjectClass.class);
        //enhancer.setCallback(new MyInterceptor());

        CallbackFilter callbackFilter = new MyCallbackFilter();
        enhancer.setCallbackFilter(callbackFilter);

        Callback callback = new MyInterceptor();
        // 构造一个Callback[]数组,第一个位置'NoOp.INSTANCE'表示不做任务处理,结合callbackFilter来看,也就是说方法名为'hello'的方法不做任务处理;
        // 第二个位置是一个自定义的Callback,也就是说方法名为'coding'的方法按照这个来处理,做了代理;
        // 同理第三个位置的方法也不做任何处理。这样就能对类中特定的方法按照特定的逻辑做代理。
        Callback[] callbacks = new Callback[]{NoOp.INSTANCE, callback, NoOp.INSTANCE};
        enhancer.setCallbacks(callbacks);

        SubjectClass subject = (SubjectClass) enhancer.create();

        subject.hello("world");
        subject.coding();
        subject.debug();
    }

输出结果:

hello world
------我是前置处理------
I am is coding
I am is debugging

可以看到,我们只对coding方法做了拦截,只给这个方法添加了额外的处理逻辑,这样就能做到选择性的对需要代理的类中的方法做特定拦截。

总结下两个动态代理的区别:
  1. JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
  2. 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值