日志追蹤對于接口故障排查非常重要,可以有效置媳、快捷的定位故障點于樟,但在多線程環(huán)境中,若沒有相關框架的支持拇囊,想要實現(xiàn)日志追蹤迂曲,就需要編碼實現(xiàn)將主線程的日志參數(shù)傳遞給子線程,本文就在線程池場景下借助MDC
實現(xiàn)了traceId
參數(shù)的透傳
1 MDC
1.1 簡介
MDC
(Mapped Diagnostic Context
寥袭,映射調試上下文)是 log4j
和 logback
提供的一種方便在多線程條件下記錄日志的功能路捧。某些應用程序采用多線程的方式來處理多個用戶的請求。在一個用戶的使用過程中传黄,可能有多個不同的線程來進行處理杰扫。典型的例子是Web
應用服務器。當用戶訪問某個頁面時膘掰,應用服務器可能會創(chuàng)建一個新的線程來處理該請求章姓,也可能從線程池中復用已有的線程。在一個用戶的會話存續(xù)期間识埋,可能有多個線程處理過該用戶的請求凡伊。這使得比較難以區(qū)分不同用戶所對應的日志。當需要追蹤某個用戶在系統(tǒng)中的相關日志記錄時窒舟,就會變得很麻煩系忙。
一種解決的辦法是采用自定義的日志格式,把用戶的信息采用某種方式編碼在日志記錄中惠豺。這種方式的問題在于要求在每個使用日志記錄器的類中银还,都可以訪問到用戶相關的信息风宁。這樣才可能在記錄日志時使用。這樣的條件通常是比較難以滿足的蛹疯。MDC
的作用是解決這個問題杀糯。MDC
可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對苍苞。MDC
中包含的內容可以被同一線程中執(zhí)行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC
的內容狼纬。當需要記錄日志時羹呵,只需要從 MDC
中獲取所需的信息即可。MDC
的內容則由程序在適當?shù)臅r候保存進去疗琉。對于一個 Web 應用來說冈欢,通常是在請求被處理的最開始保存這些數(shù)據(jù)
1.2 MDC坐標和使用
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
log4j.xml
配置樣例,追蹤日志自定義格式主要在name="traceId"
的layout
里面進行設置盈简,我們使用%X{traceId}
來定義此處會打印MDC
里面key
為traceId
的value
凑耻,如果所定義的字段在MDC
不存在對應的key,那么將不會打印柠贤,會留一個占位符
點擊了解Loback.xml文件解釋
1.3 主要方法
API 說明:
-
clear()
:移除所有MDC -
get (String key)
:獲取當前線程 MDC 中指定 key 的值 -
getCopyOfContextMap()
:將MDC從內存獲取出來香浩,再傳給線程 -
put(String key, Object o)
:往當前線程的 MDC 中存入指定的鍵值對 -
remove(String key)
:刪除當前線程 MDC 中指定的鍵值對 -
setContextMap()
:將父線程的MDC內容傳給子線程
MDC
異步線程間傳遞:
用MDC
的put時,子線程在創(chuàng)建的時候會把父線程中的inheritableThreadLocals
變量設置到子線程的inheritableThreadLocals
中臼勉,而MDC
內部是用InheritableThreadLocal
實現(xiàn)的邻吭,所以會把父線程中的上下文帶到子線程中
但在線程池中,由于線程會被重用宴霸,但是線程本身只會初始化一次囱晴,所以之后重用線程的時候,就不會進行初始化操作了瓢谢,也就不會有父線程inheritableThreadLocals
拷貝到子線程中的過程了畸写,這個時候如果還想傳遞父線程的上下文的話,就要使用getCopyOfContextMap
方法
2 多線程間使用
2.1 MDC工具類
定義MDC工具類氓扛,支持Runnable
和Callable
兩種枯芬,目的就是為了把父線程的traceId設置給子線程
import org.slf4j.MDC;
import org.springframework.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* @Description 封裝MDC用于向線程池傳遞
*/
public class MDCUtil {
// 設置MDC中的traceId值,不存在則新生成采郎,針對不是子線程的情況破停,
// 如果是子線程,MDC中traceId不為null
public static void setTraceIdIfAbsent() {
if (MDC.get(Constants.TRACE_ID) == null) {
MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (CollectionUtils.isEmpty(context)) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {//清除子線程的尉剩,避免內存溢出真慢,就和ThreadLocal.remove()一個原因
MDC.clear();
}
};
}
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
public static void setMDCContextMap(final Map<String, String> context) {
if (CollectionUtils.isEmpty(context)) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
}
}
2.2 攔截器定義和配置
package demo;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
public class RequestInterceptor extends HandlerInterceptorAdapter {
private static final List<String> paramSet = Arrays.asList("traceId");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.setParam(request);
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
MDC.clear();
}
private void setParam(HttpServletRequest request) {
// 設置要放到MDC中的參數(shù)
for (String key : paramSet) {
String val = request.getHeader(key);
if (!StringUtils.isEmpty(val)) {
MDC.put(key, val);
}
}
}
}
攔截器配置
import demo.RequestInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 攔截WEB請求
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor());
}
}
2.3 Java線程池中使用
2.3.1 配置線程池
@Configuration
public class ThreadPoolService {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(4, 8, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5536),
new ScheduledThreadFactory("demo-"), new ThreadPoolExecutor.CallerRunsPolicy());
return threadPoolExecutor;
}
}
2.3.2 使用ExecutorCompletionService方式
使用ExecutorCompletionService
實現(xiàn)多線程調用
點擊了解更多關于ExecutorCompletionService信息
/**
* 使用MDC傳遞traceId
*/
public class Demo {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
public void demo() {
ExecutorCompletionService ecs = new ExecutorCompletionService(threadPoolExecutor);
ecs.submit(MDCUtil.wrap(new TestMDC(), MDC.getCopyOfContextMap()));
}
class TestMDC implements Callable {
@Override
public Object call() throws Exception {
// TODO 代碼邏輯
return null;
}
}
}
2.3.3 使用CompletableFuture方式
使用CompletableFuture實現(xiàn)多線程調用,其中收集CompletableFuture運行結果理茎,
點擊了解更多關于CompletableFuture信息
public class Result {}
/**
* 使用MDC傳遞traceId
*/
public class Demo {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
private CompletableFuture<Result> test() {
Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
return CompletableFuture.supplyAsync(() -> {
// 必須在打印日志前設置
MDCUtil.setMDCContextMap(copyOfContextMap);
//MDC.put("subTraceId",''); //如果需要對子線程進行加線程跟蹤號黑界,可在此處設定
// TODO 業(yè)務邏輯
return new Result();
}, threadPoolExecutor).exceptionally(new Function<Throwable, Result>() {
/**捕捉異常,不會導致整個流程中斷**/
@Override
public Result apply(Throwable throwable) {
log.error("線程[{}]發(fā)生了異常[{}], 繼續(xù)執(zhí)行其他線程", Thread.currentThread().getName(), throwable.getMessage());
return null;
}
});
}
}
2.4 Spring線程池中使用
2.4.1 繼承ThreadPoolTaskExecutor
public class ThreadPoolMdcWrapper extends ThreadPoolTaskExecutor {
public ThreadPoolMdcWrapper() {
}
@Override
public void execute(Runnable task) {
super.execute(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public void execute(Runnable task, long startTimeout) {
super.execute(MDCUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
2.4.2 配置線程池
繼承ThreadPoolTaskExecutor
管嬉,重寫線程執(zhí)行的方法。到這我們就做完了大部分的準備工作朗鸠,還剩下最關鍵的就是讓程序用到我們封裝后的線程池蚯撩。我們可以在聲明線程池的時候,直接使用我們封裝好的線程池(因為繼承了ThreadPoolTaskExecutor)
點擊了解Spring線程池配置
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolMdcWrapper();
//核心線程數(shù)烛占,默認為1
taskExecutor.setCorePoolSize(1);
//最大線程數(shù)胎挎,默認為Integer.MAX_VALUE
taskExecutor.setMaxPoolSize(200);
//隊列最大長度,一般需要設置值>=notifyScheduledMainExecutor.maxNum忆家;默認為Integer.MAX_VALUE
taskExecutor.setQueueCapacity(2000);
//線程池維護線程所允許的空閑時間犹菇,默認為60s
taskExecutor.setKeepAliveSeconds(60);
//線程池對拒絕任務(無線程可用)的處理策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化線程池
taskExecutor.initialize();
return taskExecutor;
}
到這我們所做的準備工作,改造工作也就結束了芽卿,剩下的就是使用了揭芍。只要在程序異步調用時,利用聲明好的taskExecutor線程池進行調用卸例,就可以在線程上下文正確傳遞Traceid了
2.5 異步線程 AsyncConfigurer
繼承了Spring
的AsyncConfigurer
称杨,并重寫了getAsyncExecutor
方法,這樣在Spring
中使用@Async
注解開啟異步線程,會自動傳遞MDC信息給子線程,
另外關于異步線程的異常捕獲,先列舉一下一般開啟異步的方式:
- 使用
Spring
的@Async
注解開啟異步 - 通過
executor.execute
開啟異步 - 通過
executor.submit
開啟異步 - 通過
CompletableFuture
開啟異步
下面針對異步子線程的異常捕獲提供幾種解決方案:
- 重寫
AsyncConfigurer
的getAsyncUncaughtExceptionHandler方法,這種方式只能捕獲方式A開啟的異步 - 使用Future.get(),可以捕獲方式C開啟的異步
- 使用Completable.join()或者Completable.get(),可以捕獲方式D開啟的異步
- 重寫getAsyncExecutor方法時,在runnable.run()代碼塊上使用try/catch,可以捕獲方式A,B,C開啟的異步
- 使用try/catch包裹整個runnable函數(shù)式接口,這樣可以捕獲A,B,C,D開啟的異步
executor.execute(() -> {
try {
//需要開啟異步的業(yè)務邏輯方法或者代碼塊
xxx();
} catch (Throwable e) {
log.error("異常", e);
}
});
下面給出完整的代碼
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@EnableAsync
@Configuration
@RequiredArgsConstructor
public class ThreadPoolTaskConfig implements AsyncConfigurer {
@Bean("AsyncExecutor")
@Override
public ThreadPoolTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor() {
/**
* 所有線程都會委托給這個execute方法,在這個方法中我們把父線程的MDC內容賦值給子線程
* https://logback.qos.ch/manual/mdc.html#managedThreads
*
* @param runnable runnable
*/
@Override
public void execute(Runnable runnable) {
// 獲取父線程MDC中的內容筷转,必須在run方法之前姑原,否則等異步線程執(zhí)行的時候有可能MDC里面的值已經(jīng)被清空了,這個時候就會返回null
Map<String, String> context = MDC.getCopyOfContextMap();
super.execute(() -> {
// 將父線程的MDC內容傳給子線程
if (context != null) {
MDC.setContextMap(context);
}
try {
// 執(zhí)行異步操作
runnable.run();
} catch (Throwable e) {
log.info("異步線程執(zhí)行異常:{}", e.getMessage(), e);
//替換成業(yè)務異常
throw new RuntimeException("異步線程執(zhí)行異常");
} finally {
// 清空MDC內容
MDC.clear();
}
});
}
@Override
public <T> Future<T> submit(Callable<T> task) {
// 獲取父線程MDC中的內容呜舒,必須在run方法之前页衙,否則等異步線程執(zhí)行的時候有可能MDC里面的值已經(jīng)被清空了,這個時候就會返回null
Map<String, String> context = MDC.getCopyOfContextMap();
return super.submit(() -> {
// 將父線程的MDC內容傳給子線程
if (context != null) {
MDC.setContextMap(context);
}
try {
// 執(zhí)行異步操作
return task.call();
} catch (Throwable e) {
log.info("異步線程執(zhí)行異常:{}", e.getMessage(), e);
//替換成業(yè)務異常
throw new RuntimeException("異步線程執(zhí)行異常");
} finally {
// 清空MDC內容
MDC.clear();
}
});
}
};
;
// 設置核心線程數(shù)
threadPoolTaskExecutor.setCorePoolSize(30);
// 設置最大線程數(shù)
threadPoolTaskExecutor.setMaxPoolSize(50);
// 設置隊列容量
threadPoolTaskExecutor.setQueueCapacity(1000);
// 設置線程活躍時間(秒)
threadPoolTaskExecutor.setKeepAliveSeconds(60);
// 設置拒絕策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 設置線程池終止等待時間
threadPoolTaskExecutor.setAwaitTerminationSeconds(10);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (Throwable throwable, Method method, Object... objects) -> {
log.error("AsyncUncaughtExceptionHandler: ", throwable);
log.info("method: {}", method.getName());
log.info("objects: {}", objects);
};
}
}
調用測試
@Slf4j
@RestController
public class UserController {
@Autowired
@Qualifier("asyncExe")
private Executor executor;
@Autowired
private AsyncServiceImpl ayncService;
@GetMapping("/t1")
public void test1(){
log.info("開始....");
CompletableFuture.runAsync(() ->{
log.info("異步中....");
});
executor.execute(() ->{
log.info("線程池中....");
});
ayncService.test();
log.info("結束....");
}
}
@Slf4j
@Service
public class AsyncServiceImpl {
@Async("asyncExe")
public void test(){
//...具體業(yè)務邏輯
log.info("異步async中....");
}
}
2.6 多線程間傳遞 TransmittableThreadLocal
2.6.1 引言
假如使用logback/log4j
官網(wǎng)推薦的方案阴绢,顯示調用 MDC.getCopyOfContextMap()
和 MDC.setContextMap()
店乐,在向線程池提交任務的時候需要顯示的去調用。這種方式很繁瑣呻袭,而且侵入性很高眨八,可維護性也很低。
如果使用阿里的TransmittableThreadLocal
方案左电,是使用TransmittableThreadLocal
的實現(xiàn)去增強ThreadPoolExecutor
廉侧,不需要在任務提交運行的時候去顯示的調用MDC,但是TransmittableThreadLocal
的官網(wǎng)上沒有明確的結合MDC的教程篓足。
主要有2種段誊,一種是自己實現(xiàn)一個MDCAdapter
替換logback/log4j的MDCAdapter
,內部將其ThreadLocal
替換為TransmittableThreadLocal
的實現(xiàn)栈拖,在通過其他方式注入到日志框架中连舍。
另外一種方式是使用 logback-mdc-ttl
來更換項目中的logback框架,內部的思路和上面類似涩哟,也是替換了MDCAdapter
的實現(xiàn)索赏。
但是這2種方式都有很大的問題盼玄,第一種需要修改日志框架的注入實現(xiàn),在后續(xù)升級日志框架有很大的風險潜腻。第二種方式是引入了一個三方的日志框架埃儿,不可維護。
2.6.2 解決方案
總結來看上述幾種解決方案都不太理解融涣,第二種方式雖然使用了TransmittableThreadLocal
解決了包裝類的問題童番,但是沒有很好的適配MDC,修改了大量的實現(xiàn)代碼威鹿,而且不利于后續(xù)的升級維護剃斧。
在搜索的相關的資料、源碼以及TransmittableThreadLocal
的issue里专普,發(fā)現(xiàn)了一種比較簡潔的實現(xiàn)方式。
添加 HandlerInterceptor
攔截器弹沽,核心的實現(xiàn)思路是實現(xiàn) TransmittableThreadLocal
的 initialValue,beforeExecute,afterExecute
接口檀夹,在多線程數(shù)據(jù)傳遞的時候,將數(shù)據(jù)復制一份給MDC策橘。
@Component
public class TraceIdInterceptor implements HandlerInterceptor {
/**
* 實現(xiàn) TransmittableThreadLocal 的 initialValue,beforeExecute,afterExecute接口
*/
static TransmittableThreadLocal<Map<String, String>> ttlMDC = new TransmittableThreadLocal<>() {
/**
* 在多線程數(shù)據(jù)傳遞的時候炸渡,將數(shù)據(jù)復制一份給MDC
*/
@Override
protected void beforeExecute() {
final Map<String, String> mdc = get();
mdc.forEach(MDC::put);
}
@Override
protected void afterExecute() {
MDC.clear();
}
@Override
protected Map<String, String> initialValue() {
return Maps.newHashMap();
}
};
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//MDC記錄traceId
String traceId = IdUtil.fastUUID();
MDC.put("traceId", traceId);
//同時給TransmittableThreadLocal記錄traceId
ttlMDC.get().put("traceId", traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) {
//清除數(shù)據(jù)
MDC.clear();
ttlMDC.get().clear();
ttlMDC.remove();
}
}
使用 TransmittableThreadLocal
提供的包裝池,
@Bean
public Executor asyncExecutor() {
log.info("start asyncExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心線程數(shù)
executor.setCorePoolSize(10);
//配置最大線程數(shù)
executor.setMaxPoolSize(50);
//配置隊列大小
executor.setQueueCapacity(0);
//配置線程池中的線程的名稱前綴
executor.setThreadNamePrefix("async-service-");
// rejection-policy:當pool已經(jīng)達到max size的時候丽已,如何處理新任務
// CALLER_RUNS:不在新線程中執(zhí)行任務蚌堵,而是有調用者所在的線程來執(zhí)行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//執(zhí)行初始化
executor.initialize();
//使用TransmittableThreadLocal提供的包裝池
return TtlExecutors.getTtlExecutor(executor);
}
2.7 HTTP調用丟失traceId
在使用 HTTP
調用第三方服務接口時traceId
將丟失,需要對HTTP
調用工具進行改造沛婴,在發(fā)送時在request header
中添加traceId吼畏,在下層被調用方添加攔截器獲取header
中的traceId
添加到MDC中
HTTP
調用有多種方式,比較常見的有HttpClient嘁灯、OKHttp泻蚊、RestTemplate
,所以只給出這幾種HTTP調用的解決方式
2.7.1 HttpClient
實現(xiàn)HttpClient攔截器
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
String traceId = MDC.get(Constants.TRACE_ID);
//當前線程調用中有traceId丑婿,則將該traceId進行透傳
if (traceId != null) {
//添加請求體
httpRequest.addHeader(Constants.TRACE_ID, traceId);
}
}
}
實現(xiàn) HttpRequestInterceptor
接口并重寫process
方法
如果調用線程中含有traceId
性雄,則需要將獲取到的traceId
通過request
中的header
向下透傳下去
為HttpClient添加攔截器
private static CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new HttpClientTraceIdInterceptor())
.build();
通過addInterceptorFirst
方法為HttpClient
添加攔截器
2.7.2 OKHttp
實現(xiàn)OKHttp攔截器
public class OkHttpTraceIdInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
Request request = null;
if (traceId != null) {
//添加請求體
request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build();
}
Response originResponse = chain.proceed(request);
return originResponse;
}
}
實現(xiàn)Interceptor
攔截器,重寫interceptor方法羹奉,實現(xiàn)邏輯和HttpClient差不多秒旋,如果能夠獲取到當前線程的traceId則向下透傳
為OkHttp添加攔截器,調用addNetworkInterceptor
方法添加攔截器
private static OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpTraceIdInterceptor())
.build();
2.7.3 RestTemplate
實現(xiàn)RestTemplate攔截器
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
if (traceId != null) {
httpRequest.getHeaders().add(Constants.TRACE_ID, traceId);
}
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
實現(xiàn)ClientHttpRequestInterceptor
接口诀拭,并重寫intercept
方法迁筛,其余邏輯都是一樣的不重復說明
為RestTemplate添加攔截器,調用setInterceptors
方法添加攔截器
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));