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


    一、前言

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

    二、一般开发流程

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

    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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    测试效果:

    在这里插入图片描述

    三、自定义URL发布逻辑

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

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

            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2. 修改Controller实现类代码

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

    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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    参考代码如下:

    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));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    4. 测试效果

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

    在这里插入图片描述

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

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

    java.lang.IllegalArgumentException: Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".
    
    • 1

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

    spring:
      mvc:
        pathmatch:
          matching-strategy: ant_path_matcher
    
    • 1
    • 2
    • 3
    • 4

    5. 增加统一请求处理器

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

    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;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    6. 修改事件监听逻辑

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

    示例代码:

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    7. 重新测试

    在这里插入图片描述

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

    四、写在最后

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

  • 相关阅读:
    day4 力扣 11.22 素数个数统计
    C程序是如何跑起来的01 —— 普通可执行文件的构成
    Docker下varlibdockeroverlay2空间清理办法
    如何在SOLIDWORKS PDM中快速导出BOM表
    Linux——进程间通信(管道及共享内存)
    振南技术干货集:CPU,你省省心吧!(2)
    abc 325 d
    java计算机毕业设计springboot+vue地铁站自动售票系统-火车票售票系统
    华为OD面试分享9(2024年)
    第63篇-解释器与编译器适配(二)
  • 原文地址:https://blog.csdn.net/C_AJing/article/details/136309951