动态代理:如何深入理解和分析,不如手写一个(源码包分析、楼主亲测)

如何分类Java语言?

Java是静态的强类型语言,但是因为提供了类似反射等机制,也具备了部分动态语言的能力。

一、动态代理的简单描述

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似的机制做到的,比如用来包装RPC调用、面向切面的编程(AOP)。

实现动态代理的方式也有很多种,比如JDK自身提供的动态代理,也就是主要利用JDK的反射机制。还有一些更高性能的字节码操作机制,类似ASM、cglib(基于ASM)、Javassist等。

如果熟悉设计模式中的代理模式,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成的。

通过代理可以让调用者与实现者之间解耦合。比如要进行RPC调用,框架内部的寻址、序列化、反序列化,对于调用者往往是没有太大的意义的,通过代理,可以提供更加友善的界面。

二、举个栗子:Jdk Proxy实现代理

1、JDK Proxy原理

代理还是在基于反射的原理上实现的。

代理类Proxy调用处理器接口InvocationHandler,都位于java.lang.reflect反射包中。

这里写图片描述

2、代码实现
package com.newframe.controllers.api;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author:wangdong
 * @description:写一个基于动态代理的栗子
 */
public class TestMyDynamicProxy {

    public static void main(String[] args) {
        HelloImpl hello = new HelloImpl();

        //获取我自己实现的带有特殊功能的MyInvocationHandler
        //为调用目标Hello创建代理对象
        //进而程序就可以使用代理对象间接运行调用目标的逻辑
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        //构造动态代理代码实例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),HelloImpl.class.getInterfaces(),handler);
        //调用代理方法
        proxyHello.sayHello();

    }
}

/**
 * 定一个Hello的接口
 */
interface Hello{
    void sayHello();
}

/**
 * 实现这个Hello的接口
 */
class HelloImpl implements Hello{

    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

/**
 * 定一个我的执行类,调用处理器
 * 1、做一个动态代理,首先得实现一个InvocationHandler
 */
class MyInvocationHandler implements InvocationHandler{

    //定一个成员变量
    private Object target;

    //定义一个有参构造
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    //2、重写执行的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("Invoking sayHello");
        Object result = method.invoke(target,args);
        return result;
    }
}

上述JDK Proxy的例子,非常简单地实现了动态代理的构建和代理操作。

3、局限性

从API的设计和实现的角度,上述基于JDK Proxy的动态代理,有他的局限性。因为它是以接口为中心的,相当于添加了一种对于调用者没有太大意义的限制。实例化的是Proxy对象,而不是真正的被调用对象,这在实践中可能会带来各种不便和能力的退化。

如果使用的是Cglib的方式实现动态代理,我们对接口的依赖就会被克服了。

三、Cglib动态代理的分析

1、基于cglib框架的优势
  • Jdk Proxy。有的时候调用目标可能不便于实现额外的接口,从某种角度看,限定调用者必须实现接口,才可以使用动态代理是有些侵入性的。
    类似于cglib动态代理就没有这种限制。

  • 只操作我们关心的类,而不必为其他相关类增加工作量。

  • 高性能
    如果从性能角度讲。有人曾经得出结论说JDK Proxy比Cglib或者Javassist慢几十倍。坦白说,经过Jdk自身的不断发展,在典型的场景下可以提供对等的性能水平,数量级别的巨大差距基本上是广泛不存在的。
    而且反射机制性能在现代JDK中,自身已经得到了极大的改进和优化。同时JDK很多功能并不完全是反射,同样使用了ASM进行字节码操作。

2、认识分析Spring框架的Cglib包

这个是Spring框架自身有的。这个也可以,但是我们经常用的还是真正的cglib包,见下一步

位于这个包下package org.springframework.cglib.proxy,比较常用的几个类:Enhancer、InvocationHandler、MethodInterceptor

这里写图片描述

关于这个Enhancer可以理解为一个类增强器,他和Jdk Proxy不同的是它允许为非接口的类创建一个动态代理的对象。
它主要有两种回调的方式:InvocationHandlerMethodInterceptor。
官方文档API地址:Enhancer

这里写图片描述

3、认识真正的Cglib包

如果你是Maven项目,你需要导入一个Cglib包。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.8</version>
</dependency>

看一下真正的Cglib包:
同样,和Spring框架中的cglib包一样。有常用的类和接口。
关于这个Enhancer可以理解为一个类增强器,他和Jdk Proxy不同的是它允许为非接口的类创建一个动态代理的对象。
它主要有两种回调的方式:InvocationHandlerMethodInterceptor。

这里写图片描述

四、cglib基于MethodInterceptor的实现

1、实现代码:
package com.newframe.controllers.api;

/**
 * @author:wangdong
 * @description:基于cglib动态代理的实例
 * 当然你可以将不同的写在不同的包中,我这里为了方便就都写在了一个里面
 */

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

import java.lang.reflect.Method;

/**
 * 定义一个Persion类
 */
class Persion{

    //定义几个方法
    public void eat(){
        System.out.println("我叫小明,我在吃饭呢!");
    }

    public void study(){
        System.out.println("我是小明,我正在学习呢,请不要打扰我");
    }

}

/**
 * 定一个我的拦截器实现方法拦截器MethodInterceptor
 */
class MyInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("动态代理开始,我现在正要开始进行方法拦截了!");
        //Note: 此处一定要使用proxy的invokeSuper方法来调用目标类的方法
        //methodProxy.invoke(o,objects);报错:Exception in thread "main" java.lang.StackOverflowError
        methodProxy.invokeSuper(o,objects);
        System.out.println("动态代理结束,我已经完成方法拦截了!");
        return null;
    }
}



public class CglibInterceptorTest {
    public static void main(String[] args) {
        //实例化一个增强器
        Enhancer enhancer = new Enhancer();
        //设置目标类
        enhancer.setSuperclass(Persion.class);
        //设置拦截对象
        enhancer.setCallback(new MyInterceptor());
        //生成代理类并返回一个实例
        Persion persion = (Persion) enhancer.create();
        persion.eat();
        persion.study();
    }
}
2、输出结果
动态代理开始,我现在正要开始进行方法拦截了!
我叫小明,我在吃饭呢!
动态代理结束,我已经完成方法拦截了!
动态代理开始,我现在正要开始进行方法拦截了!
我是小明,我正在学习呢,请不要打扰我
动态代理结束,我已经完成方法拦截了!

五、cglib基于InvocationHandler的实现

这个和JDK Proxy不同的是,它既可以动态代理处理纯类,也可以处理实现了接口的类。

1、实现代码:
package com.newframe.controllers.api;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

import java.lang.reflect.Method;

/**
 * 定义一个接口类
 */
interface Animal{

    void run();
}

/**
 * 定义一个狗类,实现了Animal接口
 */
class Dog implements Animal{

    @Override
    public void run(){
        System.out.println("我是一只狗,我正在快乐的奔跑呢!");
    }

}

/**
 * 定义一个猫类
 */
class Cat {

    public void eat(){
        System.out.println("我是一只猫,我正在玩游戏!");
    }
}

class MyHandler implements InvocationHandler{

    private Object target;

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

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

        //在这边可以对一个类中的多个方法,做不同的操作
        //符合Aop面向切面编程的思想
        String m = method.getName();
        System.out.println(m + "方法执行开始");
        System.out.println("给" + m + "方法加个日志");
        System.out.println("给" + m + "方法加个拦截器");
        Object result = method.invoke(target,objects);

        return result;
    }
}

/**
 * @author:wangdong
 * @description:cglib基于InvocationHandler的实现
 */
public class CglibInvocationHandlerTest {
    public static void main(String[] args) {

        //动态代理纯Class类
        Cat cat = new Cat();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Cat.class);
        //这个是关键,需要加载类
        enhancer.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());

        MyHandler myHandler = new MyHandler(cat);
        enhancer.setCallback(myHandler);

        Cat c = (Cat) enhancer.create();
        c.eat();

        //动态代理处理,实现了接口的实现类
        Dog dog = new Dog();
        Enhancer en = new Enhancer();
        en.setSuperclass(Dog.class);
        en.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());
        MyHandler handler = new MyHandler(dog);
        en.setCallback(handler);

        Dog d = (Dog) en.create();
        d.run();
    }
}
2、输出结果
我是一只猫,我正在玩游戏!
我是一只狗,我正在快乐的奔跑呢!

六、结语

相信通过自己写一次代码,对整个动态代理应该有了非常清晰的认识了吧。
凡事光看是不足够的,自己动手一次,各种查询资料。能够有非常深入的思考。

最后附一个知乎大神讨论较多的关于动态代理的帖子:
动态代理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值