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

news/2024/12/22 22:31:13/

简介

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

代码实现

  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";}}

http://www.ppmy.cn/news/1503566.html

相关文章

鸿蒙父组件中如何处理子组件内点击事件

在父组件中初始化子组件时&#xff0c;将父组件中定义的方法&#xff0c;传递给子组件&#xff0c;在子组件中调用该方法&#xff0c;类似于变量传递。参考代码如下&#xff1a; class Model { value: string } Entry Component struct EntryComponent { test() { consol…

(面试必看!)锁策略

文章导读 引言考点一、重量级锁 VS 轻量级锁1、定义与原理2、主要区别3、适用场景 考点二、乐观锁 VS 悲观锁1、悲观锁&#xff08;Pessimistic Locking&#xff09;2、乐观锁&#xff08;Optimistic Locking&#xff09;3、总结 考点三、读写锁1、读写锁的特性2、读写锁的实现…

Vue前端工程

创建一个工程化的vue项目 npm init vuelatest 全默认回车就好了 登录注册校验 //定义数据模型 const registerDataref({username:,password:,rePassword: }) //校验密码的函数 const checkRePassword(rule,value,callback)>{if (value){callback(new Error(请再次输入密…

22. Hibernate 性能之缓存

1. 前言 本节和大家一起聊聊性能优化方案之&#xff1a;缓存。通过本节学习&#xff0c;你将了解到&#xff1a; 什么是缓存&#xff0c;缓存的作用&#xff1b;HIbernate 中的缓存级别&#xff1b;如何使用缓存。 2. 缓存 2.1 缓存是什么 现实世界里&#xff0c;缓存是一个…

iOS ------ 持久化

一&#xff0c;数据持久化的目的 1&#xff0c;快速展示&#xff0c;提升体验 已经加载过的数据&#xff0c;用户下次查看时&#xff0c;不需要再次从网络&#xff08;磁盘&#xff09;加载&#xff0c;直接展示给用户 2.节省用户流量 对于较大的资源数据进行缓存&#xff…

GitHub 详解教程

1. 引言 GitHub 是一个用于版本控制和协作的代码托管平台&#xff0c;基于 Git 构建。它提供了强大的功能&#xff0c;使开发者可以轻松管理代码、追踪问题、进行代码审查和协作开发。 2. Git 与 GitHub 的区别 Git 是一个分布式版本控制系统&#xff0c;用于跟踪文件的更改…

cf168(div2):D.Maximize the Root(树)

题目 给你一棵有根的树&#xff0c;由 &#x1d45b;n 个顶点组成。树中的顶点编号为 11 至 &#x1d45b;n &#xff0c;根顶点为 11 。值 &#x1d44e;&#x1d456;ai 写在第 &#x1d456;i 个顶点上。 您可以执行以下任意次数(可能为零)的操作&#xff1a;选择一个顶点…

黑马JavaWeb后端案例开发(包含所有知识点!!!)

目录 1.准备工作 环境搭建 开发规范 REST&#xff08;REpresentation State Transfer&#xff09;,表述性状态转换&#xff0c;它是一种软件架构风格 注意事项 统一响应结果 2.部门管理功能 查询部门 删除部门 新增部门 RequestMapping 3.员工管理功能 分页查询 批…