1 动态代理原理
Java程序在运行时通过ClassLoader将字节码信息加载进内存,那么在运行期系统中,遵循Java编译系统组织class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样就完成了在代码中,动态创建一个新类的能力了。
在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
目前通过Java实现动态代理的方式有如下几种:
JDK Proxy,Java原生动态代理实现方案,通过ProxyGenerator更改字节码生成代理。
ASM,一个开源的字节码生成框架,直接操作字节码(需要了解JVM字节码规范)。
Javasist,直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
CgLib,一个强大的,高性能,高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口(是对ASM的一层封装)。
2 JDK Proxy
JDK通过接口实现动态代理,基本步骤如下:
定义被代理接口。
创建接口的实现类。
创建
InvocationHandler
实例handler,用来处理代理的所有方法调用。调用
Proxy
的newProxyInstance
实例化一个proxy对象。
2.1 定义接口
public interface IPay {
void pay(int money);
}
2.2 创建接口的实现类
public class WeChatPay implements IPay{
public void pay(int money) {
System.out.println("wechat pay:"+money);
}
}
2.3 创建 InvocationHandler 的实现类
public class PayInvocationHandler implements InvocationHandler {
IPay pay;
public PayInvocationHandler(IPay pay){
this.pay = pay;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before proxy");
Object res = method.invoke(pay,args);
System.out.println("after prxoy");
return res;
}
}
说明:
InvocationHandler
只有一个invoke
方法,它会回调接口中的所有方法。
2.4 创建代理类
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IPay proxyPay = (IPay) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IPay.class},new PayInvocationHandler(new WeChatPay()));
proxyPay.pay(10);
}
注意:
"sun.misc.ProxyGenerator.saveGeneratedFile
属性设置为true
可以使代理对象保存到当前项目下。生成的代理类的类名默认为:com.sun.proxy.$ProxyN ;
2.5 代理类源码
下面为IPay
接口的代理类源码:
package com.sun.proxy;
import com.mchen.dynamic.IPay;
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 IPay {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
//传入InvocationHandler对象
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void pay(int var1) throws {
try {
//回调InvocationHandler的方法
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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("com.mchen.dynamic.IPay").getMethod("pay", Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
2.6 Proxy的优缺点
优点:
- 原生JDK实现,不需要任何依赖。
缺点:
- 只能基于接口实现。
3 CGLIB代理
CGLIB(Code Generation Library),是一个强大的,高性能,高质量的基于ASM的字节码生成库,允许我们在运行时对字节码文件进行修改和动态生成。
CGLIB的创建步骤如下:
创建需要代理的实现类
实现
MethodInterceptor
接口,用来处理 对代理类上所有方法的请求。通过
Enhancer
的create
方法生成代理对象。
3.1 创建被代理对象
public class WeChatPay implements IPay{
public void pay(int money) {
System.out.println("wechat pay:"+money);
}
}
3.2 实现MethodInterceptor接口
public class PayMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before cglib");
Object res = proxy.invokeSuper(obj,args);
System.out.println("after cglib");
return res;
}
说明:
这个接口和JDK动态代理InvocationHandler的功能和角色是一样的。
3.3 创建代理对象
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));
WeChatPay pay = new WeChatPay();
//cglib 中加强器,用来创建动态代理
Enhancer enhancer = new Enhancer();
//设置要创建动态代理的类
enhancer.setSuperclass(pay.getClass());
// 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
enhancer.setCallback(new PayMethodInterceptor());
WeChatPay proxy =(WeChatPay)enhancer.create();
proxy.pay(15);
}
注意:
CGLIB只能代理对象的非
final
和非static
方法。添加系统属性
cglib.debugLocation
,可指定CGLIB代理类生成的路径。
3.4 代理类源码
public class WeChatPay$$EnhancerByCGLIB$$39ebddcc extends WeChatPay implements Factory {
//......省略
private MethodInterceptor CGLIB$CALLBACK_0; // Enchaner传入的methodInterceptor
// ....省略
public final void pay(int var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
//如果callback为null,先绑定回调函数。
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
//若callback 不为空,则调用methodInterceptor 的intercept()方法
var10000.intercept(this, CGLIB$pay$0$Method, new Object[]{new Integer(var1)}, CGLIB$pay$0$Proxy);
} else {
//如果没有设置callback回调函数,则默认执行父类的方法
super.pay(var1);
}
}
}
注意:
CGLIB代理类的名称为:父类
父类$$EnhancerByCGLIB$$XXXXX
。CGLIB代理类会继承被代理的类,实现
Factory
接口。
3.5 CGLIB优缺点
优点:
- 能实现类代理,功能更强大。
缺点:
- 需要引入第三方依赖。
4 Javasist代理
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是JBoss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
通过Javasist生成类:
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("com.samples.Programmer");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile(System.getProperty("user.dir"));
}
4.1 Javasist生成代理
通过Javasist进行源码级别的操作创建WeChatPay
的代理类:
public static void mkproxy()throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.mchen.dynamic.WeChatPayProxy");
//设置接口
CtClass interface1 = pool.get("com.mchen.dynamic.IPay");
cc.setInterfaces(new CtClass[]{interface1});
//设置Field
CtField field = CtField.make("private com.mchen.dynamic.IPay pay;", cc);
cc.addField(field);
CtClass payClass = pool.get("com.mchen.dynamic.IPay");
CtClass[] arrays = new CtClass[]{payClass};
CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);
//设置构造函数内部信息
ctc.setBody("{this.pay=$1;}");
cc.addConstructor(ctc);
//创建前置方法
CtMethod beforePay = CtMethod.make("private void beforePay(){}",cc);
beforePay.setBody("System.out.println(\"javasist before pay\");");
cc.addMethod(beforePay);
//创建后置方法
CtMethod afterPay = CtMethod.make("private void afterPay(){}",cc);
afterPay.setBody("System.out.println(\"javasist after pay\");");
cc.addMethod(afterPay);
//创建 方法
CtMethod showInfo = CtMethod.make("public void pay(int money) {}", cc);
showInfo.setBody("{this.beforePay();" +
"this.pay.pay($1);" +
"this.afterPay();}");
cc.addMethod(showInfo);
//获取动态生成的class
Class c = cc.toClass();
//获取构造器
Constructor constructor= c.getConstructor(IPay.class);
//通过构造器实例化
IPay o = (IPay)constructor.newInstance(new WeChatPay());
o.pay(20);
cc.writeFile(System.getProperty("user.dir"));
}
步骤说明:
构造代理类对象。
添加代理类实现接口。
创建代理类构造方法,传入被代理对象。
创建前置和后置方法。
重写接口方法。
通过构造函数创建代理对象。
输出代理类的源码。
javasist提供了创建代理类更简便的工具类ProxyFactory
,使用方法和 JDK Proxy 类似。
4.2 javasist代理的源代码
public class WeChatPayProxy implements IPay {
private IPay pay;
public WeChatPayProxy(IPay var1) {
this.pay = var1;
}
private void beforePay() {
System.out.println("javasist before pay");
}
private void afterPay() {
System.out.println("javasist after pay");
}
public void pay(int var1) {
this.beforePay();
this.pay.pay(var1);
this.afterPay();
}
}
5 ASM代理
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
public static void generate()throws Exception{
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"com/mchen/asm/Comparable", null, "java/lang/Object",
new String[] { "java/lang/Cloneable" });
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
null, new Integer(-1)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
null, new Integer(0)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
null, new Integer(1)).visitEnd();
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
"(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
byte[] b = cw.toByteArray();
Class clazz = new MyClassLoader().defineClass("com.mchen.asm.Comparable",b);
System.out.println(clazz);
}
通过ASM生成如下类
public interface Comparable extends Cloneable{
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
5.1 定义接口
package com.mchen.asm;
public interface Singable {
void sing();
}
5.2 实现接口
package com.mchen.asm;
public class Singer implements Singable {
public void sing() {
System.out.println("I am singing...");
}
}
5.3 创建代理
// 生成一个class的字节数组
public static byte[] generateProxy() throws Exception {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
// 定义class版本1.8,访问权限,类名,继承类,实现接口等信息
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/mchen/asm/SingerProxy", null, "java/lang/Object", new String[]{"com/mchen/asm/Singable"});
classWriter.visitSource("SingerAgent.java", null);
{
// 定义私有属性
fieldVisitor = classWriter.visitField(ACC_PRIVATE, "delegate", "Lcom/mchen/asm/Singable;", null, null);
fieldVisitor.visitEnd();
}
{
// 定义构造器
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Lcom/mchen/asm/Singable;)V", null, null);
methodVisitor.visitParameter("delegate", 0);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(10, label0);
//调用Object的构造方法
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(11, label1);
//设置被代理对象
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitFieldInsn(PUTFIELD, "com/mchen/asm/SingerProxy", "delegate", "Lcom/mchen/asm/Singable;");
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(12, label2);
methodVisitor.visitInsn(RETURN);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
//设置本地变量
methodVisitor.visitLocalVariable("this", "Lcom/mchen/asm/SingerProxy;", null, label0, label3, 0);
methodVisitor.visitLocalVariable("delegate", "Lcom/mchen/asm/Singable;", null, label0, label3, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
// 定义方法sing
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sing", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(16, label0);
//设置前置方法
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("ASM before sing");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(17, label1);
//调用被代理对象方法
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/mchen/asm/SingerProxy", "delegate", "Lcom/mchen/asm/Singable;");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "com/mchen/asm/Singable", "sing", "()V", true);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(18, label2);
//设置后置方法
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("ASM after sing");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(19, label3);
methodVisitor.visitInsn(RETURN);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLocalVariable("this", "Lcom/mchen/asm/SingerProxy;", null, label0, label4, 0);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
注意:
ASM创建代理对象需要了解JVM字节码,并且编码工作量较大。
5.4 ASM代理源码
package com.mchen.asm;
public class SingerProxy implements Singable {
private Singable delegate;
public SingerProxy(Singable delegate) {
this.delegate = delegate;
}
public void sing() {
System.out.println("ASM before sing");
this.delegate.sing();
System.out.println("ASM after sing");
}
}
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)_我的程序人生(亦山札记)-CSDN博客_javassist