15

Bdq's blog

 4 years ago
source link: https://bdqfork.cn/articles/42?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用Javassist实现动态代理

发表于2019-11-16|更新于2020-07-06|分类于Java|评论数:0|阅读次数:238
本文字数:20855 字|阅读时长 ≈41 分钟

动态代理是Java开发中常用的一种设计模式,相比于静态代理方式,动态代理毫无疑问更加灵活,可以在运行时对目标实例进行代理增加,以此对实例方法进行各种增强。在本篇文章中,笔者将介绍如何使用Java字节码操作库Javassist实现动态代理机制。

Javassist实现动态代理机制的方式有两种,一种是使用Javassist提供的ProxyFactory实现,实现方式与JDK类似。另一种是使用Javassist操作字节码,自主实现动态代理机制。两种方式各有好处,前者比较简单,容易上手,后者比较复杂,但好在足够灵活,甚至可以复用JDK的InvocationHandler。下面笔者将介绍后者的实现思路!

JDK实现动态代理

在使用Javassist实现动态代理之前,笔者先介绍一下JDK的动态代理是怎么实现的。

首先,使用JDK内置的方法,实现一个动态代理机制,示例代码如下:



public interface Move { void move(); } public class Walk implements Move { @Override public void move() { System.out.println("I'm Walking"); } } public class DefaultInvocation implements InvocationHandler { private Object target; public DefaultInvocation(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before invoke "); Object obj = method.invoke(target,args); System.out.println("after invoke "); return obj; } } public class Test { public static void main(String[] args) throws Exception { //加入这个参数,可以将生成的代理类保存到硬盘 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Object proxy = Proxy.newProxyInstance(Walk.class.getClassLoader(), new Class[]{Move.class}, new DefaultInvocation(new Walk())); ((Move) proxy).move(); } }

笔者用的是Idea,代码目录如下图所示。

运行,查找到编译之后代理实例的class文件,就是上图所示的$Proxy0.class。反编译一下,得到下面的代码:



package com.sun.proxy; import cn.bdqfork.proxy.Move; 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 Move { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { 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 void move() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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 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")); m3 = Class.forName("cn.bdqfork.proxy.Move").getMethod("move"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }

从反编译的代理实例中可以看出,JDK在编译过程中,生成了一个目标接口的子类,并继承了Proxy类。该子类中将目标代理接口的方法作为属性,在调用相应接口方法时,并不会直接调用代理对象的方法,而是调用了InvocationHandler里面实现的invoke方法。InvocationHandler是交给了用户去实现,因此,在此基础上,用户可以实现各种方法增强。

Javassist实现动态代理

笔者在上文介绍了JDK的动态代理机制,本质上就是生成了一个实现目标接口的子类,并在相应的方法实现中调用InvocationHandler里面的invoke方法。

在Javassist中,提供了一系列的字节码操作API以在运行时灵活的生成需要的类。参考JDK的思路,只要用Javassist动态生成一个类似的代理子类,动态代理就实现了,实现思路如下。

定义ClassGenerator类

为了更好的对代理子类进行描述,定义一个ClassGenerator类,该类记录了一个代理子类的一些信息,包括类名、父类、需要实现的接口、构造函数等,并实现一些添加这些信息的方法。ClassGenerator的定义如下:



package cn.bdqfork.core.aop.proxy; import javassist.*; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author bdq * @since 2019/10/1 */ public class ClassGenerator { /** * ClassPool缓存 */ private static final Map<ClassLoader, ClassPool> POOL_CACHE = new ConcurrentHashMap<>(); /** * 构造方法名占位符 */ private static final String INIT_FLAG = "<init>"; /** * Javassist ClassPool */ private ClassPool classPool; /** * 全类名 */ private String className; /** * 简单类名 */ private String simpleName; /** * 父类 */ private String superClass; /** * 是否添加默认构造方法 */ private boolean addDefaultConstructor; /** * 需要实现的接口 */ private List<String> interfaces; /** * 构造方法 */ private List<String> constructors; /** * 属性 */ private List<String> fields; /** * 方法,包括实现接口中的所有方法 */ private List<String> methods; public ClassGenerator() { this(null); } public ClassGenerator(ClassLoader classLoader) { if (classLoader == null) { classPool = ClassPool.getDefault(); } else { classPool = POOL_CACHE.get(classLoader); if (classPool == null) { classPool = new ClassPool(true); classPool.appendClassPath(new LoaderClassPath(classLoader)); POOL_CACHE.putIfAbsent(classLoader, classPool); } } } private static String modifier(int modfier) { StringBuilder modifier = new StringBuilder(); if (Modifier.isPublic(modfier)) { modifier.append("public"); } if (Modifier.isProtected(modfier)) { modifier.append("protected"); } if (Modifier.isPrivate(modfier)) { modifier.append("private"); } if (Modifier.isStatic(modfier)) { modifier.append(" static"); } if (Modifier.isVolatile(modfier)) { modifier.append(" volatile"); } return modifier.toString(); } public ClassGenerator setClassName(String className) { this.className = className; this.simpleName = className.substring(className.lastIndexOf(".") + 1); return this; } public ClassGenerator setSuperClass(String superClass) { this.superClass = superClass; return this; } public ClassGenerator addInterface(String interfaceName) { if (interfaces == null) { interfaces = new LinkedList<>(); } interfaces.add(interfaceName); return this; } public ClassGenerator addConstructor(String constructor) { if (constructors == null) { constructors = new LinkedList<>(); } constructors.add(constructor); return this; } public ClassGenerator addConstructor(int modifier, Class<?>[] parameters, String body) { return addConstructor(modifier, parameters, null, body); } /** * 生成构造方法,最终生成的方法文本示例如下 * modifier <init> (parameters) throws exceptions{ * body * } * * @param modifier 修饰符 * @param parameterTypes 参数 * @param exceptionTypes 异常 * @param body 方法体 * @return ClassGenerator */ public ClassGenerator addConstructor(int modifier, Class<?>[] parameterTypes, Class<?>[] exceptionTypes, String body) { StringBuilder codeBuilder = new StringBuilder(); //添加方法修饰符 codeBuilder.append(modifier(modifier)) .append(" ") .append(INIT_FLAG) .append("("); //添加参数 for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { codeBuilder.append(","); } Class<?> parameter = parameterTypes[i]; codeBuilder.append(parameter.getCanonicalName()) .append(" ") .append("arg") .append(i); } codeBuilder.append(")"); //判断是否有异常,如果有,添加异常抛出 if (exceptionTypes != null && exceptionTypes.length > 0) { codeBuilder.append("throws "); for (int i = 0; i < exceptionTypes.length; i++) { if (i > 0) { codeBuilder.append(","); } Class<?> exceptionClass = exceptionTypes[i]; codeBuilder.append(exceptionClass.getCanonicalName()); } } //添加方法体 codeBuilder.append("{") .append(body) .append("}"); return addConstructor(codeBuilder.toString()); } public ClassGenerator addDefaultConstructor() { addDefaultConstructor = true; return this; } public ClassGenerator addField(String field) { if (fields == null) { fields = new LinkedList<>(); } fields.add(field); return this; } public ClassGenerator addMethod(String method) { if (methods == null) { methods = new LinkedList<>(); } methods.add(method); return this; } /** * 生成方法代码文本,生成的示例如下 * modifier returnType methodName(parameters) throws exceptions{ * body * } * * @param modifier 修饰符 * @param returnType 返回类型 * @param methodName 方法名 * @param parameterTypes 参数类型 * @param exceptionTypes 异常类型 * @param body 方法体 * @return ClassGenerator */ public ClassGenerator addMethod(int modifier, Class<?> returnType, String methodName, Class<?>[] parameterTypes, Class<?>[] exceptionTypes, String body) { StringBuilder methodBuilder = new StringBuilder(); methodBuilder.append(modifier(modifier)) .append(" ") .append(returnType.getName()); methodBuilder.append(" ").append(methodName).append("("); for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { methodBuilder.append(","); } methodBuilder.append(parameterTypes[i].getName()) .append(" arg") .append(i); } methodBuilder.append(")"); if (exceptionTypes != null && exceptionTypes.length > 0) { methodBuilder.append("throws "); for (int i = 0; i < exceptionTypes.length; i++) { if (i > 0) { methodBuilder.append(","); } methodBuilder.append(exceptionTypes[i].getName()); } } methodBuilder.append("{").append(body).append("}"); addMethod(methodBuilder.toString()); return this; } public Class<?> toClass() { return toClass(ClassGenerator.class.getClassLoader(), ClassGenerator.class.getProtectionDomain()); } /** * 构建CtClass并转换为Class返回给调用者 * * @param classLoader ClassLoader * @param protectionDomain ProtectionDomain * @return Class<?> */ public Class<?> toClass(ClassLoader classLoader, ProtectionDomain protectionDomain) { try { CtClass ctClass = classPool.makeClass(className); if (superClass != null) { ctClass.setSuperclass(classPool.get(superClass)); } if (interfaces != null) { for (String interfaceName : interfaces) { ctClass.addInterface(classPool.get(interfaceName)); } } if (addDefaultConstructor) { ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass)); } if (fields != null) { for (String field : fields) { ctClass.addField(CtField.make(field, ctClass)); } } if (constructors != null) { for (String constructor : constructors) { if (constructor.contains(INIT_FLAG)) { constructor = constructor.replace(INIT_FLAG, simpleName); } ctClass.addConstructor(CtNewConstructor.make(constructor, ctClass)); } } if (methods != null) { for (String method : methods) { ctClass.addMethod(CtNewMethod.make(method, ctClass)); } } return ctClass.toClass(classLoader, protectionDomain); } catch (Exception e) { throw new IllegalStateException(e); } } }

设置类名,添加接口名这些属性的方法比较简单,就不做介绍了,着重介绍一下方法的添加。需要注意的是,由于Javassist在生成方法的时候,需要传入方法文本,所以在ClassGenerator的addMethod中存在大量的文本拼接,以生成一个完整的方法。同时在拼接方法文本的时候,需要提取方法的描述符,检测是否有异常抛出等。在设置完代理子类的相关信息之后,在toClass方法中将代理子类编译并加载。

在toClass方法中,使用Javassist提供的字节码操作技术,访问ClassGenerator的属性,构造出一个CtClass,最后调用CtClass的toClass方法,生成一个了Class。如果对该知识不了解,可以查看本人的另一篇博客 Javassist之内省与定制(一)

定义Proxy

在定义了ClassGenerator之后,下面来定义Proxy类。ClassGenerator记录了代理子类的信息,并提供了编译代理子类并加载的方法。Proxy的作用就是根据不同的目标代理接口,构造代理子类的信息,将这些信息设置到ClassGenerator中,获取代理实例。参考JDK的实现,Proxy类也是一个抽象类,具体代码如下:



package cn.bdqfork.core.aop.proxy; import cn.bdqfork.core.utils.ReflectUtils; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; /** * @author bdq * @since 2019/9/30 */ public abstract class Proxy implements Serializable { private static final Map<String, Object> CACHE = Collections.synchronizedMap(new WeakHashMap<>()); private static final AtomicLong PROXY_COUNTER = new AtomicLong(0); public Proxy() { } /** * 抽象方法,由代理子类实现,调用代理子类的构造方法生成代理实例 * * @param handler InvocationHandler * @return 代理实例 */ public abstract Object newInstance(InvocationHandler handler); /** * 生成代理实例 * * @param classLoader ClassLoader * @param interfaces 代理接口 * @param handler InvocationHandler * @return Object * @throws IllegalArgumentException 生成失败时抛出 */ public static Object newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException { //通过接口名生成KEY来缓存实例 String key = getKey(interfaces); if (CACHE.containsKey(key)) { return CACHE.get(key); } ClassGenerator generator = new ClassGenerator(classLoader); for (Class<?> interfaceClass : interfaces) { generator.addInterface(interfaceClass.getName()); } //生成代理子类名称,例如Proxy0 String className = Proxy.class.getName() + PROXY_COUNTER.getAndIncrement(); generator.setClassName(className).setSuperClass(Proxy.class.getName()); //与JDK类似,将接口方法作为代理子类的属性 generator.addField("private java.lang.reflect.Method[] methods;"); //将InvocationHandler也作为代理子类的属性 generator.addField("private " + InvocationHandler.class.getName() + " handler;"); //添加构造方法,初始化InvocationHandler generator.addConstructor(Modifier.PUBLIC, new Class[]{InvocationHandler.class}, "$0.handler=$1;"); //添加默认构造方法 generator.addDefaultConstructor(); //扫描接口,获取所有的接口方法,并通过方法签名进行去重 Set<String> worked = new HashSet<>(); List<Method> methods = new ArrayList<>(); for (Class interfaceClass : interfaces) { for (Method method : interfaceClass.getMethods()) { if (worked.contains(ReflectUtils.getSignature(method))) { continue; } worked.add(ReflectUtils.getSignature(method)); methods.add(method); } } //根据接口方法信息,生成代理实例的方法实现 for (int i = 0; i < methods.size(); i++) { Method method = methods.get(i); StringBuilder codeBuilder = new StringBuilder(); codeBuilder.append("Object result = $0.handler.invoke($0,$0.methods[").append(i).append("],$args);"); Class<?> returnType = method.getReturnType(); if (!Void.TYPE.equals(returnType)) { codeBuilder.append("return ").append(castResult("result", returnType)); } generator.addMethod(Modifier.PUBLIC, returnType, method.getName(), method.getParameterTypes(), method.getExceptionTypes(), codeBuilder.toString()); } //添加Proxy抽象方法的实现 generator.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " handler){return new " + className + "($1);}"); try { Class<?> clazz = generator.toClass(); Proxy proxy = (Proxy) clazz.newInstance(); //生成代理实例 proxy = (Proxy) proxy.newInstance(handler); //为代理实例的methods属性赋值 Field handlerField = clazz.getDeclaredField("methods"); handlerField.setAccessible(true); handlerField.set(proxy, methods.toArray(new Method[0])); CACHE.putIfAbsent(key, proxy); return proxy; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e.getCause()); } } private static String getKey(Class<?>[] interfaces) { return Arrays.stream(interfaces) .map(Class::getName) .collect(Collectors.joining()); } /** * 对方法返回值进行转换 * * @param resultName 返回值名称 * @param returnType 返回类型 * @return */ private static String castResult(String resultName, Class<?> returnType) { if (returnType.isPrimitive()) { if (Byte.TYPE == returnType) { return resultName + "==null? (byte)0:((Byte)" + resultName + ").byteValue();"; } if (Short.TYPE == returnType) { return resultName + "==null? (short)0:((Short)" + resultName + ").shortValue();"; } if (Integer.TYPE == returnType) { return resultName + "==null? (int)0:((Integer)" + resultName + ").intValue();"; } if (Long.TYPE == returnType) { return resultName + "==null? (long)0:((Long)" + resultName + ").longValue();"; } if (Float.TYPE == returnType) { return resultName + "==null? (float)0:((Float)" + resultName + ").floatValue();"; } if (Double.TYPE == returnType) { return resultName + "==null? (double)0:((Double)" + resultName + ").doubleValue();"; } if (Character.TYPE == returnType) { return resultName + "==null? (char)0:((Character)" + resultName + ").charValue();"; } if (Boolean.TYPE == returnType) { return resultName + "==null? false:((Boolean)" + resultName + ").booleanValue();"; } throw new RuntimeException("Unknow primitive " + returnType.getCanonicalName() + " !"); } return "(" + returnType.getCanonicalName() + ")" + resultName + ";"; } }

注意到在扫描接口方法时,获取了方法签名进行去重。对于Java方法,方法的签名一般是由方法修饰符,方法名,方法参数作为签名。获取方法签名的代码如下:



public static String getSignature(Method method) { StringBuilder signBuilder = new StringBuilder(); signBuilder.append(method.getName()) .append("("); Class<?>[] parameters = method.getParameterTypes(); for (int i = 0; i < parameters.length; i++) { if (i > 0) { signBuilder.append(","); } signBuilder.append(parameters[i].getName()); } signBuilder.append(")"); return signBuilder.toString(); }

至此,使用Javassist实现动态代理机制的代码介绍完毕,使用方式和JDK一模一样,区别只是Proxy的不同。

笔者将上述的代码使用在了本人自己写的IOC容器中,使用过程中暂时没有发现什么问题,完整代码可以查看本人的IOC容器代码。点击访问 https://github.com/bdqfork/spring-toy ,Proxy代码在spring-toy-core/src/main/java/cn/bdqfork/core/aop/proxy目录下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK