diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/DegradeProperty.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/DegradeProperty.java index 8684e69..3bdfe04 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/DegradeProperty.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/DegradeProperty.java @@ -11,14 +11,7 @@ public class DegradeProperty { /** - * 是否启用熔断降级 - * enable degrade - */ - private boolean enable = false; - - /** - * 熔断降级类型 - * degrade type + * 熔断降级类型。默认none,表示不启用熔断降级 */ private String degradeType = RetrofitDegrade.NONE; } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java index 0ce0e32..96b09c6 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java @@ -7,7 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -16,21 +16,20 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import com.alibaba.csp.sentinel.SphU; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.lianjiatech.retrofit.spring.boot.core.AutoConfiguredRetrofitScannerRegistrar; import com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory; import com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory; -import com.github.lianjiatech.retrofit.spring.boot.core.DefaultErrorDecoder; -import com.github.lianjiatech.retrofit.spring.boot.core.NoValidServiceInstanceChooser; +import com.github.lianjiatech.retrofit.spring.boot.core.ErrorDecoder; import com.github.lianjiatech.retrofit.spring.boot.core.PathMatchInterceptorBdfProcessor; import com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitFactoryBean; import com.github.lianjiatech.retrofit.spring.boot.core.ServiceInstanceChooser; -import com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser; -import com.github.lianjiatech.retrofit.spring.boot.degrade.ResourceNameParser; import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegrade; +import com.github.lianjiatech.retrofit.spring.boot.degrade.resilience4j.Resilience4jRetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.degrade.sentinel.SentinelRetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.GlobalInterceptor; @@ -39,6 +38,8 @@ import com.github.lianjiatech.retrofit.spring.boot.log.LoggingInterceptor; import com.github.lianjiatech.retrofit.spring.boot.retry.RetryInterceptor; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import lombok.extern.slf4j.Slf4j; import okhttp3.ConnectionPool; import retrofit2.converter.jackson.JacksonConverterFactory; @@ -69,8 +70,7 @@ public static PathMatchInterceptorBdfProcessor prototypeInterceptorBdfProcessor( @Bean @ConditionalOnMissingBean - public RetrofitConfigBean retrofitConfigBean(@Autowired(required = false) ResourceNameParser resourceNameParser, - @Autowired(required = false) RetrofitDegrade retrofitDegrade, + public RetrofitConfigBean retrofitConfigBean(@Autowired(required = false) RetrofitDegrade retrofitDegrade, @Autowired(required = false) List globalInterceptors, @Autowired(required = false) List networkInterceptors, ServiceChooseInterceptor serviceChooseInterceptor, RetryInterceptor retryInterceptor, @@ -79,7 +79,6 @@ public RetrofitConfigBean retrofitConfigBean(@Autowired(required = false) Resour RetrofitConfigBean retrofitConfigBean = new RetrofitConfigBean(retrofitProperties); retrofitConfigBean.setGlobalInterceptors(globalInterceptors); retrofitConfigBean.setNetworkInterceptors(networkInterceptors); - retrofitConfigBean.setResourceNameParser(resourceNameParser); retrofitConfigBean.setRetrofitDegrade(retrofitDegrade); retrofitConfigBean.setServiceChooseInterceptor(serviceChooseInterceptor); retrofitConfigBean.setRetryInterceptor(retryInterceptor); @@ -116,8 +115,8 @@ public BasicTypeConverterFactory basicTypeConverterFactory() { } @Bean - public DefaultErrorDecoder defaultErrorDecoder() { - return new DefaultErrorDecoder(); + public ErrorDecoder.DefaultErrorDecoder defaultErrorDecoder() { + return new ErrorDecoder.DefaultErrorDecoder(); } @Bean @@ -141,7 +140,7 @@ public LoggingInterceptor logInterceptor() { @Bean @ConditionalOnMissingBean public ServiceInstanceChooser serviceInstanceChooser() { - return new NoValidServiceInstanceChooser(); + return new ServiceInstanceChooser.NoValidServiceInstanceChooser(); } @Bean @@ -152,17 +151,18 @@ ServiceChooseInterceptor serviceChooseInterceptor(@Autowired ServiceInstanceChoo @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = "retrofit.degrade.enable", havingValue = "true") - public ResourceNameParser resourceNameParser() { - return new DefaultResourceNameParser(); + @ConditionalOnClass(SphU.class) + @ConditionalOnProperty(name = "retrofit.degrade.degrade-type", havingValue = RetrofitDegrade.SENTINEL) + public RetrofitDegrade sentinelRetrofitDegrade() { + return new SentinelRetrofitDegrade(); } @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = "retrofit.degrade.degrade-type", havingValue = RetrofitDegrade.SENTINEL) - @ConditionalOnBean(ResourceNameParser.class) - public RetrofitDegrade retrofitDegrade(ResourceNameParser resourceNameParser) { - return new SentinelRetrofitDegrade(resourceNameParser); + @ConditionalOnClass(CircuitBreaker.class) + @ConditionalOnProperty(name = "retrofit.degrade.degrade-type", havingValue = RetrofitDegrade.RESILIENCE4J) + public RetrofitDegrade resilience4jRetrofitDegrade() { + return new Resilience4jRetrofitDegrade(CircuitBreakerRegistry.ofDefaults()); } @Bean diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java index 420039e..24fa53f 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; -import com.github.lianjiatech.retrofit.spring.boot.degrade.ResourceNameParser; import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.GlobalInterceptor; @@ -40,8 +39,6 @@ public class RetrofitConfigBean { private Class[] globalCallAdapterFactoryClasses; - private ResourceNameParser resourceNameParser; - private RetrofitDegrade retrofitDegrade; private LoggingInterceptor loggingInterceptor; diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/DefaultErrorDecoder.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/DefaultErrorDecoder.java deleted file mode 100644 index ebb60cf..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/DefaultErrorDecoder.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.core; - -/** - * - * @author 陈添明 - */ -public class DefaultErrorDecoder implements ErrorDecoder {} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ErrorDecoder.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ErrorDecoder.java index f53e07d..6fb97b8 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ErrorDecoder.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ErrorDecoder.java @@ -57,4 +57,6 @@ default RuntimeException exceptionDecode(Request request, Exception cause) { return RetrofitException.errorUnknown(request, cause); } + class DefaultErrorDecoder implements ErrorDecoder {} + } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/NoValidServiceInstanceChooser.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/NoValidServiceInstanceChooser.java deleted file mode 100644 index a0f9ed5..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/NoValidServiceInstanceChooser.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.core; - -import java.net.URI; - -import com.github.lianjiatech.retrofit.spring.boot.exception.ServiceInstanceChooseException; - -/** - * @author 陈添明 - */ -public class NoValidServiceInstanceChooser implements ServiceInstanceChooser { - - /** - * Chooses a ServiceInstance URI from the LoadBalancer for the specified service. - * - * @param serviceId The service ID to look up the LoadBalancer. - * @return Return the uri of ServiceInstance - */ - @Override - public URI choose(String serviceId) { - throw new ServiceInstanceChooseException("No valid service instance selector, Please configure it!"); - } -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitClient.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitClient.java index 3c61bdc..bd4ab43 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitClient.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitClient.java @@ -80,7 +80,7 @@ * * @return ErrorDecoder */ - Class errorDecoder() default DefaultErrorDecoder.class; + Class errorDecoder() default ErrorDecoder.DefaultErrorDecoder.class; /** * connection pool name diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java index dfc5d97..90bce0d 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java @@ -22,7 +22,6 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.github.lianjiatech.retrofit.spring.boot.config.DegradeProperty; import com.github.lianjiatech.retrofit.spring.boot.config.RetrofitConfigBean; import com.github.lianjiatech.retrofit.spring.boot.config.RetrofitProperties; import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeProxy; @@ -62,17 +61,14 @@ public RetrofitFactoryBean(Class retrofitInterface) { @Override public T getObject() throws Exception { T source = createRetrofit().create(retrofitInterface); - if (!isEnableDegrade(retrofitProperties.getDegrade(), retrofitInterface)) { + if (!isEnableDegrade(retrofitInterface)) { return source; } retrofitConfigBean.getRetrofitDegrade().loadDegradeRules(retrofitInterface); return DegradeProxy.create(source, retrofitInterface, applicationContext); } - public boolean isEnableDegrade(DegradeProperty degradeProperty, Class retrofitInterface) { - if (!degradeProperty.isEnable()) { - return false; - } + public boolean isEnableDegrade(Class retrofitInterface) { RetrofitDegrade retrofitDegrade = retrofitConfigBean.getRetrofitDegrade(); if (retrofitDegrade == null) { return false; @@ -105,7 +101,7 @@ private OkHttpClient createOkHttpClient() throws IllegalAccessException, InstantiationException, InvocationTargetException { OkHttpClient.Builder okHttpClientBuilder = createOkHttpClientBuilder(); RetrofitClient retrofitClient = retrofitInterface.getAnnotation(RetrofitClient.class); - if (isEnableDegrade(retrofitProperties.getDegrade(), retrofitInterface)) { + if (isEnableDegrade(retrofitInterface)) { okHttpClientBuilder.addInterceptor(retrofitConfigBean.getRetrofitDegrade()); } if (StringUtils.hasText(retrofitClient.serviceId())) { @@ -162,7 +158,7 @@ private Method findOkHttpClientBuilderMethod() { } @SuppressWarnings("unchecked") - private List findInterceptorByAnnotation() throws InstantiationException, IllegalAccessException { + private List findInterceptorByAnnotation() { Annotation[] classAnnotations = retrofitInterface.getAnnotations(); List interceptors = new ArrayList<>(); // 找出被@InterceptMark标记的注解。Find the annotation marked by @InterceptMark diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ServiceInstanceChooser.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ServiceInstanceChooser.java index 9394915..31b0424 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ServiceInstanceChooser.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ServiceInstanceChooser.java @@ -2,6 +2,8 @@ import java.net.URI; +import com.github.lianjiatech.retrofit.spring.boot.exception.ServiceInstanceChooseException; + /** * @author 陈添明 */ @@ -16,4 +18,12 @@ public interface ServiceInstanceChooser { */ URI choose(String serviceId); + class NoValidServiceInstanceChooser implements ServiceInstanceChooser { + + @Override + public URI choose(String serviceId) { + throw new ServiceInstanceChooseException("No valid service instance selector, Please configure it!"); + } + } + } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseRetrofitDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseRetrofitDegrade.java new file mode 100644 index 0000000..50794e1 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseRetrofitDegrade.java @@ -0,0 +1,55 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.util.RetrofitUtils; + +/** + * @author 陈添明 + * @since 2022/5/1 9:54 下午 + */ +public abstract class BaseRetrofitDegrade implements RetrofitDegrade, ResourceNameParser, EnvironmentAware { + + protected static final String HTTP_OUT = "HTTP_OUT"; + + protected static final Map RESOURCE_NAME_CACHE = new ConcurrentHashMap<>(128); + + protected Environment environment; + + @Override + public String parseResourceName(Method method) { + String resourceName = RESOURCE_NAME_CACHE.get(method); + if (resourceName != null) { + return resourceName; + } + RetrofitClient retrofitClient = method.getDeclaringClass().getAnnotation(RetrofitClient.class); + String baseUrl = RetrofitUtils.convertBaseUrl(retrofitClient, retrofitClient.baseUrl(), environment); + HttpMethodPath httpMethodPath = parseHttpMethodPath(method); + resourceName = formatResourceName(baseUrl, httpMethodPath); + RESOURCE_NAME_CACHE.put(method, resourceName); + return resourceName; + } + + protected String formatResourceName(String baseUrl, HttpMethodPath httpMethodPath) { + return String.format("%s:%s:%s", HTTP_OUT, httpMethodPath.getMethod(), baseUrl + httpMethodPath.getPath()); + } + + protected boolean isDefaultOrStatic(Method method) { + if (method.isDefault()) { + return true; + } + return Modifier.isStatic(method.getModifiers()); + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DefaultResourceNameParser.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DefaultResourceNameParser.java deleted file mode 100644 index 9cc8bc5..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DefaultResourceNameParser.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.degrade; - -import java.lang.reflect.Method; - -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; - -import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; -import com.github.lianjiatech.retrofit.spring.boot.util.RetrofitUtils; - -/** - * @author 陈添明 - */ -public class DefaultResourceNameParser implements ResourceNameParser, EnvironmentAware { - - protected Environment environment; - - @Override - public String extractResourceName(Method method) { - String resourceName; - Class declaringClass = method.getDeclaringClass(); - RetrofitClient retrofitClient = declaringClass.getAnnotation(RetrofitClient.class); - String baseUrl = retrofitClient.baseUrl(); - baseUrl = RetrofitUtils.convertBaseUrl(retrofitClient, baseUrl, environment); - HttpMethodPath httpMethodPath = parseHttpMethodPath(method); - resourceName = - String.format("%s:%s:%s", HTTP_OUT, httpMethodPath.getMethod(), baseUrl + httpMethodPath.getPath()); - return resourceName; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/ResourceNameParser.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/ResourceNameParser.java index 2039903..a675e0d 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/ResourceNameParser.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/ResourceNameParser.java @@ -1,8 +1,6 @@ package com.github.lianjiatech.retrofit.spring.boot.degrade; import java.lang.reflect.Method; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import retrofit2.http.DELETE; import retrofit2.http.GET; @@ -18,31 +16,12 @@ */ public interface ResourceNameParser { - String HTTP_OUT = "HTTP_OUT"; - - Map RESOURCE_NAME_CACHE = new ConcurrentHashMap<>(128); - - /** - * 提取资源名称,支持缓存 - * @param method 方法 - * @return 资源名称 - */ - default String extractResourceNameCache(Method method) { - String resourceName = RESOURCE_NAME_CACHE.get(method); - if (resourceName != null) { - return resourceName; - } - resourceName = extractResourceName(method); - RESOURCE_NAME_CACHE.put(method, resourceName); - return resourceName; - } - /** - * 提取资源名称 + * 解析资源名称 * @param method 方法 * @return 资源名称 */ - String extractResourceName(Method method); + String parseResourceName(Method method); /** * 解析方法路径 @@ -85,7 +64,6 @@ default HttpMethodPath parseHttpMethodPath(Method method) { PATCH patch = method.getAnnotation(PATCH.class); return new HttpMethodPath("PATCH", patch.value()); } - - return null; + throw new UnsupportedOperationException("unsupported method!" + method); } } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegrade.java index 0ab7b44..a9ad037 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegrade.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegrade.java @@ -10,6 +10,7 @@ public interface RetrofitDegrade extends Interceptor { String NONE = "none"; String SENTINEL = "sentinel"; + String RESILIENCE4J = "resilience4j"; /** * 对于指定Retrofit接口,是否允许降级 diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/release4j/Release4jRetrofitDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/release4j/Release4jRetrofitDegrade.java deleted file mode 100644 index cef8bba..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/release4j/Release4jRetrofitDegrade.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.degrade.release4j; - -import java.io.IOException; - -import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegrade; - -import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import okhttp3.Response; - -/** - * @author 陈添明 - * @since 2022/5/1 8:02 下午 - */ -public class Release4jRetrofitDegrade implements RetrofitDegrade { - - @Override - public boolean isEnableDegrade(Class retrofitInterface) { - return false; - } - - @Override - public void loadDegradeRules(Class retrofitInterface) { - CircuitBreakerRegistry registry = new CircuitBreakerRegistry.Builder() - .build(); - } - - @Override - public Response intercept(Chain chain) throws IOException { - return null; - } -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jDegrade.java new file mode 100644 index 0000000..8541f56 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jDegrade.java @@ -0,0 +1,92 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade.resilience4j; + +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_FAILURE_RATE_THRESHOLD; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_MINIMUM_NUMBER_OF_CALLS; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLIDING_WINDOW_SIZE; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLOW_CALL_DURATION_THRESHOLD; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLOW_CALL_RATE_THRESHOLD; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_WAIT_DURATION_IN_HALF_OPEN_STATE; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_WAIT_DURATION_IN_OPEN_STATE; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_WRITABLE_STACK_TRACE_ENABLED; +import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author yukdawn@gmail.com + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +@Documented +public @interface Resilience4jDegrade { + + /** + * 滑动窗口的类型 + */ + SlidingWindowType slidingWindowType() default SlidingWindowType.COUNT_BASED; + + /** + * 窗口的大小 + */ + int slidingWindowSize() default DEFAULT_SLIDING_WINDOW_SIZE; + + /** + * 在单位窗口内最少需要几次调用才能开始进行统计计算 + */ + int minimumNumberOfCalls() default DEFAULT_MINIMUM_NUMBER_OF_CALLS; + + /** + * 单位时间窗口内调用失败率达到多少后会启动断路器 + */ + float failureRateThreshold() default DEFAULT_FAILURE_RATE_THRESHOLD; + + /** + * 允许断路器自动由打开状态转换为半开状态 + */ + boolean enableAutomaticTransitionFromOpenToHalfOpen() default true; + + /** + * 在半开状态下允许进行正常调用的次数 + */ + int permittedNumberOfCallsInHalfOpenState() default DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE; + + /** + * 断路器打开状态转换为半开状态需要等待秒数 + */ + int waitDurationInOpenStateSeconds() default DEFAULT_WAIT_DURATION_IN_OPEN_STATE; + + /** + * 指定断路器应保持半开多长时间的等待持续时间,可选配置,大于1才是有效配置。 + */ + int maxWaitDurationInHalfOpenStateSeconds() default DEFAULT_WAIT_DURATION_IN_HALF_OPEN_STATE; + + /** + * 忽略的异常类列表,只有配置值之后才会加载。 + */ + Class[] ignoreExceptions() default {}; + + /** + * 记录的异常类列表,只有配置值之后才会加载。 + */ + Class[] recordExceptions() default {}; + + /** + * 慢调用比例阈值 + */ + float slowCallRateThreshold() default DEFAULT_SLOW_CALL_RATE_THRESHOLD; + + /** + * 慢调用阈值秒数,超过该秒数视为慢调用 + */ + int slowCallDurationThresholdSeconds() default DEFAULT_SLOW_CALL_DURATION_THRESHOLD; + + /** + * 启用可写堆栈跟踪的标志 + */ + boolean writableStackTraceEnabled() default DEFAULT_WRITABLE_STACK_TRACE_ENABLED; +} \ No newline at end of file diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jRetrofitDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jRetrofitDegrade.java new file mode 100644 index 0000000..d4328eb --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/resilience4j/Resilience4jRetrofitDegrade.java @@ -0,0 +1,98 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade.resilience4j; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import com.github.lianjiatech.retrofit.spring.boot.degrade.BaseRetrofitDegrade; +import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitBlockException; +import com.github.lianjiatech.retrofit.spring.boot.util.AnnotationExtendUtils; + +import io.github.resilience4j.circuitbreaker.CallNotPermittedException; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.core.StopWatch; +import okhttp3.Request; +import okhttp3.Response; +import retrofit2.Invocation; + +/** + * @author 陈添明 + * @since 2022/5/1 8:02 下午 + */ +public class Resilience4jRetrofitDegrade extends BaseRetrofitDegrade { + + protected final CircuitBreakerRegistry circuitBreakerRegistry; + + public Resilience4jRetrofitDegrade(CircuitBreakerRegistry circuitBreakerRegistry) { + this.circuitBreakerRegistry = circuitBreakerRegistry; + } + + @Override + public boolean isEnableDegrade(Class retrofitInterface) { + return AnnotationExtendUtils.isAnnotationPresentIncludeMethod(retrofitInterface, Resilience4jDegrade.class); + } + + @Override + public void loadDegradeRules(Class retrofitInterface) { + for (Method method : retrofitInterface.getMethods()) { + if (isDefaultOrStatic(method)) { + continue; + } + Resilience4jDegrade resilience4jDegrade = + AnnotationExtendUtils.findAnnotationIncludeClass(method, Resilience4jDegrade.class); + if (resilience4jDegrade == null) { + continue; + } + // 断路器配置 + CircuitBreakerConfig.Builder builder = CircuitBreakerConfig.custom() + .waitDurationInOpenState(Duration.ofSeconds(resilience4jDegrade.waitDurationInOpenStateSeconds())) + .permittedNumberOfCallsInHalfOpenState(resilience4jDegrade.permittedNumberOfCallsInHalfOpenState()) + .slidingWindowSize(resilience4jDegrade.slidingWindowSize()) + .slidingWindowType(resilience4jDegrade.slidingWindowType()) + .minimumNumberOfCalls(resilience4jDegrade.minimumNumberOfCalls()) + .failureRateThreshold(resilience4jDegrade.failureRateThreshold()) + .ignoreExceptions(resilience4jDegrade.ignoreExceptions()) + .recordExceptions(resilience4jDegrade.recordExceptions()) + .automaticTransitionFromOpenToHalfOpenEnabled( + resilience4jDegrade.enableAutomaticTransitionFromOpenToHalfOpen()) + .slowCallRateThreshold(resilience4jDegrade.slowCallRateThreshold()) + .slowCallDurationThreshold( + Duration.ofSeconds(resilience4jDegrade.slowCallDurationThresholdSeconds())) + .writableStackTraceEnabled(resilience4jDegrade.writableStackTraceEnabled()); + + if (resilience4jDegrade.maxWaitDurationInHalfOpenStateSeconds() > 0) { + builder.maxWaitDurationInHalfOpenState( + Duration.ofSeconds(resilience4jDegrade.maxWaitDurationInHalfOpenStateSeconds())); + } + circuitBreakerRegistry.circuitBreaker(parseResourceName(method), builder.build()); + } + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Method method = Objects.requireNonNull(request.tag(Invocation.class)).method(); + CircuitBreaker circuitBreaker = + circuitBreakerRegistry.find(parseResourceName(method)).orElse(null); + if (Objects.isNull(circuitBreaker)) { + // 断路器为空则直接调用返回 + return chain.proceed(request); + } + StopWatch stopWatch = StopWatch.start(); + try { + circuitBreaker.acquirePermission(); + Response response = chain.proceed(request); + circuitBreaker.onResult(stopWatch.stop().toNanos(), TimeUnit.NANOSECONDS, response); + return response; + } catch (CallNotPermittedException e) { + throw new RetrofitBlockException(e); + } catch (Throwable throwable) { + circuitBreaker.onError(stopWatch.stop().toNanos(), TimeUnit.NANOSECONDS, throwable); + throw throwable; + } + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelDegrade.java index ca96b44..bcc5c74 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelDegrade.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelDegrade.java @@ -17,7 +17,7 @@ /** * 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N) */ - double count(); + double count() default 1000; /** * 熔断时长,单位为 s diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelRetrofitDegrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelRetrofitDegrade.java index 41135c4..440549b 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelRetrofitDegrade.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/sentinel/SentinelRetrofitDegrade.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -14,9 +13,8 @@ import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.github.lianjiatech.retrofit.spring.boot.degrade.ResourceNameParser; +import com.github.lianjiatech.retrofit.spring.boot.degrade.BaseRetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitBlockException; -import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.util.AnnotationExtendUtils; import okhttp3.Request; @@ -26,18 +24,12 @@ /** * @author 陈添明 */ -public class SentinelRetrofitDegrade implements RetrofitDegrade { - - protected final ResourceNameParser resourceNameParser; - - public SentinelRetrofitDegrade(ResourceNameParser resourceNameParser) { - this.resourceNameParser = resourceNameParser; - } +public class SentinelRetrofitDegrade extends BaseRetrofitDegrade { @Override public boolean isEnableDegrade(Class retrofitInterface) { // 类或者方法上存在@SentinelDegrade -> 允许降级 - return AnnotationExtendUtils.isAnnotationPresent(retrofitInterface, SentinelDegrade.class); + return AnnotationExtendUtils.isAnnotationPresentIncludeMethod(retrofitInterface, SentinelDegrade.class); } @Override @@ -45,15 +37,12 @@ public void loadDegradeRules(Class retrofitInterface) { Method[] methods = retrofitInterface.getMethods(); List rules = new ArrayList<>(); for (Method method : methods) { - if (method.isDefault()) { - continue; - } - int modifiers = method.getModifiers(); - if (Modifier.isStatic(modifiers)) { + if (isDefaultOrStatic(method)) { continue; } // 获取熔断配置 - SentinelDegrade sentinelDegrade = AnnotationExtendUtils.findAnnotation(method, SentinelDegrade.class); + SentinelDegrade sentinelDegrade = + AnnotationExtendUtils.findAnnotationIncludeClass(method, SentinelDegrade.class); if (sentinelDegrade == null) { continue; } @@ -61,20 +50,21 @@ public void loadDegradeRules(Class retrofitInterface) { .setCount(sentinelDegrade.count()) .setTimeWindow(sentinelDegrade.timeWindow()) .setGrade(sentinelDegrade.grade()); - degradeRule.setResource(resourceNameParser.extractResourceNameCache(method)); + degradeRule.setResource(parseResourceName(method)); rules.add(degradeRule); } DegradeRuleManager.loadRules(rules); } + @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Method method = Objects.requireNonNull(request.tag(Invocation.class)).method(); - if (AnnotationExtendUtils.findAnnotation(method, SentinelDegrade.class) == null) { + if (AnnotationExtendUtils.findAnnotationIncludeClass(method, SentinelDegrade.class) == null) { return chain.proceed(request); } - String resourceName = resourceNameParser.extractResourceNameCache(method); + String resourceName = parseResourceName(method); Entry entry = null; try { entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/RetryInterceptor.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/RetryInterceptor.java index b6bce8a..33db520 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/RetryInterceptor.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/RetryInterceptor.java @@ -33,7 +33,7 @@ public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Method method = Objects.requireNonNull(request.tag(Invocation.class)).method(); // 获取重试配置 - Retry retry = AnnotationExtendUtils.findAnnotation(method, Retry.class); + Retry retry = AnnotationExtendUtils.findAnnotationIncludeClass(method, Retry.class); if (!needRetry(retry)) { return chain.proceed(request); } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/util/AnnotationExtendUtils.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/util/AnnotationExtendUtils.java index 4553334..78ed35c 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/util/AnnotationExtendUtils.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/util/AnnotationExtendUtils.java @@ -13,13 +13,13 @@ public class AnnotationExtendUtils { /** - * 查找方法上的Annotation,如果不存在,则查找类上的。 + * 查找方法及其类上的指定注解,优先返回方法上的。 + * @param 注解泛型参数 * @param method 方法 * @param annotationType 注解类型 - * @param 注解泛型参数 - * @return 方法或者类上指定的注解。 + * @return 方法及其类上的指定注解。 */ - public static A findAnnotation(Method method, Class annotationType) { + public static A findAnnotationIncludeClass(Method method, Class annotationType) { A annotation = method.getAnnotation(annotationType); if (annotation != null) { return annotation; @@ -28,13 +28,14 @@ public static A findAnnotation(Method method, Class an } /** - * 判断某个类上指定的Annotation是否存在。如果类上不存在,则继续判断每个公有方法是否存在。 + * 判断某个类及其公有方法上是否存在指定注解。 + * @param 注解泛型参数 * @param clazz 类 * @param annotationType 注解类型 - * @param 注解泛型参数 - * @return 某个类上指定的Annotation是否存在。类或公有方法存在,则返回true。 + * @return 判断某个类及其公有方法上是否存在指定注解。 */ - public static boolean isAnnotationPresent(Class clazz, Class annotationType) { + public static boolean isAnnotationPresentIncludeMethod(Class clazz, + Class annotationType) { if (clazz.isAnnotationPresent(annotationType)) { return true; } diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeR4jTest.java similarity index 76% rename from src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeTest.java rename to src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeR4jTest.java index dbbde48..d5ac9be 100644 --- a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeTest.java +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeR4jTest.java @@ -1,16 +1,17 @@ package com.github.lianjiatech.retrofit.spring.boot.test; import java.io.IOException; -import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import com.fasterxml.jackson.annotation.JsonInclude; @@ -18,21 +19,21 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; -import com.github.lianjiatech.retrofit.spring.boot.test.http.DegradeApi; +import com.github.lianjiatech.retrofit.spring.boot.test.http.DegradeR4jApi; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; /** - * @author 陈添明 - * @since 2022/1/21 4:20 下午 + * @author yukdawn@gmail.com */ +@ActiveProfiles("r4j") @SpringBootTest(classes = RetrofitTestApplication.class) @RunWith(SpringRunner.class) -public class DegradeTest { +public class DegradeR4jTest { @Autowired - private DegradeApi degradeApi; + private DegradeR4jApi degradeR4jApi; private static final ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) @@ -55,14 +56,13 @@ public void after() throws IOException { } @Test - public void test() throws IOException { - Random random = new Random(System.currentTimeMillis()); - IntStream.range(0, 1000).parallel().forEach((i) -> { + public void testDegradeR4j() { + long count = IntStream.range(0, 100).parallel().map((i) -> { try { Person mockPerson = new Person().setId(1L) .setName("test") .setAge(10); - Result mockResult = new Result<>() + Result mockResult = new Result<>() .setCode(0) .setMsg("ok") .setData(mockPerson); @@ -71,14 +71,16 @@ public void test() throws IOException { .addHeader("Content-Type", "application/text; charset=utf-8") .addHeader("Cache-Control", "no-cache") .setBody(objectMapper.writeValueAsString(mockResult)) - .setHeadersDelay(random.nextInt(1000), TimeUnit.MILLISECONDS); + .setBodyDelay(10, TimeUnit.SECONDS); server.enqueue(response); - System.out.println(degradeApi.getPerson(2L).getCode()); + return degradeR4jApi.getPerson1(2L).getCode(); } catch (Exception e) { - System.out.println("抛出异常:" + e.getMessage()); - } finally { - System.out.println("当前请求轮次: " + (i + 1)); + return 100; } - }); + }).filter(i -> i == -1).count(); + System.out.println(count); + Assert.assertTrue(count > 70L); + } -} + +} \ No newline at end of file diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeSentinelTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeSentinelTest.java new file mode 100644 index 0000000..025d74a --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/DegradeSentinelTest.java @@ -0,0 +1,86 @@ +package com.github.lianjiatech.retrofit.spring.boot.test; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; +import com.github.lianjiatech.retrofit.spring.boot.test.http.DegradeSentinelApi; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +/** + * @author yukdawn@gmail.com + */ +@ActiveProfiles("sentinel") +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +public class DegradeSentinelTest { + + @Autowired + private DegradeSentinelApi degradeSentinelApi; + + private static final ObjectMapper objectMapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + private MockWebServer server; + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + @Test + public void testDegrade() { + long count = IntStream.range(0, 100).parallel().map((i) -> { + try { + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/text; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(objectMapper.writeValueAsString(mockResult)) + .setBodyDelay(5, TimeUnit.SECONDS); + server.enqueue(response); + return degradeSentinelApi.getPerson1(2L).getCode(); + } catch (Exception e) { + return 100; + } + }).filter(i -> i == -1).count(); + System.out.println(count); + Assert.assertTrue(count > 80L); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeApi.java deleted file mode 100644 index 37307df..0000000 --- a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeApi.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.test.http; - -import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; -import com.github.lianjiatech.retrofit.spring.boot.degrade.sentinel.SentinelDegrade; -import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; -import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; - -import retrofit2.http.GET; -import retrofit2.http.Query; - -/** - * @author 陈添明 - * @since 2022/1/21 4:19 下午 - */ -@RetrofitClient(baseUrl = "${test.baseUrl}") -@SentinelDegrade(count = 100) -public interface DegradeApi { - - /** - * 其他任意Java类型
- * 将响应体内容适配成一个对应的Java类型对象返回,如果http状态码不是2xx,直接抛错!
- * - * @param id id - * @return 其他任意Java类型 - */ - @GET("person") - Result getPerson(@Query("id") Long id); - -} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeR4jApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeR4jApi.java new file mode 100644 index 0000000..535f62e --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeR4jApi.java @@ -0,0 +1,68 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.http; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.degrade.FallbackFactory; +import com.github.lianjiatech.retrofit.spring.boot.degrade.resilience4j.Resilience4jDegrade; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}", fallbackFactory = DegradeR4jApi.HttpDegradeFallbackFactory.class) +@Resilience4jDegrade(slidingWindowType = CircuitBreakerConfig.SlidingWindowType.TIME_BASED, minimumNumberOfCalls = 10, + permittedNumberOfCallsInHalfOpenState = 5) +public interface DegradeR4jApi { + + @GET("degrade/person1") + Result getPerson1(@Query("id") Long id); + + @Resilience4jDegrade(slidingWindowType = CircuitBreakerConfig.SlidingWindowType.TIME_BASED, + failureRateThreshold = 30, minimumNumberOfCalls = 10, permittedNumberOfCallsInHalfOpenState = 5) + @GET("degrade/person2") + Result getPerson2(@Query("id") Long id); + + @Service + class HttpDegradeFallbackFactory implements FallbackFactory { + Logger log = LoggerFactory.getLogger(HttpDegradeFallbackFactory.class); + + /** + * Returns an instance of the fallback appropriate for the given cause + * + * @param cause fallback cause + * @return 实现了retrofit接口的实例。an instance that implements the retrofit interface. + */ + @Override + public DegradeR4jApi create(Throwable cause) { + log.error("触发熔断了! ", cause.getMessage(), cause); + return new DegradeR4jApi() { + @Override + public Result getPerson1(Long id) { + Result fallback = new Result<>(); + fallback.setCode(-1) + .setMsg("熔断Person1") + .setData(new Person()); + return fallback; + } + + @Override + public Result getPerson2(Long id) { + Result fallback = new Result<>(); + fallback.setCode(-1) + .setMsg("熔断Person2") + .setData(new Person()); + return fallback; + } + }; + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeSentinelApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeSentinelApi.java new file mode 100644 index 0000000..144e2d0 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/DegradeSentinelApi.java @@ -0,0 +1,69 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.http; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.degrade.FallbackFactory; +import com.github.lianjiatech.retrofit.spring.boot.degrade.sentinel.SentinelDegrade; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; + +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}", fallbackFactory = DegradeSentinelApi.HttpDegradeFallbackFactory.class) +@SentinelDegrade +public interface DegradeSentinelApi { + + /** + * @param id . + * @return . + */ + @GET("degrade/person1") + Result getPerson1(@Query("id") Long id); + + @SentinelDegrade(count = 5, timeWindow = 10) + @GET("degrade/person2") + Result getPerson2(@Query("id") Long id); + + @Service + class HttpDegradeFallbackFactory implements FallbackFactory { + Logger log = LoggerFactory.getLogger(HttpDegradeFallbackFactory.class); + + /** + * Returns an instance of the fallback appropriate for the given cause + * + * @param cause fallback cause + * @return 实现了retrofit接口的实例。an instance that implements the retrofit interface. + */ + @Override + public DegradeSentinelApi create(Throwable cause) { + log.error("触发熔断了! ", cause.getMessage(), cause); + return new DegradeSentinelApi() { + @Override + public Result getPerson1(Long id) { + Result fallback = new Result<>(); + fallback.setCode(-1) + .setMsg("熔断Person1") + .setData(new Person()); + return fallback; + } + + @Override + public Result getPerson2(Long id) { + Result fallback = new Result<>(); + fallback.setCode(-1) + .setMsg("熔断Person2") + .setData(new Person()); + return fallback; + } + }; + } + } + +} \ No newline at end of file diff --git a/src/test/resources/application-r4j.yml b/src/test/resources/application-r4j.yml new file mode 100644 index 0000000..e240f23 --- /dev/null +++ b/src/test/resources/application-r4j.yml @@ -0,0 +1,5 @@ +retrofit: + # 熔断降级配置 + degrade: + # 熔断降级类型。默认none,表示不启用熔断降级 + degrade-type: resilience4j diff --git a/src/test/resources/application-sentinel.yml b/src/test/resources/application-sentinel.yml new file mode 100644 index 0000000..7513c5f --- /dev/null +++ b/src/test/resources/application-sentinel.yml @@ -0,0 +1,5 @@ +retrofit: + # 熔断降级配置 + degrade: + # 熔断降级类型。默认none,表示不启用熔断降级 + degrade-type: sentinel diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 7146113..5b46259 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -43,9 +43,7 @@ retrofit: # 熔断降级配置 degrade: - # 是否启用熔断降级 - enable: true - # 熔断降级实现方式 + # 熔断降级类型。默认none,表示不启用熔断降级 degrade-type: sentinel # 全局连接超时时间 global-connect-timeout-ms: 5000 diff --git a/src/test/resources/default-config.yml b/src/test/resources/default-config.yml index 303ab9c..4081ae1 100644 --- a/src/test/resources/default-config.yml +++ b/src/test/resources/default-config.yml @@ -41,9 +41,7 @@ retrofit: # 熔断降级配置 degrade: - # 是否启用熔断降级 - enable: false - # 熔断降级实现方式 + # 熔断降级类型。默认none,表示不启用熔断降级 degrade-type: none # 全局连接超时时间