代理就是对某一个类进行非入侵式的增强,即在其执行前后执行某些逻辑。
静态代理:在Java程序执行前,手动实现一个代理类,去实现被代理者中需要被代理的方法并完成逻辑增强。一旦被代理者的方法有增减,则需要修改代理类对应的位置,从而违背了【开闭原则】–对修改关闭,推拓展开放。
动态代理:在Java程序执行过程中,由Java代码通过反射或其他方式动态的生成一个代理类。这个代理类包含被代理者的所有方法,和所需要的方法的增强。如果被代理者的方法出现增减,代理者不需要程序员手动修改,而是会自动生成与被代理者对应的新的代理者。即为动态代理
静态代理此处跳过,本文主要讨论JDK动态代理。
第一步:先不管具体理解,按照JDK代理的代码流程实现一个案例;
第二部:根据这个案例去理解JDK动态代理
目录
代码实现
1.准备一个规范的接口
public interface IGamePlayer {
/**
* 登录
* @param name
* @param pwd
*/
public void login(String name,String pwd);
/**
* 打怪
*/
public void killBoss();
/**
* 升级
*/
public void upGread();
}
2.准备一个需要被代理的接口实现类
/**
* 玩家的实现类
*/
public class GamePlayer implements IGamePlayer {
/**
* 玩家的名字
*/
private String name = "";
/**
* 有参构造传入玩家名字
* @param name
*/
public GamePlayer(String name){
this.name = name;
}
@Override
public void login(String name, String pwd) {
System.out.println(this.name+"登录上线");
}
@Override
public void killBoss() {
System.out.println(this.name+"打怪成功");
}
@Override
public void upGread() {
System.out.println(this.name+"升级了!");
}
}
3.准备一个用来完成JDK动态代理的代理功能的类
/**
* 利用JDK proxy实现动态代理
* InvocationHandler:通过反射来实现代理一个对象
*/
public class DynamicGamePlayerProxy implements InvocationHandler {
/**
* 用于存储要被代理的对象
*/
private IGamePlayer gamePlayer;
/**
* 通过有参构造传入要被代理的对象
* @param player
*/
public DynamicGamePlayerProxy(IGamePlayer player){
this.gamePlayer = player;
}
/**
* 调用方法,并对指定方法进行增强
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("killBoss")) {
System.out.println("开启无敌模式");
}
Object obj = method.invoke(this.gamePlayer,args);
if (method.getName().equals("killBoss")) System.out.println("关闭无敌模式");
return obj;
}
}
4.测试
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
GamePlayer player = new GamePlayer("扫地僧");
DynamicGamePlayerProxy playerProxyHandler = new DynamicGamePlayerProxy(player);
//使用JDK动态代理的代理对象
IGamePlayer proxy =
(IGamePlayer) Proxy.newProxyInstance(player.getClass().getClassLoader(),//被代理者
new Class[]{IGamePlayer.class},//规范化的接口
playerProxyHandler);//代理类
proxy.login("saodis","123245432");
proxy.killBoss();
proxy.upGread();
//查看proxy到底是什么
System.out.println(proxy.getClass());
}
运行结果
分析其实现过程
1.反编译
在main方法的最后,我们查看了proxy到底是什么,显示的结果是
class com.sun.proxy.$Proxy0
为了搞清楚$Proxy0是什么,我们对其进行了反编译,在测试类的main方法中加入了一句
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
已经通过这一句完成了反编译。反编译后的文件存储在
2.初步查看反编译代码
进入$Proxy0类,首先看到的是:
public final class $Proxy0 extends Proxy implements IGamePlayer {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m5;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
...
可以看出,$Proxy0是Proxy的一个子类,并且实现了我们一开始指定的规范接口,那么这个它必定会实现我们在接口中定义的那三个方法。同时,这一对m0~m5是什么?
翻到末尾,看到了它的一段静态代码块:
...
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.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("upGread");
m4 = Class.forName("com.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("killBoss");
m5 = Class.forName("com.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
所以可以看出,m0-m5就是接口中的所有方法,包括Object自带的方法和用户创建的方法。而我们所关心的,自己创建的
upGread方法对应的是m3;
killBoss方法对应的是m4;
login方法对应的是m5;
由于我们在动态代理中是对killBoss做出了增强
所以我们先去找一下m4和killBoss在哪里出现:往上走,看到了killBoss、upGread、login三个方法的实现过程,发现几乎都是一样的,都是调用了super.h.invoke()方法。
...
public final void killBoss() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
这里的super不难理解,就是$Proxy0的父类Proxy,点击h,进入代码查看:发现h是一个成员变量,类型是InvocationHandler 。
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
这个InvocationHandler 就很熟悉了,因为在代理类DynamicGamePlayerProxy中,我们就实现了InvocationHandler 这个接口,并且实现了其内部唯一的方法:invoke,这里回顾一下:
public class DynamicGamePlayerProxy implements InvocationHandler {
/**
* 用于存储要被代理的对象
*/
private IGamePlayer gamePlayer;
...
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("killBoss")) {
System.out.println("开启无敌模式");
}
Object obj = method.invoke(this.gamePlayer,args);
if (method.getName().equals("killBoss")) System.out.println("关闭无敌模式");
return obj;
}
...
再回到$Proxy0的killBoss方法代码里,就不难理解了,这里执行
super.h.invoke(this, m4, (Object[])null);
其实就是回到了代理类DynamicGamePlayerProxy里面,来执行invoke方法了。而具体是执行哪一个方法,则已经在调用的时候通过参数输入了(即这里是执行m4方法–>m4方法对应的是killBoss方法,所以这里传入了IGamePlayer的killBoss方法,并将会执行这个方法。)
整个执行流程大致如下
通过这样连串的调用,完成了动态的代理执行
细看JDK动态代理的执行过程
从测试类中的关键一句:
//使用JDK动态代理的代理对象
IGamePlayer proxy =
(IGamePlayer) Proxy.newProxyInstance(player.getClass().getClassLoader(),
new Class[]{IGamePlayer.class},
playerProxyHandler);
先分析参数:
参数 | 是什么 |
---|---|
player.getClass().getClassLoader() | 是需要被代理的对象的类加载器 |
new Class[]{IGamePlayer.class} | 一个Class数组,里面只放了一个规范化接口 |
playerProxyHandler | 代理类 |
点击进入newProxyInstance的源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);//要求代理类不能为空
final Class<?>[] intfs = interfaces.clone();//1.将传进来的规范化接口数组备份
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//2.根据传入的ClassLoader和规范接口生成代理类的class对象【注意】这里只是一个代理类对象,不是实例
Class<?> cl = getProxyClass0(loader, intfs);
...
这里经过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);
}
这里是先判断是否接口数量小于65535,然后调用了proxyClassCache.get方法,注意上面说的注释:
如果由指定加载器和指定接口锁确定的这个代理类已经存在,则直接返回这个缓存起来的副本,否则才会通过ProxyClassFactory的工厂模式创建代理类。
至此,proxyClassCache.get的基本功能清楚了,既然说是通过ProxyClassFactory来创建的代理类,那就找到ProxyClassFactory并进入这个类:发现这是Proxy里面的一个静态内部类:
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
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();
...
注释说清楚了它的功能:
一种工厂函数,它根据传入的ClassLoader 和接口数组生成,定义并返回对应的代理类
两个成员变量
proxyClassNamePrefix;生成的所有代理类的前缀名都是$Proxy
AtomicLong nextUniqueNumber = new AtomicLong();下一个用于生成唯一代理类名称的数字
在这个静态内部类里面发现只有一个apply方法,下面展示其一部分代码:
...
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");
}
}
}
...
这里的for是判断数组中是否非public的接口都在同一个包里面,对非public的代理类,生成之后放置于原来的包下面,对public的包,统一放置于指定的包下面(com.sun.proxy这个包下)。下面一段展示了如何加载这个代理类:
/*
* Generate the specified proxy class.
*/
//加载这个代理类的字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//将字节码文件加载到 JVM内存中,返回class对象
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());
}
这里是先生成代理类的字节码文件,而后调用一个native方法defineClass0,这个方法会返回一个class,这个class对象即为代理类对象。由于defineClass0不是Java语言写的,所以到此无法深入了。所以这里看字节码的生成,即ProxyGenerator.generateProxyClass方法:
...
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) {
...
}
return var4;
可以看出if中的内容其实就是在吧这个代理类的字节码写出来(在main中设置的)。而具体的生成字节码,是在这一句
final byte[] var4 = var3.generateClassFile();
再进入generateClassFile方法,看到频繁出现的一个方法:addProxyMethod
看一下ProxyMethod是什么,发现是ProxyGenerator下面的一个内部类:
在generateClassFile中看到,完成所有方法的搜集后,会将类的字节码结构中的所有部件(包括属性、方法等,这里涉及到编译和字节码,不再展开细看),按照JVM文件规范,一步一步全部写入var13这个ByteArrayOutputStream类型的变量中
并在最后将这个字节码数组返回给Proxy中的byte[] proxyClassFile,而后调用defineClass0转为class对象返回给上一级
到此,ProxyClassFactory这个静态内部类中的apply方法就执行完成,并返回了一个Class对象给Proxy中的newProxyInstance方法的这一步
接下来,通过获取这个class对象的构造器来产生一个代理类的实例,并返回这个实例
由此,main方法中,获取代理对象这一个动态的(根据不同的对象获取对应的属性与方法)、关键的步骤完成了。而且返回的是一个命名为$Proxy开头的类
也就是说,此时返回的已经不再是GamePlayer的实例了,而是自动根据GamePlayer中所有接口、所有方法和增强逻辑合并后生成的一个新的代理类。
总结:整个代码流程大致如下
1.根据传进来的接口,获取接口里面所有的方法及其包含的异常信息
2.根据这些信息、以及增强的逻辑合并,一起生成一个字节码文件;
3.将字节码文件加载的JVM,创建一个Class对象;
4.调用Class对象的构造器创建一个实例,返回给main。
与静态代理相比
静态代理是在编译的时候就已经编译了代理类的字节码,而后将字节码加载到JVM
JDK动态代理是在Java程序运行时才根据被代理者的接口和方法生成字节码文件,而后才将这个字节码文件加载到JVM中来。JDK代理依赖于规范化的接口,被代理者必须实现接口,否则无法实现JDK动态代理。