? 深入理解动态代理源码_Linux编程_Linux公社-Linux系统门户网站 bet36手机投注_bet36 无法登陆 解决_bet36体育投注平台
手机版
你好,游客 登录 注册
背景:
阅读新闻

深入理解动态代理源码

[日期:2019-10-05] 来源:cnblogs.com/wyq178? 作者:Yrion [字体: ]

前言:? 早期学习了动态代理在实际开发中的使用场景和使用方法,我们也知道了最经典的mybatis的mapper就是采用动态代理来实现的,那么动态代理的背后是怎样的原理?为什么能实现动态代理?为什么动态代理只可以代理接口,而无法代理普通类?为什么动态代理需要传入类的classLoder和接口?带着这些疑问,我们来开启本期的主题:探究动态代理的内部原理。

本篇博客的目录

一:动态代理的基本使用方法

二:动态代理的内部运行过程

三:几个相关的问题

四:总结

一:动态代理的基本使用方法

?1.1:简单例子

首先我们来模拟一个简单的动态代理的过程:某歌手去参加一个晚会,需要唱歌,他在演奏的过程中需要别人来报幕:演奏开始、演奏结束,每个歌手都遵循这样的过程,在歌手进行表演的过程,穿插着主持人的开场白和结语,我们来用代码模拟这个场景:

1.2:代理接口

首先我们来定义一个singer接口表示我们将要代理的接口:

?

public interface Singer {
? ? /**
? ? * 表演
? ? * @param soonName
? ? */
? ? public void perform(String soonName);
}

?

1.3:接口的具体实现类

?

public class Jay implements Singer {
?
? ? public void perform(String soonName) {
? ? ? ? System.out.println("接下来我为大家唱一首"+soonName);
? ? }
}

?

1.4:辅助类,用来模拟注册人的前后台词

?

public class Presenter? {

? ? public void before(){
? ? ? ? System.out.println("请开始你的表演!");
? ? }

? ? public void after(){
? ? ? ? System.out.println("表演结束,大家鼓掌!");
? ? }
}

?

1.5:具体的代理类

? 这里用proxy.newProxyInstance来创建一个代理类,传入原始类的类加载器和接口与接口InvocationHandler,同时插入Presenter类的before与after方法,用于前置和后置处理

?

public class SingerProxy {

? private Presenter presenter;

? public SingerProxy(Presenter presenter){
? ? ? this.presenter = presenter;
? }
? ? /**
? ? * 获取代理对象
? ? * @return
? ? */
? ? public Singer getProxy(){

? ? ? ? final Singer jay = new Jay();
? ? ? ? Singer singerProxy = (Singer)Proxy.newProxyInstance(jay.getClass().getClassLoader(), jay.getClass().getInterfaces(), new InvocationHandler() {」
? ? ? ? ? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? ? ? ? ? ? ? presenter.before();
? ? ? ? ? ? ? ? method.invoke(jay, args);
? ? ? ? ? ? ? ? presenter.after();
? ? ? ? ? ? ? ? return null;
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? return singerProxy;
? ? }
}

?

1.6:测试类

?

public class Test {
? ? public static void main(String[] args) {
? ? ? ? SingerProxy singerProxy = new SingerProxy(new Presenter());
? ? ? ? Singer proxy = singerProxy.getProxy();
? ? ? ? proxy.perform("《夜曲》");
? ? }
}

?

输出:

?二:动态代理的内部探究

从上面的例子可以看出我们首先生成了一个代理类,然后用代理类来调用原始接口的方法,就可以实现我们的预设的逻辑,在原始接口的前后(或者出现异常的时候)插入我们想要的逻辑,那么究竟是为什么呢?

2.1:找到生成的代理类

我们如果需要打开生成的类,首先需要在测试类中添加这行代码,设置系统属性来保存生成的代理类的class文件:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

2.2:singerProxy类

通过动态代理生成的代理类名为:$Proxy0.class然后通过intelj idea反编译之后源代码是这样的,这里主要看到有4个方法,method的m1\m2\m3\m0;分别由反射获取的equals()、toString()、perform()、hashcode()方法,同时代理类继承了proxy并且实现了原始Singer接口,重写了perform()方法,所以这就解释了为什么代理类可以调用perform()方法,在perform方法中,又调用了父类中的InvoationHander的invoke方法,并且传入原始接口中的方法,而invoke方法在我们在创建代理类的时候重写过,所以就会按照我们自定义的逻辑调用invoke方法,按照顺序执行

?

import Java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import main.learn.proxy.Singer;

public final class $Proxy0 extends Proxy implements Singer {
? ? 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 perform(String var1) throws? {
? ? ? ? try {
? ? ? ? ? ? super.h.invoke(this, m3, new Object[]{var1});
? ? ? ? } catch (RuntimeException | Error var3) {
? ? ? ? ? ? throw var3;
? ? ? ? } catch (Throwable var4) {
? ? ? ? ? ? throw new UndeclaredThrowableException(var4);
? ? ? ? }
? ? }

? ? 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("main.learn.proxy.Singer").getMethod("perform", 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());
? ? ? ? }
? ? }
}

?

?2.3:生成代理类的过程

上面我们弄明白了,在代理类中会自动继承原始接口类并且会调用InvocationHandler将接口类中的方法传入进去,那么这个类是如何生成的呢?这就要翻生成代理类的源码了:

?

?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() {
? ? ? ? ? ? ? ? ? ? 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);
? ? ? ? }
? ? }

?

上面的代码由下来解释:

2.3.1:安全管理器检查与代理权限检查

2.3.2:判断接口的长度是否大于65535,大于不可往下进行。然后从WeakCache缓存中获取代理类,如果找不到则通过proxyClassFactory生成代理类

2.3.2.1:生成代理类的class过程如下:

1??验证传入的interface是否可被传入的classloader装载

2??验证传入的是否是一个接口,如果不是一个接口,直接抛出IllegalArgumentException异常

3??判断传入的是否重复,这里是通过一个IdentityHashMap往里面put接口的class类,如果返回值不为null表示这个接口已经注册过了(如果第一次put会返回null,按照传统的做法是先get是否为null,如果不为null再put,这行代码很妙省去了这个步骤),IdentityHashMap也是map的一种,不过它与我们普通的HashMap最大的不同在于它不会通过equals方法和hashCode方法去判断key是否重复,而是通过==运算符

4??拼接代理类的名字固定为:com.sun.proxy.$Proxy+原子自增序号,为了防止并发调用,在生成代理类名字的时候,采用了AtomicLong的getAndIncrement方法去原子递增代理类的序列号,这个方法是原子的,所以不会产生并发问题。这里也就是我们为什么看到最后的代理类是$Proxy0的原因(生成的代理类的序号是从0开始的)

5??调用ProxyGenerator.generateProxyClass方法来生成代理的class类(过程较为复杂、通过一些jvm指令去生成字节码,包括遍历方法类型、返回值、参数类型等)

6??通过defineClass将上一步产生的class字节码生成class文件,该方法也是一个native方法,需要传入类的classloader来进行装载生成的类字节码进而生成class

2.3.3: 通过反射获取构造器constractor创建一个反射实例,这个过程进行了强制构造器的private私有化反射

三:几个相关的问题

?3.1:为什么动态代理需要传入classLoader?

? 主要原因有以下几个:

1??需要校验传入的接口是否可被当前的类加载器加载,假如无法加载,证明这个接口与类加载器不是同一个,按照双亲委派模型,那么类加载层次就被破坏了

2??需要类加载器去根据生成的类的字节码去通过defineClass方法生成类的class文件,也就是说没有类加载的话是无法生成代理类的

3.2:为什么动态代理需要传入接口和只能代理接口?

?需要接口去通过ProxyGenerator类来生成代理类的字节码,在生成的过程中,需要遍历接口中的方法,包括方法签名、参数类型、返回类型从而生成新的代理类,而代理类也需要继承原始接口的方法,所以接口必须要传

3.3:如果同一个接口创建多次代理会怎么办?

在获取代理对象的时候首先会从缓存(WeakCache)里面取,如果取不到才会通过代理工厂去创建,所以如果创建多个代理类的话,最终只会产生一个代理类

四:总结

? ? 本篇博客通过一个动态代理的实际例子来分析了具体创建动态代理的过程,分析了动态代理的内部运行原理,以及分析了生成的代理类的源码,动态代理在我们的开发过程中可谓是非常常见,比如最典型的mybatis的mapper代理原理、spring的aop实现原理,进行前置增强、后置增强等就是借助了动态代理。理解了动态代理能帮助我们进一步理解一些源码,或许在以后的某些特定场景我们也可以采用动态代理来造出合适的轮子。

linux
相关资讯 ? ? ? 动态代理源码?
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款