动态代理(3)- newProxyInstance()实现原理

本文详细介绍了Java动态代理中newProxyInstance的实现原理。首先,通过接口获取所有方法并构建字符串,动态生成实现了该接口的类源代码。接着,将源代码写入文件并进行编译,生成字节码文件。然后,使用URLClassLoader加载字节码到内存,最后通过构造函数创建代理对象。整个过程涉及反射、字符串拼接、文件操作和动态编译。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于newProxyInstance方法我们先将一下整个实现的思路。
      总体思路就是他通过字符串化产生一个新的java类,再动态编译返回对象。注意是动态编译。

      简单来说就是他通过一些传递进来的参数,自己动态模拟写了一个java类,编译返回给用户,因为用户只要求得到一个对象。中间就比较自由。


 -------------------------------------------------------------------------------------------------------
       A.首先通过string构造 这个方法。 
       第一步利用传递进来的接口类,通过反射机制获得这个接口的所有方法。
    Method[] methods = inter.getMethods();
    这个inter 就是上篇中的Moveable.class

    然后遍历这个方法。       
    for(Method m: methods){
    利用字符串把中间所有的方法都相加。
            method +=
                      "@Override "rt +
                      "    public void " m .getName() +"(){" rt +
                      "    try{" rt +
                      "          Method md ="+inter         .getName()+".class.getMethod(\"" +m .getName()+"\");" rt +
                      "          h.invoke(this, md);" rt +
                      "    }catch(Exception e){" rt +
                      "          e.printStackTrace();" rt +
                      "    }" rt +
                    "  }" + rt + "    " ;
     }
   
  这段字符串后期生成的代码是这样的:
   @Override
   public void move(){
     try{
          Method md =Moveable.class.getMethod("move");
             h.invoke(this, md);
     }catch(Exception e){
          e.printStackTrace();
     }
  }
  红色是动态生成的部分,我们可以看到 对move方法的操作:
  1.首先生成一个move这个方法的对象,这里注意我们传递给invoke的那些方法是对象而不是一个个方法名,而我们通过newProxyInstance 第一个参数获得的仅仅是方法名字。所以这里我们要通过反射机制 利用得到的move方法名 再去获得方法对象md。
这时,我们可以回想第二篇中重写的invoke方法:md就是Method m这个参数,也就是move方法。
  2.调用h的invoke方法。有人会问 h 是什么,h就是newProxyInstnce 第二个参数,TimeHandler 这个h对象才能调用 TimeHandler里面的invoke方法呀。但是h是实体,但是我们现在书写的是在string里面的字符串。所以我们要继续写 一个构造方法。

String src =
                 "import java.lang.reflect.Method;" rt +
                 "public class TankTime implements "+inter .getName() +"{"  + rt +
                 "    private InvocationHandler h;" rt 
                 "    public TankTime(InvocationHandler h){" rt +
                 "          this.h = h;" rt +
                 "    }" rt +
                 "    " +method rt +
                 "}";

加上这段代码后 最后会生成下面的代码:
import java.lang.reflect.Method;
public class TankTime implements Moveable{
     private InvocationHandler h;
     public TankTime(InvocationHandler h){
          this.h = h;
     }
     @Override
     public void move(){
     try{
          Method md =Moveable.class.getMethod("move");
             h.invoke(this, md);
     }catch(Exception e){
          e.printStackTrace();
     }
  }

    
}

绿色是一开始生成的method方法。
这段代码 只要我们在编译的时候把h实体传进去,就可以了。注意,现在还是空的,等会我们会动态编译,然后传入h这个参数。

注意:这个类名TankTime其实不重要,叫什么都可以,因为它不起到什么作用,最后只要的是这个类实现了Moverable的对象。这只是一个中间量。
--------------------------------------------------------------------------------------------------------------
B.然后我们把这段代码 write成一个java文件。
String path = "c:/harvey/TankTime.java";
         File file = new File( path);
         FileWriter fw = new FileWriter( file);
         fw.write( src);
         fw.flush();
         fw.close();   

这段代码把我们生成的string 编程了一个java文件叫TankTime.java


--------------------------------------------------------------------------
C.有了这个文件,我们现在调用动态编译器去编译他:
//compiler
         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         StandardJavaFileManager fileMgr =compiler.getStandardFileManager( nullnullnull);
         Iterable units fileMgr .getJavaFileObjects(path );
         CompilationTask t = compiler.getTask( nullfileMgrnullnull,nullunits);
         t.call();
         fileMgr.close();

这段代码就是用来编译的:ToolProvider提供系统的编译器。
StandardJavaFileManager 文件管理类,通过getStandardFileManager 获得,里面三个参数 第一个监听器,第二 第三个是国际化的处理。暂时不用管。下面就是获得java文件然后编译了。别忘了关闭最后编译器。
编译完成后会在 java文件底下生成class文件。


--------------------------------------------------------------------------
D.既然已经有类class文件,我们需要把他装载到内存中好生成对象。
 //load to memory and create an instance
         URL[] urls = new URL[]{ new URL("file:/" +"c:/harvey/" )};
         URLClassLoader url = new URLClassLoader( urls);
         Class c url .loadClass("TankTime" );

上面代码 URLClassLoader会根据URL所指定的路径寻找所有的class文件,最后一句:其中一个叫TankTime的将会被装载 load。


---------------------------------------------------------------------------------------------------------------
E.这个时候生成对象。
Constructor ctr c .getConstructor(InvocationHandler.class );
         Object m = ctr.newInstance( h);

上面代码 先在这个c中寻找是不是有InvocationHandler类的构造函数或者setter方法。
然后newInstance 生成对象的时候把h 就是我们传进来的h对象放进去,生成一个对象,最后返回

这就是整个newProxyInstance的原理。
主要难的地方就是对在string 构造这个调用方法的时候,一些动态的参数需要考虑。

好了,我讲的不清楚,看源代码就懂了。
源代码:

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;


public class Proxy {
     public static Object newProxyInstance( Class inter,InvocationHandler h) throws Exception{
     
     String rt = "\r\n";  
     String method = "";
     Method[] methods = inter.getMethods();
           
     for(Method m:methods){
            method +=
                      "@Override "+ rt +
                      "    public void "+ m.getName() +"(){" + rt +
                      "    try{"+ rt +
                      "          Method md ="+inter .getName()+".class.getMethod(\"" +m .getName()+"\");" + rt +
                      "          h.invoke(this, md);" + rt +
                      "    }catch(Exception e){" + rt +
                      "          e.printStackTrace();" + rt +
                      "    }" + rt +
                    "  }" + rt + "    ";
     }
     
     String src =
                 "import java.lang.reflect.Method;" + rt +
                 "public class TankTime implements "+inter .getName() +"{" + rt +
                 "    private Moveable t;" + rt +
                 "    private InvocationHandler h;" + rt 
                 "    public TankTime(InvocationHandler h){" + rt +
                 "          this.h = h;" + rt +
                 "    }"+ rt +
                 "    "+method + rt +
                 "}";
     
      //String path = System.getProperty("user.dir")+"/src/TankTime.java";
     String path = "c:/harvey/TankTime.java";
     //System.out.println(path);
     
         //write file
         File file = new File( path);
         FileWriter fw = new FileWriter( file);
         fw.write( src);
         fw.flush();
         fw.close();   
     
         //compiler
         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         StandardJavaFileManager fileMgr = compiler.getStandardFileManager( null, null, null);
         Iterable units = fileMgr .getJavaFileObjects(path );
         CompilationTask t = compiler.getTask( null, fileMgr, null, null, null, units);
         t.call();
         fileMgr.close();
        
         //load to memory and create an instance
         URL[] urls = new URL[]{ new URL("file:/"+"c:/harvey/" )};
         URLClassLoader url = new URLClassLoader( urls);
         Class c = url .loadClass("TankTime" );
         //System.out.println(c);
        
         Constructor ctr = c.getConstructor(InvocationHandler.class );
         Object m = ctr.newInstance( h);
         //Moveable m = ( Moveable)ctr.newInstance(new Tank());
         //m.move();
     return m;
     }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值