【Spring】GoF 之代理模式

一、代理模式

在 Java 程序中的代理模式的作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为

  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强

  • A 对象无法和 B 对象直接交互时,也可以使用代理模式来解决

代理模式中的三大角色:

  • 目标对象(演员)

  • 代理对象(替身演员)

  • 目标对象和代理对象的公共接口(演员与替身演员相同的行为,可以让观众不知道是替身演员)

如果使用代理模式,对于客户端程序来说,客户端是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象
代理模式在代码实现上,包括两种形式:

  • 静态代理

  • 动态代理

二、静态代理

java 复制代码
package org.qiu.proxy.service;

/**
 * 订单业务接口
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:09
 * @since 1.0
 */
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();
}
java 复制代码
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:10
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成!");
    }

    @Override
    public void modify() {
        // 模拟网络延迟
        try {
            Thread.sleep(512);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改!");
    }

    @Override
    public void detail() {
        // 模拟网络延迟
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        orderService.generate();
        orderService.detail();
        orderService.modify();
    }
}

运行结果:

项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?

第一种方案

直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:

java 复制代码
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:10
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成!");
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        // 模拟网络延迟
        try {
            Thread.sleep(512);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改!");
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        // 模拟网络延迟
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }
}

运行效果:

需求可以满足,但显然是违背了OCP开闭原则,这种方案不可取

第二种方案

编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:

java 复制代码
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:24
 * @since 1.0
 */
public class OrderServiceImplSub extends OrderServiceImpl{
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        super.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        super.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        super.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImplSub();
        orderService.generate();
        orderService.detail();
        orderService.modify();
    }
}

运行效果:

这种方式可以解决,但是存在两个问题:

  • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象

  • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。

这种方案也不可取

第三种方案

使用代理模式(这里采用静态代理)

可以为 OrderService 接口提供一个代理类

java 复制代码
package org.qiu.proxy.service;

/**
 * 代理对象
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-16-13:32
 * @since 1.0
 */
public class OrderServiceProxy implements OrderService {

    // 目标对象
    // 这里要使用"公共接口"类型,因为公共接口耦合度低
    private OrderService orderService;

    // 通过构造方法将目标对象传递给代理对象
    public OrderServiceProxy(OrderService orderService){
        this.orderService = orderService;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        orderService.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        orderService.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));
    }
}

这种方式的优点:

符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的

java 复制代码
package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.OrderServiceImplSub;
import org.qiu.proxy.service.OrderServiceProxy;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-16-13:13
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxy = new OrderServiceProxy(target);
        // 调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}

运行效果:

以上就是代理模式中的静态代理,其中:

OrderService 接口是代理类和目标类的共同接口

OrderServiceImpl 是目标类

OrderServiceProxy 是代理类

思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用

三、动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题

在内存当中动态生成类的技术常见的包括:

  • JDK 动态代理技术:只能代理接口

  • CGLIB 动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比 JDK 动态代理要好(底层有一个小而快的字节码处理框架ASM)

  • Javassist 动态代理技术:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态"AOP"框架。

JDK 动态代理

java 复制代码
package org.qiu.proxy.service;

/**
 * 订单业务接口
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-08:23
 * @since 1.0
 */
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();
}
java 复制代码
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-08:23
 * @since 1.0
 */
public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        // 模拟网络延迟
        try {
            Thread.sleep(1024);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成!");
    }

    @Override
    public void modify() {
        // 模拟网络延迟
        try {
            Thread.sleep(512);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改!");
    }

    @Override
    public void detail() {
        // 模拟网络延迟
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单详情......");
    }
}

使用静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类 UserServiceProxy!在动态代理中UserServiceProxy 代理类是可以动态生成的,这个类不需要写。我们直接写客户端程序即可:

java 复制代码
package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-08:22
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        /**
         * 参数解析:
         * 参数一:Classloader loader 类加载器
         *      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
         * 参数二:Class<?>[] interfaces 代理类要实现的接口
         *      代理类和目标类要实现同一个接口或同一些接口
         * 参数三:InvocationHandler h 调用处理器对象(是一个接口)
         *      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
         */
        OrderService orderServiceProxy = (OrderService) 
            Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                   target.getClass().getInterfaces(), 
                                   new TimeInvocatioinHandler(target)
            );
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}
java 复制代码
package org.qiu.proxy.service;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:01
 * @since 1.0
 */
public class TimeInvocatioinHandler implements InvocationHandler {

    private Object target;

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

    /**
     * invoke 什么时候被调用?
     *  当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法被调用
     *
     * @param proxy     代理对象的引用
     * @param method    目标对象上的目标方法
     * @param args      目标方法上的实际参数
     * @return          若代理对象代用代理方法之后需要返回结果的话,invoke方法必须将目标对象目标方法执行结果继续返回
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();

        // 调用目标对象上的目标方法
        Object resultValue = method.invoke(target, args);

        long end = System.currentTimeMillis();
        System.out.println("耗时(毫秒):" + (end - begin));

        return null;
    }
}

上面调用 JDK 自带的方法比较繁琐,这里可以封装一个工具类,方便使用:

java 复制代码
package org.qiu.proxy.util;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.TimeInvocatioinHandler;

import java.lang.reflect.Proxy;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.util
 * @date 2022-11-20-09:12
 * @since 1.0
 */
public class ProxyUtil {

    /**
     * 获取代理对象(JDK动态代理)
     * @param target
     * @return
     */
    public static Object newProxyInstance(Object target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));
    }

}
java 复制代码
package org.qiu.proxy.client;

import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;

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

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-08:22
 * @since 1.0
 */
public class Test {
    public static void main(String[] args) {
        OrderService target = new OrderServiceImpl();
        /**
         * 参数解析:
         * 参数一:Classloader loader 类加载器
         *      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
         * 参数二:Class<?>[] interfaces 代理类要实现的接口
         *      代理类和目标类要实现同一个接口或同一些接口
         * 参数三:InvocationHandler h 调用处理器对象(是一个接口)
         *      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
         */
        /*
        OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));*/
        // 工具类封装
        OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

CGLIB 动态代理

CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。

使用CGLIB,需要引入它的依赖:

XML 复制代码
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

准备一个没有实现接口的类,如下:

java 复制代码
package org.qiu.proxy.service;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:22
 * @since 1.0
 */
public class UserService {
    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}

使用 CGLIB 在内存中为 UserService 类生成代理类,并创建对象:

java 复制代码
package org.qiu.proxy.client;

import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-09:23
 * @since 1.0
 */
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(方法拦截器对象);
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

java 复制代码
package org.qiu.proxy.service;

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

import java.lang.reflect.Method;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:24
 * @since 1.0
 */
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return null;
    }
}

MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:

第一个参数:目标对象

第二个参数:目标方法

第三个参数:目标方法调用时的实参

第四个参数:代理方法

在MethodInterceptor的intercept()方法中调用目标以及添加增强:

java 复制代码
package org.qiu.proxy.service;

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

import java.lang.reflect.Method;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.service
 * @date 2022-11-20-09:24
 * @since 1.0
 */
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}

回调已经写完了,可以修改客户端程序了:

java 复制代码
package org.qiu.proxy.client;

import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;

/**
 * @author 秋玄
 * @version 1.0
 * @email qiu_2022@aliyun.com
 * @project Spring
 * @package org.qiu.proxy.client
 * @date 2022-11-20-09:23
 * @since 1.0
 */
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();
    }
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:

  • --add-opens java.base/java.lang=ALL-UNNAMED

  • --add-opens java.base/sun.net.util=ALL-UNNAMED

一 叶 知 秋,奥 妙 玄 心

相关推荐
缺点内向2 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅3 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看4 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程4 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t4 小时前
ZIP工具类
java·zip
lang201509285 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan5 小时前
第10章 Maven
java·maven
百锦再6 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说6 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多6 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring