设计模式-代理模式

设计模式-代理模式

代理模式(Proxy Design Pattern)在不改变原有业务代码的情况下,通过引入代理类来给原始类附加功能,一般分为静态代理和动态代理。

案例分析

如果有一个 UserServiceImpl 类,我们希望统计其中的每个方法执行耗时。

java 复制代码
public class UserServiceImpl {

    public void test() {
        IntStream.rangeClosed(1, 100).forEach(i -> {
            if (i % 10 == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

}

我们很容易想到的就是创建一个 UserServiceImpl 的示例,在调用前后记录时间并计算差值

java 复制代码
public class Main {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        UserServiceImpl userService = new UserServiceImpl();
        userService.test();
        long end = System.currentTimeMillis();
        System.out.println("test耗时:" + (end - start) + "ms");
    }

}

但是一方面如果方法很多,那么所有的位置都需要加上这样的代码,另一方面这些与业务无关的代码有可能未来取消或改动,如果每次变化都修改源代码,会使程序出错的可能性变大,因此我们希望不改动原始代码来实现。

基于继承的代理实现
java 复制代码
public class UserServiceImplProxy extends UserServiceImpl{

    @Override
    public void test() {
        long start = System.currentTimeMillis();
        super.test();
        long end = System.currentTimeMillis();
        System.out.println("test耗时:" + (end - start) + "ms");
    }

}

public class Main {

    public static void main(String[] args) {
        UserServiceImpl proxy = new UserServiceImplProxy();
        proxy.test();
    }

}

我们使 UserServiceImplProxy 类继承 UserServiceImpl 类,使用完全相同的接口,这样在创建对象时利用多态特性,父类对象指向子类实例,就可以在不改动原始代码的情况下增加记录接口调用时间的日志。

基于接口的代理实现

首先定义 UserService 接口及实现类 UserServiceImpl

java 复制代码
public interface UserService {

    void test();

}

public class UserServiceImpl implements UserService {

    @Override
    public void test() {
        IntStream.rangeClosed(1, 100).forEach(i -> {
            if (i % 10 == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

}

我们创建一个新的类 UserServiceImplProxy 也实现这个接口

java 复制代码
public class UserServiceImplProxy implements UserService {

    private UserServiceImpl userService;

    public UserServiceImplProxy(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void test() {
        long start = System.currentTimeMillis();
        userService.test();
        long end = System.currentTimeMillis();
        System.out.println("test耗时:" + (end - start) + "ms");
    }

}

在 Proxy 代理类中,通过注入或构造器等把默认实现类传入,这样也可以达到我们的目的。

动态代理

在上述的两种实现中,不难看出这些都需要我们创建一些新的类,当类的数量很多时这是个很大的工作量,而且会影响编译和启动速度,动态代理可以在运行的过程中动态生成这样的类。

java 复制代码
public interface UserService {
    void test();
}

public class UserServiceImpl implements UserService {
    @Override
    public void test() {
        IntStream.rangeClosed(1, 100).forEach(i -> {
            if (i % 10 == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

以下是生成动态代理对象的代码:

java 复制代码
public class DynamicProxy {

    public Object createProxy(Object proxiedObject) {
        Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
        DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
    }

    private class DynamicProxyHandler implements InvocationHandler {

        private Object proxiedObject;

        public DynamicProxyHandler(Object proxiedObject) {
            this.proxiedObject = proxiedObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTimestamp = System.currentTimeMillis();
            Object result = method.invoke(proxiedObject, args);
            long endTimeStamp = System.currentTimeMillis();
            long responseTime = endTimeStamp - startTimestamp;
            String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
            System.out.println("执行:" + apiName + "耗时:" + responseTime + "ms");
            return result;
        }
    }

}

测试代码:

java 复制代码
public class Main {

    public static void main(String[] args) {
        DynamicProxy serviceImplProxy = new DynamicProxy();
        UserService userService = (UserService)serviceImplProxy.createProxy(new UserServiceImpl());
        userService.test();
    }

}

动态代理其实就是在运行过程中动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

我们常说 Spring AOP 就是基于动态代理实现的,同时 Spring 的事务中也用到了动态代理,那么看完上述的实现过程自然也知道了为什么 private 修饰的方法事务无法失效了~

相关推荐
AI人工智能+电脑小能手7 小时前
【大白话说Java面试题】【Java基础篇】第29题:静态代理和动态代理的区别是什么
java·开发语言·后端·面试·代理模式
Pkmer9 小时前
古法编程: 适配器模式
java·设计模式
灰子学技术1 天前
Envoy 使用的设计模式技术文档
设计模式
Carl_奕然1 天前
【智能体】Agent的四种设计模式之:ReAct
人工智能·设计模式·语言模型
二哈赛车手2 天前
新人笔记---多策略搭建策略执行链实现RAG检索后过滤
java·笔记·spring·设计模式·ai·策略模式
楼田莉子2 天前
仿Muduo的高并发服务器:Channel模块与Poller模块
linux·服务器·c++·学习·设计模式
qq_三哥啊2 天前
【mitmproxy】提取 OpenCode 的 API 接口
网络·代理模式
geovindu2 天前
go: Strategy Pattern
开发语言·设计模式·golang·策略模式
qq_三哥啊2 天前
【mitmproxy】通过 mitmproxy 的本地捕获代理模式获取 OpenCode 发起的 AI API 请求的详细信息
网络·系统安全·代理模式
嵌入式学习_force2 天前
02_state
设计模式·蓝牙