♻️ 重构代码,重构自动降级

This commit is contained in:
冷冷 committed 2019-02-24 19:37:09 +08:00
1 parent fbd44d0b1e
commit 35e0c6327f
18 files changed
+328 -866

No files matched your search

+11 -12
View File
@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~
~ Copyright (c) 2018-2025, lengleng All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: lengleng (wangiegie@gmail.com)
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: lengleng (wangiegie@gmail.com)
~
-->
@@ -1,20 +1,18 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.component;
@@ -1,3 +1,20 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.component;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -1,37 +1,29 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.feign;
import com.netflix.hystrix.HystrixCommand;
import feign.Feign;
import feign.RequestInterceptor;
import feign.hystrix.HystrixFeign;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.security.oauth2.client.AccessTokenContextRelay;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
@@ -52,17 +44,4 @@ public class PigxFeignConfiguration {
return new PigxFeignClientInterceptor(oAuth2ClientContext, resource, accessTokenContextRelay);
}
@Configuration
@ConditionalOnClass({HystrixCommand.class, HystrixFeign.class})
protected static class HystrixFeignConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@ConditionalOnProperty("feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder(FeignContext feignContext) {
return PigxHystrixFeign.builder(feignContext)
.decode404()
.errorDecoder(new PigxFeignErrorDecoder());
}
}
}
@@ -1,129 +0,0 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
*/
package com.pig4cloud.pigx.common.security.feign;
import cn.hutool.core.io.IoUtil;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.security.util.ConcurrentDateFormat;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import static feign.Util.checkNotNull;
import static java.lang.String.format;
import static java.util.Locale.US;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* 异常处理,将返回的数据反序列化成 R
*
* @author L.cm
*/
public class PigxFeignErrorDecoder extends ErrorDecoder.Default {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
private static final String REGEX = "^[0-9]+$";
@Override
public Exception decode(String methodKey, Response response) {
PigxFeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers()));
return new RetryableException(exception.getMessage(), response.request().httpMethod(), exception, retryAfter);
}
private static PigxFeignException errorStatus(String methodKey, Response response) {
try {
if (response.body() != null) {
Reader reader = response.body().asReader();
return new PigxFeignException(R.builder()
.msg(IoUtil.read(reader))
.code(CommonConstants.FAIL).build());
}
} catch (IOException ignored) { // NOPMD
}
String message = format("status %s reading %s", response.status(), methodKey);
return new PigxFeignException(message);
}
@Nullable
private <T> T firstOrNull(Map<String, Collection<T>> map) {
String key = feign.Util.RETRY_AFTER;
if (map.containsKey(key) && !map.get(key).isEmpty()) {
return map.get(key).iterator().next();
}
return null;
}
/**
* Decodes a {@link feign.Util#RETRY_AFTER} header into an absolute date, if possible. <br> See <a
* href="https://tools.ietf.org/html/rfc2616#section-14.37">Retry-After format</a>
*/
static class RetryAfterDecoder {
static final ConcurrentDateFormat RFC822_FORMAT = ConcurrentDateFormat.of
("EEE, dd MMM yyyy HH:mm:ss 'GMT'", US, TimeZone.getTimeZone(ZoneId.of("GMT")));
private final ConcurrentDateFormat rfc822Format;
RetryAfterDecoder() {
this(RFC822_FORMAT);
}
RetryAfterDecoder(ConcurrentDateFormat rfc822Format) {
this.rfc822Format = checkNotNull(rfc822Format, "rfc822Format");
}
private long currentTimeMillis() {
return System.currentTimeMillis();
}
/**
* returns a date that corresponds to the first time a request can be retried.
*
* @param retryAfter String in <a href="https://tools.ietf.org/html/rfc2616#section-14.37">Retry-After format</a>
*/
@Nullable
Date apply(@Nullable String retryAfter) {
if (retryAfter == null) {
return null;
}
if (retryAfter.matches(REGEX)) {
long deltaMillis = SECONDS.toMillis(Long.parseLong(retryAfter));
return new Date(currentTimeMillis() + deltaMillis);
}
try {
return rfc822Format.parse(retryAfter);
} catch (ParseException ignored) {
return null;
}
}
}
}
@@ -1,57 +0,0 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
*/
package com.pig4cloud.pigx.common.security.feign;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.Getter;
/**
* Fegin 异常
*
* @author L.cm
*/
public class PigxFeignException extends RuntimeException {
@Getter
private final R result;
public PigxFeignException(R result) {
super(result.getMsg());
this.result = result;
}
public PigxFeignException(String message) {
super(message);
this.result = R.builder()
.code(CommonConstants.FAIL)
.msg(message).build();
}
/**
* 提高性能
*
* @return {Throwable}
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
}
@@ -1,57 +0,0 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
*/
package com.pig4cloud.pigx.common.security.feign;
import feign.hystrix.FallbackFactory;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 默认 Fallback,避免写过多fallback类
*
* @param <T> 泛型标记
* @author L.cm
*/
@Slf4j
@NoArgsConstructor
public final class PigxFeignFallbackFactory<T> implements FallbackFactory<T> {
public static final PigxFeignFallbackFactory INSTANCE = new PigxFeignFallbackFactory();
private static final ConcurrentMap<Class<?>, Object> FALLBACK_MAP = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public T create(final Class<?> type, final Throwable cause) {
return (T) FALLBACK_MAP.computeIfAbsent(type, key -> {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(key);
enhancer.setCallback(new PigxFeignFallbackMethod(type, cause));
return enhancer.create();
});
}
@Override
public T create(Throwable cause) {
return null;
}
}
@@ -1,38 +0,0 @@
package com.pig4cloud.pigx.common.security.feign;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
/**
* @author lengleng
* @date 2019-01-22
*/
@Slf4j
@AllArgsConstructor
public class PigxFeignFallbackMethod implements MethodInterceptor {
private Class<?> type;
private Throwable cause;
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
log.error("Fallback class:[{}] method:[{}] message:[{}]",
type.getName(), method.getName(), cause.getMessage());
if (R.class == method.getReturnType()) {
final R result = cause instanceof PigxFeignException ?
((PigxFeignException) cause).getResult() : R.builder()
.code(CommonConstants.FAIL)
.msg(cause.getMessage()).build();
return result;
}
return null;
}
}
@@ -1,289 +0,0 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
*/
package com.pig4cloud.pigx.common.security.feign;
import com.netflix.hystrix.HystrixCommand;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixDelegatingContract;
import feign.hystrix.SetterFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* 自定义 Hystrix Feign 实现默认 fallBack
*
* @author L.cm
*/
public class PigxHystrixFeign {
public static PigxHystrixFeign.Builder builder(FeignContext feignContext) {
return new PigxHystrixFeign.Builder(feignContext);
}
public static final class Builder extends Feign.Builder {
private Contract contract = new Contract.Default();
private SetterFactory setterFactory = new SetterFactory.Default();
private final FeignContext feignContext;
public Builder(FeignContext feignContext) {
this.feignContext = feignContext;
}
/**
* Allows you to override hystrix properties such as thread pools and command keys.
*/
public PigxHystrixFeign.Builder setterFactory(SetterFactory setterFactory) {
this.setterFactory = setterFactory;
return this;
}
@Override
public <T> T target(Target<T> target) {
Class<T> targetType = target.type();
FeignClient feignClient = AnnotatedElementUtils.getMergedAnnotation(targetType, FeignClient.class);
String factoryName = feignClient.name();
SetterFactory setterFactoryBean = this.getOptional(factoryName, feignContext, SetterFactory.class);
if (setterFactoryBean != null) {
this.setterFactory(setterFactoryBean);
}
Class<?> fallback = feignClient.fallback();
if (fallback != void.class) {
return targetWithFallback(factoryName, feignContext, target, this, fallback);
}
Class<?> fallbackFactory = feignClient.fallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factoryName, feignContext, target, this, fallbackFactory);
}
return build().newInstance(target);
}
@SuppressWarnings("unchecked")
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target<T> target,
PigxHystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
/* We take a sample fallback from the fallback factory to check if it returns a fallback
that is compatible with the annotated feign interface. */
Object exampleFallback = fallbackFactory.create(new RuntimeException());
Assert.notNull(exampleFallback,
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
feignClientName));
if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
throw new IllegalStateException(
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
feignClientName, exampleFallback.getClass(), target.type()));
}
return builder.target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context,
Target<T> target,
PigxHystrixFeign.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder.target(target, fallbackInstance);
}
@SuppressWarnings("unchecked")
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
Class<?> beanType, Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s",
fallbackMechanism, beanType, feignClientName));
}
if (!targetType.isAssignableFrom(beanType)) {
throw new IllegalStateException(
String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
fallbackMechanism, beanType, targetType, feignClientName));
}
return (T) fallbackInstance;
}
@Nullable
private <T> T getOptional(String feignClientName, FeignContext context, Class<T> beanType) {
return context.getInstance(feignClientName, beanType);
}
/**
* @see #target(Class, String, Object)
*/
public <T> T target(Target<T> target, @Nullable T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
.newInstance(target);
}
/**
* @see #target(Class, String, FallbackFactory)
*/
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
return build(fallbackFactory).newInstance(target);
}
/**
* Like {@link Feign#newInstance(Target)}, except with {@link HystrixCommand
* fallback} support.
*
* <p>Fallbacks are known values, which you return when there's an error invoking an http
* method. For example, you can return a cached result as opposed to raising an error to the
* caller. To use this feature, pass a safe implementation of your target interface as the last
* parameter.
* <p>
* Here's an example:
* <pre>
* {@code
*
* // When dealing with fallbacks, it is less tedious to keep interfaces small.
* interface GitHub {
* @RequestLine("GET /repos/{owner}/{repo}/contributors")
* List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
* }
*
* // This instance will be invoked if there are errors of any kind.
* GitHub fallback = (owner, repo) -> {
* if (owner.equals("Netflix") && repo.equals("feign")) {
* return Arrays.asList("stuarthendren"); // inspired this approach!
* } else {
* return Collections.emptyList();
* }
* };
*
* GitHub github = HystrixFeign.builder()
* ...
* .target(GitHub.class, "https://api.github.com", fallback);
* }</pre>
*
* @see #target(Target, Object)
*/
public <T> T target(Class<T> apiType, String url, T fallback) {
return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
}
/**
* Same as {@link #target(Class, String, T)}, except you can inspect a source exception before
* creating a fallback object.
*/
public <T> T target(Class<T> apiType, String url, FallbackFactory<? extends T> fallbackFactory) {
return target(new Target.HardCodedTarget<T>(apiType, url), fallbackFactory);
}
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public PigxHystrixFeign.Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
return build(null);
}
/**
* Configures components needed for hystrix integration.
*/
Feign build(@Nullable final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory((target, dispatch) ->
new PigxHystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory));
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
@Override
public PigxHystrixFeign.Builder logLevel(Logger.Level logLevel) {
return (PigxHystrixFeign.Builder) super.logLevel(logLevel);
}
@Override
public PigxHystrixFeign.Builder client(Client client) {
return (PigxHystrixFeign.Builder) super.client(client);
}
@Override
public PigxHystrixFeign.Builder retryer(Retryer retryer) {
return (PigxHystrixFeign.Builder) super.retryer(retryer);
}
@Override
public PigxHystrixFeign.Builder logger(Logger logger) {
return (PigxHystrixFeign.Builder) super.logger(logger);
}
@Override
public PigxHystrixFeign.Builder encoder(Encoder encoder) {
return (PigxHystrixFeign.Builder) super.encoder(encoder);
}
@Override
public PigxHystrixFeign.Builder decoder(Decoder decoder) {
return (PigxHystrixFeign.Builder) super.decoder(decoder);
}
@Override
public PigxHystrixFeign.Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
return (PigxHystrixFeign.Builder) super.mapAndDecode(mapper, decoder);
}
@Override
public PigxHystrixFeign.Builder decode404() {
return (PigxHystrixFeign.Builder) super.decode404();
}
@Override
public PigxHystrixFeign.Builder errorDecoder(ErrorDecoder errorDecoder) {
return (PigxHystrixFeign.Builder) super.errorDecoder(errorDecoder);
}
@Override
public PigxHystrixFeign.Builder options(Request.Options options) {
return (PigxHystrixFeign.Builder) super.options(options);
}
@Override
public PigxHystrixFeign.Builder requestInterceptor(RequestInterceptor requestInterceptor) {
return (PigxHystrixFeign.Builder) super.requestInterceptor(requestInterceptor);
}
@Override
public PigxHystrixFeign.Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
return (PigxHystrixFeign.Builder) super.requestInterceptors(requestInterceptors);
}
}
}
@@ -1,217 +0,0 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
*/
package com.pig4cloud.pigx.common.security.feign;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommand.Setter;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Target;
import feign.hystrix.FallbackFactory;
import feign.hystrix.SetterFactory;
import org.springframework.lang.Nullable;
import rx.Completable;
import rx.Observable;
import rx.Single;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static feign.Util.checkNotNull;
/**
* @author L.cm
* <p>
* 降级注入
*/
public final class PigxHystrixInvocationHandler implements InvocationHandler {
private static final String EQUALS = "equals";
private static final String HASH_CODE = "hashCode";
private static final String TO_STRING = "toString";
private final Target<?> target;
private final Map<Method, MethodHandler> dispatch;
@Nullable
private final FallbackFactory<?> fallbackFactory;
private final Map<Method, Method> fallbackMethodMap;
private final Map<Method, Setter> setterMethodMap;
PigxHystrixInvocationHandler(
Target<?> target, Map<Method,
MethodHandler> dispatch,
SetterFactory setterFactory,
FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet());
}
/**
* If the method param of InvocationHandler.invoke is not accessible, i.e in a package-private
* interface, the fallback call in hystrix command will fail cause of access restrictions. But
* methods in dispatch are copied methods. So setting access to dispatch method doesn't take
* effect to the method in InvocationHandler.invoke. Use map to store a copy of method to invoke
* the fallback to bypass this and reducing the count of reflection calls.
*
* @return cached methods map for fallback invoking
*/
private static Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>(dispatch.size());
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
/**
* Process all methods in the target so that appropriate setters are created.
*/
private static Map<Method, Setter> toSetters(
SetterFactory setterFactory, Target<?> target, Set<Method> methods) {
Map<Method, Setter> result = new LinkedHashMap<>(methods.size());
for (Method method : methods) {
method.setAccessible(true);
result.put(method, setterFactory.create(target, method));
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
if (EQUALS.equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if (HASH_CODE.equals(method.getName())) {
return hashCode();
} else if (TO_STRING.equals(method.getName())) {
return toString();
}
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return PigxHystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
@Nullable
@SuppressWarnings("unchecked")
protected Object getFallback() {
Object fallback;
try {
if (fallbackFactory == null) {
fallback = PigxFeignFallbackFactory.INSTANCE.create(target.type(), getExecutionException());
} else {
fallback = fallbackFactory.create(getExecutionException());
}
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else if (isReturnsCompletable(method)) {
((Completable) result).await();
return null;
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};
if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
} else if (isReturnsCompletable(method)) {
return hystrixCommand.toObservable().toCompletable();
}
return hystrixCommand.execute();
}
private boolean isReturnsCompletable(Method method) {
return Completable.class.isAssignableFrom(method.getReturnType());
}
private boolean isReturnsHystrixCommand(Method method) {
return HystrixCommand.class.isAssignableFrom(method.getReturnType());
}
private boolean isReturnsObservable(Method method) {
return Observable.class.isAssignableFrom(method.getReturnType());
}
private boolean isReturnsSingle(Method method) {
return Single.class.isAssignableFrom(method.getReturnType());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PigxHystrixInvocationHandler) {
PigxHystrixInvocationHandler other = (PigxHystrixInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
}
@@ -1,21 +1,18 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.util;
@@ -1,20 +1,18 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.util;
@@ -1,21 +1,18 @@
/*
* *************************************************************************
* Copyright (c) 2018-2025, dreamlu.net All rights reserved.
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: chunmeng.lu (qq596392912@gmail.com)
* *************************************************************************
* Author: lengleng (wangiegie@gmail.com)
*/
/**
@@ -27,7 +24,7 @@
*/
@NonNullApi
@NonNullFields
package com.pig4cloud.pigx.common.security.feign;
package com.pig4cloud.pigx.common.security.util;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package org.springframework.cloud.openfeign;
import feign.Target;
import feign.hystrix.FallbackFactory;
import lombok.AllArgsConstructor;
import org.springframework.cglib.proxy.Enhancer;
/**
* @author 冷冷
* <p>
* 默认 Fallback,避免写过多fallback类
*/
@AllArgsConstructor
public class PigxFallbackFactory<T> implements FallbackFactory<T> {
private final Target<T> target;
@Override
@SuppressWarnings("unchecked")
public T create(Throwable cause) {
final Class<T> targetType = target.type();
final String targetName = target.name();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetType);
enhancer.setUseCache(true);
enhancer.setCallback(new PigxFeignFallback<>(targetType, targetName, cause));
return (T) enhancer.create();
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package org.springframework.cloud.openfeign;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.R;
import feign.FeignException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author lengleng
* <p>
* fallBack 代理处理
*/
@Slf4j
@AllArgsConstructor
public class PigxFeignFallback<T> implements MethodInterceptor {
private final Class<T> targetType;
private final String targetName;
private final Throwable cause;
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String errorMessage = cause.getMessage();
log.error("MicaFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
Class<?> returnType = method.getReturnType();
if (R.class != returnType) {
return null;
}
FeignException exception = (FeignException) cause;
byte[] content = exception.content();
String str = StrUtil.str(content, CharsetUtil.UTF_8);
return R.builder().code(CommonConstants.FAIL)
.msg(str).build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PigxFeignFallback<?> that = (PigxFeignFallback<?>) o;
return targetType.equals(that.targetType);
}
@Override
public int hashCode() {
return Objects.hash(targetType);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package org.springframework.cloud.openfeign;
import feign.hystrix.HystrixFeign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author lengleng
* <p>
* HystrixFeignTargeter 配置
*/
@Configuration
@ConditionalOnClass(HystrixFeign.class)
@ConditionalOnProperty("feign.hystrix.enabled")
public class PigxHystrixFeignTargeterConfiguration {
@Bean
@Primary
public Targeter micaFeignTargeter() {
return new PigxHystrixTargeter();
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package org.springframework.cloud.openfeign;
import feign.Feign;
import feign.Target;
import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import org.springframework.lang.Nullable;
/**
* @author lengleng
* <p>
* 注入默认的 fallbackFactory
*/
@SuppressWarnings("unchecked")
class PigxHystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof HystrixFeign.Builder)) {
return feign.target(target);
}
HystrixFeign.Builder builder = (HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
PigxFallbackFactory micaFallbackFactory = new PigxFallbackFactory(target);
return (T) builder.target(target, micaFallbackFactory);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder.target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder.target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context, Class<?> beanType,
Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format("No " + fallbackMechanism +
" instance of type %s found for feign client %s", beanType, feignClientName));
}
if (!targetType.isAssignableFrom(beanType)) {
throw new IllegalStateException(String.format(
"Incompatible " + fallbackMechanism + " instance. Fallback/fallbackFactory of " +
"type %s is not assignable to %s for feign client %s", beanType, targetType, feignClientName));
}
return (T) fallbackInstance;
}
@Nullable
private <T> T getOptional(String feignClientName, FeignContext context, Class<T> beanType) {
return context.getInstance(feignClientName, beanType);
}
}
@@ -1,4 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.PigxHystrixFeignTargeterConfiguration,\
com.pig4cloud.pigx.common.security.component.GlobalExceptionHandlerResolver,\
com.pig4cloud.pigx.common.security.feign.PigxFeignConfiguration,\
com.pig4cloud.pigx.common.security.service.PigxUserDetailsServiceImpl