吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1826|回复: 14
收起左侧

[Java 原创] JAVA Proxy动态代{过}{滤}理

[复制链接]
Qvv1koa 发表于 2023-11-15 10:26

@TOC

什么是动态代{过}{滤}理

在学习大部分框架的时候,都会使用到动态代{过}{滤}理机制,下面让我们来了解一下什么是动态代{过}{滤}理

给大家讲个故事吧,主人公小明(XiaoMing),是一个单身程序员,他想找对象,但是没有时间,小明的父母(Parent)看到这个情况,非常着急,于是要替小明找对象,这个流程下,小明就是一个被代{过}{滤}理对象,他的父母就是代{过}{滤}理对象

动态代{过}{滤}理中,非常重要的几个东西

被代{过}{滤}理对象 小明(XiaoMing)
代{过}{滤}理对象 小明的父母(Parent):代{过}{滤}理对象的主要作用就是对被代{过}{滤}理对象的增强和通知

代码说明动态代{过}{滤}理

  • 找对象接口(只定义一个方法,就是人要找对象)
    public interface People {
    void findMM();
    }
  • 小明(小明是一个人,又要找对象,因此实现了一下People接口)
    public class XiaoMing implements People {
    @Override
    public void findMM() {
        System.out.println("=================我是小明,我要找对象");
    }
    }
  • 小明的父母(小明要替小明找对象)

    public class Parent{
    
    public void heplXm(){
        //找对象前的准备工作(对找对象的增强处理)
         after();
        //帮助小明找对象(代{过}{滤}理小明找对象)
        XXX.findMM();
        //找到对象后的善后工作(对找对象的增强处理)
        before();
    }
    
    private void before() {
        System.out.println("===========找到对象后还要给他准备结婚");
    }
    
    private void after() {
        System.out.println("===========小明父母准备帮小明找对象了,找他要了照片");
    }
    }

    但是现在来看Parent这个方法,并没有真正的帮助到小明,因为XXX.findMM()这个动作,还是小明自己在调用的,他的父母并没有帮上他什么忙。

  • 此时,我们就要引入Proxy动态代{过}{滤}理了,下面我们写一个测试类来进行动态代{过}{滤}理
    public void test01(){
        People proxy = (People) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{People.class},new Parent(new XiaoMing()));
        proxy.findMM();
    }
    //针对Proxy.newProxyInstance的说明
    //1. ClassLoader loader 当前处理类的类加载器
    //2. Class<?>[] interfaces 被代{过}{滤}理的接口的类型(是一个数组)
    //3. InvocationHandler h 代{过}{滤}理类
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

    引入动态代{过}{滤}理后,Parent就升级了,就会变成这个样子,只有实现了InvocationHandler 接口,才可以使用JDK自带的动态代{过}{滤}理

  • 修改后的Parent

    public class Parent implements InvocationHandler {
    
    private People people;
    
    public Parent(People p) {
        this.people = p;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        after();
        //调用people下的方法,null就是被调用方法传入的参数
        //findMM是一个无参方法,所以此处传null
        method.invoke(people,null);
        before();
        return null;
    }
    
    private void before() {
        System.out.println("===========找到对象后还要给他准备结婚");
    }
    
    private void after() {
        System.out.println("===========小明父母准备帮小明找对象了,找他要了照片");
    }
    }

    运行test01,执行修改后的代码得到以下结果
    在这里插入图片描述
    发现,代码被正常执行了,并且没有显示调用XiaoMing.findMM(),而且在没有修改XiaoMing.findMM()的前提下,还增加了一些功能。

动态代{过}{滤}理解析

那问题来了,到底是谁调用了findMM()这个方法呢
此时就会有人回答是Parent 中的  method.invoke(people,null);

【面试😒】
动态代{过}{滤}理中的 invoke方法,到底是谁在调用的?
根据Debug,我们可以看到是$Proxy4调用的invoke($Proxy4的命名格式为 $Proxy + 序号,所以你执行出来不一定是$Proxy几)
在这里插入图片描述
这个$Proxy4存在于jvm的缓存中,在运行中我们是看不到具体的代码,但是,为了方便使用,Proxy给我们提供了对应的代码,可以获取到对应的class文件,让我们更加直观的看到Proxy的代{过}{滤}理实现,使用以下代码,可以将$Proxy从内存中获取出来,变成文件,方便阅读

-获取$Proxy0.class

    public void test02(){
        byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{People.class});
        try {
            FileOutputStream fos = new FileOutputStream("$Proxy0.class");
            fos.write($Proxy0s);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在根目录下得到了$Proxy0.class,反编译打开

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import a.demo.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 m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    //重写equals
    public final boolean equals(Object var1) throws  {...}
    //重写toString
    public final String toString() throws  {...}

    //重点看这里!!!
    public final void findMM() throws  {
        try {
            //调用了父类的h.invoke,这个h就是$Proxy0
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //重写hashCode
    public final int hashCode() throws  {...}

    //加载一些初始化类
    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("a.demo.People").getMethod("findMM");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

手写一个简单的动态代{过}{滤}理

源码方面,我们稍后在读,我们先手写一个简单的动态代{过}{滤}理,通过这个来理解Proxy.newProxyInstance

准备工作如下

  1. 手写一个新的newProxyInstance
    起名就叫myNewProxyInstance吧,我们首先需要要明确的是。myNewProxyInstance,要具有生成一个类的功能,生成的类要类似于我们获取的$Proxy0,那如何生成一个类呢,通过拼凑字符串的方式生成一个$Proxy0.java文件
  2. 使用流的方式,将字符串写到$Proxy0.java文件中
  3. 将$Proxy0.java文件编译成.class文件
  4. 将这个.class文件加载到JVM中
  5. 在内存中执行,然后返回实例

开始写代码了

  • myInvocationHandler 用于方法的实例化(调用其invok)

    
    public interface myInvocationHandler {
    
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
- myProxy  代{过}{滤}理啦 啦啦啦啦啦啦啦
```java
package org.process;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class myProxy {

    protected myInvocationHandler h;

    private static String rt = "\r\n";

    public static Object newProxyInstance(myClassLoader loader,
                                          Class<?>[] interfaces,
                                          myInvocationHandler h)
            throws IllegalArgumentException
    {

        //1. 以字符串的方式拼出一个代{过}{滤}理类
        String sJava = getJavaStr(interfaces);
        //2. 使用流的方式,将字符串写到.java文件中
        File file = cratejavaFile(sJava,"$Proxy0");
        //3. 将.java文件编译成.class文件
        compiler(file);
        //4. 将这个.class文件加载到JVM中
        Class<?> $Proxy0 = loader.findClass("$Proxy0");
        //这里先不删除生成的$Proxy0.java文件,实际上要删除的
        //file.delete();
        Object o = null;
        try {
            Constructor<?> c = $Proxy0.getConstructor(myInvocationHandler.class);
            //5. 在内存中执行,返回实例
            o = c.newInstance(h);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return o;
    }

    private static void compiler(File file) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static File cratejavaFile(String sJava,String sKey) {
        String basePath = myClassLoader.class.getResource("").getPath();
        File file = new File(basePath + sKey + ".java");
        try {

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(sJava);
            fileWriter.flush();
            fileWriter.close();

        }catch (Exception e ){
            e.printStackTrace();
        }

        return file;
    }

    /** 生成代{过}{滤}理对象$Proxy0的源代码
     * @Param interfaces 抽象对象
     * @Return String
     */
    private static String getJavaStr(Class<?>[] interfaces) {

        String ClazzName = "$Proxy" + 0;
        String src = CrateStr(interfaces,ClazzName);

        return src;
    }

    private static String CrateStr(Class<?>[] interfaces, String clazzName) {
        //获取包名
        String name = myProxy.class.getPackage().getName();

        StringBuilder src = new StringBuilder();

        src.append("package " + rt);
        src.append(name);
        src.append(";" + rt);
        //引入反射相关的包
        src.append("import java.lang.reflect.Method;" + rt);
        src.append("import org.process.myProxy;" + rt);

        //动态代{过}{滤}理类实现被代{过}{滤}理接口
        src.append("public class ");
        src.append(clazzName);
        //开始拼接接口信息
        src.append(" extends myProxy implements ");

        for(int i = 0 ; i < interfaces.length ;i ++ ){
            Class clazz = interfaces[i];

            if(interfaces.length == 1){
                src.append(clazz.getName());

            }else if(interfaces.length - 1 == i){
                src.append(clazz.getName());
            }else{
                src.append(clazz.getName() + ",");
            }
        }
        src.append("{" + rt);

        src.append("public ");
        src.append(clazzName);
        src.append(" (myInvocationHandler h) {" + rt);

        src.append("this.h = h;" + rt);
        src.append("}" + rt);
        //通过反射获取代{过}{滤}理接口的所有方法并激活
        for(Class clazz : interfaces){

            for (Method m : clazz.getMethods()) {
                src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + rt);

                src.append("try{" + rt);
                if(m.getParameterTypes().length > 0){
                    src.append("Method m = " + clazz.getName() + ".class.getMethod(\"" + m.getName() + "\",null);" + rt);
                }else{
                    src.append("Method m = " + clazz.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + rt);
                }

                src.append("this.h.invoke(this,m,null);" + rt);
                src.append("}catch(Throwable e){e.printStackTrace();}" + rt);
                src.append("}" + rt);
            }
        }
        src.append("}");
        return src.toString();
    }
}

这里需要用到类加载器,我们又在新建了一个类继承了一下ClassLoader,然后重写了一下他的findclass方法,enmmm。。。就是为了用于将生成的.class文件装载到内存中。

  • myClassLoader
    
    package org.process;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class myClassLoader extends ClassLoader{

private File baseDir;

public myClassLoader(){
    String basePath = myClassLoader.class.getResource("").getPath();
    this.baseDir = new File(basePath);
}

@Override
protected Class<?> findClass(String name) {
    String className = myClassLoader.class.getPackage().getName() + "." + name;
    if(baseDir != null){
        File classFile = new File(baseDir,name.replaceAll("\\.", "/") + ".class");
        if(classFile.exists()){
            FileInputStream in = null;
            ByteArrayOutputStream out = null;
            try{
                in = new FileInputStream(classFile);
                out = new ByteArrayOutputStream();
                byte [] buff = new byte[1024];
                int len;
                while ((len = in.read(buff)) != -1) {
                    out.write(buff, 0, len);
                }
                return defineClass(className, out.toByteArray(), 0,out.size());
            }catch (Exception e) {
                e.printStackTrace();
            }finally{
                if(null != in){
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(null != out){
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                //先不删除,可以看到class文件内容
                //classFile.delete();
            }
        }
    }
    return null;
}

}

好像也么啥其他的了,然后就复制了一下Partent类,改了一下他的实现关系
- Parent1 
```java
package org.process;

import org.example.People;

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

public class Parent1 implements myInvocationHandler {

    private People people;

    public Parent1(People p) {
        this.people = p;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        after();
        method.invoke(people,null);
        before();
        return null;
    }

    private void before() {
        System.out.println("===========Parent1  找到对象后还要给他准备结婚");
    }

    private void after() {
        System.out.println("===========Parent1  小明父母准备帮小明找对象了,找他要了照片");
    }

}

到这里 代码算是写完了,然后写一个测试类看看情况

    public void test04(){
        People proxy = (People) myProxy.newProxyInstance(new myClassLoader(), new Class[]{People.class},new Parent1(new XiaoMing()));
        proxy.findMM();
    }

执行结果如下:
https://img-blog.csdnimg.cn/3fe9382d374d46189a786e1cf6f064d9.png
完成喽,运行成功~

动态代{过}{滤}理应用

老代码已经在生产上运营,不方便修改的情况下(或者压根就改不了),还需要对其功能进行扩容,就会有大量的应用,具体逻辑请参照Parent

动态代{过}{滤}理源码

看下载链接

proxy-20230509.zip

27.9 KB, 下载次数: 4, 下载积分: 吾爱币 -1 CB

本文源码

免费评分

参与人数 4吾爱币 +11 热心值 +4 收起 理由
末初 + 2 + 1 AOP
wushaominkk + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
坦然 + 1 + 1 我很赞同!
杨辣子 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Qvv1koa 发表于 2023-11-19 16:43
Edith123 发表于 2023-11-17 21:23
正好在看这个,感觉不太好理解

多想,多看看框架内的代{过}{滤}理代码就好啦,代{过}{滤}理这个东西就是应该你干的事情交给别人了,自己拿现成的了,反射反一切
myFreefly 发表于 2023-11-15 15:28
你好早安啊 发表于 2023-11-15 18:04
hexiaopidan 发表于 2023-11-15 23:02
感谢分享
Edith123 发表于 2023-11-17 21:23
正好在看这个,感觉不太好理解
JoursCode 发表于 2023-11-18 22:17
感谢分享,学到了
uphold 发表于 2023-11-18 22:58
感谢分享,学到了
npz 发表于 2023-11-18 23:34
在获取流那一块,使用 try-with-resources 岂不是更好点?
 楼主| Qvv1koa 发表于 2023-11-19 16:46
npz 发表于 2023-11-18 23:34
在获取流那一块,使用 try-with-resources 岂不是更好点?

可以的,这么写就是好读一点,哈哈哈
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 20:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表