当前位置: 首页 > news >正文

SpringBoot使用泛型出入参+策略模式+反射+缓存实现统一POST接口入口

简介

某些情况下需要统一入口,如:提供给第三方调用的接口等。减少接口对接时的复杂性。

代码实现

  1. GenericController.java
    统一入口,通过bean name进行调用service层invoke方法
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import java.lang.reflect.InvocationTargetException;
import java.util.Map;@Slf4j
@RestController
@RequestMapping("api")
@RequiredArgsConstructor
public class GenericController {private final ObjectMapper objectMapper;@PostMapping("/{serviceName}")public Object invokeService(@PathVariable String serviceName, @RequestBody(required = false) Map<String, Object> requestBody) throws InvocationTargetException, IllegalAccessException {
//        log.info("{}接口入参:{}", serviceName, requestBody);// 从缓存获取ServiceInfoServiceInfo<?> serviceInfo = ServiceCacheUtils.cache.get(serviceName);//这里可以进行判空,但是没必要。没有的就让它抛异常。// 将请求参数转换为具体的类型Object requestObject = objectMapper.convertValue(requestBody, serviceInfo.getRequestType());// 调用Service的invoke方法并获取返回值Object responseObject = serviceInfo.invoke(requestObject);
//        log.info("{}接口出参:{}", serviceName, responseObject);return responseObject;}}
  1. GenericService.java
/*** 通用接口*/
public interface GenericService<T> {/*** 通用方法*/Object invoke(T request);
}
  1. UserGenericServiceImpl.java
    实现通用接口用户service层类
public class UserGenericServiceImpl implements GenericService<UserDTO> {@Overridepublic User invoke(UserDTO dto) {log.info("UserDTO:{}", dto);User user = new User();user.setId(1L);user.setName("Meta39");return user;}}
  1. HelloWorldGenericServiceImpl.java
    实现通用接口打招呼service层类
@Slf4j
@Service("helloWorld")
public class HelloWorldGenericServiceImpl implements GenericService<GenericServiceDTO> {@Overridepublic String invoke(GenericServiceDTO dto) {log.info("GenericServiceDto: {}", dto);return "Hello World";}}
  1. ServiceInfo.java
    缓存存储反射调用的类和请求参数、返回参数实体类
@Getter
@AllArgsConstructor
public class ServiceInfo<T> {private final GenericService<T> service;private final Method method;private final Class<T> requestType;public Object invoke(Object requestObject) throws IllegalAccessException, InvocationTargetException {return method.invoke(service, requestObject);}
}
  1. GenericServiceDto.java
    第1个数据传输对象看看泛型入参是否可用
@Data
public class GenericServiceDTO {private String name;
}
  1. UserDTO.java
    第2个数据传输对象看看泛型入参是否可用
@Data
public class UserDTO {private Integer id;
}
  1. ServiceCacheUtils.java
    把 spring bean 放入缓存并存储对应的请求类型,这样就可以知道每个 GenericService 接口的实现类具体的泛型请求类型是什么。
/*** 缓存创建的 bean* @since 2024-07-16*/
public abstract class ServiceCacheUtils {private ServiceCacheUtils() {}public static ConcurrentHashMap<String, ServiceInfo<?>> cache = new ConcurrentHashMap<>();}
  1. ApplicationContextUtils.java
    获取 bean 工具类
/*** 获取Bean*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {//构造函数私有化,防止其它人实例化该对象private ApplicationContextUtils() {}private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextUtils.applicationContext = applicationContext;}//通过name获取 Bean.(推荐,因为bean的name是唯一的,出现重名的bean启动会报错。)public static Object getBean(String name) {return applicationContext.getBean(name);}//通过class获取Bean.(确保bean的name不会重复。因为可能会出现在不同包的同名bean导致获取到2个实例)public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}//通过name,以及Clazz返回指定的Bean(这个是最稳妥的)public static <T> T getBean(String name, Class<T> clazz) {return applicationContext.getBean(name, clazz);}public static String[] getBeanNamesForType(Class<?> type) {return applicationContext.getBeanNamesForType(type);}}
  1. ServiceCacheApplicationRunner.java
    SpringBoot启动完成后立马把实现了GenericService的类的bean name 和ServiceInfo存储的请求参数类型写入缓存
/*** 启动后把所有实现了 GenericService 接口的类写入缓存。这样在调用方法的时候就可以直接获取类进行方法调用。** @since 2024-07-16*/
@Component
public class ServiceCacheApplicationRunner implements ApplicationRunner {@Override@SuppressWarnings("unchecked")public void run(ApplicationArguments args) throws NoSuchMethodException {String[] beanNames = ApplicationContextUtils.getBeanNamesForType(GenericService.class);for (String beanName : beanNames) {GenericService<Object> service = (GenericService<Object>) ApplicationContextUtils.getBean(beanName);Type[] genericInterfaces = service.getClass().getGenericInterfaces();ParameterizedType parameterizedType = (ParameterizedType) genericInterfaces[0];Class<Object> requestType = (Class<Object>) parameterizedType.getActualTypeArguments()[0];Method method = service.getClass().getMethod("invoke", requestType);// 显式类型转换ServiceInfo<Object> serviceInfo = new ServiceInfo<>(service, method, requestType);//写入缓存ServiceCacheUtils.cache.put(beanName, serviceInfo);}}}

测试

helloWorld

在这里插入图片描述
控制台输出

2024-07-31 23:22:47.204  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.s.s.i.HelloWorldGenericServiceImpl   : GenericServiceDto: GenericServiceDTO(name=哈哈哈哈哈)
2024-07-31 23:22:47.212  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.springboot3demo.filter.GlobalFilter  : 请求内容:
method: POST
uri: /api/helloWorld
request: { "name":"哈哈哈哈哈" }
2024-07-31 23:22:47.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.springboot3demo.filter.GlobalFilter  : 响应内容:
status: 200
response: Hello World

user

在这里插入图片描述
控制台输出

2024-07-31 23:24:46.199  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.s.s.impl.UserGenericServiceImpl      : UserDTO:UserDTO(id=1)
2024-07-31 23:24:46.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.springboot3demo.filter.GlobalFilter  : 请求内容:
method: POST
uri: /api/user
request: { "id":1 }
2024-07-31 23:24:46.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.springboot3demo.filter.GlobalFilter  : 响应内容:
status: 200
response: {"id":1,"name":"Meta39"}

注意事项

这种方式实现的统一入口,暂时发现一个弊端,没法使用Spring validation 参数校验框架,否则会抛异常。但是可以通过Spring Assert在代码里判断。

@Slf4j
@Service("helloWorld")
public class HelloWorldGenericServiceImpl implements GenericService<GenericServiceDTO> {@Overridepublic String invoke(GenericServiceDTO dto) {log.info("GenericServiceDto: {}", dto);//如下所示Assert.notNull(dto, "请求体不能为空");return "Hello World";}}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 连锁企业组网的优化解决方案
  • 通过Java实现插入排序(直接插入,希尔)与选择排序(直接选择,堆排)
  • 12. 计算机网络TCP四次挥手
  • 【avue+vue2+elementui】删除、rules、页面跳转和其他问题
  • 探索编程世界:大学新生入门指南
  • uniapp小程序中富文本内容渲染图片不展示的问题
  • 大模型的一些思考
  • MATLAB(10)分类算法
  • json-server(快速搭建本地 RESTful API 的工具)
  • 集群、分布式和微服务
  • Java SpringTask定时自动化处理
  • 装修新选择:探索浦东地区口碑排名前五的大平层装修公司!
  • 本地node搭建web服务器
  • Redis 典型应用-缓存
  • Phalco安装过程以及踩的一些坑(mac环境)
  • 网络传输文件的问题
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • Angular4 模板式表单用法以及验证
  • create-react-app项目添加less配置
  • Fastjson的基本使用方法大全
  • Magento 1.x 中文订单打印乱码
  • MySQL的数据类型
  • Vue.js 移动端适配之 vw 解决方案
  • 从重复到重用
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 基于web的全景—— Pannellum小试
  • 目录与文件属性:编写ls
  • 如何实现 font-size 的响应式
  • 用简单代码看卷积组块发展
  • 找一份好的前端工作,起点很重要
  • 正则学习笔记
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 整理一些计算机基础知识!
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • #APPINVENTOR学习记录
  • #stm32驱动外设模块总结w5500模块
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (26)4.7 字符函数和字符串函数
  • (33)STM32——485实验笔记
  • (9)STL算法之逆转旋转
  • (javaweb)Http协议
  • (PADS学习)第二章:原理图绘制 第一部分
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (差分)胡桃爱原石
  • (第一天)包装对象、作用域、创建对象
  • (二)WCF的Binding模型