上篇介绍过Java代理的定义和使用,下面我们分析下jdk1.8中的Proxy类的核心源码,想想就兴奋,坚持10分钟看完不睡着!或者睡着更好,能帮你入眠,我也很开心!甚至还发现了源码中有无用的代码或者作者忘记使用的代码,一起来看吧!
分析newProxyInstance逻辑
话不多说,直接看源码,最核心的newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
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);
...省略
}
- ClassLoader参数为类加载器
- interfaces参数为实现的接口,可以传数组
- InvocationHandler参数为InvocationHandler的实现类,这个实现类的构造函数入参需要包含目标实现的接口类
getProxyClass0 获取或生成代理类
方法返回了代理类,其内部如下:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
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);
}
根据源码名称和注释可知,如果代理类已经存在,那么直接返回缓存中的代理类;否则,通过ProxyClassFactory生成代理对类,进入WeakCache类大概看下get方法,内部使用了ConcurrentMap来实现缓存,如果不存在时将会使用Proxy中的静态内部类ProxyClassFactory来生成代理类字节码,内部会去调用ProxyGenerator.generateProxyClass去实现:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
...
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
...
}
cue一下WeakCache
还要注意下,WeakCache是一个弱引用的缓存!说到弱引用,学习过jvm相关知识的同学应该有些印象,没听过也没关系,我们只要知道这个概念就好,当缓存的对象不再被其他对象引用时,就会被垃圾回收掉。此处埋个坑,以后写篇弱引用及弱引用缓存的使用文章。
这么说还不形象是吧,想象一下你是一个舔狗,当女神需要时你有用,女神不需要你会被一个叫GC的爪机丢到垃圾桶里。可是,女神又有什么错呢?是你自己不够好罢了!
说回正题,这种先获取缓存,缓存没有再生成的编程思想在很多时候都有使用。
反射创建代理类对象
至此,我们已经获取到代理类,存在于内存中。那下面就得创建一个代理类对象并返回了,别忘了我们的初衷,newProxyInstance创建一个代理实例对象。那如何创建呢?没错,就是反射,直接看剩下的代码:
/*
* Invoke its constructor with the designated invocation handler.
* 使用特定的InvocationHandler调用动态代理类的构造函数
*/
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});
}
... 省略
获取到代理类的构造函数,并将入参的InvocationHandler h作为构造函数参数创建代理类对象,并返回。最终我们得到一个代理类对象实例。
总结newProxyInstance流程图
我将newProxyInstance关键步骤总结画图如下,用了三个软件画的图,难搞:
代理类执行方法的底层步骤
代理类字节码文件
你应该好奇,上面的代理类字节码是加载生成在内存中,我们也看不到具体什么样子,说着很玄乎,我们能否将动态生成的字节码文件以文件形式输出呢?当然可以!有两种方式:
- 设置系统参数(不同jdk版本可能不一致)
查看jdk中的ProxyGenerator类中的常量saveGeneratedFiles
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
在main方法内部,设置系统属性:
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Class[] interfaces = {UserDao.class};
UserDao dao = new UserDaoImpl();
UserDao userDao = (UserDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), interfaces, new MyInvocationHandler(dao));
userDao.add(1, 2);
}
在项目根路径会生成com.sun.proxy.$Proxy0.class文件
为什么设置这个参数就可以生成?因为在generateProxyClass方法内部会根据常量值是否write文件
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);
}
}
});
}
- 自己手动写文件,代码参考如下:
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
genClassByProxy(interfaces);
}
private static void genClassByProxy(Class<?>[] interfaces) {
byte[] proxyClassBytes = ProxyGenerator.generateProxyClass("$Proxy0", interfaces);
// 将字节码输出到文件 "ProxyClass.class" 中
FileOutputStream fos = null;
try {
fos = new FileOutputStream("E:\\diy\\java-study-master\\spring-study\\src\\main\\java\\com\\spring\\proxy\\" + "$Proxy0.class");
fos.write(proxyClassBytes);
fos.flush();
} catch (IOException e) {
if (fos!=null){
try {
fos.flush();
fos.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
e.printStackTrace();
}
}
执行main方法我们将会在指定目录下生成一个$Proxy0.class文件,打开该文件我们就揭开了动态代理的面纱。
哦~ 莎莉哇莎莉哇 ~ 是谁送你来到我身边~ 我身边~
import com.spring.dao.UserDao;
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 UserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
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 int add(int var1, int var2) throws {
try {
return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
...
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.spring.dao.UserDao").getMethod("add", Integer.TYPE, Integer.TYPE);
m4 = Class.forName("com.spring.dao.UserDao").getMethod("sub", Integer.TYPE, 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());
}
}
}
这个生成的代理类继承了Proxy类,实现目标类接口。我们还可以看到,代理类中:
- 有一个构造函数,参数为InvocationHandler
- static静态代码块内,通过反射的方式加载了目标类的方法
- 重写了目标类的方法和一些如haschCode,equals,toString等方法。重写的方法内部,都是执行InvocationHandler.invoke执行目标方法
理解JDK的Proxy动态代理
至此,你应该理解,为什么写Jdk动态代理需要先写一个InvocationHandler实现类,并传入目标类了吧
class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前执行" + method.getName() + Arrays.toString(args));
Object res = method.invoke(object, args);
System.out.println("方法执行后执行" + res);
return res;
}
}
总结使用JDK动态代理执行方法顺序如下:
- 创建代理类对象
- 通过代理类对象,调用对应方法
- 代理类方法,调用自定义的InvocationHandler 方法invoke(this, m3, new Object[]{var1, var2});传入三个参数:当前代理类,目标类方法和方法入参
- 自定义的InvocationHandler 方法可以加上方法执行前后的操作,实际通过反射调用method.invoke(object, args);加载目标类方法
发现proxy源码的优化点
在Proxy类中,创建代理类对象时,有将invacationHandler声明为final变量的操作,
final InvocationHandler ih = h;
但是后续没有使用,你可以自己打开1.8的源码查看,可能是作者后面忘记使用了吧,即return cons.newInstance(new Object[]{h});应该改成return cons.newInstance(new Object[]{ih});不知道高版本的java是否优化这个地方。
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});
至此,打完收工!码字不易,感谢你的坚持观看!
如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可