Spring Boot项目中不使用@RequestMapping相关注解,如何动态发布自定义URL路径

一、前言

在Spring Boot项目开发过程中,对于接口API发布URL访问路径,一般都是在类上标识@RestController或者@Controller注解,然后在方法上标识@RequestMapping相关注解,比如:@PostMapping@GetMapping注解,通过设置注解属性,发布URL。在某些场景下,我觉得这样发布URL太麻烦了,不适用,有没有什么其他方法自由发布定义的接口呢?答案是肯定的。

二、一般开发流程

按照上面的描述,我们先看一下一般常用的开发代码:

java 复制代码
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class TestController {

    @RequestMapping("/test/url")
    public String test(@RequestParam String name, @RequestBody Map<String, Object> map) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("hello, ").append(name).append(", receive param:");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue());
        }
        return stringBuilder.toString();
    }

}

测试效果:

三、自定义URL发布逻辑

参考步骤二的测试截图效果,我们自定义发布一个URL。

1. 新建一个spring boot项目,导入相关依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

2. 修改Controller实现类代码

去掉@RestController@RequestMapping相关注解,示例代码如下:

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Map;

// @RestController
@Component
public class TestController {

    //@RequestMapping("/test/url")
    @ResponseBody  // 注意:此注解需要添加,不能少
    public String test(/*@RequestParam*/ String name,/* @RequestBody*/ Map<String, Object> map) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("hello, ").append(name).append(", receive param:");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue());
        }
        return stringBuilder.toString();
    }

}

3. 自定义一个事件监听,实现URL发布功能

参考代码如下:

java 复制代码
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 注册一个web容器初始化以后的事件监听,注册自定义URL
 */
@Component
public class CustomRegisterUrl implements ApplicationListener<WebServerInitializedEvent> {
    /**
     * 标识事件监听器是否已经注册,避免重复注册
     */
    private volatile AtomicBoolean flag = new AtomicBoolean(false);

    /**
     * 需要发布的地址
     */
    public static final String CUSTOM_URL = "/test/url";

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;


    @Autowired
    private TestController testController;

    @SneakyThrows
    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        if (flag.compareAndSet(false, true)) {
            // 构建请求映射对象
            RequestMappingInfo requestMappingInfo = RequestMappingInfo
                    .paths(CUSTOM_URL) // 请求URL
                    .methods(RequestMethod.POST, RequestMethod.GET) // 请求方法,可以指定多个
                    .build();
            // 发布url,同时指定执行该请求url的具体类变量的的具体方法
            requestMappingHandlerMapping.registerMapping(requestMappingInfo, testController, testController.getClass().getMethod("test", String.class, Map.class));
        }
    }
}

4. 测试效果

同样请求:http://localhost:8080/test/url?name=jack

可以看到,此时请求效果并不是正常的,存在参数丢失,怎么办呢?

注意:如果请求出现如下错误:

powershell 复制代码
java.lang.IllegalArgumentException: Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".

可以在application.yaml文件中添加如下内容:

yaml 复制代码
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

5. 增加统一请求处理器

为了实现参数可以正常解析,同时方便增加自定义处理逻辑,我们可以增加一个统一的请求处理器,参考示例:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;

@Component
public class CustomHandlerUrl {

    public static final Method HANDLE_CUSTOM_URL_METHOD;

    private static final ObjectMapper OBJECTMAPPER = new ObjectMapper();

    @Autowired
    private TestController testController;

    static {
        // 提前准备好参数对象
        Method tempMethod = null;
        try {
            tempMethod = CustomHandlerUrl.class.getMethod("handlerCustomUrl", HttpServletRequest.class, HttpServletResponse.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        HANDLE_CUSTOM_URL_METHOD = tempMethod;
    }

    @ResponseBody
    /**
     *  拦截自定义请求的url,可以做成统一的处理器,这里我只做简单实现,专门处理test
     */
    public Object handlerCustomUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 获取参数 get方式请求参数
        String name = request.getParameter("name");
        // 获取 post方式请求参数
        Map<String, Object> map = OBJECTMAPPER.readValue(request.getInputStream(), Map.class);

        // 执行业务方法
        String result = testController.test(name, map);
        return result;

    }
}

6. 修改事件监听逻辑

修改事件监听逻辑,此时注册URL时,绑定统一处理器就行了。

示例代码:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 注册一个web容器初始化以后的事件监听,注册自定义URL
 */
@Component
public class CustomRegisterUrl implements ApplicationListener<WebServerInitializedEvent> {
    /**
     * 标识事件监听器是否已经注册,避免重复注册
     */
    private volatile AtomicBoolean flag = new AtomicBoolean(false);

    /**
     * 需要发布的地址
     */
    public static final String CUSTOM_URL = "/test/url";

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Autowired
    private CustomHandlerUrl customHandlerUrl;


    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        if (flag.compareAndSet(false, true)) {
            // 构建请求映射对象
            RequestMappingInfo requestMappingInfo = RequestMappingInfo
                    .paths(CUSTOM_URL) // 请求URL
                    .methods(RequestMethod.POST, RequestMethod.GET) // 请求方法,可以指定多个
                    .build();
            // 发布url,指定一下url的处理器
            requestMappingHandlerMapping.registerMapping(requestMappingInfo, customHandlerUrl, CustomHandlerUrl.HANDLE_CUSTOM_URL_METHOD);
        }
    }
}

7. 重新测试

此时,请求可以发现,效果和使用@RestController+@RequestMapping注解就一样了。

四、写在最后

自定义发布URL路径一般情况下很少使用,不过针对特殊url的处理,以及自定义rpc框架发布url时,选择这样处理好了,可以达到出其不意的效果。

相关推荐
gentle_ice1 小时前
leetcode——矩阵置零(java)
java·算法·leetcode·矩阵
Future_yzx1 小时前
基于SpringBoot+WebSocket的前后端连接,并接入文心一言大模型API
spring boot·websocket·文心一言
stevewongbuaa1 小时前
一些烦人的go设置 goland
开发语言·后端·golang
whisperrr.2 小时前
【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南
java·架构·tomcat
火烧屁屁啦3 小时前
【JavaEE进阶】应用分层
java·前端·java-ee
m0_748257463 小时前
鸿蒙NEXT(五):鸿蒙版React Native架构浅析
java
我没想到原来他们都是一堆坏人3 小时前
2023年版本IDEA复制项目并修改端口号和运行内存
java·ide·intellij-idea
Suwg2094 小时前
【由浅入深认识Maven】第1部分 maven简介与核心概念
java·maven
花心蝴蝶.5 小时前
Spring MVC 综合案例
java·后端·spring