java业务代码自动降级实现
业务需求
因业务需要,需要对调用其他部门的接口,做自动降级逻辑的实现,也就是说,在其他部门的接口发生异常的时候,需要进行降级处理,并且降级逻辑需要可配置可控,并且可以按照部门维度进行降级。
需要实现的功能
手动触发开关进行开启和关闭降级
比较传统的做法是使用Hystrix,步骤如下
- 项目中引入 Hystrix 依赖
- 项目启动类添加 @EnableHystrix 修饰开启 Hystrix
- 设置指定需要降级处理的方法,提供降级方法
- 在配置中心增加配置进行降级的开关
但是Hystrix的功能比较庞大,例如隔离、熔断、降级机制等等,本需求只需要降级和按部门维度降级。故决定自己实现一个简易版的降级功能。
降级开关没开启的时候,如果可降级的方法异常则自动降级
一般的,降级其实就是一个兜底的过程,但是往往异常是不可预知的,所以,如果可以降级的方法发生了异常,需要自动降级,这一点Hystrix的功能完全能够满足。但是Hystrix学习成本比较高,我们要做的就是发生异常就降级,很简单就能实现。
具体实现方式
自定义降级注解
我们设想的是,在需要降级的方法上,加上一个注解,就能够实现降级的全部功能。所以先定义一个注解。
package com.f4.ts.enterprise.degrade;
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 tengqy
* @create 2022-04-21 8:35
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnterpriseDegrade {
/**
* 是否降级
*
* @return
*/
boolean value() default true;
/**
* 需要降级的部门接口
* 假设有部门A和部门B
* 部门A有接口C和D
* 部门B有接口E和F
* 则在C和D的方法上配置A,并且降级开关打开的时候,就会直接降级接口C和D
* @return
*/
String[] bu() default "";
/**
* 降级日志描述,方便定位问题
*
* @return
*/
String desc() default "";
}
使用方式类似如下:
@EnterpriseDegrade(bu = {A}, desc = "不进行接口C的调用")
public Response C() {}
切面和切点配置
定义好了注解,则需要定义注解的处理方法,也就是切面和切点的配置,以及是进行何种处理,是前置处理还是后置处理还是环绕处理。
代码如下:
package com.f4.ts.enterprise.degrade;
import cn.hutool.core.util.ReflectUtil;
import com.f4.ts.enterprise.config.DegradeConfig;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 降级切面实现
*
* @author tengqy
* @create 2022-04-21 8:37
*/
@Aspect
@Slf4j
@Component
@SuppressWarnings("all")
public class EnterpriseDegradeAspect {
@Autowired
private DegradeConfig degradeConfig;
public EnterpriseDegradeAspect() {
}
@Pointcut("@annotation(com.f4.ts.enterprise.degrade.EnterpriseDegrade)")
public void annotationPointcut() {
}
@Around("annotationPointcut() && @annotation(EnterpriseDegrade)")
public Object doAround1(ProceedingJoinPoint joinPoint) throws Throwable {
if (degradeConfig.getStartDegrade()) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String name = method.getName();
EnterpriseDegrade annotation = method.getAnnotation(EnterpriseDegrade.class);
String[] bu = annotation.bu();
if (Boolean.TRUE.equals(annotation.value())) {
if (bu != null && bu.length > 0) {
for (String s : bu) {
if (degradeConfig.getBus().contains(s)) {
//指定的部门接口需要降级
}
}
}
}
}
return joinPoint.proceed();
}
}
以上代码,就是在配置了EnterpriseDegrade的方法上,进行环绕处理,其中degradeConfig使用的Apollo的配置,bus为需要进行降级的部门集合,startDegrade为是否开启降级
@Value("#{'${degrade.bus}'.split(',')}")
private List<String> bus;
@Value("${degrade.startDegrade:false}")
private Boolean startDegrade;
配置降级后需要调用的方法
在使用Hystrix的时候我们经常会这样配置降级方法
@HystrixCommand(fallbackMethod = "indexError")
public Object index() {
return restTemplate.getForObject("http://testservice", String.class);
}
public Object indexError() {
return "{\"code\": 999,\"message\": \"服务断路\"}";
}
在index方法发生异常并降级后,会自动调用indexError的方法,进行降级处理。
所以我们也这样使用,但是我们固定一下,如果方法A调用失败后,自动调用A的降级方法ADegrade,固定为原方法名称+“Degrade”,这样我们就省略了fallbackMethod的配置
则原降级切面方法配置代码如下:
package com.f4.ts.enterprise.degrade;
import cn.hutool.core.util.ReflectUtil;
import com.f4.ts.enterprise.config.DegradeConfig;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 企业降低切面
* 降级切面实现
*
* @author tengqy
* @create 2022-04-21 8:37
* @date 2022/04/22
*/
@Aspect
@Slf4j
@Component
public class EnterpriseDegradeAspect {
/**
* 降级配置
*/
@Autowired
private DegradeConfig degradeConfig;
public EnterpriseDegradeAspect() {
}
/**
* 切入点
*/
@Pointcut("@annotation(com.f4.ts.enterprise.degrade.EnterpriseDegrade)")
public void annotationPointcut() {
}
/**
*
*
* @param joinPoint 连接点
* @return {@link Object}
* @throws Throwable throwable
*/
@Around("annotationPointcut() && @annotation(EnterpriseDegrade)")
public Object doAround1(ProceedingJoinPoint joinPoint) throws Throwable {
if (degradeConfig.getStartDegrade()) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String name = method.getName();
EnterpriseDegrade annotation = method.getAnnotation(EnterpriseDegrade.class);
String[] bu = annotation.bu();
String desc = annotation.desc();
if (Boolean.TRUE.equals(annotation.value())) {
if (bu != null && bu.length > 0) {
for (String s : bu) {
if (degradeConfig.getBus().contains(s)) {
//指定的bu需要降级
Method actualMethod = ReflectUtil.getMethodByName(joinPoint.getTarget().getClass(), name + "Degrade");
actualMethod.setAccessible(Boolean.TRUE);
log.info("开始降级,方法为:{},参数为:{},bu为:{},具体描述为:{}", method.getName(), joinPoint.getArgs(), s, desc);
return actualMethod.invoke(joinPoint.getTarget(), joinPoint.getArgs());
}
}
}
}
}
//开关关闭,不降级则调用原始方法
return joinPoint.proceed();
}
}
使用方法
//原始业务方法
@EnterpriseDegrade(bu = {A}, desc = "不进行接口C的调用")
public Response C() {}
//降级后调用的方法
public Response CDegrade() {}
实现自动降级
在某些情况下,我们要实现在原始方法发生异常的时候,自动进行降级处理,比如接口调用超时等异常,一种方法是,在每一个方法里都进行手动try catch,然后手动调用降级后的方法,这样通俗易懂,但是需要在每一个方法里面都修改,不优雅,既然我们已经使用的注解和切面切点来进行降级,那么为何不进一步加个异常处理呢?具体实现方法代码如下
package com.f4.ts.enterprise.degrade;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.dubbo.rpc.RpcException;
import com.f4.ts.enterprise.config.DegradeConfig;
import com.f4.ts.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.ResourceAccessException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* 降级切面实现
*
* @author tengqy
* @create 2022-04-21 8:37
* @date 2022/04/22
*/
@Aspect
@Slf4j
@Component
public class EnterpriseDegradeAspect {
/**
* 降级配置
*/
@Autowired
private DegradeConfig degradeConfig;
public EnterpriseDegradeAspect() {
}
@Pointcut("@annotation(com.f4.ts.enterprise.degrade.EnterpriseDegrade)")
public void annotationPointcut() {
}
/**
*
* @param joinPoint 连接点
* @return {@link Object}
* @throws Throwable throwable
*/
@Around("annotationPointcut() && @annotation(EnterpriseDegrade)")
public Object doAround1(ProceedingJoinPoint joinPoint) throws Throwable {
if (degradeConfig.getStartDegrade()) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String name = method.getName();
EnterpriseDegrade annotation = method.getAnnotation(EnterpriseDegrade.class);
String[] bu = annotation.bu();
String desc = annotation.desc();
if (Boolean.TRUE.equals(annotation.value())) {
if (bu != null && bu.length > 0) {
for (String s : bu) {
if (degradeConfig.getBus().contains(s)) {
//指定的bu需要降级
Method actualMethod = ReflectUtil.getMethodByName(joinPoint.getTarget().getClass(), name + "Degrade");
actualMethod.setAccessible(Boolean.TRUE);
log.info("开始降级,方法为:{},参数为:{},bu为:{},具体描述为:{}", method.getName(), joinPoint.getArgs(), s, desc);
return actualMethod.invoke(joinPoint.getTarget(), joinPoint.getArgs());
}
}
}
}
}
try {
return joinPoint.proceed();
} catch (ResourceAccessException e) {
return doInvokeAfterException(joinPoint, e);
}
}
/**
* 可降级的方法调用异常,自动降级
* @param joinPoint join
* @param e e
* @return Object
* @throws IllegalAccessException i
* @throws InvocationTargetException i
*/
private Object doInvokeAfterException(ProceedingJoinPoint joinPoint, Exception e) throws IllegalAccessException, InvocationTargetException {
log.error("可降级的方法调用异常,自动降级开启 ", e);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String name = method.getName();
EnterpriseDegrade annotation = method.getAnnotation(EnterpriseDegrade.class);
String desc = annotation.desc();
Method actualMethod = ReflectUtil.getMethodByName(joinPoint.getTarget().getClass(), name + "Degrade");
actualMethod.setAccessible(Boolean.TRUE);
log.info("开始自动降级,方法为:{},参数为:{},具体描述为:{}", method.getName(), joinPoint.getArgs(), desc);
return actualMethod.invoke(joinPoint.getTarget(), joinPoint.getArgs());
}
}
至此,我们就实现了一个能自动降级、能开关控制降级、能按照部门维度降级,只需要加注解和写一个降级方法的全局降级工具了。
作者:tengqingya
转载请保留出处