背景
- 對接餓了嗎商戶推送接口:配置一個回調接口,但是根據不同的類型碼,進行不同的業務處理,所以需要做到根據類型分發
思路
- 通過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 不同的類型碼會自動分發