場景
在微服務架構中,有比較的服務,如果我們需要把一個參數在多個服務中傳遞,如:auth信息、語言信息、請求ID等等,那麼我們可以通過下面的方式進行處理
相關技術
- ThreadLocal
- Filter
- RequestInterceptor
實戰
一、封裝請求相關數據保存工具
我們可以利用ThreadLocal來保存數據,這裏推薦使用InheritableThreadLocal
,因爲這個類可以讓子線程以及子子線程同樣可以拿到數據,而且即使主線程已經做了移除,也不影響。
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
@Slf4j
public class RequestDataThreadLocal {
private static final InheritableThreadLocal<ThreadBindData> threadLocal = new InheritableThreadLocal<>();
/**
* 設置數據
* @param data
*/
public static void set(ThreadBindData data) {
threadLocal.set(newData);
}
/**
* 刪除數據
*/
public static void remove() {
threadLocal.remove();
}
/**
* 獲取數據
* @return
*/
public static ThreadBindData get(){
return threadLocal.get();
}
}
其中ThreadBindData是用來保存數據的實體類,具體根據自己的需要來處理既可以,比如我這裏要放入語言信息和auth信息,則內容如下:
/**
* 線程綁定的數據
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ThreadBindData {
/**
* 語言
*/
private String lang;
/**
* auth信息
*/
private String authorization;
}
二、獲取並保存請求參數
我們可以通過Filter把所有請求的參數獲取起來,然後保存在ThreadLocal之中,供後續使用。同時在請求結束的時候移除掉數據。
package com.megvii.aipark.common.web;
import com.megvii.aipark.common.domain.ThreadBindData;
import com.megvii.aipark.common.domain.enums.LocaleEnum;
import com.megvii.aipark.common.tool.RequestDataThreadLocal;
import com.megvii.aipark.common.tool.TraceIdThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
@Slf4j
public class WebFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 從Request獲取數據然後保存起來。
String langStr = req.getHeader("lang");
String authorizationStr = req.getHeader("authorization");
ThreadBindData threadBindData = new ThreadBindData();
if(StringUtils.isNotEmpty(langStr)){
threadBindData.setLang(langStr);
}
if(StringUtils.isNotEmpty(authorizationStr)){
threadBindData.setAuthorization(authorizationStr);
}
RequestDataThreadLocal.set(threadBindData);
try {
chain.doFilter(request, response);
} finally {
// 請求結束移除數據
RequestDataThreadLocal.remove();
}
}
@Override
public void destroy() {
}
}
接下來,我們把這個Filter注入到Spring容器中,
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class WebConfig {
@Bean
public Filter webFilter() {
return new WebFilter();
}
@Bean
public FilterRegistrationBean filterProxy(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
DelegatingFilterProxy httpBasicFilter = new DelegatingFilterProxy();
registrationBean.setFilter(httpBasicFilter);
Map<String,String> m = new HashMap<>();
m.put("targetBeanName","webFilter");
m.put("targetFilterLifecycle","true");
registrationBean.setInitParameters(m);
List<String> urlPatterns = new ArrayList<>();
urlPatterns.add("/*");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}
三、配置Feign調用參數傳遞
我們可以統一的對Feign進行配置,確保調用後續服務的時候把參數傳遞了下去。
package com.megvii.aipark.common.web;
import com.megvii.aipark.common.domain.ThreadBindData;
import com.megvii.aipark.common.tool.RequestDataThreadLocal;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
ThreadBindData data = RequestDataThreadLocal.get();
if (data != null) {
template.header("lang", data.getLang());
template.header("authorization", data.getAuthorization());
}
};
}
}
通過上面的處理,我們就可以在項目的任何地方通過RequestDataThreadLocal.get()
方式來獲取數據。