读了上一篇文章(【翻译】怎么自定义feign的重试机制)的同学多少了解一些了。这篇文章,我们从头到尾编写一个feign configuration。
1 编写FeignConfiguration
编写FeignConfiguration,实现RequestInterceptor
接口:
@Component
public class MyFeignConfiguration implements RequestInterceptor {
public static final String TOKEN_STR = "token";
public static final String SEC_STR = "sec_str";
// salt
public static final String key = "prepared_salt";
@Bean
public Logger.Level logLevel() {
return Logger.Level.BASIC;
}
/**
* 持续3秒 间隔1000毫秒,重试 3 次
*
* @return
*/
@Bean
public Retryer retryer() {
return new MyRetryer(1000L, 3000L, 3);
}
@Bean
public Request.Options options() {
return new Request.Options(5000, 5000);
}
/**
* 特定错误重试
*
* @return
*/
@Bean
public CloudErrorDecoder cloudErrorDecoder() {
return new MyErrorDecoder();
}
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 加密请求路径
String method = request.getRequestURL().toString();
String token = AESUtil.encryptData(key, method);
requestTemplate.header(TOKEN_STR, token);
requestTemplate.header(METHOD_URL, method);
}
}
}
实现,特定错误重试;制定重试次数,对请求进行加密,防止被刷。
PS:这里加密信息可以自定义,这里只是举个例子。
2 创建重试类MyRetryer
创建重试类MyRetryer
,需要实现接口Retryer
,指定最大重试次数、两次重试间隔时间、最大持续时间等参数,然后在continueOrPropagate
方法中,编写具体的重试逻辑等定制化内容:
public class MyRetryer implements Retryer {
private Logger logger = LoggerFactory.getLogger(CloudRetryer.class);
// 最大重试次数
private final int maxAttempts;
// 两次重试间隔时间
private final long period;
// 最大持续时间
private final long maxPeriod;
public CloudRetryer() {
// 默认参数,1秒内重试最多5次,每次间隔100毫秒
this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
public CloudRetryer(long period, long maxPeriod, int maxAttempts) {
this.period = period;
this.maxPeriod = maxPeriod;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
@Override
public void continueOrPropagate(RetryableException e) {
// 打印重试日志
logger.info("Feign/retry/attempt/[{}]/due/to/][{}] ", attempt, e.getMessage());
// 如果超出重试次数,抛出异常,停止重试;否则继续休眠period时间
if(attempt++ == maxAttempts){
throw e;
}
try {
Thread.sleep(period);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
@Override
public Retryer clone() {
return new CloudRetryer(this.period, this.maxPeriod, this.maxAttempts);
}
}
3 定制编码器MyErrorDecoder
定制编码器MyErrorDecoder
,需要实现接口ErrorDecoder
,实现特定错误重试:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String s, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if(exception instanceof RetryableException){
return exception;
}
// 自定义一些异常,进行重试
if(504 == response.status()){
return new RetryableException("504 资源未找到", response.request().httpMethod(),
new Date(System.currentTimeMillis()) );
}
return exception;
}
}
如果判断是RetryableException,继续返回该异常,会继续重试,否则判断是否是需要重试的异常,如果是,返回RetryableException异常。
4 服务解密
增加拦截器进行解密
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
拦截器代码,在 preHandle 方法中解密header中的token信息,判断解密后的字符串和加密前的字符串是否相等,如果相等,则放行。如果请求为白名单请求,则放行,其他,进行拦截。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
public class MyInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj){
// 解密
String token = AESUtil.decryptData(MyFeignConfiguration.key, request.getHeader(MyFeignConfiguration.TOKEN_STR));
// 经过加密的请求或者白名单请求放行,否则拦截
if(Objects.equals(qsc_multi_token, request.getHeader(FeignCloudConfiguration.SEC_STR))
|| whiteList().contanis(request.getRequestURI()) {
return true;
}
logger.info("request/invalid/url/[{}]", request.getRequestURL());
return false;
}
private String whiteList() {
StringBuilder whilteStr = new StringBuilder();
whilteStr.append("/user/getUserInfo");
}
}
5 调用例子
在url中填入域名或者ip:port,在 path 中指明需要访问的路径。
这里可以通过继承服务的接口定义接口,也可以直接在接口内添加对应的请求方法,使用PostMappint或者GetMapping添加具体访问路径。
@FeignClient(name = "user-service", path = "user-service", url = "127.0.0.1:8081", configuration = MyFeignConfiguration.class)
public interface UserFeign extends UserServiceI {
}
所有发生在我们身上的事件都是一个经过仔细包装的礼物。只要我们愿意面对它有时候有点丑恶的包装,带着耐心和勇气一点一点的拆开包装的话,我们会惊喜的看到里面珍藏的礼物。 ----遇见未知的自己