美文网首页
Spring Aop 动态代理

Spring Aop 动态代理

作者: 求心丶 | 来源:发表于2021-03-06 21:04 被阅读0次

前言

首先,我们要对代理(Proxy)是什么有一个准确的认识,代理是一种设计模式,通俗的说,就是为目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。其目的有两个:
一、通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性。
二、通过代理对象在不改变目标对象的基础上对目标对象进行增强(业务逻辑或功能)。

代理模式

代理模式的UML类图如下:


代理模式.jpg

可以看出,代理模式的特点是,目标对象和代理对象均实现了同一个接口,且代理对象内关联了一个目标对象。在调用时,实际的执行者为代理对象,代理对象在执行时回调用目标对象的方法并对其进行增强,下面通过静态代理模式进行详细说明。

静态代理模式

这里,我们就以生活中的租房场景为例介绍静态代理模式。租房时,我们可以自己租房,也可以通过房屋中介租房,代码如下:
租房者公共接口

package com.cube.proxy.jt;

/**
 * @author cube.li
 * @date 2021/3/6 14:25
 * @description 接口
 */
public interface Person {

    /**
     * 租房
     */
    void rentHouse();
}

承租人实现类

package com.cube.proxy.jt;

/**
 * @author cube.li
 * @date 2021/3/6 14:28
 * @description 租房者
 */
public class Tenant implements Person {

    private String name;

    public Tenant(String name) {
        this.name = name;
    }

    @Override
    public void rentHouse() {
        System.out.println(this.name + " 正在租房中...");
    }
}

房屋中介实现类

package com.cube.proxy.jt;

/**
 * @author cube.li
 * @date 2021/3/6 14:32
 * @description 中介
 */
public class Intermediary implements Person {

    private String name;

    /**
     * 租房者
     */
    private Person tenant;

    public Intermediary(String name, Person tenant) {
        this.name = name;
        this.tenant = tenant;
    }

    @Override
    public void rentHouse() {
        System.out.println("中介 " + this.name + "确定租房预算,带领查看房源...");
        tenant.rentHouse();
        System.out.println("中介 " + this.name + "带领客户租房完成,合同签订完成,收取佣金,准备跑路...");
    }
}

调用示例

package com.cube.proxy.jt;

/**
 * @author cube.li
 * @date 2021/3/6 14:32
 * @description 中介
 */
public class Intermediary implements Person {

    private String name;

    /**
     * 租房者
     */
    private Person tenant;

    public Intermediary(String name, Person tenant) {
        this.name = name;
        this.tenant = tenant;
    }

    @Override
    public void rentHouse() {
        System.out.println("中介 " + this.name + "确定租房预算,带领查看房源...");
        tenant.rentHouse();
        System.out.println("中介 " + this.name + "带领客户租房完成,合同签订完成,收取佣金,准备跑路...");
    }
}

调用结果如下:

中介 王五确定租房预算,带领查看房源...
李四 正在租房中...
中介 王五带领客户租房完成,合同签订完成,收取佣金,准备跑路...
-----------------
中介 陈大确定租房预算,带领查看房源...
张三 正在租房中...
中介 陈大带领客户租房完成,合同签订完成,收取佣金,准备跑路...

由上面代码可以看出,代理对象(中介)对目标对象(承租者)在租房(业务逻辑)这一过程中对其进行了增强。可以通过这一个示例对代理模式有一个更深刻的认识,代理过程并不是完全的代理(替代),不可能出现承租者(目标对象)完全不参与租房(业务逻辑)过程,完全交由房屋中介(代理对象),这样会出现房屋中介自己租了房自己去住将承租者完全撇开,显然是不合适的;这也是为什么代理对象必须要关联一个目标对象的原因,代理对象只是对目标对象进行增强,并不能将其与目标对象完全隔离。
并且,还可以看出,静态代理模式的一个缺点是:必须要手动为每一个目标对象创建一个代理对象,在大量使用代理模式的业务场景下,静态代理模式显然不是最合适的选择。


AOP

AOP(Aspect Orient Programming),也即面向切面编程,作为面向对象编程的一种补充,是一种成熟的编程方式。AOP与OOP互为补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面,可以这样理解:面向对象编程是从静态角度考虑程序结构,而面向切面编程则是从动态角度考虑程序运行过程。
AOP实现可以分为两类(按AOP框架修改源码的时机)
一、静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的AOP代理类(生成的.class文件被修改了,需要特定的编辑器),以AspectJ为代表。
二、动态AOP实现:AOP框架在运行阶段生成AOP代理(在内存中以JDK动态代理或cglib动态生成AOP代理类),以实现对目标对象的增强,以Spring AOP为代表。
AOP相关的概念Aspect、Joinpoint、Advice、Pointcut这里就不作展开了。

Spring 动态代理

Spring默认使用Jdk动态代理,也可以使用cglib代理,在需要代理类而不是接口的时候,Spring会自动切换为cglib代理。

1.Jdk动态代理

Jdk动态代理借助Proxy类和InvocationHandler接口实现,Proxy用来创建代理对象,每一个代理对象都会关联一个InvocationHandler实例(实现接口),代理对象调用方法时,此次调用会被指派给其关联的InvocationHandler实例由其执行。
使用jdk动态代理的代码如下:
实现InvocationHandler接口

package com.cube.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author cube.li
 * @date 2021/3/6 18:24
 * @description
 */
public class RentInvocationHandler implements InvocationHandler {

    /**
     * 被代理的目标对象
     */
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("租房前预处理工作...");
        Object returnValue = method.invoke(target, args);
        System.out.println("租房后善后工作...");
        return returnValue;
    }
}

创建代理对象调用

package com.cube.proxy.jdk;

import com.cube.proxy.stat.Person;
import com.cube.proxy.stat.Tenant;
import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author cube.li
 * @date 2021/3/6 18:27
 * @description jdk动态代理
 */
public class JdkProxyClient {

    public static void main(String[] args) {
        //目标对象
        Person target = new Tenant("李四");
        //InvocationHandler
        InvocationHandler handler = new RentInvocationHandler(target);
        //获取代理对象
        Person proxyInstance = (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
        //通过代理对象调用
        proxyInstance.rentHouse();
        printProxyClass();
    }

    /**
     * 将jdk动态代理的.class文件输出
     */
    private static void printProxyClass() {
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{Person.class});
        try {
            String pathDir = "E:\\";
            String path = "$Proxy0.class";
            File f = new File(pathDir);
            if (!f.exists()) {
                f.mkdir();
            }
            path = f.getAbsolutePath() + path;
            f = new File(path);
            if (f.exists()) {
                f.delete();
            }
            f.createNewFile();

            try (FileOutputStream fos = new FileOutputStream(path)) {
                fos.write(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

调用结果如下:

租房前预处理工作...
李四 正在租房中...
租房后善后工作...

为了对Jdk动态代理机制一窥究竟,在debug模式下查看下代理对象的类型


代理对象类型.png

发现代理对象proxyInstance的类型是$Proxy0,将其对应的class文件输出并反编译查看,其源码如下:

import com.cube.proxy.stat.Person;
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 Person {
  private static Method m1;
  
  private static Method m2;
  
  private static Method m3;
  
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final void rentHouse() {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.cube.proxy.stat.Person").getMethod("rentHouse", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

可以看出$Proxy0这个类继承了Proxy类、实现了Person接口,并且其中的rentHouse()方法内调用了与其关联的InvocationHandler实例的invoke()方法;由于java不支持多继承,经Jdk动态代理的代理对象已经继承了Proxy类,因此,Jdk动态代理只能对接口进行代理而无法再对类进行代理
实际上,由Jdk动态代理生成的代理对象的类型都是以$Proxy开头,后面的数字自零开始递增,每生成一个代理对象该数字加一。

2.cglib动态代理

cglib动态代理通过MethodInterceptor接口实现,其定义如下:

package net.sf.cglib.proxy;
 
/**
 * General-purpose {@link Enhancer} callback which provides for "around advice".
 * @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
 * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
 */
public interface MethodInterceptor
extends Callback
{
    /**
     * 所有生成的代理方法都会调用这个方法,而不是原始方法.
     * 原始方法可以通过反射使用Method对象调用,也可以使用MethodProxy调用.
     *
     * @param obj    被增强的对象,目标对象
     * @param method 被拦截的方法
     * @param args   参数
     * @param proxy  用户调用父类未被拦截的方法,根据需要可多次调用
     * @return 被代理方法的返回值, 如果是void则忽略
     * @throws Throwable
     */
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
 
}

示例代码如下:
奔跑者类

package com.cube.proxy.cglib;

/**
 * @author cube.li
 * @date 2021/3/6 20:34
 * @description 奔跑者
 */
public class Runner {

    private String name;

    public Runner() {
    }

    public Runner(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println(this.name + " 跑的飞快...");
    }
}

实现MethodInterceptor

package com.cube.proxy.cglib;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author cube.li
 * @date 2021/3/6 20:37
 * @description
 */
public class RunnerMethodInterceptor implements MethodInterceptor {

    /**
     * 目标对象
     */
    private Object target;

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

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("踢两脚 加速...");
        Object returnVal = method.invoke(target, objects);
        System.out.println("拽两下 减速...");
        return returnVal;
    }
}

创建代理对象并调用

package com.cube.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;

/**
 * @author cube.li
 * @date 2021/3/6 20:33
 * @description
 */
public class CglibProxyClient {

    public static void main(String[] args) {
        //目标对象
        Runner runner = new Runner("李四");
        //代理对象
        RunnerMethodInterceptor interceptor = new RunnerMethodInterceptor(runner);
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(runner.getClass());
        //设置回调
        enhancer.setCallback(interceptor);
        Runner proxyInstance = (Runner) enhancer.create();
        proxyInstance.run();
    }
}

输出结果

踢两脚 加速...
李四 跑的飞快...
拽两下 减速...

在示例中,cglib对Runner类进行了动态代理,增强其原有业务逻辑,因此cglib是对jdk动态代理的补充,Spring在被代理对象没有实现接口时采用cglib动态代理替代jdk动态代理。

综上,本文对动态代理进行了浅显的说明,示例代码见https://gitee.com/li-cube/share.git
以后有时间再分别对Jdk动态代理、cglib动态代理原理进行剖析。

相关文章

  • spring aop 汇总

    静态代理、动态代理和cglib代理 aop 使用 Spring AOP - 注解方式使用介绍spring aop ...

  • Spring学习系列--3.AOP

    Spring Aop Aop面向切面编程 Aop入门动态代理 动态代理,其实与Aop的原理有些相似,可以用动态代理...

  • Spring AOP 实现原理

    Spring AOP 实现原理 静态代理 众所周知 Spring 的 AOP 是基于动态代理实现的,谈到动态代理就...

  • 2018-01-31

    spring aop @(iJava)[spring framework] [toc] aop底层原理采用动态代理...

  • Spring AOP内部调用失效问题

    Spring AOP基本原理 Spring AOP是基于动态代理机制实现的,通过动态代理机制生成目标对象的代理对象...

  • Spring AOP 学习笔记(1) ---- 代理模式

    参考文章 spring aop 官方文档 掘金spring aop 教程 掘金动态代理 代理模式分类 根据代理类的...

  • Spring AOP DefaultAdvisorAutoPro

    Spring AOP源码目录 Spring AOP源码01:Jdk动态代理底层源码Spring AOP源码02:P...

  • 深入探究Java动态代理

    深入探究Java动态代理 提起Java的动态代理,大家首先就会想到Spring的AOP,Spring在实现AOP的...

  • 2018-09-16

    AOP的XML配置: AOP的先关术语: Spring底层的AOP实现原理 动态代理: JDK动态代理:只能对实现...

  • spring aop 同类中调用失败分析以及解决

    这是由于 Spring AOP (包括动态代理和 CGLIB 的 AOP) 的限制导致的.Spring AOP...

网友评论

      本文标题:Spring Aop 动态代理

      本文链接:https://www.haomeiwen.com/subject/wkojqltx.html