Spring AOP(二) 修饰者模式和JDK Proxy

 在上边一篇文章中我们介绍了Spring AOP的基本概念,今天我们就来学习一下与AOP实现相关的修饰者模式和Java Proxy相关的原理,为之后源码分析打下基础。

修饰者模式

 Java设计模式中的修饰者模式能动态地给目标对象增加额外的职责(Responsibility)。它使用组合(object composition),即将目标对象作为修饰者对象(代理)的成员变量,由修饰者对象决定调用目标对象的时机和调用前后所要增强的行为。

 装饰模式包含如下组成部分:

  • Component: 抽象构件,也就是目标对象所实现的接口,有operation函数
  • ConcreteComponent: 具体构件,也就是目标对象的类
  • Decorator: 抽象装饰类,也实现了抽象构件接口,也就是目标类和装饰类都实现了相同的接口
  • ConcreteDecorator: 具体装饰类,其中addBeavior函数就是增强的行为,装饰类可以自己决定addBeavior函数和目标对象函数operation函数的调用时机。
修饰者模式的类图

 修饰者模式调用的时序图如下图所示。程序首先创建目标对象,然后创建修饰者对象,并将目标对象传入作为其成员变量。当程序调用修饰者对象的operation函数时,修饰者对象会先调用目标对象的operation函数,然后再调用自己的addBehavior函数。这就是类似于AOP的后置增强器,在目标对象的行为之后添加新的行为。

修饰者模式的时序图

 Spring AOP的实现原理和修饰者模式类似。在上一篇文章中说到AOP的动态代理有两种实现方式,分别是JDK Proxy和cglib。

 如下图所示,JDK Proxy的类结构和上文中修饰者的类图结构类似,都是代理对象和目标对象都实现相同的接口,代理对象持有目标对象和切面对象,并且决定目标函数和切面增强函数的调用时机。
 而cglib的实现略有不同,它没有实现实现相同接口,而是代理对象继承目标对象类。


两种动态代理的对标

 本文后续就讲解一下JDK Proxy的相关源码分析。

JDK Proxy

 JDK提供了Proxy类来实现动态代理的,可通过它的newProxyInstance函数来获得代理对象。JDK还提供了InvocationHandler类,代理对象的函数被调用时,会调用它的invoke函数,程序员可以在其中实现所需的逻辑。

 JDK Proxy的基本语法如下所示。先构造一个InvocationHandler的实现类,然后调用ProxynewProxyInstance函数生成代理对象,传入类加载器,目标对象的接口和自定义的InvocationHandler实例。

public class CustomInvocationHandler implements InvocationHandler {
    private Object target;

    public CustomInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invocation");
        Object retVal = method.invoke(target, args);
        System.out.println("After invocation");
        return retVal;
    }
}

CustomInvocationHandler customInvocationHandler = new CustomInvocationHandler(
        helloWord);
//通过Proxy.newProxyInstance生成代理对象
ProxyTest proxy = (ProxyTest) Proxy.newProxyInstance(
        ProxyTest.class.getClassLoader(),
       proxyObj.getClass().getInterfaces(), customInvocationHandler);

生成代理对象

 我们首先来看一下ProxynewProxyInstance函数。newProxyInstance函数的逻辑大致如下:

  • 首先根据传入的目标对象接口动态生成代理类
  • 然后获取代理类的构造函数实例
  • 最后将InvocationHandler作为参数通过反射调用构造函数实例,生成代理类对象。
     具体源码如下所示。
public static Object newProxyInstance(ClassLoader loader,
                                        Class<?>[] interfaces,
                                        InvocationHandler h)
    throws IllegalArgumentException
{

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    // 1 动态生成代理对象的类
    Class<?> cl = getProxyClass0(loader, intfs);

    // ... 代码省略,下边代码其实是在try catch中的
    if (sm != null) {
        checkNewProxyPermission(Reflection.getCallerClass(), cl);
    }
    // 2 获取代理类的构造函数
    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;
            }
        });
    }
    // 3调用构造函数,传入InvocationHandler对象
    return cons.newInstance(new Object[]{h});
}

getProxyClass0函数的源码如下所示,通过代理类缓存获取代理类信息,如果不存在则会生成代理类。

// 生成代理类
private static Class<?> getProxyClass0(ClassLoader loader,
                                        Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 如果已经有Proxy类的缓存则直接返回,否则要进行创建
    return proxyClassCache.get(loader, interfaces);
}

生成代理类

 JDK Proxy通过ProxyClassFactory生成代理类。其apply函数大致逻辑如下:

  • 校验接口是否符合规范
  • 生成代理类的名称和包名
  • 生成代理类字节码
  • 根据字节码生成代理类Class
// 生成代理类的工厂类
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 所有代理类名的前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 生成唯一类名的原子Long对象
    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) {
            // 通过loader找到接口对应的类信息。
            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;
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // 记录非公开的代理接口,以便于生成的代理类和原来的类在同一个路径下。 
        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");
                }
            }
        }
        // 如果没有非公开的Proxy接口,使用com.sun.proxy报名
        if (proxyPkg == null) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        long num = nextUniqueNumber.getAndIncrement();
        // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,$Proxy1……依次递增  
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 生成代理类字节码
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 根据字节码返回相应的Class实例
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

 其中关于字节码生成的部分逻辑我们就暂时不深入介绍了,感兴趣的同学可以自行研究。

$Proxy反编译

 我们来看一下生成的代理类的反编译代码。代理类实现了Object的基础函数,比如toStringhasCodeequals,也实现了目标接口中定义的函数,比如说ProxyTest接口的test函数。

$Proxy中函数的实现都是直接调用了InvocationHandlerinvoke函数。

public final class $Proxy0 extends Proxy
  implements ProxyTest 
// 会实现目标接口,但是由于集成了Proxy,所以无法再集成其他类
{
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;
  // 构造函数要传入一个InvocationHandler对象
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  // equal函数
  public final boolean equals(Object paramObject)
    throws 
  {
      try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }
  // test函数,也就是ProxyTest接口中定义的函数
  public final void test(String paramString)
    throws 
  {
    try
    {
      // 调用InvocationHandler的invoke函数
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }
  // 获取各个函数的Method对象
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.proxy.test2.HelloTest").getMethod("say", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
}

后记

 下一篇文章就是AOP的源码分析了,希望大家继续关注。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,284评论 6 86
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • title: Jdk动态代理原理解析 tags:代理 categories:笔记 date: 2017-06-14...
    行径行阅读 19,240评论 3 36
  • 一、基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念。代理这个词本身并不是计...
    小李弹花阅读 16,435评论 2 40
  • 今天是我坚持运动第120天,回想当时运动的想法,觉得不可能坚持这么长时间,大概因为自己身体太弱,我小时消化吸收不好...
    南城半世阅读 362评论 0 1