Java动态代理机制详解

class文件简介及加载

     Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象:

      class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的、具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列。或者是Java虚拟机规范

     下面通过一段代码演示手动加载 class文件字节码到系统内,转换成class对象,然后再实例化的过程:

     a. 定义一个 Programmer类:

[java]  view plain  copy
  1. package samples;  
  2. /** 
  3.  * 程序猿类 
  4.  * @author louluan 
  5.  */  
  6. public class Programmer {  
  7.   
  8.     public void code()  
  9.     {  
  10.         System.out.println("I'm a Programmer,Just Coding.....");  
  11.     }  
  12. }  
[java]  view plain  copy
  1. package samples;  
  2. /** 
  3.  * 程序猿类 
  4.  * @author louluan 
  5.  */  
  6. public class Programmer {  
  7.   
  8.     public void code()  
  9.     {  
  10.         System.out.println("I'm a Programmer,Just Coding.....");  
  11.     }  
  12. }  
      b. 自定义一个类加载器:

[java]  view plain  copy
  1. package samples;  
  2. /** 
  3.  * 自定义一个类加载器,用于将字节码转换为class对象 
  4.  * @author louluan 
  5.  */  
  6. public class MyClassLoader extends ClassLoader {  
  7.   
  8.     public Class<?> defineMyClass( byte[] b, int off, int len)   
  9.     {  
  10.         return super.defineClass(b, off, len);  
  11.     }  
  12.       
  13. }  
[java]  view plain  copy
  1. package samples;  
  2. /** 
  3.  * 自定义一个类加载器,用于将字节码转换为class对象 
  4.  * @author louluan 
  5.  */  
  6. public class MyClassLoader extends ClassLoader {  
  7.   
  8.     public Class<?> defineMyClass( byte[] b, int off, int len)   
  9.     {  
  10.         return super.defineClass(b, off, len);  
  11.     }  
  12.       
  13. }  
      c. 然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化:

[java]  view plain  copy
  1. package samples;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.net.URL;  
  9.   
  10. public class MyTest {  
  11.   
  12.     public static void main(String[] args) throws IOException {  
  13.         //读取本地的class文件内的字节码,转换成字节码数组  
  14.         File file = new File(".");  
  15.         InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
  16.         byte[] result = new byte[1024];  
  17.           
  18.         int count = input.read(result);  
  19.         // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
  20.         MyClassLoader loader = new MyClassLoader();  
  21.         Class clazz = loader.defineMyClass( result, 0, count);  
  22.         //测试加载是否成功,打印class 对象的名称  
  23.         System.out.println(clazz.getCanonicalName());  
  24.                   
  25.                //实例化一个Programmer对象  
  26.                Object o= clazz.newInstance();  
  27.                try {  
  28.                    //调用Programmer的code方法  
  29.                     clazz.getMethod("code").invoke(o, );  
  30.                    } catch (IllegalArgumentException | InvocationTargetException  
  31.                         | NoSuchMethodException | SecurityException e) {  
  32.                      e.printStackTrace();  
  33.                   }  
  34.  }  
  35. }  
[java]  view plain  copy
  1. package samples;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.net.URL;  
  9.   
  10. public class MyTest {  
  11.   
  12.     public static void main(String[] args) throws IOException {  
  13.         //读取本地的class文件内的字节码,转换成字节码数组  
  14.         File file = new File(".");  
  15.         InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
  16.         byte[] result = new byte[1024];  
  17.           
  18.         int count = input.read(result);  
  19.         // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
  20.         MyClassLoader loader = new MyClassLoader();  
  21.         Class clazz = loader.defineMyClass( result, 0, count);  
  22.         //测试加载是否成功,打印class 对象的名称  
  23.         System.out.println(clazz.getCanonicalName());  
  24.                   
  25.                //实例化一个Programmer对象  
  26.                Object o= clazz.newInstance();  
  27.                try {  
  28.                    //调用Programmer的code方法  
  29.                     clazz.getMethod("code").invoke(o, );  
  30.                    } catch (IllegalArgumentException | InvocationTargetException  
  31.                         | NoSuchMethodException | SecurityException e) {  
  32.                      e.printStackTrace();  
  33.                   }  
  34.  }  
  35. }  
     以上代码演示了,通过字节码加载成class 对象的能力,下面看一下在代码中如何生成class文件的字节码。

在运行期的代码中生成二进制字节码

   由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。


      

在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。

Java字节码生成开源框架介绍--ASM:

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。

下面通过ASM 生成下面类Programmer的class字节码:

[java]  view plain  copy
  1. package com.samples;  
  2. import java.io.PrintStream;  
  3.   
  4. public class Programmer {  
  5.   
  6.     public void code()  
  7.     {  
  8.         System.out.println("I'm a Programmer,Just Coding.....");  
  9.     }  
  10. }  
[java]  view plain  copy
  1. package com.samples;  
  2. import java.io.PrintStream;  
  3.   
  4. public class Programmer {  
  5.   
  6.     public void code()  
  7.     {  
  8.         System.out.println("I'm a Programmer,Just Coding.....");  
  9.     }  
  10. }  

     使用ASM框架提供了ClassWriter 接口,通过访问者模式进行动态创建class字节码,看下面的例子:

[java]  view plain  copy
  1. package samples;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6.   
  7. import org.objectweb.asm.ClassWriter;  
  8. import org.objectweb.asm.MethodVisitor;  
  9. import org.objectweb.asm.Opcodes;  
  10. public class MyGenerator {  
  11.   
  12.     public static void main(String[] args) throws IOException {  
  13.   
  14.         System.out.println();  
  15.         ClassWriter classWriter = new ClassWriter(0);  
  16.         // 通过visit方法确定类的头部信息  
  17.         classWriter.visit(Opcodes.V1_7,// java版本  
  18.                 Opcodes.ACC_PUBLIC,// 类修饰符  
  19.                 "Programmer"// 类的全限定名  
  20.                 "java/lang/Object");  
  21.           
  22.         //创建构造函数  
  23.         MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>""()V");  
  24.         mv.visitCode();  
  25.         mv.visitVarInsn(Opcodes.ALOAD, 0);  
  26.         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object""<init>","()V");  
  27.         mv.visitInsn(Opcodes.RETURN);  
  28.         mv.visitMaxs(11);  
  29.         mv.visitEnd();  
  30.           
  31.         // 定义code方法  
  32.         MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code""()V",  
  33.                 );  
  34.         methodVisitor.visitCode();  
  35.         methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System""out",  
  36.                 "Ljava/io/PrintStream;");  
  37.         methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
  38.         methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream""println",  
  39.                 "(Ljava/lang/String;)V");  
  40.         methodVisitor.visitInsn(Opcodes.RETURN);  
  41.         methodVisitor.visitMaxs(22);  
  42.         methodVisitor.visitEnd();  
  43.         classWriter.visitEnd();   
  44.         // 使classWriter类已经完成  
  45.         // 将classWriter转换成字节数组写到文件里面去  
  46.         byte[] data = classWriter.toByteArray();  
  47.         File file = new File("D://Programmer.class");  
  48.         FileOutputStream fout = new FileOutputStream(file);  
  49.         fout.write(data);  
  50.         fout.close();  
  51.     }  
  52. }  
[java]  view plain  copy
  1. package samples;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6.   
  7. import org.objectweb.asm.ClassWriter;  
  8. import org.objectweb.asm.MethodVisitor;  
  9. import org.objectweb.asm.Opcodes;  
  10. public class MyGenerator {  
  11.   
  12.     public static void main(String[] args) throws IOException {  
  13.   
  14.         System.out.println();  
  15.         ClassWriter classWriter = new ClassWriter(0);  
  16.         // 通过visit方法确定类的头部信息  
  17.         classWriter.visit(Opcodes.V1_7,// java版本  
  18.                 Opcodes.ACC_PUBLIC,// 类修饰符  
  19.                 "Programmer"// 类的全限定名  
  20.                 "java/lang/Object");  
  21.           
  22.         //创建构造函数  
  23.         MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>""()V");  
  24.         mv.visitCode();  
  25.         mv.visitVarInsn(Opcodes.ALOAD, 0);  
  26.         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object""<init>","()V");  
  27.         mv.visitInsn(Opcodes.RETURN);  
  28.         mv.visitMaxs(11);  
  29.         mv.visitEnd();  
  30.           
  31.         // 定义code方法  
  32.         MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code""()V",  
  33.                 );  
  34.         methodVisitor.visitCode();  
  35.         methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System""out",  
  36.                 "Ljava/io/PrintStream;");  
  37.         methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
  38.         methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream""println",  
  39.                 "(Ljava/lang/String;)V");  
  40.         methodVisitor.visitInsn(Opcodes.RETURN);  
  41.         methodVisitor.visitMaxs(22);  
  42.         methodVisitor.visitEnd();  
  43.         classWriter.visitEnd();   
  44.         // 使classWriter类已经完成  
  45.         // 将classWriter转换成字节数组写到文件里面去  
  46.         byte[] data = classWriter.toByteArray();  
  47.         File file = new File("D://Programmer.class");  
  48.         FileOutputStream fout = new FileOutputStream(file);  
  49.         fout.write(data);  
  50.         fout.close();  
  51.     }  
  52. }  
      上述的代码执行过后,用Java反编译工具(如JD_GUI)打开D盘下生成的Programmer.class,可以看到以下信息:

        再用上面我们定义的类加载器将这个class文件加载到内存中,然后 创建class对象,并且实例化一个对象,调用code方法,会看到下面的结果:

    以上表明:在代码里生成字节码,并动态地加载成class对象、创建实例是完全可以实现的。

Java字节码生成开源框架介绍--Javassist:

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

下面通过Javassist创建上述的Programmer类:

[java]  view plain  copy
  1. import javassist.ClassPool;  
  2. import javassist.CtClass;  
  3. import javassist.CtMethod;  
  4. import javassist.CtNewMethod;  
  5.   
  6. public class MyGenerator {  
  7.   
  8.     public static void main(String[] args) throws Exception {  
  9.         ClassPool pool = ClassPool.getDefault();  
  10.         //创建Programmer类       
  11.         CtClass cc= pool.makeClass("com.samples.Programmer");  
  12.         //定义code方法  
  13.         CtMethod method = CtNewMethod.make("public void code(){}", cc);  
  14.         //插入方法代码  
  15.         method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
  16.         cc.addMethod(method);  
  17.         //保存生成的字节码  
  18.         cc.writeFile("d://temp");  
  19.     }  
  20. }  
[java]  view plain  copy
  1. import javassist.ClassPool;  
  2. import javassist.CtClass;  
  3. import javassist.CtMethod;  
  4. import javassist.CtNewMethod;  
  5.   
  6. public class MyGenerator {  
  7.   
  8.     public static void main(String[] args) throws Exception {  
  9.         ClassPool pool = ClassPool.getDefault();  
  10.         //创建Programmer类       
  11.         CtClass cc= pool.makeClass("com.samples.Programmer");  
  12.         //定义code方法  
  13.         CtMethod method = CtNewMethod.make("public void code(){}", cc);  
  14.         //插入方法代码  
  15.         method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
  16.         cc.addMethod(method);  
  17.         //保存生成的字节码  
  18.         cc.writeFile("d://temp");  
  19.     }  
  20. }  
通过JD-gui反编译工具打开Programmer.class 可以看到以下代码:



代理的基本构成:

        代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。

      

      

      上面的这幅代理结构图是典型的静态的代理模式:

       当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

       为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。

下面以一个代理模式实例阐述这一问题:

   将车站的售票服务抽象出一个接口TicketService,包含问询,卖票,退票功能,车站类Station实现了TicketService接口,车票代售点StationProxy则实现了代理角色的功能,类图如下所示。


对应的静态的代理模式代码如下所示:

[java]  view plain  copy
  1. package com.foo.proxy;  
  2.   
  3. /** 
  4.  * 售票服务接口实现类,车站 
  5.  * @author louluan 
  6.  */  
  7. public class Station implements TicketService {  
  8.   
  9.     @Override  
  10.     public void sellTicket() {  
  11.         System.out.println("\n\t售票.....\n");  
  12.     }  
  13.   
  14.     @Override  
  15.     public void inquire() {  
  16.         System.out.println("\n\t问询。。。。\n");  
  17.     }  
  18.   
  19.     @Override  
  20.     public void withdraw() {  
  21.         System.out.println("\n\t退票......\n");  
  22.     }  
  23.   
  24. }  
[java]  view plain  copy
  1. package com.foo.proxy;  
  2.   
  3. /** 
  4.  * 售票服务接口实现类,车站 
  5.  * @author louluan 
  6.  */  
  7. public class Station implements TicketService {  
  8.   
  9.     @Override  
  10.     public void sellTicket() {  
  11.         System.out.println("\n\t售票.....\n");  
  12.     }  
  13.   
  14.     @Override  
  15.     public void inquire() {  
  16.         System.out.println("\n\t问询。。。。\n");  
  17.     }  
  18.   
  19.     @Override  
  20.     public void withdraw() {  
  21.         System.out.println("\n\t退票......\n");  
  22.     }  
  23.   
  24. }  

[java]  view plain  copy
  1. package com.foo.proxy;  
  2. /** 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值