java动态代理(类加载、asm、cglib、javassist)

KinglyJn      2012-11-17

class文件简介及加载

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


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

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

a. 定义自己的类加载器:

package classloadertest;

public class MyClassLoader<T> extends ClassLoader {

	/**
	 * 自定义一个类加载器,用于将字节码转换为class对象 
	 * @param name   The expected binary name of the class, or null if not known
	 * @param bs
	 * @param off
	 * @param len
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Class<T> defineMyClass(String name, byte[] bs, int off, int len) {
		return (Class<T>) super.defineClass(name, bs, off, len);
	}
}


b. 测试:

package classloadertest;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import javax.persistence.UniqueConstraint;
import sun.reflect.CallerSensitive;

/**
 * 程序猿类
 * @author zhangqingli
 *
 */
public class Programmer {
	
	public void code() {
		System.out.println("I am a Programmer, just coding ...");
	}
	
	public static void main(String[] args) throws Exception {
		// 获取字节码数组
		String classRoot = Programmer.class.getResource("/").getPath();
		InputStream is = new FileInputStream(classRoot+"/classloadertest/Programmer.class");
		byte[] bs = new byte[is.available()];
		int len = is.read(bs);
		
		// 将字节码数组转化为内存中的类对象
		Class<Programmer> clazz = new MyClassLoader<Programmer>().defineMyClass(null, bs, 0, len);
		System.out.println("生成的class对象为:" + clazz.getCanonicalName()); 
		
		// 利用反射生成对象并调用对象的方法
		Object o = clazz.newInstance();
		clazz.getMethod("code", null).invoke(o, null);
	}
}



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

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


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

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

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

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

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

package asmtest;

/**
 * 程序猿类
 * @author zhangqingli
 *
 */
public class Programmer {
	
	public void code() {
		System.out.println("I am a Programmer, just coding ...");
	}
}


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

package asmtest;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import net.sf.cglib.asm.ClassWriter;
import net.sf.cglib.asm.MethodVisitor;
import net.sf.cglib.asm.Opcodes;

/**
 * 使用asm动态创建class字节码
 * @author zhangqingli
 *
 */
public class MyGenerator {

	public static void main(String[] args) throws IOException {
		ClassWriter classWriter = new ClassWriter(0);  
        // 通过visit方法确定类的头部信息  
        classWriter.visit(Opcodes.V1_7,	// java版本  
                Opcodes.ACC_PUBLIC,		// 类修饰符  
                "Programmer", 			// 类的全限定名  
                null, "java/lang/Object", null);  
          
        //创建构造函数  
        MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);  
        mv.visitCode();  
        mv.visitVarInsn(Opcodes.ALOAD, 0);  
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");  
        mv.visitInsn(Opcodes.RETURN);  
        mv.visitMaxs(1, 1);  
        mv.visitEnd();  
          
        // 定义code方法  
        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",  
                null, null);  
        methodVisitor.visitCode();  
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",  
                "Ljava/io/PrintStream;");  
        methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",  
                "(Ljava/lang/String;)V");  
        methodVisitor.visitInsn(Opcodes.RETURN);  
        methodVisitor.visitMaxs(2, 2);  
        methodVisitor.visitEnd();  
        classWriter.visitEnd();   
        
        // 使classWriter类已经完成  
        // 将classWriter转换成字节数组写到文件里面去  
        byte[] data = classWriter.toByteArray();  
        //
        File dir = new File(System.getProperty("user.dir")+"/tmp"); 
        if (!dir.exists()) {
        	dir.mkdirs();
        }
        File file = new File(dir, "Programmer.class");
        //
        FileOutputStream fout = new FileOutputStream(file);  
        fout.write(data);  
        fout.close();  
	}
}


上述的代码执行过后,用Java反编译工具(如JD_GUI)可以看到生成的Programmer.class字节码文件。

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

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

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

package javassisttest;

import java.io.File;
import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 * 通过Javassist创建Programmer.class字节码
 * @author zhangqingli
 *
 */
public class MyGenerater {
	
	public static void main(String[] args) throws CannotCompileException, IOException {
		ClassPool pool = ClassPool.getDefault();
		
		// 创建Programmer类
		CtClass cc = pool.makeClass("javassisttest.Programmer");
		// 定义code方法
		CtMethod codeMethod = CtNewMethod.make("public void code(){}", cc);
		// 插入方法代码
		codeMethod.insertBefore("System.out.print(\"哈哈哈before\");");
		cc.addMethod(codeMethod);
		
		//保存生成的字节码
		File dir = new File(System.getProperty("user.dir")+"/tmp"); 
        if (!dir.exists()) {
        	dir.mkdirs();
        }
        cc.writeFile(dir.getCanonicalPath());
	}
}


java代理(需要实现特定接口)

package test;
public interface IService {
	void add();
	void update();
}


public static void main(String[] args) {
    /*
     * 被代理类
     */
    final IService targetService = new IService() {
        @Override
        public void add() {
          	System.out.println("IService add >>>>>>>>>>>>>>");
          	Integer.parseInt("aaa");
        }

        @Override
        public void update() {
          	System.out.println("IService update >>>>>>>>>>>>>>");
        }
    };

    /*
     * 代理类
     */
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
          //Around 环绕通知表示整个过程
          Object result = null;
          try {
              //Before 前置通知
               System.out.println("before------------------");
               result = method.invoke(targetService, args);
              //AfterReturning 返回通知
              System.out.println("afterReturning------------------");
          } catch (Exception e) {
              //AfterThrowing 异常通知
              System.out.println("afterThrowing------------------");
          } finally {
              //After 后置通知
              System.out.println("after------------------");
          }
          return result;
        }
    };
    IService iServiceProxy = (IService) Proxy.newProxyInstance(
      		targetService.getClass().getClassLoader(),
      		targetService.getClass().getInterfaces(), 
      		invocationHandler);

    /*
     * 使用代理类
     */
    iServiceProxy.add();
    System.out.println();
    iServiceProxy.update();
}


//执行结果
before------------------
IService add >>>>>>>>>>>>>>
afterThrowing------------------
after------------------

before------------------
IService update >>>>>>>>>>>>>>
afterReturning------------------
after------------------



cglib代理(不需要实现特定的接口,它是通过生成目标子类实现代理的)

class MyService {
	public Integer add(Integer id) {
		System.out.println("MyService add >>>>>>>>>>>>>>>>>");
		Integer.parseInt("aaa");
		return id;
	}

	public void update() {
		System.out.println("MyService update >>>>>>>>>>>>>>>");
	}
}


public static void main(String[] args) {
  	/*
	 * 创建代理子类
	 */
	Enhancer enhancer = new Enhancer();
  	enhancer.setSuperclass(MyService.class);
  	enhancer.setCallback(new MethodInterceptor() {
	@Override
	public Object intercept(Object obj, Method method, 
           Object[] args, MethodProxy methodProxy) throws Throwable {
			//Around 环绕通知表示整个过程
            Object result = null;
            try {
              	//Before 前置通知
             	 System.out.println("before------------------");

              	//使用method和methodProxy都能调用invoke方法,并且执行效果一样
             	 result = methodProxy.invokeSuper(obj, args);

             	 //AfterReturning 返回通知
              	System.out.println("afterReturning------------------");
            } catch (Exception e) {
              	//AfterThrowing 异常通知
             	 System.out.println("afterThrowing------------------");
            } finally {
              	//After 后置通知
             	 System.out.println("after------------------");
            }
            return result;
          }
	});
	MyService myServiceProxy = (MyService) enhancer.create();
		
    /*
     * 使用代理子类
     */
    Integer i = myServiceProxy.add(111);
    System.out.println(i);
    myServiceProxy.update();
}


//执行结果
before------------------
MyService add >>>>>>>>>>>>>>>>>
afterThrowing------------------
after------------------
null
  
before------------------
MyService update >>>>>>>>>>>>>>>
afterReturning------------------
after------------------



Tags:


Share: