JVM字节码(七)
动态代理
日常开发中,我们经常会遇到一些与我们业务无关却又无法避免的需求,比如:统计每个接口的访问量、给每个接口打印日志……等等,这些都是很常见的需求。如果在每个接口里编写增加访问量或者打印日志的代码,势必会引入一些冗余且无关业务的代码。
因此,Java提出动态代理的概念,将我们的主业务放在被代理类中执行,而与业务关系并非不大但的代码则放在调用句柄InvocationHandler中执行,调用句柄会通过反射的方式,调用被代理类的方法。通过调用句柄创建代理类,来实现动态代理。为了实现动态代理,我们需要了解:java.lang.reflect.InvocationHandler调用句柄接口和java.lang.reflect.Proxy类。
接口:
package com.leolin.jvm.bytecode;
public interface Subject {
void request();
}
被代理类,即执行主业务的类:
package com.leolin.jvm.bytecode;
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("From RealSubject");//主业务
}
}
调用句柄:
package com.leolin.jvm.bytecode;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SubjectHandler implements InvocationHandler {
private Object object;
public SubjectHandler(Object object) {
this.object = object; //被代理对象,这里传入RealSubject实例
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象在执行方法时,会执行调用句柄的invoke方法,我们在被代理对象执行方法的前后,添加打印操作
System.out.println("Before Call method:" + method);
//通过反射调用被代理对象的方法
Object result = method.invoke(this.object, args);
System.out.println("After Call method:" + method);
return result;
}
}
调用句柄一般执行诸如:打印日志、统计接口访问量、打开数据库连接和释放数据库连接等等。句柄中我们声明了一个成员变量object,在创建句柄时,我们会把之前的RealSubject实例传进去,当执行代理对象的调用方法时,会转而执行句柄的invoke方法,而method.invoke(Object obj, Object... args)就是对被代理对象进行方法调用,我们可以在这一句的前后添加我们要执行的业务逻辑。
客户端:
package com.leolin.jvm.bytecode;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//主业务对象(被代理对象)
RealSubject rs = new RealSubject();
//通过被代理对象构造出调用句柄
InvocationHandler ds = new SubjectHandler(rs);
Class<?> cls = rs.getClass();
/*
* 通过Proxy类生成被代理对象,这里需要传入三个参数:
* 第一个参数:被代理对象的类加载器
* 第二个参数:被代理对象所实现的接口
* 第三个参数:封装被代理对象的句柄
* */
Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), ds);
subject.request();
}
}
运行代码,得到如下结果:
Before Call method:public abstract void com.leolin.jvm.bytecode.Subject.request() From RealSubject After Call method:public abstract void com.leolin.jvm.bytecode.Subject.request()
可以看到,我们通过Proxy类所生成的代理对象,在执行request()的时候,会在调用被代理对象RealSubject.request()的前后,执行我们所编写的打印代码。
这里,我们看看在Proxy.newProxyInstance()方法中,都发生了什么:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//com.sun.proxy.$Proxy0程序运行期动态创建出来
System.out.println(subject.getClass());
System.out.println(subject.getClass().getSuperclass());
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我们重点关注第22、32、42行上。22行,我们通过getProxyClass0获取到代理类的class对象,32行我们通过class对象获取到一个构造方法,这个构造方法要求传入一个调用句柄参数,而在42行,我们将之前传入的调用句柄作为参数,传给构造方法,生成代理对象。32行和42行都好理解,于是我们又将重点转移到22行,getProxyClass0方法是如何生成代理对象的class对象呢?我们来看下getProxyClass0的方法:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
getProxyClass0所生成的代理类,要求实现的接口数不超过65535,一般业务开发不会达到这个量级,接下来就是从proxyClassCache中获取一个class对象,我们重点看上面代码的注释:如果代理类的class对象已经存在于类加载器的实现,则返回一份缓存中的拷贝,否则代理类的class对象将由ProxyClassFactory创建。
最开始,代理类的class对象一定不存在于proxyClassCache中,所以我们来看看ProxyClassFactory是如何生成代理类的class对象:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
如果从proxyClassCache中获取class对象,却发现class对象不存在,最终程序会执行到ProxyClassFactory.apply(ClassLoader loader, Class<?>[] interfaces)方法来创建一个class对象。ProxyClassFactory.apply方法中,会校验所传入的类加载器是否有权限加载类,构造代理对象的类名和访问标志,最终通过ProxyGenerator.generateProxyClass(final String var0, Class<?>[] var1, int var2)这个方法,生成一个字节数组,这个字节数组就是我们的class对象。于是,我们转而看一下ProxyGenerator.generateProxyClass这个方法:
public class ProxyGenerator {
……
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
……
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
……
}
这段代码中会生成一个类型为ProxyGenerator的变量var3,这个变量在调用generateClassFile()方法时,会根据我们传入的类名、实现接口和访问标志生成一个字节数组,要知道,一个class文件本身就是一个字节数组,在这个方法中,还会为我们重写代理类的的hashCode()、equals(Object obj)和toString()方法,因为篇幅的原因,这里就不再多介绍generateClassFile()方法。这里我们注意到上面有个环境变量:sun.misc.ProxyGenerator.saveGeneratedFiles,一般在运行期生成的代理类的class对象,在我们工程下面是不会有class文件的,但如果我们修改这个变量为true,会将运行期生成代理类的class文件。这里我们在Client.main(String[] args)函数的开头添加如下代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
重新执行Client的代码,可以看到在我们工程下面多出一个目录:com.sun.proxy,这个目录下面有个$Proxy0.class文件,就是我们的代理类的class文件,我们用idea反编译的结果看看对应的Java代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.leolin.jvm.bytecode.Subject;
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 Subject {
private static Method m1;
private static Method m2;
private static Method m3;
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 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 request() throws {
try {
super.h.invoke(this, m3, (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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.leolin.jvm.bytecode.Subject").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,代理类$Proxy0的构造参数要求传入一个InvocationHandler调用句柄的实例,然后再将InvocationHandler实例传给父类Proxy的构造方法。代理类中还有4个Method类型的静态变量,Method类型可以帮助我们在程序运行期间,执行某个对象的目标方法。在静态代码块中分别给这4个Method变量赋值,m1、m2、m3、m4分别用于执行equals、toString、request、hashCode方法,而代理类重写之前的四个方法,并将对应的Method变量传入到调用句柄的invoke方法,调用句柄最终会执行被代理对应对应的方法,如果被代理对象本身没有重写该方法,如:equals、toString和hashCode,则调用父类的方法。
浙公网安备 33010602011771号