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

news2024/11/13 23:32:40

简介

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

代码实现

  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);
        // 从缓存获取ServiceInfo
        ServiceInfo<?> 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> {

    @Override
    public 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> {

    @Override
    public 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;

    @Override
    public 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> {

    @Override
    public String invoke(GenericServiceDTO dto) {
        log.info("GenericServiceDto: {}", dto);
        //如下所示
        Assert.notNull(dto, "请求体不能为空");
        return "Hello World";
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1964615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python爬虫入门(结合网站代码演示)

原理 第一步发送请求 与浏览器请求访问服务器地址一样&#xff0c;python程序向服务器发送访问请求&#xff0c;服务器返回数据。 在python中我们可以使用 第二步解析网页内容 浏览器在接收到服务器返回的数据后&#xff0c;会自行解析内容最后呈现出我们所看到的界面。但是在程…

Centos7.6安装Nginx(yum安装和源码安装)

Centos7.6安装Nginx&#xff08;yum安装和源码安装&#xff09; 简介 yum安装 源码安装 安装后的事情 常见问题 简介 Nginx&#xff08;发音为“engine X”&#xff09;是一个高性能的HTTP和反向代理服务器&#xff0c;也可以作为邮件代理服务器使用。它被广泛应用于高流量…

linux进程控制——进程等待——wait、waitpid

前言&#xff1a;本节内容仍然是进程的控制&#xff0c;上一节博主讲解的是进程控制里面的进程创建、进程退出、终止。本节内容将讲到进程的等待——等待是为了能够将子进程的资源回收&#xff0c;是父进程等待子进程。 我们前面的章节也提到过等待&#xff0c; 那里的等待是进…

《C++基础入门与实战进阶》专栏介绍

&#x1f680; 前言 本文是《C基础入门与实战进阶》专栏的说明贴&#xff08;点击链接&#xff0c;跳转到专栏主页&#xff0c;欢迎订阅&#xff0c;持续更新…&#xff09;。 专栏介绍&#xff1a;以多年的开发实战为基础&#xff0c;总结并讲解一些的C/C基础与项目实战进阶内…

在MySQL中COUNT(*)、COUNT(列)和COUNT(DISTINCT 列)有什么区别

本文还发布在我的 medium 和 掘金 上 这篇文章让我们看看MySQL中count(*)和count(column_name)有什么区别。也许你知道它们都是计算结果行数的&#xff0c;那么在使用的时候如何选择呢。 我在MySQL库中创建了一个t_hero表 CREATE TABLE t_hero (id int NOT NULL,name char(10)…

软件测试——用例篇(上)

概念 什么是测试⽤例&#xff1f; 测试⽤例&#xff08;Test Case&#xff09;是为了实施测试⽽向被测试的系统提供的⼀组集合&#xff0c;这组集合包含&#xff1a;测试环境、操作步骤、测试数据、预期结果等要素 设计测试⽤例原则⼀&#xff1a; 测试⽤例中⼀个必需部分是对…

AI-WEB-1.0 靶机

AI-WEB-1.0 一、安装靶机环境 下载地址&#xff1a; https://www.vulnhub.com/entry/ai-web-1,353/ 下载压缩文件打开 开启虚拟机 二、信息收集 1.查看NAT模式IP段 编辑–>虚拟网络编辑器 御剑2014查IP 找到ip之后就访问网站 用扫描目录的工具扫描当前网站的目录 访问…

复制知乎文字内容

复制知乎文字内容 以edge浏览器为例&#xff1a; 以edge浏览器为例&#xff1a; 先点击F12&#xff08;也就是鼠标右键->检查&#xff09;再点击F1选择禁用Javascript 之后知乎的文字就可以正常复制了。 &#xff08;注意&#xff1a;退出浏览器的时候记得把这一条恢复过…

XAML 热重载应用程序

XAML 热重载&#xff08;Hot Reload&#xff09;是一个在开发过程中提高效率的功能&#xff0c;它允许开发者在应用程序运行时修改 XAML 代码&#xff0c;而不需要重新启动应用程序。这个功能非常适合于调试和即时预览 UI 更改。以下是如何在应用程序中使用 XAML 热重载的一些基…

CVE-2022-21663: WordPress <5.8.3 版本对象注入漏洞深入分析

引言 在网络安全领域&#xff0c;技术的研究与讨论是不断进步的动力。本文针对WordPress的一个对象注入漏洞进行分析&#xff0c;旨在分享技术细节并提醒安全的重要性。特别强调&#xff1a;本文内容仅限技术研究&#xff0c;严禁用于非法目的。 漏洞背景 继WordPress CVE-2…

系统设计中15 个最重要的权衡

系统设计的第一法则&#xff1a;一切都与权衡有关。 在设计系统时&#xff0c;我们需要决定要包含哪些功能以及要忽略哪些功能。每次我们做这个决定时&#xff0c;我们都在进行权衡。在本文中&#xff0c;我们将探讨系统设计中遇到的15个最常见的权衡问题&#xff0c;并使用实…

第十四天学习笔记2024.7.25

Git安装 1.安装git 2.创建git仓库 3.创建文件并且提交文件 &#xff08;git add . 将文件提交到缓存&#xff09;&#xff08;git commit -m 说明&#xff09;&#xff08;git log 查看历史&#xff09; 4.分支创建与解决分支冲突问题 创建主机&#xff08;git 192.1…

手机三要素接口怎么对接呢?(二)

一、什么是手机三要素&#xff1f; 手机三要素又叫运营商三要素&#xff0c;运营商实名认证&#xff0c;运营商实名核验&#xff0c;手机三要素实名验证&#xff0c;手机三要素实名核验&#xff0c;每个人的称呼都不同&#xff0c;但是入参和出参是一样的。 输入姓名、身份证…

24暑假算法刷题 | Day25 | 回溯算法 III | LeetCode 491. 非递减子序列,46. 全排列,47. 全排列 II

目录 491. 非递减子序列题目描述题解 46. 全排列题目描述题解 47. 全排列 II题目描述题解 491. 非递减子序列 点此跳转题目链接 题目描述 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任…

MELLE: Autoregressive Speech Synthesis without Vector Quantization

2024.7MICROSOFT 文章目录 MELLE: Autoregressive Speech Synthesis without Vector Quantizationabstractmethod损失函数 Autoregressive Image Generation without Vector Quantizationabstractmethods更好的AR modelresults MELLE: Autoregressive Speech Synthesis without…

UI框架与MVC模式详解(3)——MVC\MVP\MVVM

【PDI模式】 前文中&#xff0c;我们详细讲解了为实现一个涉及UI的功能所必须得三者&#xff0c;简称PDI&#xff1a; Panel类&#xff1a;主要实现交互逻辑、显示逻辑的地方以及保存界面相关的数据的地方Data类&#xff1a;数据管理类&#xff0c;主要是业务相关的数据Inter…

小白也能轻松学的计算机网络零基础入门(附学习路线 + 计算机网络教程)

本文作者&#xff1a;程序员鱼皮 免费编程学习 - 编程导航网&#xff1a;https://www.code-nav.cn 介绍 计算机网络是指将地理位置不同的多台计算机或设备通过通信线路进行连接&#xff0c;从而实现信息传递和资源共享&#xff0c;或者组成一个更完整的计算机系统。 如今&…

【MySQL】用户管理连接池原理{数据库权限/连接池/mysql访问逻辑}

文章目录 1.普通用户的引入用户创建用户删除用户修改用户密码 2.数据库的权限给用户授权回收权限实操过程 3.简略介绍mysql连接池3.一个用户注册/登录/使用网页版mysql逻辑 1.普通用户的引入 用户 MySQL中的用户&#xff0c;都存储在系统数据库mysql的user表中 mysql> use…

Internet Download Manager(IDM)2024最新免费版电脑下载管理器

1. Internet Download Manager&#xff08;IDM&#xff09;是一款流行的下载管理软件&#xff0c;以其高速、稳定的下载性能著称。 2. IDM支持多线程下载&#xff0c;可以显著提高下载速度。 3. 该软件还具备恢复中断下载的功能&#xff0c;对于网络不稳定的用户非常实用。 …

蓝鹏测径仪非标定制订制流程

测径仪通常属于非标定制设备&#xff0c;非标定制意味着这些设备不是按照标准规格批量生产的&#xff0c;而是根据特定的客户需求和应用场景设计和制造的。例如&#xff0c;某些测径仪可能需要特殊的测量范围、精度、传感器或软件来满足特定的工业检测要求。 测径仪非标定制的…