環(huán)境:SpringBoot2.7.12
本文將介紹如何為API接口動(dòng)態(tài)添加開(kāi)關(guān)功能。通過(guò)這個(gè)功能,我們可以控制API接口的正常訪(fǎng)問(wèn)或顯示提示信息。這有助于在開(kāi)發(fā)和維護(hù)過(guò)程中更好地管理和控制API接口的行為。通過(guò)使用開(kāi)關(guān)功能,我們可以輕松地在不同情況下調(diào)整接口的行為,提高系統(tǒng)的靈活性和可維護(hù)性。
為什么要給API接口添加開(kāi)關(guān)功能呢?從以下幾方面考慮:
首先,定義一個(gè)AOP切面(Aspect),該切面負(fù)責(zé)控制接口(Controller)的開(kāi)關(guān)行為。
在切面中,我們可以使用Spring AOP的切入點(diǎn)(Pointcut)來(lái)指定需要攔截的方法。一旦方法被攔截,我們可以在切面的通知(Advice)中定義相應(yīng)的默認(rèn)行為。接下來(lái)我們將一步一步的實(shí)現(xiàn)接口開(kāi)關(guān)功能。
@Target({ElementType.TYPE, ElementType.METHOD})public @interface ApiSwitch { /**接口對(duì)應(yīng)的key,通過(guò)可以該key查詢(xún)接口是否關(guān)閉*/ String key() default "" ; /**解析器beanName,通過(guò)具體的實(shí)現(xiàn)獲取key對(duì)應(yīng)的值*/ String resolver() default "" ; /**開(kāi)啟后降級(jí)方法名*/ 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)) ; }}
這里就提供兩種默認(rèn)的實(shí)現(xiàn)。
@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 { // 對(duì)應(yīng)接口開(kāi)關(guān)的key String key = apiSwitch.key() ; // 解析器bean的名稱(chēng) String resolverName = apiSwitch.resolver() ; // 降級(jí)方法名 String fallback = apiSwitch.fallback() ; SwitchResolver resolver = null ; // 根據(jù)指定的beanName獲取具體的解析器;以下都不考慮不存在的情況 if (StringUtils.hasLength(resolverName)) { resolver = this.context.getBean(resolverName, SwitchResolver.class) ; } else { resolver = this.context.getBean(MAPPINGS.get(this.switchProperties.getResolver())) ; } // 解析器不存在則直接調(diào)用目標(biāo)接口 if (resolver == null || !resolver.resolver(key)) { return pjp.proceed() ; } // 調(diào)用降級(jí)的方法;關(guān)于降級(jí)的方法簡(jiǎn)單點(diǎn),都必須在當(dāng)前接口類(lèi)中,同時(shí)還不考慮方法參數(shù)的情況 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" ; }
通過(guò)該接口修改具體哪個(gè)接口的開(kāi)關(guān)狀態(tài)。(注意:這里有小問(wèn)題,如果接口上指定了resolver類(lèi)型且配置文件中指定的類(lèi)型不一致,就會(huì)出現(xiàn)不生效問(wèn)題。這個(gè)問(wèn)題大家可以自行解決)
@GetMapping("/q1")@ApiSwitch(key = "swtich$q1", fallback = "q1_fallback", resolver = "redisResolver")public Object q1() { return "q1" ;}public Object q1_fallback() { return "接口維護(hù)中" ;}
這是完整的配置示例,這里除了key必須外,其它的都可以不填寫(xiě)。
具體測(cè)試結(jié)果就不貼了,大家可以自行測(cè)試基于jvm內(nèi)存和redis的方式。
總結(jié):通過(guò)上述介紹,我們可以看到使用Spring AOP實(shí)現(xiàn)接口的開(kāi)關(guān)功能是一種非常有效的方法。通過(guò)定義AOP切面和切入點(diǎn),我們可以精確地?cái)r截需要控制的方法,并在通知中根據(jù)開(kāi)關(guān)狀態(tài)執(zhí)行相應(yīng)的邏輯。這種技術(shù)手段有助于提高代碼的可維護(hù)性和可擴(kuò)展性,同時(shí)提供更好的靈活性和控制性來(lái)管理接口的行為
本文鏈接:http://www.www897cc.com/showinfo-26-34561-0.html優(yōu)雅實(shí)現(xiàn)API接口開(kāi)關(guān):讓你的應(yīng)用更可控
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: Go 淺析主流日志庫(kù):從設(shè)計(jì)層學(xué)習(xí)如何集成日志輪轉(zhuǎn)與切割功能