代理模式--在工作中的实际应用

代理模式,即在不改变原有功能的基础上,通过增加一个代理类,为原有功能增加新的功能。代理模式侧重点在于解耦,将核心的业务逻辑与非核心的额外逻辑进行解耦,通过对原始功能进行代理从而附加一些额外的功能。类似游戏中的装备附魔,比如一把剑的核心功能只有砍怪,但是可以通过附魔,让这把剑带着火焰去砍怪,这把带火焰的剑相对于原有的剑就增加了一个额外的火焰,后续还可以对之增加光晕、特效等功能。这里要与一个相似的设计模式-装饰器设计模式做一个区分,装饰器设计模式简单理解就是增强原有方法。比如还是这把剑,可以通过锻造将其打造为轻剑、重剑,轻剑速度快,重剑伤害高,这就是对原有的剑的功能--砍怪,做了增强。所以简单说:代理模式是增加功能,装饰器模式是增强功能。

1、背景:

在线上的服务中,为提升任务的处理速度,所以使用了异步线程池进行处理。同时,为了追踪执行的整个过程,使用MDC在日志中增加一个trace_id做任务追踪。这样就导致了一个问题,在主线程中添加了一个trace_id,在异步处理时新建的线程就无法获得主线程中的trace_id,就会导致追踪中断。为此,需要在子线程执行运行之前将主线程中的trace_id获取到并传递到子线程中去,从而达到想要的效果。所以,需要对Runnable接口和Callable接口都做处理,由于增加trace_id相对于整个任务来讲是个新增的功能,负责链路追踪,所以使用代理模式去处理。同时,如果使用静态代理方式,那么需要为Runnable接口和Callable接口都增加一个代理类,而且所做的处理都是相同的,即在执行任务前获取主线程的trace_id,执行完任务后清除MDC,防止内存泄露,导致两个代理类变得很相似,增加了维护的个数,也使得代码变得不够优雅。所以使用JDK动态代理,为Runnable接口和Callable接口动态创建代理对象。

为此,定义一个动态代理类TraceTaskProxy,用来生成Runnable接口和Callable接口的代理对象,在执行子线程任务时,使用代理对象去执行。

2、代码:

java 复制代码
public class TraceTaskProxy {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MDCHandler<>(target));
    }

    private static class MDCHandler<T> implements InvocationHandler {

        private final T t;

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

        Map<String, String> previous = MDC.getCopyOfContextMap();

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                // 主线程需要在MDC中设置一个traca_id 否则子线程也不会获取到
                if (previous != null) {
                    MDC.setContextMap(previous);
                }
                return method.invoke(t, args);
            } finally {
                MDC.clear();
            }
        }
    }
}

3、具体使用:

java 复制代码
// 异步线程池配置
public class AsyncConfig implements AsyncConfigurer {
    @Override
    @Bean(name = "defaultExecutor")
    public Executor getAsyncExecutor() {
        return initExecutor("defaultExecutor", 10);
    }

    private DecoratedExecutorService initExecutor(String executorServiceName, int maxPoolSizeAvailableProcessors) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorMdcUtil();
        // 核心线程池数量,方法: 返回可用处理器的Java虚拟机的数量。
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        // 最大线程数量
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * maxPoolSizeAvailableProcessors);
        // 线程池的队列容量
        executor.setQueueCapacity(Runtime.getRuntime().availableProcessors() * 2);
        // 线程名称的前缀
        executor.setThreadNamePrefix(executorServiceName + "-");
        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
		// ExecutorServiceMetrics 是包含指标搜集功能的ExecutorService,属于第三方功能
        ExecutorService monitorExecutorService = ExecutorServiceMetrics.monitor(
                Metrics.globalRegistry, executor.getThreadPoolExecutor(), executorServiceName);
        return new DecoratedExecutorService(monitorExecutorService);
    }
	// 省略其它需要重写的方法
}
java 复制代码
// 对ExecutorServiceMetrics进行包装,让它使用功能更强的Runnable和Callable对象
public class DecoratedExecutorService implements ExecutorService {

    private final ExecutorService delegate;

    public DecoratedExecutorService(ExecutorService executorService) {
        delegate = executorService;
    }
	
    //...省略其它需要重写的方法
	
    @Override
    public <T> Future<T> submit(Callable<T> task) {
		// 使用TraceTaskProxy创建的代理对象添加将trace_id传递到子线程的功能,又不影响任务的正常执行
        return delegate.submit(TraceTaskProxy.createProxy(task));
    }

    @Override
    public Future<?> submit(Runnable task) {
		// 使用TraceTaskProxy创建的代理对象添加将trace_id传递到子线程的功能,又不影响任务的正常执行
        return delegate.submit(TraceTaskProxy.createProxy(task));
    }

}

4、总结:

代理模式侧重点是解耦,同时也能起到代码复用的效果(动态代理),常在为核心业务增加新的功能需求的场景下使用,比如增加指标搜集,日志监控等上,但也不限于此,它的主要目的是解耦。 同时代理模式有别于装饰者模式,一个是增加,一个是增强。

相关推荐
平凡之路无尽路12 小时前
智能体设计模式:构建智能系统的实践指南
人工智能·设计模式·自然语言处理·nlp·aigc·vllm
冷崖1 天前
工厂模式-创建型
c++·设计模式
何中应1 天前
【面试题-5】设计模式
java·开发语言·后端·设计模式·面试题
沐森1 天前
在实战中运用泛型和动态trait(特质)
设计模式
lomocode1 天前
改一个需求动 23 处代码?你可能踩进了这个坑
后端·设计模式
喷火龙8号1 天前
JWT 认证方案深度对比:单 Token 扩展刷新 vs 双 Token 验证
后端·设计模式·架构
fakerth2 天前
【OpenHarmony】设计模式模块详解
c++·单例模式·设计模式·openharmony
alibli2 天前
一文学会设计模式之创建型模式及最佳实现
c++·设计模式
1024肥宅2 天前
前端常用模式:提升代码质量的四大核心模式
前端·javascript·设计模式
郝学胜-神的一滴3 天前
设计模式依赖于多态特性
java·开发语言·c++·python·程序人生·设计模式·软件工程