shigen
日更文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。
🧑💻🧑💻🧑💻Make things different and more efficient
接近凌晨了,今天的稿子还没来得及写,甚是焦虑,于是熬了一个夜也的给它写完。正如我的题目所说:《自定义注解实现服务动态开关》,接下来和shigen
一起来揭秘吧。
前言
在shigen
实习的时候,遇到了业务场景:**实现服务的动态开关,避免redis的内存被打爆了。**当时的第一感受就是这个用nacos
配置一下不就可以了,nacos
不就是有一个注解refreshScope
,配置中心的配置文件更新了,服务动态的更新。当时实现是这样的:
在我的nacos
上这样配置的:
service:enable: true
那对应的java
部分的代码就是这样的:
class Service {@Value("service.enable")private boolean serviceEnable;public void method() {if (!serviceEnable) {return;}// 业务逻辑}
}
貌似这样是可以的,因为我们只需要动态的观察数据的各项指标,遇到了快要打挂的情况,直接把布尔值换成false
即可。
但是不优雅,我们来看看有什么不优雅的:
- 配置的动态刷新是有延迟的。
nacos
的延迟是依赖于网络的; - 不亲民。万一哪个开发改坏了配置,服务就是彻底的玩坏了;而且,如果业务想做一个动态的配置,任何人都可以在系统上点击开关,类似于下边的操作:
nacos
配置的方式直接不可行了!
那给予以上的问题,相信部分的伙伴已经思考到了:那我把配置放在redis
中呗,内存数据库,直接用外部接口控制数据。
很好,这种想法打开了今天的设计思路。我们先协一点伪代码:
@getMapping(value="switch")
public Integer switch() {Integer status = redisTemplate.get("key");if (status == 1) {status = 0;} else {status = 1;}redisTemplate.set("key", status);return status;
}@getMapping(value= "pay")
public Result pay() {Integer status = redisTemplate.get("key");if (status ==0) {throw new Bizexception("服务不可用");} else {doSometing();}
}
貌似超级完美了,但是想过没有,业务的侵入很大呢。而且,万一我的业务拓展了,别的地方也需要这样的配置,岂不是直接复制粘贴?那就到此为止吧。
我觉得任何业务的设计都是需要去思考的,一味的写代码,做着CRUD的各种操作,简直是等着被AI取代吧。
那接下来分享shigen
的设计,带着大家从我的视角分析我的思考和设计点、关注点。
代码设计
注解设计
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceSwitch {String switchKey();String message() default "当前业务已关闭,请稍后再试!";}
我在设计的时候,考虑到了不同的业务模块和失败的信息,这些都可以抽取出来,在使用的时候,直接加上注解即可。具体的方法和拦截,我们采用spring的AOP来做。
常量类
public class Constants {public static final String ON = "1";public static final String OFF = "0";public static class Service {public static final String ORDER = "service-order";public static final String PAY = "service-pay";}}
既然涉及到了业务模块和状态值,那配置一个常量类是再合适不过了。
业务代码
@ServiceSwitch(switchKey = Constants.Service.PAY)public Result pay() {log.info("paying now");return Result.success();}
业务代码上,我们肯定喜欢这样的设计,直接加上一个注解标注我们想要控制的模块。
请注意,核心点来了,我们注解的AOP怎么设计?
AOP设计
老方式,我们先看一下代码:
@Aspect
@Component
@Slf4j
public class ServiceSwitchAOP {@Resourceprivate RedisTemplate<String, String> redisTemplate;/*** 定义切点,使用了@ServiceSwitch注解的类或方法都拦截 需要用注解的全路径*/@Pointcut("@annotation(main.java.com.shigen.redis.annotation.ServiceSwitch)")public void pointcut() {}@Around("pointcut()")public Object around(ProceedingJoinPoint point) {// 获取被代理的方法的参数Object[] args = point.getArgs();// 获取被代理的对象Object target = point.getTarget();// 获取通知签名MethodSignature signature = (MethodSignature) point.getSignature();try {// 获取被代理的方法Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());// 获取方法上的注解ServiceSwitch annotation = method.getAnnotation(ServiceSwitch.class);// 核心业务逻辑if (annotation != null) {String switchKey = annotation.switchKey();String message = annotation.message();/*** 配置项: 可以存储在mysql、redis 数据字典*/String configVal = redisTemplate.opsForValue().get(switchKey);if (Constants.OFF.equals(configVal)) {// 开关关闭,则返回提示。return new Result(HttpStatus.FORBIDDEN.value(), message);}}// 放行return point.proceed(args);} catch (Throwable e) {throw new RuntimeException(e.getMessage(), e);}}
}
拦截我的注解,实现一个切点,之后通知切面进行操作。在切面的操作上,我们读取注解的配置,然后从redis中拿取对应的服务状态。如果服务的状态是关闭的,直接返回我们自定义的异常类型;服务正常的话,继续进行操作。
接口测试
最后,我写了两个接口实现了服务的调用和服务模块状态值的切换。
@RestController
@RequestMapping(value = "serviceSwitch")
public class ServiceSwitchTestController {@Resourceprivate ServiceSwitchService serviceSwitchService;@GetMapping(value = "pay")public Result pay() {return serviceSwitchService.pay();}@GetMapping(value = "switch")public Result serviceSwitch(@RequestParam(value = "status", required = false) String status) {serviceSwitchService.switchService(status);return Result.success();}
}
代码测试
测试服务正常
此时,redis中服务的状态值是1,服务也可以正常的调用。
测试服务不正常
我们先调用接口,改变服务的状态:
再次调用服务:
发现服务403错误,已经不能调用了。我们改变一下状态,服务又可以用了,这里就不做展示了。
以上就是今天分享的全部内容了,觉得不错的话,记得点赞 在看 关注
支持一下哈,您的鼓励和支持将是shigen
坚持日更的动力。同时,shigen
在多个平台都有文章的同步,也可以同步的浏览和订阅:
平台 | 账号 | 链接 |
---|---|---|
CSDN | shigen01 | shigen的CSDN主页 |
知乎 | gen-2019 | shigen的知乎主页 |
掘金 | shigen01 | shigen的掘金主页 |
腾讯云开发者社区 | shigen | shigen的腾讯云开发者社区主页 |
微信公众平台 | shigen | 公众号名:shigen |
与shigen
一起,每天不一样!