JDK动态代理使用及原理解析

JDK动态代理使用及原理解析

  • 一、动态代理的使用

  •   1.1 动态代理简单示例

  •   1.2 创建代理实例在代理类中

  •   1.3 公用的代理类

  • 二、如何进入到代理类中的invoke的?

  •   2.1 拿到$Proxy0这个类

  •   2.2 $Proxy0的解析

  • 三、Proxy.newProxyInstance是如何生成一个代理对象然后返回的?

一、动态代理的使用

1.1 动态代理简单示例

     动态代理类图如下:

JDK动态代理使用及原理解析

     主体和实际主体代码如下所示:

/**
* 主体
*/

public interface Subject {
//主体方法
void subjectMethod();
}

/**
* 实际主体
*/

public class RealSubject implements Subject {
@Override
public void subjectMethod() {
System.out.println("执行方法subjectMethod...");
}
}

     代理类代码如下所示,可看到代理类中使用了反射调用:

public class ProxyHandler implements InvocationHandler {
private Subject subject;

public ProxyHandler(Subject subject) {
this.subject = subject;
}

/**
* @param proxy 调用方法的代理实例
* @param method 被代理对象中的方法
* @param args 调用方法时的参数
*/

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法subjectMethod前");
/**
* method.invoke(Object obj, Object... args)
* @param obj :方法被调用的对象
* @param args :方法被调用的参数
* @return Object: 方法调用返回的结果
*/

Object o = method.invoke(subject, args);
System.out.println("执行方法subjectMethod后");
return o;
}
}

     请求者中声明了实际主体和调动处理程序:

/**
* Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* @param loader:代理类的类加载器
* @param interfaces:代理类要实现的接口列表
* @param h:将方法调用分派到的调用处理程序
* @return 返回代理对象,是对被代理对象的增强
*/

Subject proxySubject = (Subject) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
new Class<?>[]{Subject.class}, new ProxyHandler(new RealSubject()));
proxySubject.subjectMethod();

     输出如下:

执行方法subjectMethod前
执行方法subjectMethod...
执行方法subjectMethod后

1.2 创建代理实例在代理类中

     在实际应用中,创建代理实例(Proxy.newProxyInstance)往往是放在代理类中JDK动态代理使用及原理解析

     代码如下所示:

public class ProxyHandlerOther implements InvocationHandler {
private Subject subject;

public Subject getProxySubject(Subject subject) {
this.subject = subject;
return (Subject) Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
this.subject.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法subjectMethod前");
Object o = method.invoke(subject, args);
System.out.println("执行方法subjectMethod后");
return o;
}
}

     请求者直接创建代理并调用方法即可:

Subject otherProxySubject = new ProxyHandlerOther().getProxySubject(new RealSubject());
otherProxySubject.subjectMethod();

1.3 公用的代理类

     可将代理类中的代理对象和通知公用出来,如下所示:JDK动态代理使用及原理解析

     代码如下:

public class ProxyHandlerCommon implements InvocationHandler {
private Object subject;
private Advice advice;

public Object getProxyObject(Object subject, Advice advice) {
this.subject = subject;
this.advice = advice;
return Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
this.subject.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();
Object o = method.invoke(subject, args);
advice.after();
return o;
}
}

     通知接口和默认通知类如下:

/**
* 公用通知接口
*/

public interface Advice {
void before();
void after();
}

/**
* 默认通知
*/

public class AdviceDefault implements Advice{

@Override
public void before() {
System.out.println("Advice before...");
}

@Override
public void after() {
System.out.println("Advice after...");
}
}

     请求者直接指定主体和通知即可:

//公用的代理类
Object commonProxy = new ProxyHandlerCommon().getProxyObject(new RealSubject(), new AdviceDefault());
((Subject) commonProxy).subjectMethod();

如何进入到代理类中的invoke的?

     请求者中使用Proxy.newProxyInstance生成了代理实例,那么代理实例是如何进入到代理类中的invoke方法的呢?

     我们在请求者中设置断点如下图所示:JDK动态代理使用及原理解析     如上图可看到,代理实例实际上是一个名为$Proxy0的一个类,内部有个h实例(ProxyHandler),h实例内部有一个subject(RealSubject)。

     猜测:这个$Proxy0也实现了Subject接口,执行主体方法subjectMethod时实际上是去执行h实例里的invoke方法。

拿到$Proxy0这个类,有以下两种方式:

2.1 拿到$Proxy0这个类

方法一:saveGeneratedFiles属性

     找源码可知打开saveGeneratedFiles属性即可,在请求者中加上该属性:

//保存生成的代理类class文件,默认false
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

     运行程序时,会看到项目下生成$Proxy0.class这个类(一般位于com.sun.proxy.$Proxy.class)。

     为什么加上该属性就可打印文件,后面会说到。    

方法二、从内存中把$Proxy0写入到文件$Proxy0.class

public static void createProxyClassFile(){
/**
* JDK自带的工具,把内存里的字节码转换字节码数组
* 从内存中把$Proxy0写入到文件$Proxy0.class中
* 参数1:类名
* 参数2:该类实现的接口
*/

byte [] $Proxy0Byte = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class});
try{
//字节码数组写入到文件
FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
fileOutputStream.write($Proxy0Byte);
fileOutputStream.close();
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

2.2 $Proxy0的解析

     $Proxy0.class源码如下:

public final class $Proxy0 extends Proxy implements Subject {
private static Method m3;
//...省略
public final void subjectMethod() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
//...省略
m3 = Class.forName("com.mm.mmblogs.b4.Subject").getMethod("subjectMethod");
//...省略
}
}

由上可知:

  • $Proxy0实现了Subject接口,所以它也有subjectMethod方法

  • $Proxy0继承了$Proxy类,里面的变量InvocationHandler即h就是Proxy.newProxyInstance时传入的ProxyHandler实例

  • $Proxy0中subjectMethod方法调用了h中的invoke方法

Proxy.newProxyInstance是如何生成一个代理对象然后返回的?

  • 下面代码经过手动删减

     进入到Proxy.newProxyInstance方法,代码大致如下:

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();

//生成代理类对象
Class<?> cl = getProxyClass0(loader, intfs);

//拿到代理类的构造函数
final Constructor<?> cons = cl.getConstructor(constructorParams);

//返回代理实例
return cons.newInstance(new Object[]{h});
}

     再来看看getProxyClass0方法:

private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 接口数量不可大于065535
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}

// 缓存中存在,则直接返回
// 否则通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}

     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) {

//生成指定的代理类的字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);

//解析字节码,生成$Proxy的Class对象并返回
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);

}
}
}

     继续跟进ProxyGenerator.generateProxyClass方法,这里还看到了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();
//判断sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//...省略
Files.write(var2, var4, new OpenOption[0]);
return null;
}
});
}

return var4;
}

     generateClassFile方法中,主要是为代理类组装信息,如组装接口中的所有方法、组装构造函数、组装字段信息等等,然后写入文件,最后以字节数组的方式返回。

     Proxy.newProxyInstance方法总结如下:

  • 生成代理类对象,如果缓存没有则创建。创建时先生成指定的代理类的字节码文件,然后在解析称class对象返回。

  • 拿到代理类的构造函数,并创建代理实例返回。

  • sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件,默认为false。

  • 代理类工厂ProxyClassFactory生成的代理类名前缀为$Proxy,且用AtomicLong保证各个代理类名唯一。


本篇文章来源于微信公众号: 冥oo迹

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/10819.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!