在实际项目开发中,在系统与系统对接,或者微服务与微服务对接中,往往采用暴露接口的方式,接口即对应一个服务,有时我们为了减少服务的注册量,实现服务的复用,经常在一个服务中接收多种数据。下面做一个简单的演示,通过自定义注解+反射实现一个接口接收多种数据。
案例介绍: 水果商城系统中,水果有很多种,水果的一些属性信息需要由其它微服务来推送,现在假设有苹果(Apple)、香蕉(Banana)、橙子(Orange)三种水果类型,水果商城系统需要接收每种水果的详细介绍信息,通常的逻辑是为每种水果构建一个Controller接口来接收,编码逻辑如下:
/**
* 处理苹果数据接口
*/
@RestController
public class AppleController {
@PostMapping("/fruit/apple")
public Object getAppleDetail(@RequestBody JSONObject requestData) {
// TODO 数据处理
return "苹果数据接收成功";
}
}
/**
* 处理香蕉数据接口
*/
@RestController
public class BananaController {
@PostMapping("/fruit/banana")
public Object getAppleDetail(@RequestBody JSONObject requestData) {
// TODO 数据处理
return "香蕉数据接收成功";
}
}
/**
* 处理橙子数据接口
*/
@RestController
public class OrangeController {
@PostMapping("/fruit/orange")
public Object getAppleDetail(@RequestBody JSONObject requestData) {
// TODO 数据处理
return "橙子数据接收成功";
}
}
以上代码可以实现我们的要求,但如果做服务注册的话,几十上百种水果类型,可能要注册很多服务,这显然是不合理的,所以我们要在编码时实现服务的复用,通过一个服务接口做到接收所有类型的水果数据。下面进行编码实现。
一些注释都在代码中标明。
基础控制器主要用来写一些通用的方法,方便代码的复用,这里没有过多的业务处理,就不着重去写。
/**
* 水果类查询基础父类
*/
@RequestMapping("/fruit")
@RestController
public class FruitBaseController {
public Object getFruitDetail(String fruitName, Map<String , Object> dataMap) {
return "水果:" + fruitName + "介绍信息为空";
}
// TODO 其它的基础方法
}
/**
* 自定义注解:水果详情注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitDetail {
String value() default "";
}
/**
* 水果详情service接口
*/
public interface IFruitDetailService {
public String getFruitDetail(String fruitName, Map<String , Object> requestData);
}
这里不讨论业务数据处理的具体逻辑,仅仅做一个实现。
/**
* 苹果 详情数据处理服务
*/
@Service
@FruitDetail("Apple")
public class AppleFruitDetailService implements IFruitDetailService {
@Override
public String getFruitDetail(String fruitName, Map<String, Object> requestData) {
System.out.println("接收苹果详情数据");
// TODO 一些具体业务操作
return "苹果详情数据处理完毕";
}
}
/**
* 香蕉 详情数据处理服务
*/
@Service
@FruitDetail("Banana")
public class BananaFruitDetailService implements IFruitDetailService {
@Override
public String getFruitDetail(String fruitName, Map<String, Object> requestData) {
System.out.println("接收香蕉详情数据");
// TODO 一些具体业务操作
return "香蕉详情数据处理完毕";
}
}
/**
* 橙子 详情数据处理服务
*/
@Service
@FruitDetail("Orange")
public class OrangeFruitDetailService implements IFruitDetailService {
@Override
public String getFruitDetail(String fruitName, Map<String, Object> requestData) {
System.out.println("接收橙子详情数据");
// TODO 一些具体业务操作
return "橙子详情数据处理完毕";
}
}
@RestController
public class FruitDetailController extends FruitBaseController{
@Autowired
private AnnotationUtil annotationUtil;
/**
* 接收数据接口
* @param requestData 数据
* @return
*/
@PostMapping("/fruitDetail")
public Object fruitDetail(@RequestBody JSONObject requestData) {
// 接口数据
List<Map> requestDataList = getRequestDataList(requestData);
if (CollectionUtils.isEmpty(requestDataList)) {
return "请求数据不能为空";
}
// 进行数据校验
List<Map> fruitNameEmptyList = requestDataList.stream().filter(fruitData -> ObjectUtils.isEmpty(fruitData.get("FRUIT_NAME"))).collect(Collectors.toList());
if (fruitNameEmptyList.size() > 0) {
return "水果的名称必填";
}
StringBuffer resultBuf = new StringBuffer();
// 循环处理数据
requestDataList.forEach(fruitData -> {
// 水果名称
String fruitName = fruitData.get("FRUIT_NAME").toString();
// 返回结果
String result = null;
result = annotationUtil.getSpecificMethodForFruitDetail( fruitName, fruitData);
resultBuf.append(result);
});
return resultBuf.toString();
}
/**
* 请求的数据 转换
* @param requestData
* @return
*/
public List<Map> getRequestDataList(JSONObject requestData) {
try {
Object requestData1 = requestData.get("RequestData");
return JSONArray.parseArray(JSON.toJSONString(requestData1), Map.class);
}catch (Exception e) {
return null;
}
}
}
/**
* 工具组件
*/
@Component
public class AnnotationUtil {
@Autowired
private ApplicationContext applicationContext;
/**
* 方式一:获取指定业务方法
* @param packagePath
* @param fruitName
* @param dataMap
* @return
*/
public String getSpecificMethodForFruitDetail( String fruitName, Map<String , Object> dataMap) {
Map<String, Object> beansWithAnnotationMap = applicationContext.getBeansWithAnnotation(FruitDetail.class);
IFruitDetailService fruitDetailService = null;
// 匹配对应的bean
for (Map.Entry<String, Object> beanEntry : beansWithAnnotationMap.entrySet()) {
String annotationValue = beanEntry.getValue().getClass().getAnnotation(FruitDetail.class).value();
if (annotationValue.equals(fruitName)) {
fruitDetailService = (IFruitDetailService) beanEntry.getValue();
break;
}
}
String fruitDetail = fruitDetailService.getFruitDetail(fruitName, dataMap);
return fruitDetail;
}
/**
* 方式二:获取指定业务方法
* 需引入 reflections 包
* @param packagePath
* @param fruitName
* @param dataMap
* @return
*/
public String getSpecificMethodForFruitDetail(String packagePath, String fruitName, Map<String , Object> dataMap) {
Reflections reflections = new Reflections(packagePath);
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(FruitDetail.class);
List<Class<?>> fruitNameList = typesAnnotatedWith.stream().filter(bean -> bean.getAnnotation(FruitDetail.class).value().equals(fruitName)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(fruitNameList)) {
return null;
}
Class<?> fruitClass = fruitNameList.get(0);
// 创建bean实例
IFruitDetailService fruitDetailService = (IFruitDetailService) applicationContext.getAutowireCapableBeanFactory().createBean(fruitClass, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
return fruitDetailService.getFruitDetail(fruitName, dataMap);
}
使用postman工具对接口 “http://localhost:端口号/fruit/fruitDetail” 进行访问,报文格式如下:
{
"RequestData":[
{
"FRUIT_NAME":"Apple",
"DESCRIBE":"香甜可口,好吃不贵",
"PRICE":"12.00"
},
{
"FRUIT_NAME":"Apple",
"DESCRIBE":"香甜可口,好吃不贵",
"PRICE":"22.00"
}
]
}
这种方式通过一个接口,即只用注册一个服务即可接收所有类型水果的数据,把主要精力放在业务处理逻辑上。