1. 简介

代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。简而言之,既能使被代理对象无入侵,又能附加代理自己的操作,使方法增强功能

2. 图解

水果店代理销售海南芝麻蕉,此外还销售苹果、橘子等其他水果。
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP

代理的主要实现技术与方法如下图所示,本篇主要讲静态代理与动态代理的主要实现方式,原理部分的深入,以及ASM字节码技术,将放到后续篇幅讲解。
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP

3. 案例实现

下面分多个版本,通过逐步演进的方式,讲解代理模式,其中版本1到版本6为静态代理的逐步演进过程,版本7-9为JDK动态代理内容,AspjectJ静态代理与Cglib动态代理单独演示。

3.1 版本v1

假设水果店有待销苹果,并参与秒杀活动,定义Apple类,待销水果接口Sellable,接口秒杀方法secKill(),代码如下:

package com.wzj.proxy.v1;

/**
 * @Author: wzj
 * @Date: 2020/8/3 10:29
 * @Desc: 苹果
 */
public class Apple implements Sellalbe {

    @Override
    public void secKill() {
        System.out.println("苹果正在秒杀中...");
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package com.wzj.proxy.v1;

/**
 * @Author: wzj
 * @Date: 2020/8/3 10:56
 * @Desc: 待销水果
 */
public interface Sellalbe {
    /**
     * 秒杀
     */
    public void secKill();
}

现有个问题1,如需记录秒杀时间,该如何修改代码呢?

3.2 版本v2

针对问题1,直接修改秒杀方法

package com.wzj.proxy.v2;

import java.util.Random;

/**
 * @Author: wzj
 * @Date: 2020/8/3 10:29
 * @Desc: 待销苹果
 * 问题1:记录苹果秒杀的具体时间
 * 最简单的做法就是修改代码
 */
public class Apple implements Sellalbe {

    @Override
    public void secKill() {
        Long start = System.currentTimeMillis();
        System.out.println("苹果正在秒杀中...");
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Long end = System.currentTimeMillis();

        System.out.println("记录秒杀时间为:" + (end - start));
    }
}

测试代码如下

package com.wzj.proxy.v2;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:13
 * @Desc:
 */
public class Test2 {
    public static void main(String[] args) {
        new Apple().secKill();
    }
}

结果:

苹果正在秒杀中...
记录秒杀时间为:2944

现在遇上问题2,如果无法改变方法源码呢,对源代码无入侵,该如何做呢?

3.3 版本v3

针对问题2,很容易想到,用继承解决。新增一个类Apple2,从Apple继承。

package com.wzj.proxy.v3;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:25
 * @Desc: 继承实现
 */
public class Apple2 extends Apple {

    @Override
    public void secKill() {
        long start = System.currentTimeMillis();
        super.secKill();
        long end = System.currentTimeMillis();
        System.out.println("记录秒杀时间为:" + (end - start));
    }

}

测试类:

package com.wzj.proxy.v3;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:13
 * @Desc:
 */
public class Test3 {
    public static void main(String[] args) {
        new Apple2().secKill();
    }
}

结果:

苹果正在秒杀中...
记录秒杀时间为:1660

需求总是变化不断,问题3又来了,如果需要记录秒杀开始前与开始后的日志,该如何修改呢?可能想到设计一个日志类Apple3,从Apple类继承;
如果需求变化了,先要记录日志,再记录秒杀时间,是不是要继续设计一个先日志后时间的类Apple4,从Apple3继承?
如果需求又变了,先记录秒杀时间,再记录秒杀前后日志,是不是又得重新设计Apple5,从Apple4继承?
如果需求再次变化,需要先记录权限,然后记录秒杀时间,最后秒杀日志,发现需要一直不停的设计新的类,并继承于各种派生出来的类,容易产生类爆炸,且难以复用,不灵活,该如何解决呢?

3.4 版本v4

出现问题3的根源是,继承破坏封装,集合框架的创始人Joshua Bloch,在其著作《effective java》一书时,指出复合优于继承这一原则,很好的解决了继承带来的脆弱性。针对问题3,设计一个类记录秒杀时间的代理类AppleTimeProxy

package com.wzj.proxy.v4;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:46
 * @Desc: 记录时间的代理
 * 组合优于继承
 */
public class AppleTimeProxy implements Sellalbe{

    Apple apple;

    public AppleTimeProxy(Apple apple) {
        this.apple = apple;
    }

    @Override
    public void secKill() {
        long start = System.currentTimeMillis();
        apple.secKill();
        long end = System.currentTimeMillis();
        System.out.println("记录秒杀时间为:" + (end - start));
    }
}

3.5 版本v5

针对问题3,如果要代理各种类型,比如代理记录秒杀时间,代理记录日志,每个类只需要单一代理各自的功能,只需要增加代码如下:

package com.wzj.proxy.v5;

/**
 * @Author: wzj
 * @Date: 2020/8/3 21:39
 * @Desc: 记录日志的代理类
 */
public class AppleLogProxy implements Sellalbe {

    Apple apple;

    public AppleLogProxy(Apple apple) {
        this.apple = apple;
    }

    @Override
    public void secKill() {
        System.out.println("秒杀开始...");
        apple.secKill();
        System.out.println("秒杀结束...");
    }
}

package com.wzj.proxy.v5;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:46
 * @Desc: 记录时间的代理
 * 组合优于继承
 */
public class AppleTimeProxy implements Sellalbe {

    Apple apple;

    public AppleTimeProxy(Apple apple) {
        this.apple = apple;
    }

    @Override
    public void secKill() {
        long start = System.currentTimeMillis();
        apple.secKill();
        long end = System.currentTimeMillis();
        System.out.println("记录秒杀时间为:" + (end - start));
    }
}

3.6 版本v6

进一步优化上述代码,将组合进来的类型Apple,改造成接口sellable

package com.wzj.proxy.v6;

/**
 * @Author: wzj
 * @Date: 2020/8/3 21:39
 * @Desc: 记录日志的代理类
 */
public class AppleLogProxy implements Sellalbe {

    Sellalbe sellalbe;

    public AppleLogProxy(Sellalbe sellalbe) {
        this.sellalbe = sellalbe;
    }

    @Override
    public void secKill() {
        System.out.println("秒杀开始...");
        sellalbe.secKill();
        System.out.println("秒杀结束...");
    }
}

package com.wzj.proxy.v6;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:46
 * @Desc: 记录时间的代理
 * 组合优于继承
 */
public class AppleTimeProxy implements Sellalbe {

    Sellalbe sellalbe;

    public AppleTimeProxy(Sellalbe sellalbe) {
        this.sellalbe = sellalbe;
    }

    @Override
    public void secKill() {
        long start = System.currentTimeMillis();
        sellalbe.secKill();
        long end = System.currentTimeMillis();
        System.out.println("记录秒杀时间为:" + (end - start));
    }
}

这样做可以达到,在不增加类的情况下,可以实现自由嵌套先记录时间,后打印日志,或者先打印日志,后记录时间,可以在客户端发起,不用修改源代码。测试代码如下:

package com.wzj.proxy.v6;

/**
 * @Author: wzj
 * @Date: 2020/8/3 21:44
 * @Desc: 如何实现代理的各种组合?继承?Decorator?
 *  代理的对象改成Sellable类型-越来越像decorator了,为了实现嵌套
 */
public class Test6 {
    public static void main(String[] args) {
        //先记录时间,后打印日志
        new AppleLogProxy(new AppleTimeProxy(new Apple())).secKill();
        System.out.println("===========================");
        //先打印日志,后记录时间
        new AppleTimeProxy(new AppleLogProxy(new Apple())).secKill();
    }
}

结果如下:

秒杀开始...
苹果正在秒杀中...
记录秒杀时间为:2047
秒杀结束...
===========================
秒杀开始...
苹果正在秒杀中...
秒杀结束...
记录秒杀时间为:2099

针对问题3的需求,如果要增加记录权限的功能,或者是权限与记录时间的功能组合,或者权限与记录日志的功能组合,或者权限、时间、日志三个功能按照任意顺序的组合,都不需要像v3版本那样,每个需求的变更,都需要设计新的类,各类之间冗余度高,且臃肿。直接设计一个单独权限的代理类,在客户端做各种嵌套调用即可。

3.7 Aspject实现静态代理

第一种方式为配置实现,在maven中加入Aspject相关依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

目标对象

package com.wzj.spring.v1;

import com.wzj.proxy.v9.Sellalbe;

import java.util.Random;

/**
 * @Author: wzj
 * @Date: 2020/8/3 10:29
 * @Desc: 待销苹果
 */
public class Apple {

    public void secKill() {
        System.out.println("苹果正在秒杀中...");
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

切面类

package com.wzj.spring.v1;

import com.wzj.proxy.v6.Sellalbe;

import java.lang.reflect.Method;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:46
 * @Desc: 记录时间的代理
 */
public class AppleTimeProxy {

    private void after() {
        System.out.println("方法执行结束时间:" + System.currentTimeMillis());
    }

    private void before() {
        System.out.println("方法执行开始时间:" + System.currentTimeMillis());
    }
}

app.xml配置文件

    <bean id="tank" class="com.mashibing.dp.spring.v1.Tank"/>
    <bean id="timeProxy" class="com.mashibing.dp.spring.v1.TimeProxy"/>

    <aop:config>
        <aop:aspect id="time" ref="timeProxy">
            <aop:pointcut id="onmove" expression="execution(void com.mashibing.dp.spring.v1.Tank.move())"/>
            <aop:before method="before" pointcut-ref="onmove"/>
            <aop:after method="after" pointcut-ref="onmove"/>
        </aop:aspect>
    </aop:config>

测试类

package com.wzj.spring.v1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: wzj
 * @Date: 2020/8/5 15:09
 * @Desc:
 */
public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
        Apple apple = (Apple)context.getBean("apple");
        apple.secKill();
    }
}

第二种方式为注解实现,切面类如下

package com.wzj.spring.v2;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * @Author: wzj
 * @Date: 2020/8/3 15:46
 * @Desc: 记录时间的代理
 */
@Aspect
public class AppleTimeProxy {

    @Before("execution (void com.wzj.spring.v2.Apple.secKill())")
    private void after() {
        System.out.println("方法执行结束时间:" + System.currentTimeMillis());
    }

    @After("execution (void com.wzj.spring.v2.Apple.secKill())")
    private void before() {
        System.out.println("方法执行开始时间:" + System.currentTimeMillis());
    }
}

配置类app-auto.xml

   <aop:aspectj-autoproxy/>

    <bean id="apple" class="com.wzj.spring.v2.Apple"/>
    <bean id="timeProxy" class="com.wzj.spring.v2.AppleTimeProxy"/>

测试类

package com.wzj.spring.v2;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: wzj
 * @Date: 2020/8/5 15:09
 * @Desc:
 */
public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("app-auto.xml");
        Apple apple = (Apple)context.getBean("apple");
        apple.secKill();
    }
}

结果

方法执行开始时间:1596979946478
苹果正在秒杀中...
方法执行结束时间:1596979948708

3.8 静态代理与动态代理

3.8.1 静态代理

以上版本都是基于静态代理的实现,代理类由程序员编写源码,再编译成字节码文件,在编译期间生成,静态代理类图
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP
缺点

  • 每一个代理类只能服务于一种类型的对象,比如上面的水果店例子,只能代理Sellable类型的对象,如果该水果店除了卖水果,门口还有小孩玩的投币玩具车,新增一个Player接口,那么需要新增代理类;如果一个系统有100多个类需要通过代理来增强功能,程序规模庞大时无法胜任;
  • 如果接口增加方法,实现类与代理类都需要增加,代码维护复杂性增加。

3.8.2 动态代理

代理对象在运行期间动态生成,可以代理任何对象任何方法
优点

  • 代理对象在运行期间生成,源码量大大减少;
  • 接口中的所有方法都被转移到一个集中的方法中处理(例如JDK动态代理的InvocationHandlerinvoke()方法)。

3.8.3 实现方式

3.8.3.1 JDK实现

实现步骤如下:

  • 通过实现InvocationHandlet接口,并实现invoke()方法创建自己的调用处理器;
  • 通过Proxy.newProxyInstance方法创建代理对象,该方法有三个参数,分别为被代理类的类加载器、被代理类接口、以及InvocationHandlet的实现类;
    代码实现版本V7如下
package com.wzj.proxy.v7;

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

/**
 * @Author: wzj
 * @Date: 2020/8/4 8:45
 * @Desc: 通过实现InvocationHandlet接口创建自己的调用处理器
 */
public class LogHandler implements InvocationHandler {

    Apple apple;

    public LogHandler(Apple apple) {
        this.apple = apple;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行日志开始,方法名为:" + method.getName());
        Object o = method.invoke(apple, args);
        System.out.println("方法执行日志结束,方法名为:" + method.getName());

        return o;
    }
}

package com.wzj.proxy.v7;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @Author: wzj
 * @Date: 2020/8/4 8:55
 * @Desc: JDK实现动态代理
 */
public class ProxyGenerator {
    public Object createProxy(Apple apple, InvocationHandler handler) {
        Object o = Proxy.newProxyInstance(apple.getClass().getClassLoader(),
                apple.getClass().getInterfaces(), handler);
        return o;
    }
}

测试类

package com.wzj.proxy.v7;

import com.wzj.proxy.v6.AppleLogProxy;
import com.wzj.proxy.v6.AppleTimeProxy;

import java.lang.reflect.InvocationHandler;

/**
 * @Author: wzj
 * @Date: 2020/8/3 21:44
 * @Desc: 如果想让LogProxy可以重用,不仅可以代理Apple,还可以代理任何其他可以代理的类型 Object
 *  (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
 *  分离代理行为与被代理对象
 *  使用jdk的动态代理
 */
public class Test7 {
    public static void main(String[] args) {
        Apple apple = new Apple();
        InvocationHandler handler = new LogHandler(apple);
        Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, handler);
        sellalbe.secKill();
    }
}

结果:

方法执行日志开始,方法名为:secKill
苹果正在秒杀中...
方法执行日志结束,方法名为:secKill

针对版本7,如果现在有个橘子类Orange,实现了打折接口Discount,也需要在打折接口前后打印日志,如何使用动态代理呢,毕竟打印日志是通用功能,
该如何修改代码呢?

版本8,通过泛型实现,代码如下

package com.wzj.proxy.v8;

/**
 * @Author: wzj
 * @Date: 2020/8/4 17:45
 * @Desc: 待销橘子
 */
public class Orange implements Discount {

    /**
     * 打折优惠
     */
    @Override
    public int calculateBySourcePrice(int price) {
        int i= 9;
        System.out.println("橘子打折优惠, 一律9元");
        return i;
    }
}

package com.wzj.proxy.v8;

/**
 * @Author: wzj
 * @Date: 2020/8/5 20:56
 * @Desc: 折扣优惠接口
 */
public interface Discount {
    public int calculateBySourcePrice(int price);
}

package com.wzj.proxy.v8;

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

/**
 * @Author: wzj
 * @Date: 2020/8/4 8:45
 * @Desc: 使用泛型,代理任何对象的任何行为
 */
public class LogHandler<T> implements InvocationHandler {

    T t;

    public LogHandler(T t) {
        this.t = t;
    }

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

    private void after(Method method) {
        System.out.println("方法执行日志结束,方法名为:" + method.getName());
    }

    private void before(Method method) {
        System.out.println("方法执行日志开始,方法名为:" + method.getName());
    }
}

package com.wzj.proxy.v8;

import com.wzj.proxy.v8.LogHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @Author: wzj
 * @Date: 2020/8/4 8:55
 * @Desc: 使用泛型,代理任何类的任何接口
 */
public class ProxyGenerator<T> {
    public Object createProxy(T t, InvocationHandler handler) {
        Object o = Proxy.newProxyInstance(t.getClass().getClassLoader(),
                t.getClass().getInterfaces(), handler);
        return o;
    }
}

测试类:

package com.wzj.proxy.v8;


import java.lang.reflect.InvocationHandler;

/**
 * @Author: wzj
 * @Date: 2020/8/3 21:44
 * @Desc: 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object
 *  (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
 *  分离代理行为与被代理对象
 *  使用jdk的动态代理
 *
 *  增加泛型,既可以代理苹果秒杀的接口,也可以代理橘子打折接口,实现任何对象任何接口的代理
 */
public class Test8 {
    public static void main(String[] args) {
        Apple apple = new Apple();
        InvocationHandler appHandler = new LogHandler(apple);
        Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, appHandler);
        sellalbe.secKill();

        System.out.println("=================================");

        Orange orange = new Orange();
        InvocationHandler orgHandler = new LogHandler(orange);
        Discount discount = (Discount) new ProxyGenerator().createProxy(orange, orgHandler);
        discount.calculateBySourcePrice(10);
    }
}

结果:

方法执行日志开始,方法名为:secKill
苹果正在秒杀中...
方法执行日志结束,方法名为:secKill
=================================
方法执行日志开始,方法名为:calculateBySourcePrice
橘子打折优惠, 一律9元
方法执行日志结束,方法名为:calculateBySourcePrice

有人会有疑问,invoke()方法并没有显示调用,为何会执行呢?下面的版本9将代理类生成出来就会揭晓答案。

版本9

package com.wzj.proxy.v9;

/**
 * @Author: wzj
 * @Date: 2020/8/4 21:23
 * @Desc: 打印生成的代理
 */
public class Test9 {
    public static void main(String[] args) {
        Apple apple = new Apple();
        //在项目目录下生成代理类
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        LogHandler handler = new LogHandler(apple);
        Sellalbe sellalbe = (Sellalbe) new ProxyGenerator().createProxy(apple, handler);
        sellalbe.secKill();
    }
}

代理类如图:
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP
发现生成了一个$Proxy0的类,

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.wzj.proxy.v9.Sellalbe;
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 Sellalbe {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    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 void secKill() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            m3 = Class.forName("com.wzj.proxy.v9.Sellalbe").getMethod("secKill");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

进入该类,在调用secKill()方法里面有这么一段代码super.h.invoke(this, m3, (Object[])null),显然是在代理类调用secKill()方法时,里面调用了自定义的handlerinvoke()方法。下图来通过调试验证一下
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP
显然h是自定义的LogHandler

3.8.3.2 Cglib实现

图解如下:
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP
代码实现:

package com.wzj.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author: wzj
 * @Date: 2020/8/7 11:09
 * @Desc:
 */
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //代理类的父类信息
        System.out.println("代理类的父类信息:" + o.getClass().getSuperclass().getName());
        //前置增强
        before(method);
        Object result = null;
        //调用被代理对象的方法
        result = methodProxy.invokeSuper(o, objects);
        //后置增强
        after(method);
        return result;


    }

    private void after(Method method) {
        System.out.println("方法执行日志结束,方法名为:" + method.getName());
    }

    private void before(Method method) {
        System.out.println("方法执行日志开始,方法名为:" + method.getName());
    }


}

package com.wzj.cglib;

import net.sf.cglib.proxy.Enhancer;

/**
 * @Author: wzj
 * @Date: 2020/8/7 15:13
 * @Desc:
 */
public class CglibProxy<T> {
    public T createProxy(T t) {
        //增强器
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(t.getClass());
        //设置回调的拦截器
        enhancer.setCallback(new LogMethodInterceptor());
        T proxy = (T) enhancer.create();
        return proxy;
    }
}

package com.wzj.cglib;


import java.util.Random;

/**
 * @Author: wzj
 * @Date: 2020/8/3 10:29
 * @Desc: 待销苹果
 */
public class Apple  {

    public void secKill() {
        System.out.println("苹果正在秒杀中...");
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试代码:

package com.wzj.cglib;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

/**
 * @Author: wzj
 * @Date: 2020/8/7 14:16
 * @Desc:
 */
public class CglibTest {
    public static void main(String[] args) {
        //打印代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, (String)System.getProperties().get("user.dir"));
        Apple apple = new Apple();
        Apple proxy = (Apple) new CglibProxy().createProxy(apple);
        proxy.secKill();
    }
}

结果:

代理类的父类信息:com.wzj.cglib.Apple
方法执行日志开始,方法名为:secKill
苹果正在秒杀中...
方法执行日志结束,方法名为:secKill

同样,代理类的信息会打印在工程所在目录下,如图:
【趣味设计模式系列】之【代理模式1--基本原理、实战及框架应用】-LMLPHP
这里会有三个类的生成,只有一个是代理类,继承Apple,其他两个类继承FastClass,其中一个class为生成的代理类中的每个方法建立了索引,另外一个则为我们被代理类的所有方法包含其父类的方法建立了索引。

public class Apple$$EnhancerByCGLIB$$3a0529f7 extends Apple implements Factory
public class Apple$$EnhancerByCGLIB$$3a0529f7$$FastClassByCGLIB$$17333818 extends FastClass
public class Apple$$FastClassByCGLIB$$ca2d2eb9 extends FastClass
3.8.3.3 JDK与Cglib实现动态代理时的区别
  • JDK动态代理的被代理类必须要实现接口。

  • cglib动态代理是通过继承被代理类来实现,如果被代理类为final字所修饰的非protect与public类,则没法代理。

3.8.3.4 JDK与Cglib性能比较

性能测试设计了如下5个类JdkDynamicProxyTestCglibProxyTestTargetTargetImplProxyPerformanceTest

package com.wzj.proxy.v10;

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

/**
 * @Author: wzj
 * @Date: 2020/8/7 21:58
 * @Desc: JDK动态代理
 */
public class JdkDynamicProxyTest implements InvocationHandler {

    private Target target;

    private JdkDynamicProxyTest(Target target) {
        this.target = target;
    }

    public static Target newProxyInstance(Target target) {
        return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(),
                new Class<?>[]{Target.class},
                new JdkDynamicProxyTest(target));

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

package com.wzj.proxy.v10;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author: wzj
 * @Date: 2020/8/7 22:02
 * @Desc: Cglib代理测试
 */
public class CglibProxyTest implements MethodInterceptor {

    private CglibProxyTest() {

    }

    public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetInstanceClazz);
        enhancer.setCallback(new CglibProxyTest());
        return (Target) enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, objects);
    }
}

package com.wzj.proxy.v10;

/**
 * @Author: wzj
 * @Date: 2020/8/7 21:54
 * @Desc: 被代理类
 */
public interface Target {

    int test(int i);

}

package com.wzj.proxy.v10;

/**
 * @Author: wzj
 * @Date: 2020/8/7 21:57
 * @Desc:
 */
public class TargetImpl implements Target {

    @Override
    public int test(int i) {
        return i + 1;
    }
}

package com.wzj.proxy.v10;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author: wzj
 * @Date: 2020/8/7 21:53
 * @Desc: 代理性能测试
 */
public class ProxyPerformanceTest {
    public static void main(String[] args) {
        //创建测试对象
        Target nativeTest = new TargetImpl();
        Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(nativeTest);
        Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class);

        //预热一下
        int preRunCount = 10000;
        runWithoutMonitor(nativeTest, preRunCount);
        runWithoutMonitor(cglibProxy, preRunCount);
        runWithoutMonitor(dynamicProxy, preRunCount);

        //执行测试
        Map<String, Target> tests = new LinkedHashMap<String, Target>();
        tests.put("Native   ", nativeTest);
        tests.put("Dynamic  ", dynamicProxy);
        tests.put("Cglib    ", cglibProxy);
        int repeatCount = 3;
        int runCount = 1000000;
        runTest(repeatCount, runCount, tests);
        runCount = 50000000;
        runTest(repeatCount, runCount, tests);
    }


    private static void runTest(int repeatCount, int runCount, Map<String, Target> tests) {
        System.out.println(
                String.format("\n===== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] =====",
                        repeatCount, runCount, System.getProperty("java.version")));
        for (int i = 0; i < repeatCount; i++) {
            System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1)));
            for (String key : tests.keySet()) {
                runWithMonitor(tests.get(key), runCount, key);
            }
        }
    }

    private static void runWithoutMonitor(Target target, int runCount) {
        for (int i = 0; i < runCount; i++) {
            target.test(i);
        }
    }

    private static void runWithMonitor(Target target, int runCount, String tag) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < runCount; i++) {
            target.test(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms");
    }
}

测试结果:
JDK1.6.0_43

===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.6.0_43] =====

--------- test : [1] ---------
[Native   ] Total Time:4ms
[Dynamic  ] Total Time:57ms
[Cglib    ] Total Time:59ms

--------- test : [2] ---------
[Native   ] Total Time:7ms
[Dynamic  ] Total Time:34ms
[Cglib    ] Total Time:40ms

--------- test : [3] ---------
[Native   ] Total Time:6ms
[Dynamic  ] Total Time:27ms
[Cglib    ] Total Time:42ms

===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.6.0_43] =====

--------- test : [1] ---------
[Native   ] Total Time:358ms
[Dynamic  ] Total Time:985ms
[Cglib    ] Total Time:1340ms

--------- test : [2] ---------
[Native   ] Total Time:230ms
[Dynamic  ] Total Time:564ms
[Cglib    ] Total Time:817ms

--------- test : [3] ---------
[Native   ] Total Time:177ms
[Dynamic  ] Total Time:404ms
[Cglib    ] Total Time:726ms

JDK1.7.0_79

===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.7.0_79] =====

--------- test : [1] ---------
[Native   ] Total Time:0ms
[Dynamic  ] Total Time:40ms
[Cglib    ] Total Time:59ms

--------- test : [2] ---------
[Native   ] Total Time:10ms
[Dynamic  ] Total Time:10ms
[Cglib    ] Total Time:70ms

--------- test : [3] ---------
[Native   ] Total Time:0ms
[Dynamic  ] Total Time:30ms
[Cglib    ] Total Time:50ms

===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.7.0_79] =====

--------- test : [1] ---------
[Native   ] Total Time:500ms
[Dynamic  ] Total Time:933ms
[Cglib    ] Total Time:1490ms

--------- test : [2] ---------
[Native   ] Total Time:262ms
[Dynamic  ] Total Time:595ms
[Cglib    ] Total Time:781ms

--------- test : [3] ---------
[Native   ] Total Time:172ms
[Dynamic  ] Total Time:406ms
[Cglib    ] Total Time:693ms

JDK1.8.0_161

===== run test : [repeatCount=3] [runCount=1000000] [java.version=1.8.0_161] =====

--------- test : [1] ---------
[Native   ] Total Time:6ms
[Dynamic  ] Total Time:40ms
[Cglib    ] Total Time:94ms

--------- test : [2] ---------
[Native   ] Total Time:5ms
[Dynamic  ] Total Time:23ms
[Cglib    ] Total Time:30ms

--------- test : [3] ---------
[Native   ] Total Time:11ms
[Dynamic  ] Total Time:20ms
[Cglib    ] Total Time:28ms

===== run test : [repeatCount=3] [runCount=50000000] [java.version=1.8.0_161] =====

--------- test : [1] ---------
[Native   ] Total Time:273ms
[Dynamic  ] Total Time:990ms
[Cglib    ] Total Time:1419ms

--------- test : [2] ---------
[Native   ] Total Time:241ms
[Dynamic  ] Total Time:562ms
[Cglib    ] Total Time:851ms

--------- test : [3] ---------
[Native   ] Total Time:210ms
[Dynamic  ] Total Time:551ms
[Cglib    ] Total Time:855ms

笔者机器cpu是英特尔酷睿I5,4核,从测试结果看出,JDK动态代理的速度已经比CGLib动态代理的速度要稍微快一点,并不像一些资料或博客所说的那样,cglib比jdk快几倍。

4. Spring框架中的动态代理源码解析

笔者用的Spring版本是5.1.6,其中有个创建代理的类为DefaultAopProxyFactory,部分源码如下

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
                //config.isOptimize()   是否对代理类的生成使用策略优化 其作用是和isProxyTargetClass是一样的 默认为false
                //config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象 默认为false
                //hasNoUserSuppliedProxyInterfaces目标类是否有接口存在 且只有一个接口的时候接口类型不是
                //SpringProxy类型
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
                        //上面的三个方法有一个为true的话,则进入到这里
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
                        //判断目标类是否是接口,如果目标类是接口的话,则使用JDK的方式生成代理对象
                        //如果目标类是Proxy类型,则还是使用JDK的方式生成代理对象
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
                        //配置了使用Cglib进行动态代理  或者目标类没有接口 那么使用Cglib的方式创建代理对象
			return new ObjenesisCglibAopProxy(config);
		}
		else {
                        //上面的三个方法没有一个为true 那使用JDK的提供的代理方式生成代理对象
			return new JdkDynamicAopProxy(config);
		}
	}

可见SpringAOP底层源码在实现时采用了JDK与Cglib两种动态代理方式

5. 总结

本篇主要讲了代理的好处,即对被代理类无入侵,同时又可以使原目标类功能增强;接着讲述了静态代理的迭代演进过程,以及静态代理与动态代理的主要技术实现、与区别,最后通过Spring源码分析了底层所用的代理技术,后续将深入源码分析动态代理是如何一步一步生成代理类的整个过程,敬请期待。


附:githup源码下载地址:https://github.com/wuzhujun2006/design-patterns

08-10 06:05