java都有哪些动态代理机制?

在这里插入图片描述

1:java都有哪些动态代理机制

java中的动态代理目前有JDK动态代理,javassist,cglib,asm,实现方式不尽相同,但是原理相同,都是在运行期动态生成字节码然后进行加载。

2:动态代理实现原理分析

正常的开发流程是,开发人员编写程序,编译器编译java源代码生成.class的字节码文件,但是需要注意,.class其实也是一种符合某种规范的文件,只不过并不是人类可读的文件,而是一种二进制文件,那么这种文件谁能够解析呢,JVM,JVM能够加载这些文件,经过各种解析之后,生成Class对象,其中执行加载工作的是类加载器抽象类java.lang.ClassLoader,详细的过程如下图:
在这里插入图片描述
那么动态代理在哪里发挥作用呢?答案是在运行期动态的生成符合JVM规范的class文件,执行重新加载的工作,比如动态创建某个已加载的类的子类的class字节码文件,然后再执行加载的工作,此时这个过程如下图:

在这里插入图片描述
这就是动态代理的原理,当拥有了这样动态编写代码的能力之后,任何代码我们都能够动态生成,功能就会变得相当强大。
下面我们来看一个使用类加载器加载的例子。

2.1:定义待加载的类

  • 源代码
package yudaosourcecode.huohuo;
/**
 * 程序猿类
 * @author louluan
 */
public class Programmer {
 
	public void code() {
		System.out.println("I'm a Programmer,Just Coding.....");
	}
}

2.2:定义类加载器

  • 源码
package yudaosourcecode.huohuo;
/**
 * 自定义一个类加载器,用于将字节码转换为class对象
 * @author louluan
 */
public class MyClassLoader extends ClassLoader {
 
	public Class<?> defineMyClass( byte[] b, int off, int len) {
		return super.defineClass(b, off, len);
	}
	
}

2.3:定义测试类

该类使用2.2:定义类加载器中定义的类加载器加载2.1:定义待加载的类.class文件到JVM中生成Class对象,然后使用Class对象创建实例对象。

package yudaosourcecode.huohuo;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;

public class MyTest {

    public static void main(String[] args) throws Exception {
        //读取本地的class文件内的字节码,转换成字节码数组
        File file = new File(".");
        InputStream input =
                new FileInputStream("/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/target/classes/yudaosourcecode/huohuo/Programmer.class");
        byte[] result = new byte[1024];

        int count = input.read(result);
        // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
        MyClassLoader loader = new MyClassLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        //测试加载是否成功,打印class 对象的名称
        System.out.println(clazz.getCanonicalName());
        //实例化一个Programmer对象
        Object o = clazz.newInstance();
        try {
            //调用Programmer的code方法
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}

运行:

yudaosourcecode.huohuo.Programmer
I'm a Programmer,Just Coding.....

可以看到执行成功了,动态代理的执行过程也大概如此,只不过,加载的并不是磁盘中现成的class文件,而是在代码中动态生成字节码文件,然后使用类加载器执行动态加载。而当前Java中就有框架提供了这样的能力,如asm,javasist等。

3:asm

asm对于使用者的要求较高,需要熟悉字节码文件的格式,了解字节码相关的指令,如如何设置版本号,描述方法签名,类签名等,都必须和字节码文件一一对应。如下是用asm生成动态字节码的例子,为了方便查看生成结果,将字节码写到.class文件中:

package yudaosourcecode.huohuo;
 
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyGenerator {
 
	public static void main(String[] args) throws IOException {
 
		System.out.println();
		ClassWriter classWriter = new ClassWriter(0);
		// 通过visit方法确定类的头部信息
		classWriter.visit(Opcodes.V1_8,// 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.....by asm!!!");
		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 file = new File("/Users/xb/Desktop/temp/mycls/Programmer.class");
		FileOutputStream fout = new FileOutputStream(file);
		fout.write(data);
		fout.close();
	}
}

运行生成文件如下:

在这里插入图片描述

使用jd-gui查看生成的字节码内容如下:
在这里插入图片描述
然后使用2.3:定义测试类中的类进行测试(注意修改字节码文件路径),输出结果如下:

Programmer
I'm a Programmer,Just Coding.....by asm!!!

4:javassist

如果说asm是面向字节码来动态生成字节码文件的话,那么javassit就是面向java源代码来生成字节码文件的,因此javassit的难度相对于和asm要小的多,复杂度低得多,编程效率高得多,下面使用javassit来动态生成一个字节码文件:

package yudaosourcecode.huohuo;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
 
public class MyGeneratorByJavassit {
 
	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();
        //创建Programmer类		
		CtClass cc= pool.makeClass("com.samples.ProgrammerJavassit");
		//定义code方法
		CtMethod method = CtNewMethod.make("public void code(){}", cc);
		//插入方法代码
		method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....by javassit!!!\");");
		cc.addMethod(method);
		//保存生成的字节码
		cc.writeFile("/Users/xb/Desktop/temp/mycls");
	}
}

生成字节码如下:

在这里插入图片描述
在这里插入图片描述

然后使用2.3:定义测试类中的类进行测试(注意修改字节码文件路径),输出结果如下:

com.samples.ProgrammerJavassit
I'm a Programmer,Just Coding.....by javassit!!!

5:jdk动态代理

jdk动态代理实现是通过实现和被代理类相同的接口来提供相同的功能,我们来看一个电动汽车的例子,对于电动汽车拥有2个最基础的功能,首先是可以充电,我们来定义一个接口Rechargable来表述这种能力,如下:

package yudaosourcecode.huohuo;
/**
 * 可充电设备接口
 * @author louluan
 */
public interface Rechargable {
 
	void recharge();
}

其实是可以驾驶,我们来定义一个接口Vehicle来表述这种能力,如下:

package yudaosourcecode.huohuo;
/**
 * 交通工具接口
 * @author louluan
 */
public interface Vehicle {
	void drive();
}

然后定义一个电动汽车类,如下:

package com.foo.proxy;
/**
 * 电能车类,实现Rechargable,Vehicle接口
 * @author louluan
 */
public class ElectricCar implements Rechargable, Vehicle {
 
	@Override
	public void drive() {
		System.out.println("Electric Car is Moving silently...");
	}
 
	@Override
	public void recharge() {
		System.out.println("Electric Car is Recharging...");
	}
 
}

接下来提供java.lang.reflect.InvocationHandler的实现类提供代理实现:

package com.foo.proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
public class InvocationHandlerImpl implements InvocationHandler {
 
	private ElectricCar car;
	
	public InvocationHandlerImpl(ElectricCar car)
	{
		this.car=car;
	}
	
	@Override
	public Object invoke(Object paramObject, Method paramMethod,
			Object[] paramArrayOfObject) throws Throwable {
		System.out.println("You are going to invoke "+paramMethod.getName()+" ...");
		paramMethod.invoke(car, null);
		System.out.println(paramMethod.getName()+" invocation Has Been finished...");
		return null;
	}
 
}

测试代码:

package yudaosourcecode.huohuo;

import java.lang.reflect.Proxy;

public class InvocationHandlerImplTest {

    public static void main(String[] args) {
        Object o =
                Proxy.newProxyInstance(InvocationHandlerImplTest.class.getClassLoader(),
                new Class[] { Rechargable.class, Vehicle.class },
                new InvocationHandlerImpl(new ElectricCar()));
        // 充电
        ((Rechargable)o).recharge();
        // 行驶
        ((Vehicle)o).drive();
    }
}

运行:

You are going to invoke recharge ...
Electric Car is Recharging...
recharge invocation Has Been finished...
You are going to invoke drive ...
Electric Car is Moving silently...
drive invocation Has Been finished..

接下来我们看下Proxy.newProxyInstance生成的代理类的字节码到底是什么样子的,通过如下工具类sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[]),查看字节码工具类和生成代码如下:

package yudaosourcecode.huohuo;
 
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;
 
public class ProxyUtils {
 
	/*
	 * 将根据类信息 动态生成的二进制字节码保存到硬盘中,
	 * 默认的是clazz目录下
         * params :clazz 需要生成动态代理类的类
         * proxyName : 为动态生成的代理类的名称
         */
	public static void generateClassFile(Class clazz,String proxyName)
	{
		//根据类信息和提供的代理类名称,生成字节码
                byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); 
		/*String paths = clazz.getResource(".").getPath();
		System.out.println(paths);
		FileOutputStream out = null;  */
        FileOutputStream out = null;
        try {
            //保留到硬盘中
            out = new FileOutputStream("/Users/xb/Desktop/temp/mycls/"+proxyName+".class");
            out.write(classFile);  
            out.flush();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                out.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
	}
	
}
package yudaosourcecode.huohuo;

import java.lang.reflect.Proxy;

public class MyTest {

    public static void main(String[] args) throws Exception {
        Object o =
                Proxy.newProxyInstance(InvocationHandlerImplTest.class.getClassLoader(),
                        new Class[] { Rechargable.class, Vehicle.class },
                        new InvocationHandlerImpl(new ElectricCar()));
        ProxyUtils.generateClassFile(o.getClass(), "ElectricCarProxy");
    }
}

生成后的字节码使用jd-gui打开查看,我本地如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import yudaosourcecode.huohuo.Rechargable;
import yudaosourcecode.huohuo.Vehicle;

// 实现了java.lang.reflect.proxy类
// 实现了Recharge接口,Vehicle接口,并实现了对应的方法
public final class ElectricCarProxy extends Proxy implements Rechargable, Vehicle {
  // 被代理对象的equals方法
  private static Method m1;
  // 被代理对象的Rechage接口定义的recharge方法
  private static Method m3;
  // 被代理对象的Object中定义的toString方法
  private static Method m2;
  // 被代理对象Vehicle接口中定义的drive方法
  private static Method m4;
  // 被代理对象Object中的hashCode方法
  private static Method m0;
  // 使用InvocationHandler作为参数
  public ElectricCarProxy(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  // recharge方法的实现
  // 内部使用InvocationHandler方法的invoke方法执行具体调用
  public final void recharge() {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  // drive方法的实现
  // 内部使用InvocationHandler方法的invoke方法执行具体调用
  public final void drive() {
    try {
      this.h.invoke(this, m4, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  // 静态代码块中通过反射获取被代理对象的相关方法,用于调用InvocationHandler
  // 的invoke方法时作为参数传递,以便调用真实的方法
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("yudaosourcecode.huohuo.Rechargable").getMethod("recharge", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m4 = Class.forName("yudaosourcecode.huohuo.Vehicle").getMethod("drive", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

再通过下图看下实际过程中的调用关系:

在这里插入图片描述
jdk的动态代理有个缺点就是,必须是父接口中的方法才能够被代理,甚至如果是没有实现接口的类就无法被代理,这个问题可以通过cglib(code generation library),接下来我们看下如何使用CGLIB。

6:cglib

类似于jdk动态代理中的java.lang.reflect.InvocationHandler,cglib也有类似的角色net.sf.cglib.proxy.MethodInterceptor,源码如下:

package net.sf.cglib.proxy;

public interface MethodInterceptor
extends Callback
{
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;

}

下面看个例子,定义一个Programmer类,一个Hacker类:

package yudaosourcecode.huohuo.cglib;
/**
 * 程序猿类
 * @author louluan
 */
public class Programmer {
 
	public void code()
	{
		System.out.println("I'm a Programmer,Just Coding.....by cglib!!!");
	}
}
package yudaosourcecode.huohuo.cglib;
 
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
/*
 * 实现了方法拦截器接口
 */
public class Hacker implements MethodInterceptor {
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
		proxy.invokeSuper(obj, args);
		System.out.println("****  Oh,what a poor programmer.....");
		return null;
	}
 
}

测试类:

package yudaosourcecode.huohuo.cglib;
 
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
/*
 * 实现了方法拦截器接口
 */
public class Hacker implements MethodInterceptor {
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
		proxy.invokeSuper(obj, args);
		System.out.println("****  Oh,what a poor programmer.....");
		return null;
	}
 
}

运行:

**** I am a hacker,Let's see what the poor programmer is doing Now...
I'm a Programmer,Just Coding.....by cglib!!!
****  Oh,what a poor programmer.....

参考文章列表

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值