spring boot 接口路由
背景
- 对接饿了吗商户推送接口:配置一个回调接口,但是根据不同的类型码,进行不同的业务处理,所以需要做到根据类型分发
思路
- 通过switch 方式获取类型码,调用不同的处理方法:弊端 1.几十个类型码需要写几十个判断 2.扩展性很差,需要硬编码。3.多人协作管理代码混乱
- 做一个类似于springmvc 的dispacher 请求分发中心。 优点:1.多人协作只用写接口方法。2.插拔式代码减少耦合。3.只关注自己的业务,不需改动分发中心代码
目的
- 需要根据类型码自动找到对应的方法执行,返回统一的数据
- 多人可协作
- 编码简单
设计思路
- 通过自定义注解的方式,将所有自定义注解,在容器初始化的时候收集到容器中(类似于springmvc 的@RequestMapping 所达到的效果)。key:类型码 value :该类型对应的业务方法
- 不能破坏springmvc 的请求执行流程。所以需要在controller 内进行接口分发。
- 统一处理响应值,进行转换(自定义响应对象-------》转换-----》平台(elm 要求的响应对象))
编码
自定义注解
package com.elm.AnnotationCenter; import java.lang.annotation.*;
// 注解到对应的业务接口 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface ElmRouterAnnotation {
// 类型码 String key() default "" ; }
初始化容器的扫描 ElmRouterAnnotation ,存放类型码与方法的映射到map容器中
package com.elm.AnnotationCenter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.*; /** * @author coderxiao * @date [2020-01-16] * @description elm自定义注解扫描 */ @ComponentScan public class ElmAnntaionScan implements ApplicationListener<ContextRefreshedEvent> { private static Logger logger = LoggerFactory.getLogger(ElmAnntaionScan.class); public static LinkedHashMap<String,Method> routerRegisterMapping=new LinkedHashMap<>(); /** * 扫描注册 * @param event */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { if(event.getApplicationContext().getParent() == null) {
//先扫描类 Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(Service.class); if(!CollectionUtils.isEmpty(beans)){ Set<Map.Entry<String, Object>> entries = beans.entrySet(); Iterator<Map.Entry<String, Object>> iterator = entries.iterator(); while(iterator.hasNext()){ Map.Entry<String, Object> map = iterator.next(); Class<?> aClass = map.getValue().getClass(); Method[] methods = aClass.getMethods(); if((methods!=null) && (methods.length>0)){ for (Method method: methods ) {
// 再扫描方法 ElmRouterAnnotation annotation = method.getAnnotation(ElmRouterAnnotation.class); if(annotation!=null){ String key = annotation.key(); if(StringUtils.isEmpty(key)){ logger.error("项目启动失败:类:{}--->key{}不能为空,方法名{}",aClass.getName(),key,method.getName()); int a=1/0; } Class<?>[] args= method.getParameterTypes(); if((args==null)||(args.length!=2)){ logger.error("========>>>>项目启动失败:类:{}-->方法名{}参数不能为空",aClass.getName(),method.getName()); Assert.state(false); } /* if(!"javax.servlet.http.HttpServletRequest".equals(args[0].getName())){ logger.error("========>>>>项目启动失败:类:{}-->方法名{}请求参数类型错误",aClass.getName(),method.getName()); int a=1/0; }*/ if(!"com.lws.utils.InfResultVoPro".equals(args[1].getName())){ logger.error("========>>>>项目启动失败:类:{}-->方法名{}请求参数类型错误",aClass.getName(),method.getName()); Assert.state(false); } if(routerRegisterMapping.containsKey(key)){ logger.error("========>>>>项目启动失败:类:{}-->key{}不能重复,方法名{}",aClass.getName(),method.getName()); Assert.state(false); }
// 存放映射到容器中 routerRegisterMapping.put(key,method); } } } } } } } }
初始化bean(用于容器启动的时候扫描注解)
package com.config; import com.elm.AnnotationCenter.ElmAnntaionScan; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; /** * @author coderxiao * @date [2020-01-10] * @description 饿了吗配置文件 这里的配置文件只写应用配置,业务配置在自己的代码中加 */ @Configuration public class LwsElmConfig { @Bean public ElmAnntaionScan annotationScan(){return new ElmAnntaionScan(); } }
分发中心
package com.elm.api.controller; import com.elm.AnnotationCenter.ElmAnntaionScan; import com.elm.api.ElmRestVo; import com.utils.InfResultVoPro; import com.utils.LwsBeanUtils; import eleme.openapi.sdk.api.entity.message.OMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import red.sea.common.utils.JSON; import red.sea.commons.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.LinkedHashMap; @RequestMapping("/elm/elmCallBack") @RestController public class ElmCallBackCenterController { private static Logger logger = LoggerFactory.getLogger(ElmCallBackCenterController.class);
// 暴露给饿了吗调用的统一接口 OMessage 请求对象 @RequestMapping("/router") public Object router(@ModelAttribute OMessage message, HttpServletRequest request){ InfResultVoPro retVo=new InfResultVoPro(); LinkedHashMap<String,Method> routerMapping= ElmAnntaionScan.routerRegisterMapping; if(StringUtils.isEmpty(message.getRequestId())){ logger.info("接口存活检测-------"); return ElmRestVo.retData(retVo); } logger.info("请求原始数据:"+JSON.toJSONString(message)); retVo.setOriginData(message); String type=String.valueOf(message.getType()); if(routerMapping.containsKey(type)){
// 分发中心 Method method=routerMapping.get(type); try { logger.info("开始进入elm请求分发{}",type);
//这里着重说明一下,类需要被实例化,才能被反射调用 BeanUtils.getBean(beanName|class) 获取spring 托管的bean
method.invoke(BeanUtils.getBean(method.getDeclaringClass()),message.getMessage(),retVo); }catch (Exception e) { logger.error("elm路由分发异常",e); e.printStackTrace(); retVo.setError(""); } }
//这是统一数据转换 return ElmRestVo.retData(retVo); } }
需要扫描的业务方法
package com.elm.test.service.impl; import com.elm.AnnotationCenter.ElmRouterAnnotation; import com.utils.InfResultVoPro; import org.springframework.stereotype.Service; /** * 这是是service 注解才会被扫描到 */ @Service public class ElmTest { /** * 将注解和方法进行映射 * @param msg * @param retVo */ @ElmRouterAnnotation(key = "1") public void myTest(String msg, InfResultVoPro retVo){ System.out.println("elm----------->MyTest"); } }
当访问 http://{domain}:{port}/elm/elmCallBack//router
传入OMessage 不同的类型码会自动分发