[Spring 系列] Spring AOP 之代理模式①

568 阅读6分钟

前言

Hi 我是来自西部大嫖客杰西

现在已经是 2020 春节期间了,先祝大家新年快乐。俗话说的好,人一年四季都是懒懒的。到了春节,人也变得极其容易困,因为春困秋乏嘛。想写点文章吧,也不知道写一些啥,所以决定回顾以下 Spring AOP 的一些知识,也算是 2020 年,保持随时学习的态度。

关于系列文章

Spring AOP,其实不是一个特别难的东西。它的难点在于,要是你要理解它,你就需要具备一些必须的知识,例如说 JDK 各个版本的特性;例如说动态代理神器 Cglib;例如说 Java 的文件操作等等基础。

所以我决定将这一个系列分开来写。从一点一点的知识点入手,慢慢去看到 Spring AOP 的整个布局。

系列文章有:

代理模式

代理模式其实应用非常广泛,在各种编程语言中都有大量的运用。

那么什么是代理模式?代理模式其实是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

上面的解释来自于百度百科,是一个比较正经的含义解读,值得我们在学习代理模式的时候反复理解的概念。如果你没明白上面的话,我可以举一个简单的小栗子说明一下。

在生活中,我们或多或少会听到一些词,例如“代理商”/“区域代理”等等。其实这些就是现实版的代理模式。假如说身边有一个篮球鞋代理商,其实我们都知道他的鞋子不是他自己生产的,他只是代替实际生产鞋子的厂商拿出去卖,而且我们也不知道实际上他的货源究竟是来自于哪里的,因为他的鞋子有可能是来自莆田,也可能来自于美国耐克。

从上面的例子,我们可以总结出一些特点:

  1. 专人做专门的事情,代理商对外负责销售,生厂商对内负责生产。
  2. 代理商可以作为前台的身份,统一对接所有需要买鞋子的人,屏蔽了实际生厂商。(毕竟代理不仅仅可以掌握一家厂商的货源,他可以代理多家厂商)

那么将上面的特点转成编程的话,我们可以说:

  1. 专人做专门的事情,对于面向对象来说职责更加分明了,提高代码的重用性,可读性。
  2. 代理对象可以作为前台,这里有利于屏蔽真实的调用代码,这样有利于封装性,同时也调高了代码的扩展性。

代理模式的名词

其实根据上面我讲的那个小栗子,我们可以轻易分出这个设计模式的组成:

  1. 客户 (在编程我们说的是客户端)
  2. 代理对象 (在编程我们说的是代理类)
  3. 目标对象 (在编程我们说的是实际操作对象)

下面下面会示例代码可以让你加深理解。

代理模式的类型

根据程序不同的运行时期这个维度上区分的话,代理模式的类型分为两种

  • 静态代理
  • 动态代理

静态代理

所谓的静态代理,简单来说就是开发人员在开发的过程中直接硬编码,将代理对象与目标对象结合,然后再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。下面有一段示例代码。

首先我们先上一个设计模式的 UML 图

编写 Subject 的代码

public interface Subject {
    public void sale();
}

然后编写 RealSubject 的代码

public class RealSubject implements Subject {
    @Override
    public void sale() {
        System.out.println("卖东西");
    }
}

接下来 Proxy 的代码

public class ProxySubject implements Subject {

    private Subject realSubject;

    public ProxySubject(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void sale() {
        realSubject.sale();
    }
}

最后写一下 Client 端的代码

public class Client {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.sale();
    }
}

输出:

卖东西

动态代理

所谓的动态代理,与静态代理相反。动态代理是在程序编译期或运行期,会根据条件来生成代理类。

在动态代理中主要分为两种技术:

  1. Cglib
  2. Java Dynamic Proxy

Cglib

这是来自 Cglib 官方学习资料

使用 Cglib 之前我们先导入一下相关的 lib

    <!-- cglib 目前最新版本是 3.3.0-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>

    <!-- commons-lang3 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>
  </dependencies>

然后我们开始编写 RealObject 目标对象

class RealObject {
    public void execute() {
        System.out.println("卖东西");
    }
}

然后我们编写 ProxySubject 代理类

class ProxySubject implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before execute");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("afer execute");
        return result;
    }
}

编写客户端

public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealObject.class);
        enhancer.setCallback(new ProxySubject());
        RealObject executor = (RealObject) enhancer.create();
        executor.execute();
    }
}

输出

before execute
卖东西
afer execute

Java Dynamic Proxy

Java JDK 本身是具备了动态代理的。

编写接口 Subject

public interface Subject {

    public void sell();

}

编写目标对象 RealSubject

public class RealSubject implements Subject {
    @Override
    public void sell() {
        System.out.println("卖东西");
    }
}

编写处理器 ProxyInvocationHandler

public class ProxyInvocationHandler implements InvocationHandler {
    
    Subject subject;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before invoke ");
        subject.sell();
        System.out.println("after invoke ");
        return null;
    }
}

编写客户端 Client

public class Client {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        ProxyInvocationHandler myInvocationHandler =
                new ProxyInvocationHandler(realSubject);
        Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);
        proxyClass.sell();
    }
}

结果:

before invoke 
卖东西
after invoke 

拓展

代理模式与装饰着模式的区别

其实代理模式和装饰者模式在写法上有很多相似的地方,但是我们需要从设计模式的思想上对他们进行分解。

它们的区别在于:

  1. 代理模式是为了加以控制,而装饰者模式为了增强功能。
  2. 代理模式强调的是透明访问,装饰模式强调的是自由构建。

结语

我写代理模式的原因是因为,这是一个比较重要的设计模式。如果我们要理解 Spring AOP 的话,那么我们就必须认识并简单运用的了代理模式。有了基本的认识,我们就可以去探索 Spring 究竟是怎么通过代理模式实现 AOP 的。