首页 > 其他分享 >JdkProxy的进阶知识

JdkProxy的进阶知识

时间:2023-04-12 20:22:48浏览次数:47  
标签:知识 java 进阶 JdkProxy 代理 class Fight public

如果想增强一个方法的功能,无非就是直接在方法体内直接修改。但这也无非给一些有代码洁癖人士一丝丝不悦!于是乎我们即不想在原来的代码里修改,又不想把原有的代码重新写一次,那么前辈们就发明了代理.
注意:本文以 JdkProxy 为基础展开所有描述!

参与对象

那么一个代理过程参与的对象有以下几项:

  • 目标接口
  • 目标类(Target)
  • 代理基类(Proxy)
  • 生成的代理类
  • 调用处理程序(InvocationHandler)
目标接口。

至于为什么要用接口,这是JdkProxy的理论知识。文章结束后你也会明白!

public interface Fight {

    /**
     * 射击
     */
    void shot();

    /**
     * 炸弹
     */
    void bomb();
}
目标类

就是需要被代理的类!

public class BeautifulCountryTarget implements Fight {


    private static final Logger LOGGER = LoggerFactory.getLogger(BeautifulCountryTarget.class);


    /**
     * 射击
     */
    @Override
    public void shot() {
        
        LOGGER.debug("M4 shot ---> big goose!");
        
    }

    /**
     * 炸弹
     */
    @Override
    public void bomb() {
        
        LOGGER.debug("HIMARS fire ---> big goose!");
    }
}
调试和输出结果
public class JdkProxy {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);


    /**
     * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
     *  
     * 1:生成的 代理对象 是实现了 目标接口
     * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
     * 
     */
    public static void main(String[] args) throws InterruptedException {
        
        // jdkProxy
        jdkProxyTest();
        
    }
    
    
    public static void jdkProxyTest() throws InterruptedException {
        //类加载器,负责把生成的class($Proxy???)文件加载到JVM
        ClassLoader loader = JdkProxy.class.getClassLoader();

        //需要代理的目标(对象)
        BeautifulCountryTarget target = new BeautifulCountryTarget();

        //增强目标方法的处理程序
        InvocationHandler handler = (proxy, method, args) -> {

            LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");

            //方法调用,(目标对象,参数)
            return method.invoke(target, args);

        };


        //创建代理人
        Fight w_k_l_Fight = (Fight)Proxy.newProxyInstance(
                loader,
                new Class[]{Fight.class},
                handler
        );
        
        //打印下类路径,方便使用 arthas 进行反张译
        LOGGER.debug("proxy<w_k_l_Fight> class {}", w_k_l_Fight.getClass());
    
        //代理人调用方法
        w_k_l_Fight.shot();
        w_k_l_Fight.bomb();
        
    }

}

打印出

19:22:41.839 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- proxy<w_k_l_Fight> class class jdk.proxy1.$Proxy0
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!

可以看出代理生效了,-_-!

那么通过 Proxy.create函数生成的是一字节码文件(至于怎么生成,太高端没去研究),它被 loader 加载到JVM,通过 debug只看了类名$Proxy0

模拟手写代理类

既然是系统自己生成的,那么我们自己可以自己写一个,不用系统生成的~。由理论知识可以写出以下 代理类

/**
 * 模拟 通过 Proxy.create 出来的 $Proxy?? 类,
 * 
 */
public class SimulateProxy extends Proxy implements Fight {
    
    
    protected SimulateProxy(InvocationHandler h) {
        super(h);
    }
    
    
    /**
     * 射击
     */
    @Override
    public void shot() {
        try {
            // 代理的方法
            Method pMethod = Fight.class.getMethod("shot");

            //调用增强处理器
            super.h.invoke(this, pMethod, null);   
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 炸弹
     */
    @Override
    public void bomb() {
        try {
            // 代理的方法
            Method pMethod = Fight.class.getMethod("bomb");

            //调用增强处理器
            super.h.invoke(this, pMethod, null);   
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
调试输出
public class JdkProxy {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);


    /**
     * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
     *  
     * 1:生成的 代理对象 是实现了 目标接口
     * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
     * 
     */
    public static void main(String[] args) throws InterruptedException {
        
        // 模拟代理类
        simulateProxyTest();
        
    }
    
    
    public static void simulateProxyTest() {
        
        //需要代理的目标(对象)
        BeautifulCountryTarget target = new BeautifulCountryTarget();

        //增强目标方法的函数
        InvocationHandler handler = (proxy, method, args) -> {

            LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");

            return method.invoke(target, args);

        };
        
        //创建代理人 这里换成自己手写的
        Fight w_k_l_Fight = new SimulateProxy(handler);


        //代理人调用方法
        w_k_l_Fight.shot();
        w_k_l_Fight.bomb();
    }

}

同样输出

19:38:23.950 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!

总结

我们先来看下系统生成的$Proxy0是什么样。
现在通过在main方法添加 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");可以把系统生成的代理class文件写到目录里root/jdk/proxy1/$Proxy0.class。我这里直接贴出来,方便和上下文对比。

public final class $Proxy0 extends Proxy implements Fight {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;
    private static final Method m4;

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

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        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 shot() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void bomb() {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            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.coffeetime.aop.Fight").getMethod("shot");
            m4 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("bomb");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

那么对比下系统生成的和自己手写的代理类区别 SimulateProxy和 $Proxy0,可以看出系统生成的代理类比我们手写的比较优雅一些!

  1. 利用静态方法把需要代理的目标方法在加载阶段就初始化了,而不需要每次调用的时候去通过反射获取到.
  2. 多了 equals,toString,hashCode,有意思的是代理类和目标类 equalsture, 但是不同的实例!
    19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight equals target ? true
    19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight == target ? false
    
  3. 通过生成的代理类可以看出,的确是实现了 目标接口(和目标类一样),和代理类是兄弟关系,但又胜似兄弟!
  4. 对异常的处理

本文示例代码

标签:知识,java,进阶,JdkProxy,代理,class,Fight,public
From: https://www.cnblogs.com/m78-seven/p/17311107.html

相关文章

  • JavaScript基础知识
    JavaScript基础知识JavaScript是什么?JavaScript是一门编程语言,可以实现很多的网页交互效果。开web页面的脚本语言JavaScript的书写位置?内部JavaScript写在body结束标签上方script里面外部JavaScript通过scriptsrc=引入js文件但是script里面不要写内容,否则会被忽略JavaSc......
  • 最新版本 Stable Diffusion 开源 AI 绘画工具之图生图进阶篇
    (✨目录)......
  • 「解题报告」UOJ605 [UER #9] 知识网络
    好像并不是很难的题?虽然从上午想到现在才开始写,还因为不知道__builtin_popcount(x)传入的是int调了一个多小时题目就是要求一个全源最短路。直接求显然不太现实,考虑分析标签的性质。发现,同一标签内的所有点到某个点\(u\)的最短路的差值一定不超过\(1\),因为同一标签下的点......
  • 计算机基础知识试题及答案(怀旧用)
    第一部分一、单项选择题1.世界上第一台电子数字计算机取名为()。A.UNIVACB.EDSACC.ENIACD.EDVAC2.操作系统的作用是()。A.把源程序翻译成目标程序B.进行数据处理C.控制和管理系统资源的使用D.实现软硬件的转换3.个人计算机简称为PC机,这......
  • C语言数组基础知识(关于索引)
    #include<stdio.h>intmain(){inti;//遍历输出分别值inta[]={1,2,3,4,5};for(i=0;i<5;i++){printf("%d\t",a[i]);//12345};printf("\n");//若给的值不够就用0补齐......
  • 【基础知识】PCB布局设计入门步骤
    准备是成功的基石,在PCB设计中也是如此。改进和增长将伴随经验,首先做好准备能够充分利用经验获得成功。为了帮助你做好准备,下面分享一些基本的PCB布局设计步骤。从良好的原材料入手是您PCB布局设计的第一步无论打算执行什么任务,要想成功,就需要从优质的材料入手。就像高级木匠不会用......
  • 基础知识小结
    为什么会存在这个  大概在2021年中左右,我决定未来5-8年还是在搞技术,所以我就在想我该如何完善自己的知识体系,要怎么样才能成为一个合格的、专业的前端工程师,如果后面不止于前端,我要怎么样才能在软件行业走的更远。所以就有了先提升很基础的知识点,虽然大学专业是软件工程,但是......
  • javaEE进阶小结与回顾(五)
    字符集字符集基础一堆字符的集合,包含很多字符,并且每个字符都有一个数字编号与之对应常见字符集有:ASCII字符集,GBK字符集,Unicode字符集等计算机根据字符集,可对字符进行编码,以便计算机识别和存储各种文字常用字符集ASCII字符集美国信息交换标准代码,包括了数......
  • Android脱壳基础知识
    JVM的类加载器:BootstrapClassLoader引导类加载器:C/C++代码实现的加载器,用于加载制定的JDK核心库,比如java.lang.*、java.util.*等这些系统类。Java虚拟机的启动就是通过Bootstrap,该ClassLoader在java里无法获取,负责加载/lib下的类ExtensionsClassLoader拓展类加载器:Java中的......
  • #yyds干货盘点 前端小知识点扫盲笔记记录2
    前言大家好我是歌谣今天继续进行前端知识的一些总结想加入前端巅峰交流群私信我innerHTML和innerText的使用<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge">......