关于java:Java进阶-Proxy动态代理机制详解

2次阅读

共计 6467 个字符,预计需要花费 17 分钟才能阅读完成。

一、Jvm 加载对象

在说 Java 动静代理之前,还是要说一下 Jvm 加载对象的过程,这个仍旧是了解动静代理的基础性原理:

Java 类即源代码程序 .java 类型文件,通过编译器编译之后就被转换成字节代码 .class 类型文件,类加载器负责读取字节代码,并转换成 java.lang.Class 对象,形容类在元数据空间的数据结构,类被实例化时,堆中存储实例化的对象信息,并且通过对象类型数据的指针找到类。

过程形容:源码 ->.java 文件 ->.class 文件 ->Class 对象 -> 实例对象

所以通过 New 创建对象,专断其背地很多实现细节,了解上述过程之后,再理解一个罕用的设计模式,即代理模式。

二、代理模式

1、根本形容

代理模式给某一个 (指标) 对象提供一个代理对象,并由代理对象持有指标对象的援用。所谓代理,就是一个对象代表另一个对象执行相应的动作程序。而代理对象能够在客户端和指标对象之间起到中介的作用。

代理模式在理论的生存中场景很多,例如中介、律师、代购等行业,都是简略的代理逻辑,在这个模式下存在两个要害角色:

指标对象角色:即代理对象所代表的对象。

代理对象角色:外部含有指标对象的援用,能够操作指标对象;AOP 编程就是基于这个思维。

2、静动静模式

  • 动态代理:在程序运行之前确定代理角色,并且明确代理类和指标类的关系。
  • 动静代理:基于 Java 反射机制,在 JVM 运行时动态创建和生成代理对象。

三、动态代理

基于上述动态代理的概念,用一段代码进行形容实现,根本逻辑如下:

  • 明确指标对象即被代理的对象;
  • 定义代理对象,通过结构器持有指标对象;
  • 代理对象中定义前后置加强办法;

指标对象与前后置加强代码就组成了代理对象,这样就不必间接拜访指标对象,像极了电视剧中那句话:我是律师,我的当事人不不便和你对话。

public class Proxy01 {public static void main(String[] args) {TargetObj targetObj = new TargetObj() ;
        ProxyObj proxyObj = new ProxyObj(targetObj) ;
        proxyObj.invoke();}
}
class TargetObj {public void execute (){System.out.println("指标类办法执行...");
    }
}
class ProxyObj {
    private TargetObj targetObj ;
    /**
     * 持有指标对象
     */
    public ProxyObj (TargetObj targetObj){this.targetObj = targetObj ;}
    /**
     * 指标对象办法调用
     */
    public void invoke (){before () ;
        targetObj.execute();
        after () ;}
    /**
     * 前后置解决
     */
    public void before (){System.out.println("代理对象前置解决...");
    }
    public void after (){System.out.println("代理对象后置解决...");
    }
}

动态代理明确定义了代理对象,即有一个代理对象的 .java 文件加载到 JVM 的过程,很显然的一个问题,在理论的开发过程中,不可能为每个指标对象都定义一个代理类,同样也不能让一个代理对象去代理多个指标对象,这两种形式的保护老本都极高。

代理模式的实质是在指标对象的办法前后置入加强操作,然而又不想批改指标类,通过后面反射机制能够晓得,在运行的时候能够获取对象的构造信息,基于 Class 信息去动态创建代理对象,这就是动静代理机制。

顺便说一句:技术的底层实现逻辑不好了解是家喻户晓,然而根底知识点并不简单,例如代理模式的基本原理,然而联合到理论的简单利用中(AOP 模式),很难惟妙惟肖的了解到是基于反射和动静代理的形式实现的。

四、动静代理

1、场景形容

基于一个场景来形容动静代理和动态代理的区别,即最近几年很火的概念,海内代购:

在代购刚衰亡的初期,是一些常去海内出差的人,会接代购需要,即代理人固定;起初就衰亡海内代购平台,海淘等一系列产品,即用户代购需要(指标对象)由代购平台去实现,然而具体谁来操作这个就看即时调配,这个场景与动静代理的原理相似。

2、根底 API 案例

首先看两个外围类,这里简述下概念,看完根本过程再细聊:

  • Proxy- 创立代理对象,外围参数:

    • ClassLoader:(指标类)加载器;
    • Interfaces:(指标类)接口数组;
    • InvocationHandler:代理调用机制;
  • InvocationHandler- 代理类调用机制:

    • invoke:这个上篇说的反射原理;
    • method:反射类库中的外围 API;

指标对象和接口

interface IUser {Integer update (String name) ;
}
class UserService implements IUser {
    @Override
    public Integer update(String name) {
        Integer userId = 99 ;
        System.out.println("UserId="+userId+";updateName="+name);
        return userId ;
    }
}

代理对象执行机制

class UserHandler implements InvocationHandler {
    private Object target ;
    public UserHandler (Object target){this.target = target ;}
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before()...");
        Object result = method.invoke(target, args);
        System.out.println("after()...");
        return result;
    }
}

具体组合形式

public class Proxy02 {public static void main(String[] args) {
        /*
         * 生成 $Proxy0 的 class 文件
         */
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        /*
         * 指标对象信息
         */
        IUser userService = new UserService();
        ClassLoader classLoader = userService.getClass().getClassLoader();
        Class<?>[] interfaces = UserService.class.getInterfaces() ;
        /*
         * 创立代理对象
         */
        InvocationHandler userHandler = new UserHandler(userService);
        /*
         * 代理类对象名
         * proxyClassName=com.java.proxy.$Proxy0
         */
        String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();
        System.out.println("proxyClassName="+proxyClassName);
        /*
         * 具体业务实现模仿
         */
        IUser proxyUser1 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
        IUser proxyUser2 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
        proxyUser1.update("cicada") ;
        proxyUser2.update("smile") ;
    }
}

这里之所以要生成代理类的构造信息,因为从 JVM 加载的过程看不到相干内容,要害信息再次被专断:

javap -v Proxy02.class

查看代理类名称

/*
 * proxyClassName=com.java.proxy.$Proxy0
 */
String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();
System.out.println("proxyClassName="+proxyClassName);

下意识输入代理对象名称,这里即对应 JVM 机制,找到 Class 对象名,而后剖析构造,这样就明确动静代理具体的执行原理了。

生成代理类.class 文件

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

通过下面 JVM 加载对象的机制可知,形容代理类的 Class 对象肯定存在,只是在运行时并没有生成显式的 .class 文件,通过下面生成代理类 .class 的语法,会在我的项目目录的 /com/java/proxy 门路下创立文件。

顺便说一句:作为一只程序员,简单总是和咱们环环相绕,说好的简略点呢?

3、代理类构造

继承与实现

class $Proxy0 extends Proxy implements IUser {}

从代理类的性能来思考,能够想到须要继承 Proxy 与实现 IUser 接口,还有就是持有调用机制的具体实现类,用来做业务加强。

构造方法

public $Proxy0(InvocationHandler var1) throws  {super(var1);
}

通过构造方法,持有 UserHandler 具体的执行机制对象。

接口实现

final class $Proxy0 extends Proxy implements IUser {
    private static Method m3;
    public final Integer update(String var1) throws  {
        try {return (Integer)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
        }
    }
}

指标类的根本需要 update() 办法,通过代理类进行承接,并基于 UserHandler 实现具体的加强业务解决。

根底办法

final class $Proxy0 extends Proxy implements IUser {
    private static Method m0;
    private static Method m1;
    private static Method m2;
    public $Proxy0(InvocationHandler var1) throws  {super(var1);
    }
    static {
        try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.java.proxy.IUser").getMethod("update", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());
        }
    }
    public final boolean equals(Object var1) throws  {
        try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
        }
    }
    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 int hashCode() throws  {
        try {return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }
}

基于 Object 类,定义 Java 中几个罕用办法 equals()判断,toString()办法,hashCode()值,这个在剖析 Map 源码的时候有说过为什么这几个办法通常都是一起呈现。

4、JDK 源码

下面是案例执行的过程和原理,还有一个关键点要明确,即 JDK 源码的逻辑:

IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);

Proxy 提供的静态方法newProxyInstance(),通过各个参数的传入,构建一个新的代理 Class 对象,即 $Proxy0 类的构造信息,这里再回首看下三个外围参数:

  • ClassLoader:基于 JVM 运行过程,所以须要获取指标类 UserService 的类加载器;
  • Interfaces:指标类 UserService 实现的接口,从面向对象来思考,接口与实现拆散,代理类通过实现 IUser 接口,模仿指标类的需要;
  • InvocationHandler:代理类提供的性能封装即 UserHandler,能够在指标办法调用前后做加强解决;

最初总结一下动静代理的实现的核心技术点:Jvm 加载原理、反射机制、面向对象思维;每次浏览 JDK 的源码都会惊叹设计者的巧夺天工,滴水穿石保持才会有播种。

JVM 类加载机制 | 代理模式 | AOP 切面编程 | 自定义日志记录 | Map 源码剖析

五、源代码地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

浏览标签

【Java 根底】【设计模式】【构造与算法】【Linux 零碎】【数据库】

【分布式架构】【微服务】【大数据组件】【SpringBoot 进阶】【Spring&Boot 根底】

【数据分析】【技术导图】【职场】

正文完
 0