环境:SpringBoot2.7.12
本文将介绍如何为API接口动态添加开关功能。通过这个功能,我们可以控制API接口的正常访问或显示提示信息。这有助于在开发和维护过程中更好地管理和控制API接口的行为。通过使用开关功能,我们可以轻松地在不同情况下调整接口的行为,提高系统的灵活性和可维护性。
为什么要给API接口添加开关功能呢?从以下几方面考虑:
首先,定义一个AOP切面(Aspect),该切面负责控制接口(Controller)的开关行为。
在切面中,我们可以使用Spring AOP的切入点(Pointcut)来指定需要拦截的方法。一旦方法被拦截,我们可以在切面的通知(Advice)中定义相应的默认行为。接下来我们将一步一步的实现接口开关功能。
@Target({ElementType.TYPE, ElementType.METHOD})public @interface ApiSwitch { /**接口对应的key,通过可以该key查询接口是否关闭*/ String key() default "" ; /**解析器beanName,通过具体的实现获取key对应的值*/ String resolver() default "" ; /**开启后降级方法名*/ String fallback() default "" ; }
public interface SwitchResolver { boolean resolver(String key) ; public void config(String key, Integer onoff) ; }
@Componentpublic class ConcurrentMapResolver implements SwitchResolver { private Map<String, Integer> keys = new ConcurrentHashMap<>() ; @Override public boolean resolver(String key) { Integer value = keys.get(key) ; return value == null ? false : (value == 1) ; } public void config(String key, Integer onoff) { keys.put(key, onoff) ; }}
@Componentpublic class RedisResolver implements SwitchResolver { private final StringRedisTemplate stringRedisTemplate ; public RedisResolver(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate ; } @Override public boolean resolver(String key) { String value = this.stringRedisTemplate.opsForValue().get(key) ; return !(value == null || "0".equals(value)) ; } @Override public void config(String key, Integer onoff) { this.stringRedisTemplate.opsForValue().set(key, String.valueOf(onoff)) ; }}
这里就提供两种默认的实现。
@Component@Aspectpublic class ApiSwitchAspect implements ApplicationContextAware { private ApplicationContext context ; private final SwitchProperties switchProperties ; public static final Map<String, Class<? extends SwitchResolver>> MAPPINGS; static { // 初始化所有的解析器 Map<String, Class<? extends SwitchResolver>> mappings = new HashMap<>() ; mappings.put("map", ConcurrentMapResolver.class) ; mappings.put("redis", RedisResolver.class) ; MAPPINGS = Collections.unmodifiableMap(mappings) ; } public ApiSwitchAspect(SwitchProperties switchProperties) { this.switchProperties = switchProperties ; } @Pointcut("@annotation(apiSwitch)") private void onoff(ApiSwitch apiSwitch) {} @Around("onoff(apiSwitch)") public Object ctl(ProceedingJoinPoint pjp, ApiSwitch apiSwitch) throws Throwable { // 对应接口开关的key String key = apiSwitch.key() ; // 解析器bean的名称 String resolverName = apiSwitch.resolver() ; // 降级方法名 String fallback = apiSwitch.fallback() ; SwitchResolver resolver = null ; // 根据指定的beanName获取具体的解析器;以下都不考虑不存在的情况 if (StringUtils.hasLength(resolverName)) { resolver = this.context.getBean(resolverName, SwitchResolver.class) ; } else { resolver = this.context.getBean(MAPPINGS.get(this.switchProperties.getResolver())) ; } // 解析器不存在则直接调用目标接口 if (resolver == null || !resolver.resolver(key)) { return pjp.proceed() ; } // 调用降级的方法;关于降级的方法简单点,都必须在当前接口类中,同时还不考虑方法参数的情况 if (!StringUtils.hasLength(fallback)) { // 未配置的情况 return "接口不可用" ; } Class<?> clazz = pjp.getSignature().getDeclaringType() ; Method fallbackMethod = clazz.getDeclaredMethod(fallback) ; return fallbackMethod.invoke(pjp.getTarget()) ; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext ; } }
@GetMapping("/onoff/{state}")public Object onoff(String key, @PathVariable("state") Integer state) { String resolverType = switchProperties.getResolver(); if (!StringUtils.hasLength(resolverType)) { SwitchResolver bean = this.context.getBean(ApiSwitchAspect.MAPPINGS.get("map")) ; if (bean instanceof ConcurrentMapResolver resolver) { resolver.config(key, state) ; } } else { SwitchResolver resolver = this.context.getBean(ApiSwitchAspect.MAPPINGS.get(resolverType)) ; resolver.config(key, state) ; } return "success" ; }
通过该接口修改具体哪个接口的开关状态。(注意:这里有小问题,如果接口上指定了resolver类型且配置文件中指定的类型不一致,就会出现不生效问题。这个问题大家可以自行解决)
@GetMapping("/q1")@ApiSwitch(key = "swtich$q1", fallback = "q1_fallback", resolver = "redisResolver")public Object q1() { return "q1" ;}public Object q1_fallback() { return "接口维护中" ;}
这是完整的配置示例,这里除了key必须外,其它的都可以不填写。
具体测试结果就不贴了,大家可以自行测试基于jvm内存和redis的方式。
总结:通过上述介绍,我们可以看到使用Spring AOP实现接口的开关功能是一种非常有效的方法。通过定义AOP切面和切入点,我们可以精确地拦截需要控制的方法,并在通知中根据开关状态执行相应的逻辑。这种技术手段有助于提高代码的可维护性和可扩展性,同时提供更好的灵活性和控制性来管理接口的行为
本文链接:http://www.28at.com/showinfo-26-34561-0.html优雅实现API接口开关:让你的应用更可控
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com