一文理解动态代理和静态代理

简介: 一文理解动态代理和静态代理

动态代理和静态代理


1.了解一下代理模式


代理模式

为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。


其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

有关代理模式更具体的介绍可以看看菜鸟教程上的介绍。


https://www.runoob.com/design-pattern/proxy-pattern.html


2.静态代理


创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

package Day41;
/**
 * @Author Zhongger
 * @Description 静态代理
 * @Date 2020.3.13
 */
public interface HelloInterface {
    void sayHello();
}
class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello Zhongger!");
    }
}
class HelloProxy implements HelloInterface{
    private HelloInterface helloInterface;
    @Override
    public void sayHello() {
        if (helloInterface==null){
            helloInterface=new Hello();
        }
        System.out.println("Before---代理类的Hello Zhongger");
        helloInterface.sayHello();
        System.out.println("After---代理类的Hello Zhongger");
    }
}
class StaticProxyTest{
    public static void main(String[] args) {
        HelloProxy helloProxy = new HelloProxy();
        helloProxy.sayHello();
    }
}

使用静态代理很容易就完成了对一个类的代理操作。

但是静态代理的缺点是:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。


3.JDK动态代理


利用反射机制在运行时创建代理类。

接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。

class ProxyHandler implements InvocationHandler{
    private Object object;
    public ProxyHandler(Object object) {
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "+method.getName());
        method.invoke(object,args);
        System.out.println("After invoke "+method.getName());
        return null;
    }
}


执行动态代理:

class DynamicProxyTest{
    public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        HelloInterface hello=new Hello();
        ProxyHandler proxyHandler = new ProxyHandler(hello);
        HelloInterface proxyInstance =(HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), proxyHandler);
        proxyInstance.sayHello();
    }
}

运行结果如下:


20200313194155698.png


通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例。针对不同的代理类,传入相应的代理程序控制器InvocationHandler。


如果新来一个被代理类MyBaby,如:


interface MyBabyInterface{
    void sayBaby();
}
class MyBaby implements MyBabyInterface{
    @Override
    public void sayBaby() {
        System.out.println("Hello Baby!");
    }
}

只需要在主方法中添加几行代码就可以了:

class DynamicProxyTest{
    public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        HelloInterface hello=new Hello();
        ProxyHandler proxyHandler = new ProxyHandler(hello);
        HelloInterface proxyInstance =(HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), proxyHandler);
        proxyInstance.sayHello();
        System.out.println("-----------------------");
        MyBabyInterface myBaby=new MyBaby();
        ProxyHandler proxyHandler2 = new ProxyHandler(myBaby);
        MyBabyInterface proxyInstance2 =(MyBabyInterface) Proxy.newProxyInstance(myBaby.getClass().getClassLoader(), myBaby.getClass().getInterfaces(), proxyHandler2);
        proxyInstance2.sayBaby();
    }
}

20200313195356515.png


动态代理的底层原理是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。


为什么类可以动态的生成?


这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。


Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:


1.通过一个类的全限定名来获取定义此类的二进制字节流

2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3.在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:


从ZIP包获取,这是JAR、EAR、WAR等格式的基础

从网络中获取,典型的应用是 Applet

运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 $Proxy 的代理类的二进制字节流

由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类


从数据库中获取等等


所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。但是如何计算?如何生成?情况也许比想象的复杂得多,需要借助现有的字节操作类库。


常见的字节码操作类库:


Apache BCEL (Byte Code Engineering Library):是Java classworking广泛使用的一种框架,它可以深入到JVM汇编语言进行类操作的细节。


ObjectWeb ASM:是一个Java字节码操作框架。它可以用于直接以二进制形式动态生成stub根类或其他代理类,或者在加载时动态修改类。


CGLIB(Code Generation Library):是一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。


Javassist:是Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。


4.CGLIB动态代理


由于本人水平有限,写不出案例来,就总结一下CGLIB动态代理的一些知识点吧。

CGLIB 创建动态代理类的模式是:


  • 查找目标类上的所有非final 的public类型的方法定义;
  • 将这些方法的定义转换成字节码;
  • 将组成的字节码转换成相应的代理的class对象;
  • 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求。


5.JDK动态代理与CGLIB动态代理对比


JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。


cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:


最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。


平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。


代码实现简单。


基于类似 cglib 框架的优势:


无需实现接口,达到代理类无侵入

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

高性能


6.一些面试题


(1)描述动态代理的几种实现方式?分别说出相应的优缺点


代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。


静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。

缺点:不同的接口要有不同的代理类实现,会很冗余JDK 动态代理:


为了解决静态代理中,生成大量的代理类造成的冗余,引入了动态代理。


JDK 动态代理:


只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象

jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口

优点:解决了静态代理中冗余的代理实现类问题。

缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。


CGLIB 代理:


由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;

CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。

实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。

但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。

同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。

缺点:技术实现相对难理解些。


(2)为什么JDK动态代理的被代理对象是接口?


阿里P7面试题,鉴于本人水平有限就不写了!有兴趣的朋友可以去网上搜一下看看~

相关文章
|
Java 程序员
动态代理
动态代理
68 0
|
9月前
|
设计模式 Java
动态代理详解
【2月更文挑战第7天】
动态代理详解
|
9月前
|
设计模式 缓存 监控
静态代理与动态代理
静态代理与动态代理
57 0
|
设计模式 IDE Java
代理模式之静态代理和动态代理~
代理模式之静态代理和动态代理~
|
设计模式 Java 程序员
动态代理竟然如此简单!(一)
这篇文章我们来聊一下 Java 中的动态代理。 动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。
164 0
动态代理竟然如此简单!(一)
|
Java 编译器 Maven
动态代理竟然如此简单!(二)
这篇文章我们来聊一下 Java 中的动态代理。 动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。
108 0
动态代理竟然如此简单!(二)
|
Java 数据库连接 API
动态代理的实际应用
最近在用 Python 的 SQLAlchemy 库时(一个类似于 Hibernate 的 ORM 框架),发现它的 Events 事件还挺好用。 简单说就是当某张表的数据发生变化(曾、删、改)时会有一个事件回调,这样一些埋点之类的需求都可以实现在这里,同时和业务代码完全解耦,维护起来也很方便。
|
设计模式 Java API
静态代理、动态代理(JDK动态代理,Cglib动态代理)(1)
静态代理、动态代理(JDK动态代理,Cglib动态代理)(1)
137 0
静态代理、动态代理(JDK动态代理,Cglib动态代理)(1)
|
Java 索引
静态代理与JDK动态代理与CGLIB动态代理(下)
静态代理与JDK动态代理与CGLIB动态代理(下)
|
Java Spring
静态代理与JDK动态代理与CGLIB动态代理(上)
静态代理与JDK动态代理与CGLIB动态代理(上)