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

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

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、总结:

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

相关推荐
bigbread11 小时前
策略模式 + 工厂方法模式在项目中的实战应用
设计模式
高晶12 小时前
C 语言实现 BMS 系统中 SPI 通信协议实战指南
设计模式·程序员
代钦塔拉12 小时前
【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店
c++·算法·设计模式
高晶13 小时前
2A3 单端甲类功放从零制作全攻略:电路设计、焊接调试到煲机维护一站式指南
前端·设计模式
代钦塔拉16 小时前
【C++/Qt 进阶】设计模式系列(4):原型模式——用“克隆“代替“重新构造“
c++·qt·设计模式
博风18 小时前
11、设计模式应用举例:迭代器模式:使用多个集合存储对象
设计模式·迭代器模式
日和雫1 天前
面向对象与面向过程,谁才是“银弹”?
设计模式
geovindu2 天前
rust: Iterator Pattern
开发语言·后端·设计模式·rust·行为模式·迭代器模
高晶2 天前
ARM 嵌入式 Linux 系统启动流程实战指南
设计模式·程序员
高晶2 天前
BMS 通信开发实战:从 PMBus 协议到数据采集的完整指南
设计模式·程序员