微服務中跨服務傳遞參數

場景

在微服務架構中,有比較的服務,如果我們需要把一個參數在多個服務中傳遞,如: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()方式來獲取數據。

發佈了81 篇原創文章 · 獲贊 18 · 訪問量 5515
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章