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

devtools/2024/10/21 22:31:34/

简介

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

代码实现

  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/devtools/87661.html

相关文章

Python面试题:结合Python技术,讲解如何使用Jinja2进行模板渲染

Jinja2 是一个现代的、设计精美的 Python 模板引擎。它使用类似于 Django 的模板语言来渲染文本文件。Jinja2 提供了动态网页生成的强大功能&#xff0c;是很多 Web 框架&#xff08;如 Flask&#xff09;的默认模板引擎。下面我将通过几个例子展示如何在 Python 中使用 Jinja2…

input禁止输入的4种方法

方法1、 readonly <input type"text" value"哈哈哈" readonly"readonly"> //使用readonly&#xff0c;字段为只读可复制 方法2、 disabled <input type"text" value"哈哈哈" disabled"disabled"> //只…

C++ AVL树

一.概念 二叉搜索树在左右子树较为平衡的情况下搜索效率为O(log n)&#xff0c;但是如果数据是接近有序插入二叉树中的结构就会是一颗左斜树或者右斜树的状态。 为了提升效率俄罗斯数学家 G. M. Adelson-Velsky和E. M. Landis在1962年共同发明的。其思想为当二叉树中插入新节点…

netstat 详解

优质博文&#xff1a;IT-BLOG-CN 一、netstat参数 参数说明-a 或–all显示所有连线中的Socket-A <网络类型>或–<网络类型> 列出该网络类型连线中的相关地址-c或–continuous持续列出网络状态-C或–cache显示路由器配置的快取信息-e或–extend显示网络其他相关信…

Python爬虫入门02:Fiddler下载使用教程

文章目录 手机抓包全攻略&#xff1a;Fiddler 工具深度解析引言Fiddler 工具简介为什么选择 Fiddler&#xff1f; 安装与配置 Fiddler步骤一&#xff1a;下载与安装步骤二&#xff1a;配置浏览器代理步骤三&#xff1a;安装 HTTPS 证书 配置手机以使用 Fiddler步骤一&#xff1…

Mybatis与Mybatis-plus配置控制台打印完整带参数SQL语句

Mybatis-plus的sql打印 application.yaml中。Mybatis把第一行换成mybatis mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl Mybatis的SQL打印亦或者是 application中增加 logging:level:com:sky:mapper: debugservice: infocontroller…

Mybatis注解

目录 1. Select 2. Insert 3. Update 4. Delete 5. Results 6. Param 7. One和 Many MyBatis 是一个持久层框架&#xff0c;支持通过 XML 或注解的方式来配置 SQL 映射。使用 MyBatis 注解可以更简洁地配置 SQL 语句&#xff0c;使代码更加清晰。以下是一些常用的 MyBatis 注解…