代理模式,即在不改变原有功能的基础上,通过增加一个代理类,为原有功能增加新的功能。代理模式侧重点在于解耦,将核心的业务逻辑与非核心的额外逻辑进行解耦,通过对原始功能进行代理从而附加一些额外的功能。类似游戏中的装备附魔,比如一把剑的核心功能只有砍怪,但是可以通过附魔,让这把剑带着火焰去砍怪,这把带火焰的剑相对于原有的剑就增加了一个额外的火焰,后续还可以对之增加光晕、特效等功能。这里要与一个相似的设计模式-装饰器设计模式做一个区分,装饰器设计模式简单理解就是增强原有方法。比如还是这把剑,可以通过锻造将其打造为轻剑、重剑,轻剑速度快,重剑伤害高,这就是对原有的剑的功能--砍怪,做了增强。所以简单说:代理模式是增加功能,装饰器模式是增强功能。
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、代码:
public class TraceTaskProxy {
@SuppressWarnings("unchecked")
public static T createProxy(T target) {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MDCHandler(target));
}
private static class MDCHandler implements InvocationHandler {
private final T t;
public MDCHandler(T t) {
this.t = t;
}
Map 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、具体使用:
// 异步线程池配置
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);
}
// 省略其它需要重写的方法
}
// 对ExecutorServiceMetrics进行包装,让它使用功能更强的Runnable和Callable对象
public class DecoratedExecutorService implements ExecutorService {
private final ExecutorService delegate;
public DecoratedExecutorService(ExecutorService executorService) {
delegate = executorService;
}
//...省略其它需要重写的方法
@Override
public Future submit(Callable 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、总结:
代理模式侧重点是解耦,同时也能起到代码复用的效果(动态代理),常在为核心业务增加新的功能需求的场景下使用,比如增加指标搜集,日志监控等上,但也不限于此,它的主要目的是解耦。
同时代理模式有别于装饰者模式,一个是增加,一个是增强。