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时,选择这样处理好了,可以达到出其不意的效果。

相关推荐
跳动的梦想家h40 分钟前
黑马点评 秒杀下单出现的问题:服务器异常---java.lang.NullPointerException: null(已解决)
java·开发语言·redis
苹果醋343 分钟前
前端面试之九阴真经
java·运维·spring boot·mysql·nginx
哎呦没1 小时前
Spring Boot OA:企业办公自动化的高效路径
java·spring boot·后端
真心喜欢你吖1 小时前
Spring Boot与MyBatis-Plus的高效集成
java·spring boot·后端·spring·mybatis
2401_857636391 小时前
实验室管理技术革新:Spring Boot系统
数据库·spring boot·后端
2401_857600951 小时前
实验室管理流程优化:Spring Boot技术实践
spring boot·后端·mfc
2402_857589361 小时前
企业办公自动化:Spring Boot OA管理系统开发与实践
java·spring boot·后端
G丶AEOM2 小时前
JVM逃逸分析机制
java·jvm
无聊写博客2 小时前
JDK、JRE、JVM的区别
java·开发语言·jvm
message丶小和尚2 小时前
SpringBoot升级全纪录之项目启动
java·spring boot·mybatis