设计模式-动态代理模式

摘要:代理模式有静态代理模式和动态代理模式,静态代理模式比较简单就过了,主要来看看动态代理模式以及动态代理模式在使用。

动态代理是一种增强代码功能的方式、手段,其实我们可以通过继承重写、装饰也能对代码功能进行新功能的增加。但有些情况下使用代理模式、动态代理模式可能更适合。

你在使用动态代理的时候有没有想过他是什么时候生成的,怎么生成的,为何调用一个接口方法就能调用委托类的方法并返回相关方法值;

下面从代理示例、分析代理类生成过程以及代理类结构信息一步步了解代理类的生成原理……

一、动态代理模式示例

1、接口

public interface People {    void say();    void eat();}

2、委托代理类

public class Student implements People {

    @Override    public void say() {        System.out.println("HELLO JAVA HELLO DYNAMIC PROXY");    }
    @Override    public void eat() {        System.out.println("input byte  return Class object");    }}

3、调用处理器(InvocationHandler)、从静态代理的名词来说他更像是代理关系

public class MyInvocationHandler implements InvocationHandler {    /**     * 委托类     */    private Object target;    private MyInvocationHandler() {    }    public MyInvocationHandler(Object object) {        this.target = object;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("调用委托类方法前置处理");        //委托类方法调用        Object result = method.invoke(target, args);        System.out.println("调用委托类方法后置处理");        return result;    }}

4、动态生成代理类

private Object getProxy(ClassLoader classLoader, Class[] interfaces, InvocationHandler        invocationHandler) {    Object proxy = java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);    return proxy;}

5、代理类方法执行

Student student = new Student();ClassLoader classLoader = JdkProxy.class.getClassLoader();Class[] interfaces = Student.class.getInterfaces();MyInvocationHandler invocationHandler = new MyInvocationHandler(student);People people = (People) getProxy(classLoader, interfaces, invocationHandler);people.say();

6、执行结果​​​​​​​

调用委托类方法前置处理HELLO JAVA HELLO DYNAMIC PROXY调用委托类方法后置处理

二、代理类类原始信息

1、代理类反编译后的类结构信息

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)​​​​​​​

package com.sun.proxy;import com.sb.imports.People;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements People {    private static Method m1;    private static Method m3;    private static Method m2;    private static Method m4;    private static Method m0;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }    public final boolean equals(Object var1) throws  {        try {            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final void eat() throws  {        try {            super.h.invoke(this, m3, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    public final String toString() throws  {        try {            return (String)super.h.invoke(this, m2, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    public final void say() throws  {        try {            super.h.invoke(this, m4, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    public final int hashCode() throws  {        try {            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }
    static {        try {            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));            m3 = Class.forName("com.sb.imports.People").getMethod("eat");            m2 = Class.forName("java.lang.Object").getMethod("toString");            m4 = Class.forName("com.sb.imports.People").getMethod("say");            m0 = Class.forName("java.lang.Object").getMethod("hashCode");        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

这就是动态生成的代理类类结构信息,看到这个突然想起了枚举类,枚举类被反编译之后的情形和这个“差不多”。

编译后看到定义了很多方法类变量,而且所有的动态代理生成的类中有m0、m1,m2 这三个Object的方法,而其他的方法都是接口方法。

2、调用动态代理对象的方法为何就能调用到委托类具体对象方法

(1)、通过反编译后的类信息可以看到所有方法中都调用了InvocationHandler接口的invoke(proxy,method,args) 方法。

(2)、InvocationHandler 接口的实现类重写了invoke(proxy,method,args) 方法,而方法中通过反射调用了委托类方法,所以代理类方法的调用都能执行委托类对应方法。

(3)、个人理解InvocationHandler(调用处理器)很像静态代理中的代理关系,把委托类和代理类关联起来。

(4)、代理类到委托类的执行流程

        代理类方法—> InvocationHandler.invoke()方法—>委托类对应方法

        返回结果<—返回到代理类方法<—返回到InvocationHandler.invoke方法<—委托类方法执行结束

3、代理类是什么时候生成的

    从java程序到生成机器码cpu执行主要的阶段有:

(1)、java文件被javac编译器编译生成jvm字节码文件即.class 文件

(2)、JVM类加载器查找、加载 .class文件并生成Class对象

(3)、解释器、编译器将JVM字节码生成机器字节码并运行。

    但是通过测试你会发现在 

那动态代理类的生成是在哪一步生成的那,编译阶段确实能改变.class 文件比如 类构造函数但是编译后不存在代理类,触发类加载后也不生成代理类,那就只有最后一步了即运行时生成代理类。

说明:如果使用JDK.Proxy需要设置 

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true”); 才能看到代理类.class 文件。

 

三、动态代理的使用场景

其实代理类使用场景还是很多,如果要添加切面功能就要用到动态代理,比如spring AOP使用的动态代理;Rpc调用;下面列举几种使用动态代理的场景

1、统计某个API访问次数

2、切面日志打印

3、功能增强

这里说一个排查问题时使用代理模式的场景

问题描述:

公司使用RocketMQ做组与组之间的消息传递,我们这边的流程如下:

(1)、接收上游MQ

(2)、业务处理、入库数据组装、下游消息数据组装

(3)、业务数据入库

(4)、如果业务数据入库成功则发送MQ消息给下游

(5)、如果MQ服务端返回失败则重试N次,如果还是失败则将消息保存失败消息表

(6)、定时任务扫表重新发送失败消息保证最终一致性

现在问题是有时候会出现业务数据入库成功但是下游说没收到消息,而消失发送失败表中也没记录该条消息,那到底是我们没发送还是MQ服务丢消息了还是下游没消费成功。

(1)、添加一个消息丢失排查表,在业务数据入库

(2)、发送消息入消息日志记录表

(3)、发送消息

(4)、判断消息发送返回值,如果成功则更新消息日志记录表状态为成功;如果失败则标记为失败

(5)、如果失败则记录到消息失败表,定时任务扫表保持最终一致性

那这里的第2、4部怎么做,第一种方式修改原有代码业务逻辑添加这一部分逻辑,第二使用代理模式,不改变原有代码,业务逻辑;当然了这里选择第二种方式来实现。

Spring AOP和Dubbo 的方法调用都使用了动态代理,Dubbo中提供了两种方式来生成代理类即 JdkProxyFactory和JavassistProxyFactory 两种方式。

 

四、总结

1、动态代理类的生成涉及到接口、委托类、InvocationHandler接口实现类、代理类生成器(jdk、javasist ……)

2、动态生成的代理类继承Proxy类实现接口。

3、动态生成的代理类方法都调用了父类Proxy类中的InvocationHandler 类的invoke方法;

4、代理类和委托类的关联关系是由InvocationHandler接口实现类维护,而具体实现是InvocationHandler接口实现类重写invoke方法来实现。

5、拦截、增强都是在InvocationHandler接口实现类的invoke方法中实现。

6、同一个、同一批接口代理类的名称都一样,因为代理类的类结构是有缓存,不会每次都重新生成字节码文件……

五、知识点扩展

1、代理类的生成就是动态生成JVM字节码文件即动态生成.class文件,具体的生成方式我们放在JVM 相关文章来学习。

2、字节码文件生成框架:jdk.proxy; cglib;javasist dynamic proxy;javasist bytecode proxy;asm; 

3、字节码文件生成也就是使用上面这些字节码框架来编写满足JVM规范的 .class文件。

4、JVM的运行时加载的是.class文件,上一篇文章中说了类加载器加载类的过程:.class文件加载、连接(校验、准备、解析)、初始化。所以JVM的运行和 .java文件无关!

到这里单个设计模式的文章告一段落,下一篇写设计模式在结算系统中的使用来实战上面学到的一些设计模式,也算是设计模式的总结。

你可能感兴趣的:(设计模式)