目录结构
- JDK代理的用法
- 实现原理
- 总结
JDK代理用法
用法可以按照如下步骤来完成:
1、给定一个接口(Target接口)
2、以及一个接口的实现类(TargetImpl类)
3、通过Proxy相关的API,可以获取一个实现(TargetImpl类)的代理对象。
4、并且通过Proxy相关的API,可以在代理对象中增加一些TargetImp所没有的功能。
接着我们把上面的步骤一步步完成:
首先Target接口:
public interface Target {
void sayHello();
}
然后有TargetImpl类:
public class TargetImp implements Target {
@Override
public void sayHello(){
System.out.println("hello");
}
}
那么被代理的类都有了,那我们就可以生成代理对象来将TargetImpl类进行增强,主要是通过过Proxy.newProxyInstance静态方法来生成代理对象,并实现InvocationHandler类来添加增强的操作。
public static void main(String[] args) {
// 被代理的对象
Target TargetImpl = new TargetImp();
// Proxy.newProxyInstance静态方法返回一个实现了Target接口的对象,该对象就是代理对象(proxyOfTarget)
// InvocationHandler是代理对象调用转发类,要求实现其invoke方法。
// 当proxyOfTarget对象调用方法时,实际上会将调用转到invoke方法。
// 那么invoke方法可以先做一些增强/代理的操作,然后再调用实际的对象。
Target proxyOfTarget = (Target) Proxy.newProxyInstance(Target.class.getClassLoader(), new Class[]{Target.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在被代理对象之前做一些增强操作
System.out.println("this is enhancing code!");
// 调用被代理对象,实现功能,并返回结果。
return method.invoke(TargetImpl, args);
}
});
proxyOfTarget.sayHello();
}
运行main方法得到:
至此我们就通过Proxy的newProxyInstance获得了TargetImpl对象的代理对象。 并且代理的操作都封装在InvocationHandler的invoke方法内。Proxy的基本使用就是如此简单,可以看出一个明显的特点:就是被代理类得有一个接口,因为newProxyInstance返回的是Object对象,需要通过接口来将其转化。究其根因还是和Proxy的内部实现机制有关。
实现原理
代理对象的类
其实代理对象也有个对应的类(代理类),只是这个代理类是Proxy自动生成的,我们在代码中看不到。Proxy生成代理类之后,根据这个类创建了代理对象并返回。接下来我们来看看这个代理类,进一步看看JDK代理的原理。
编译程序之前加上虚拟机参数:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
即可将自动生成的代理类保存下来:
代理类名称为$Proxy0,内容如下(为了方便展示,我将部分代码省略):
public final class $Proxy0 extends Proxy implements Target {
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) {
....
}
}
// 重点
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
...
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.company.Target").getMethod("sayHello");
} catch (NoSuchMethodException var2) {
....
}
}
}
可以看出$Proxy0类继承了Proxy类并实现了Target接口,并实现了sayHello方法,除此之外还有实现了toString等Object的方法。因此我们代理对象调用的sayHello就是这个类的方法。里面的实现逻辑很简单:
super.h.invoke(this, m3, (Object[])null);
其中父类的h成员是:
正是调用newProxyInstance方法传进来的:
也就是我们生成代理对象时实现的InvocationHandler,而调用sayHello方法就是调用invoke方法。
至此我们明白了:Proxy.newProxyInstance方法为我们生成了一个代理类,这个代理类实现了被代理类的接口,并且所有实现就是简单地把调用转发给invoke方法,而invoke方法正是由我们所实现的。接下来我们看看JDK如何生成这个代理类。
生成代理类
以Proxy.newProxyInstance方法为入口,我将一些关键的代码截取出来,形成整个动作的逻辑。(因为大部分操作只涉及reflect类,没有native方法,可以只看java代码就可以梳理整个逻辑)在次此前需要理清两个概念:
1、生成代理类($Proxy0类)其实就是生成代理类的class字节码,并且该字节是不变的,不用每次都生成。因为被代理类接口(Target接口)是固定,因此自动生成的代理类($Proxy0类)里面的方法就是固定不变的,也就是一些object方法加上Target接口的方法(变化的部分都保留在super.h成员变量上,这个变量在父类Proxy上,可以通过$Proxy0的构造方法传入)。那么对于一个Target接口,我们可以只生成一次class字节码,并且转为Class对象将其缓存起来,以后下次调用生成Target接口的代理对象时,直接根据这个Class对象创建对象即可。
2、如何自动生成class字节码:JDK里面的内部实现是根据Target接口的方法直接拼接$Proxy0类的class字节码,并输出到一个byte数组中,之后创建对象的时候提供类加载器实例化即可。在生成的过程中,如果虚拟机参数saveGeneratedFiles为True,就落盘保存一下。生成字节码的具体实现在sun.misc.ProxyGenerator.generateClassFile方法上,其核心就是输出一个符合class规范的字节数组,并且包含了所有Target接口的方法,其每个方法的实现都是一样的:直接转给InvocationHandler对象处理。
创建代理对象实例的方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
{
Objects.requireNonNull(h);
...
// 获取代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
...
// 拿到构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
// 根据传入的invocationHandler对象创建代理对象,并放回
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
...
}
Proxy中getProxyClass0方法
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
可以看到实际是从proxyClassCache对象中拿到的Class对象。proxyClassCache对象是 WeakCache类的实例,其中的get方法就是检查缓存(ConrruentHashMap对象)中是否有代理类的Class对象,如果有则立即返回,如果没有则新建。我们直接看新建部分的代码: 新建代理类Class对象的是ProxyClassFactory类:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 代理类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 代理类名称的计数器
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) {
// 检查该接口是否可以通过给定的ClassLoader来加载
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");
}
// 是否为接口类型
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 检查传入的接口不重复
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;
/*
* 非public接口,代理类的包名与接口的包名相同
*/
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) {
// public接口,使用com.sun.proxy package包名
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 拼接代理类名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 调用方法生成代理类的class字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 让加载器loader加载,生成Class对象返回。
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
ProxyGenerator.generateProxyClass方法如下:
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) {
// 异步保留class文件
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
...
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
generateClassFile方法的部分代码如下,可以看出字节码拼凑的过程:
private byte[] generateClassFile() {
...
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
var14.writeInt(-889275714);
var14.writeShort(0);
var14.writeShort(49);
this.cp.write(var14);
var14.writeShort(this.accessFlags);
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
var14.writeShort(this.interfaces.length);
Class[] var17 = this.interfaces;
int var18 = var17.length;
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
...
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
var14.writeShort(0);
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
总结
1、实际上JDK动态代理就是帮我们自动生成了一个实现了被代理类(或被代理类接口)的所有方法的代理类。该代理类实现方法的方式很简单,直接将方法转给InvocationHandler对象的invoke方法。而InvocationHandler对象由用户创建代理对象的时候传入,由用户掌控。用户可以将其转发给被代理类的实际对象,也可以在转发前做一些增强操作。
2、生成代理类的过程是运行时发生的,并且自动生成的Class字节码不会保存,对比静态代理方法多花费写时间。但对比静态代理需要手动编写代理类,JDK动态代理可方便太多了。
3、可以看出JDK代理的方式是依赖被代理对象是有接口的,从而根据其接口的方法来生成代理类的字节码,并且只需要生成一次即可。如果被代理对象没有接口,也就是没有通以的方法集合,那么每个代理类字节码都不一样,那么需用CGLIB库来实现,该库对字节码的操作更加灵活。