转载大佬的文章: http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/
自定义feign拦截器
package com.wm.commonfeign.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @class_name: CustomFeignConfig
* @description: 服务间调用携带authorization请求头,拦截器统一处理
* @author: wm_yu
* @create: 2019/07/26
**/
@Configuration
@Slf4j
public class CustomFeignConfig implements RequestInterceptor {
private static final String TOKEN_HEADER = "authorization";
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest request = getHttpServletRequest();
//获取请求参数 TODO 获取请求参数失败 RequestInterceptor是在请求前给request添加参数,实际上参数并没有同步过来,待解决,建议使用feign官方的输出日志
String jsonString = this.getRequestJsonString(request);
//输出调用feign日志
log.info("feign调用请求方法日志打印,methodName={},Parameter={}",request.getMethod(),jsonString);
if (ObjectUtils.isEmpty(request)) {
requestTemplate.header(TOKEN_HEADER, getHeaders(request).get(TOKEN_HEADER));
}
}
/**
* 获取请求的request
* @return
*/
private HttpServletRequest getHttpServletRequest() {
try {
// hystrix隔离策略会导致RequestContextHolder.getRequestAttributes()返回null
// 解决方案:http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (!ObjectUtils.isEmpty(attributes)){
return attributes.getRequest();
}
return null;
} catch (Exception e) {
log.error("feign拦截器中获取HttpServletRequest error...",e.getMessage(),e);
return null;
}
}
/**
* 获取所有的header中的参数
* @param request
* @return
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
/**
* 获取request中的json数据
* @param request
* @return
*/
public String getRequestJsonString(HttpServletRequest request){
try {
int contentLength = request.getContentLength();
if (contentLength < 0) {
return null;
}
byte buffer[] = new byte[contentLength];
for (int i = 0; i < contentLength;) {
int readlen = request.getInputStream().read(buffer, i, contentLength - i);
if (readlen == -1) {
break;
}
i += readlen;
}
String charEncoding = request.getCharacterEncoding();
if (charEncoding == null) {
charEncoding = "UTF-8";
}
return new String(buffer, charEncoding);
} catch (Exception e) {
log.error("解析request中的流异常....",e.getMessage(),e);
}
return null;
}
}
途中代码处返回null
解决方法:自定义熔断策略
package com.wm.commonfeign.strategy;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @class_name: CustomFeignHystrixConcurrencyStrategy
* @description: 自定义隔离策略 解决RequestContextHolder.getRequestAttributes()问题 为null
* @author: wm_yu
* @create: 2019/07/26
**/
@Component
@Primary
@Slf4j
public class CustomFeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy{
private HystrixConcurrencyStrategy hystrixConcurrencyStrategy;
public CustomFeignHystrixConcurrencyStrategy() {
try {
this.hystrixConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.hystrixConcurrencyStrategy instanceof CustomFeignHystrixConcurrencyStrategy) {
// Welcome to singleton hell...
return;
}
HystrixCommandExecutionHook commandExecutionHook =
HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy =
HystrixPlugins.getInstance().getPropertiesStrategy();
this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
} catch (Exception e) {
log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
}
}
private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
if (log.isDebugEnabled()) {
log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
+ this.hystrixConcurrencyStrategy + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
+ metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
}
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return new WrappedCallable<>(callable, requestAttributes);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixThreadPoolProperties threadPoolProperties) {
return this.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties);
}
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.hystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
return this.hystrixConcurrencyStrategy.getRequestVariable(rv);
}
static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> target;
private final RequestAttributes requestAttributes;
public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
this.target = target;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return target.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}
配置fegin支持hystrix,并指定熔断的超时
feign:
hystrix:
enabled: true
# hystrix配置
hystrix:
shareSecurityContext: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
feign提供给其他服务调用
package com.wm.examapi.feign;
import com.wm.commoncore.constant.ServiceConstant;
import com.wm.commoncore.model.Response;
import com.wm.commonfeign.config.CustomFeignConfig;
import com.wm.examapi.feign.fallback.ExaminationServiceClientFallbackImpl;
import com.wm.examapi.model.Examination;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @class_name: ExaminationServiceClient
* @description: 考试接口 .... FeignClient需要指定configuration为CustomFeignConfig,用于服务间调用携带token
* @author: wm_yu
* @create: 2019/07/26
**/
@FeignClient(name = ServiceConstant.EXAM_SERVICE, configuration = CustomFeignConfig.class, fallback = ExaminationServiceClientFallbackImpl.class)
public interface ExaminationServiceClient {
/**
* 查询考试数量
*
* @param examination 租户标识
* @return ResponseBean
*/
/**
* 注意这里只能用RequestMapping指定请求方法为post,不能使用@PostMapping
* @param examination
* @return
*/
@RequestMapping(method = RequestMethod.POST,value = "/api/exam/getExaminationCount")
Response<Integer> findExaminationCount(@RequestBody Examination examination);
}
配置熔断类:
package com.wm.examapi.feign.fallback;
import com.wm.commoncore.constant.BussiConstant;
import com.wm.commoncore.model.Response;
import com.wm.examapi.feign.ExaminationServiceClient;
import com.wm.examapi.model.Examination;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @class_name: ExaminationServiceClientFallbackImpl
* @description: 考试服务熔断
* @author: wm_yu
* @create: 2019/07/26
**/
@Slf4j
@Component
public class ExaminationServiceClientFallbackImpl implements ExaminationServiceClient {
private Throwable throwable;
@Override
public Response<Integer> findExaminationCount(@RequestBody Examination examination) {
log.error("调用{}异常, {}, {}", "findExaminationCount", examination.getTenantCode(),throwable);
return Response.createError(BussiConstant.EXAM_SERVICE_FEIGN_ERROR,0);
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
}
创建熔断工厂,生成熔断实例
package com.wm.examapi.feign.factory;
import com.wm.examapi.feign.ExaminationServiceClient;
import com.wm.examapi.feign.fallback.ExaminationServiceClientFallbackImpl;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @class_name: UserServiceClientFallbackFactory
* @description: 考试系统熔断器工厂
* @author: wm_yu
* @create: 2019/07/26
**/
@Component
public class ExaminationServiceClientFallbackFactory implements FallbackFactory<ExaminationServiceClient> {
@Override
public ExaminationServiceClient create(Throwable throwable) {
ExaminationServiceClientFallbackImpl examinationServiceClientFallback = new ExaminationServiceClientFallbackImpl();
examinationServiceClientFallback.setThrowable(throwable);
return examinationServiceClientFallback;
}
}
启动类扫描feign包
package com.wm.userservice;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@SpringBootApplication
@EnableDiscoveryClient //注册中心发现
@MapperScan("com.wm.userservice.mapper")
@EnableFeignClients(basePackages = {"com.wm.examapi.feign"}) //扫描fegin包
@ComponentScan({"com.wm.examapi.feign","com.wm.userservice","com.wm.commonfeign"}) //主要是需要扫描到内部的包(内部不同模块),如:fegin配置等
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
/**
* 跨域过滤器
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
}
这里多配置一个vue跨域解决的
本来是想在拦截器中输出日志信息,发现不行,这个待处理,目前个人理解的原因的:
后面采用官方的feign日志输出:
很简单:
创建一个日志配置类:
如下:
package com.wm.commonfeign.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @class_name: FeignLogConfig
* @description: 配置feign日志输出
* @author: wm_yu
* @create: 2019/07/27
**/
/**
* Feign logging
* A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the DEBUG level.
*
* application.yml
*
* logging.level.project.user.UserClient: DEBUG
*
*
* The Logger.Level object that you may configure per client, tells Feign how much to log. Choices are:
*
* 可以为每个客户端配置日志级别(Logger.Level),选项有:
*
* NONE, No logging (DEFAULT).
* BASIC, Log only the request method and URL and the response status code and execution time.
* HEADERS, Log the basic information along with request and response headers.
* FULL, Log the headers, body, and metadata for both requests and responses.
* For example, the following would set the Logger.Level to FULL:
*
* 下例将日志级别设为全部(FULL):
*
* @Configuration
* public class FooConfiguration {
* @Bean
* Logger.Level feignLoggerLevel() {
* return Logger.Level.FULL;
* }
* }
*/
@Configuration
public class FeignLogConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
注释掉的为官方文档中的解释
在yml中配置需要输出的日志级别和指明feign类
logging:
level:
root: info
####sql日志
com.wm.examservice.mapper: debug
#### feign日志
com.wm.examapi.feign: debug