通用分布式接口紧急开关
内容纲要
应用场景
- 在微服务上线或者修改完成后更新后,可以对该服务中的一些接口进行接口放行或者接口关闭,做到紧急刹车的功能,以减少系统损失
实现方法
- 可以使用aop + 自定义注解 + Java反射 + redis完成
- 可以使用HandlerMethod + 自定义注解 + Java反射 + 拦截器 + redis完成
两种方案的对比
- aop 方案会在每个方式对象添加一层代理,编写简单
- HandlerMethod相对aop方案来说,HandlerMethod不需要使用到代理类,相对于代理来说性能会有所提升,但是编写难度大(本文实现方式)
实现思路
- 在SpringMVC中
RequestMappingHandlerMapping
类负责处理@RequestMapping
标记的Controller的方法,将每个方法转变为HandlerMethod
并存储在mappingRegistry
成员变量中,可以使用getHandlerMethods()
方法获取 - 获取到所有的
HandlerMethod
后则可以对每一个HandlerMethod
进行判断是否存在@MappingSwitch
注解如果存在则认为是需要处理的请求方法,将该方法的信息存储到MappingSwitchInfo
类中,并存储到redis中,方便后续拦截器的数据获取 HandlerMethod
处理完成后,则需要向SpringMVC中添加拦截器,以拦截HandlerMethod
,判断HandlerMethod
的方法或者类上是否存在@RequestMapping
注解,如果存在则需要获取在redis中存储的信息判断当前接口是否运行通过
详细代码
- MappingSwitchInfo.java
/**
* MappingSwitch信息封装类
*
* @author Clay
* @date 2024/1/15 17:33
*/
@Data
public class MappingSwitchInfo {
/**
* 应用名称
*/
private String applicationName;
/**
* 类名
*/
private String className;
/**
* 方法名称
*/
private String methodName;
/**
* 描述MappingSwitch注解的value可以为空
*/
private String description;
/**
* HandlerMethod中的uri
*/
private Set<String> uris;
/**
* 当前方法请求类型
*/
private Set<String> httpMethods;
/**
* 当前方法的状态,true为正常放行,false为关闭
*/
private Boolean state;
public static class MappingSwitchConstant {
/**
* redis 的前缀
*/
public static String MAPPING_SWITCH = "mapping:switch:";
}
/**
* 获取redis key的方法
* key 生成规则
* 类注解: {前缀}:{applicationName}:{className}
* 方法注解: {前缀}:{applicationName}:{className}:{methodStr}
*
* @param applicationName 应用名称
* @param handlerMethod 请求方法
* @param isMethod 是否为方法注解
* @return key
*/
public static String getKey(String applicationName, HandlerMethod handlerMethod, Boolean isMethod) {
String packageName = handlerMethod.getBeanType().getPackage().getName() + ".";
String name = handlerMethod.getBeanType().getName();
String className = name.replace(packageName, "");
String methodStr = "";
if (isMethod) {
Method method = handlerMethod.getMethod();
StringJoiner joiner = new StringJoiner(", ", "(", ")");
for (Class<?> paramType : method.getParameterTypes()) {
joiner.add(paramType.getSimpleName());
}
methodStr = ":" + method.getName() + joiner;
}
return MappingSwitchInfo.MappingSwitchConstant.MAPPING_SWITCH + applicationName + ":" + className + methodStr;
}
}
- MappingSwitchService.java
/**
* MappingSwitch开关处理类
*
* @author Clay
* @date 2024/1/15 17:20
*/
@Slf4j
public class MappingSwitchService implements InitializingBean, ApplicationContextAware {
private ApplicationContext applicationContext;
private String applicationName = "";
@Resource
private RedisTemplate<String, MappingSwitchInfo> redisTemplate;
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
Map<String, MappingSwitchInfo> mappingSwitchInfoMap = new HashMap<>();
map.forEach((info, handlerMethod) -> {
//判断方法上是否存在注解
MappingSwitch method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), MappingSwitch.class);
if (method != null) {
MappingSwitchInfo mappingSwitchInfo = new MappingSwitchInfo();
mappingSwitchInfo.setClassName(handlerMethod.getBeanType().getName());
mappingSwitchInfo.setMethodName(handlerMethod.getMethod().getName());
mappingSwitchInfo.setApplicationName(applicationName);
mappingSwitchInfo.setState(Boolean.TRUE);
mappingSwitchInfo.setDescription(method.value());
//获取到uri
PatternsRequestCondition patternsCondition = info.getPatternsCondition();
if (patternsCondition != null) {
Set<String> uris = patternsCondition.getPatterns();
mappingSwitchInfo.setUris(uris);
}
//获取到请求类型
RequestMethodsRequestCondition infoMethodsCondition = info.getMethodsCondition();
Set<RequestMethod> methods = infoMethodsCondition.getMethods();
if (!methods.isEmpty()) {
Set<String> methodSet = methods.stream().map(Enum::toString).collect(Collectors.toSet());
mappingSwitchInfo.setHttpMethods(methodSet);
}
//获取到当前的key
String key = MappingSwitchInfo.getKey(applicationName, handlerMethod, Boolean.TRUE);
//初始化
initRedisCache(key, mappingSwitchInfo);
//添加到临时缓存中
mappingSwitchInfoMap.put(key, mappingSwitchInfo);
}
//判断类上是否存在注解
MappingSwitch controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), MappingSwitch.class);
if (controller != null) {
//获取到key
String key = MappingSwitchInfo.getKey(applicationName, handlerMethod, Boolean.FALSE);
if (!mappingSwitchInfoMap.containsKey(key)) {
// 创建存储对象
MappingSwitchInfo mappingSwitchInfo = new MappingSwitchInfo();
mappingSwitchInfo.setClassName(handlerMethod.getBeanType().getName());
mappingSwitchInfo.setApplicationName(applicationName);
mappingSwitchInfo.setState(Boolean.TRUE);
mappingSwitchInfo.setDescription(controller.value());
//获取到RequestMapping,以获取到当前Controller的请求路径
RequestMapping requestMapping = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), RequestMapping.class);
if (requestMapping != null) {
String[] value = requestMapping.value();
Set<String> uris = new HashSet<>();
Collections.addAll(uris, value);
mappingSwitchInfo.setUris(uris);
}
//初始化对象
initRedisCache(key, mappingSwitchInfo);
//将对象添加到临时缓存
mappingSwitchInfoMap.put(key, mappingSwitchInfo);
}
}
});
if (!mappingSwitchInfoMap.isEmpty()) {
redisTemplate.opsForValue().multiSet(mappingSwitchInfoMap);
}
}
/**
* 初始化缓存对象
*
* @param key redis key
* @param info 存储对象
*/
private void initRedisCache(String key, MappingSwitchInfo info) {
//获取redis中当前的key
MappingSwitchInfo cacheInfo = redisTemplate.opsForValue().get(key);
//如果redis中的状态为false,则不能更改redis中的状态
if (cacheInfo != null && !cacheInfo.getState()) {
info.setState(Boolean.FALSE);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
Environment environment = applicationContext.getEnvironment();
String applicationName = environment.getProperty("spring.application.name");
if (ObjectUtils.isEmpty(applicationName)) {
log.error("applicationName can not be null");
throw new RuntimeException("applicationName can not be null");
}
this.applicationName = applicationName;
}
}
- MappingSwitchInterceptor.java
/**
* MappingSwitch注解拦截器
*
* @author Clay
* @date 2024/1/15 18:08
*/
@Slf4j
public class MappingSwitchInterceptor implements HandlerInterceptor {
@Resource
private Environment environment;
@Resource
private RedisTemplate<String, MappingSwitchInfo> redisTemplate;
private String applicationName;
@PostConstruct
public void init() {
String applicationName = environment.getProperty("spring.application.name");
if (ObjectUtils.isEmpty(applicationName)) {
log.error("applicationName can not be null");
throw new RuntimeException("applicationName can not be null");
}
this.applicationName = applicationName;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前方法是否为HandlerMethod
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
//判断当前方法上是否有MappingSwitch注解
MappingSwitch method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), MappingSwitch.class);
if (method != null) {
checkMethodState(handlerMethod, Boolean.TRUE);
}
//判断Controller类上是否有MappingSwitch注解
MappingSwitch controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), MappingSwitch.class);
if (controller != null) {
checkMethodState(handlerMethod, Boolean.FALSE);
}
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* 检查方法的装填
*
* @param handlerMethod 方法对象
* @param isMethod 是否为方法, true为方法注解,false为Controller类注解
*/
private void checkMethodState(HandlerMethod handlerMethod, Boolean isMethod) {
MappingSwitchInfo mappingSwitchInfo = redisTemplate.opsForValue().get(MappingSwitchInfo.getKey(applicationName, handlerMethod, isMethod));
if (mappingSwitchInfo != null && !mappingSwitchInfo.getState()) {
throw new RuntimeException("当前接口关闭,请稍后再试");
}
}
}
共有 0 条评论