求助! springboot 如何获取 url 上的参数,@PathVariable 复用问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Aliberter
V2EX    Java

求助! springboot 如何获取 url 上的参数,@PathVariable 复用问题

  •  
  •   Aliberter 2021-05-26 10:43:18 +08:00 4013 次点击
    这是一个创建于 1602 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在工作中有这样一个需求,需要我把 c#的一个项目里的接口都转成 java 实现。这些接口都有共同的请求规则:/{controller}/{action}/{apiVersion}/{userId}/{clientName},举例子比如: http://127.0.0.1:8080/home/index/6.0.0/0/Any. 前面两个参数是 controller 名和方法名,这个我都可以在 @RequestMapping 里写死,但是后面的 apiVersion 、userId 、clientName 这些参数,我不可能在每个 controller 方法的注解上都写上占位符然后用 @PathVariable 获取吧,太 low 了,后期想统一维护都没法维护,而且这些参数要求如果 url 里没有的话要赋默认值。所以问问大佬们,springboot 中如何有没有更好的实现方式呢?无论是拦截器、aop,想来想去都没有太明确的思路。

    原 c#项目里是用路由实现的,这样配置后都会自动去找 controller 里的方法,并且给方法入参赋(默认)值:

    routes.MapRoute( name: "Default", url: "{controller}/{action}/{apiVersion}/{userId}/{clientName}", defaults: new { cOntroller= "Home", action = "Index", apiVersion = "6.0.0", userId = "0", clientName = ClientNames.Any } }); 

    我现在写出来只能是这样的,但我不能 200 多个接口都这样写吧:

    @RestController @RequestMapping("/home") public class HomePageController { @RequestMapping("/index/{apiVersion}/{userId}/{clientName}") public IndexResponse index(@PathVariable("apiVersion") String apiVersion, @PathVariable("userId") String userId, @PathVariable("clientName") String clientName) { return null; } } 

    真有大佬能提供解决思路,我愿意有偿哈~多谢了

    24 条回复    2021-05-28 07:45:43 +08:00
    taogen
        1
    taogen  
       2021-05-26 11:04:09 +08:00
    我觉得这样写可以啊,一个 action 不就是一个方法吗。难道 C# 中不用写 200 多个方法(接口)?还是你觉得每个接口都加一个 @RequestMapping 很麻烦?
    agzou
        2
    agzou  
       2021-05-26 11:06:02 +08:00
    切面加自定义注解实现
    Aliberter
        3
    Aliberter  
    OP
       2021-05-26 11:10:07 +08:00
    @taogen 不是,我是觉得每一个方法都要写那三个 @PathVariable 麻烦,本来就是共性的东西,所以想问问怎么实现比较优雅
    KotlinAmai
        4
    KotlinAmai  
       2021-05-26 11:10:12 +08:00   5
    可以通过转发实现

    @RestController
    public class DefaultController {

    @RequestMapping("/{controller}/{action}/{apiVersion}/{userId}/{clientName}")
    public void index(@PathVariable String controller,
    @PathVariable String action,
    @PathVariable String apiVersion,
    @PathVariable String userId,
    @PathVariable String clientName, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    request.setAttribute("apiVersion", apiVersion);
    request.setAttribute("userId", userId);
    request.setAttribute("clientName", clientName);

    request.getRequestDispatcher(String.format("/%s/%s", controller, action)).forward(request, response);
    }

    @RequestMapping("/home/index")
    public String hello(HttpServletRequest request) {
    System.out.println(request.getAttribute("apiVersion"));
    System.out.println(request.getAttribute("userId"));
    System.out.println(request.getAttribute("clientName"));
    return "Hello World!";
    }

    }
    Ticmtc
        5
    TicSmtc  
       2021-05-26 11:10:52 +08:00
    自己解析 url 然后反射?
    TicSmtc
        6
    TicSmtc  
       2021-05-26 11:12:28 +08:00
    4 楼说的这个法子貌似挺好
    timethinker
        7
    timethinker  
       2021-05-26 11:13:17 +08:00   2
    这里的问题就是把一些原本更适合放在 Header 中的参数放到了 URL 上。

    如果楼主确实需要一种解决方案,我个人的做法可能就是写一个 Filter,然后对 Request 进行包装( HttpServletRequestWrapper )并重写 getRequestURI()方法,相当于 rewrite,把这些 URL 路径参数转移到一个 ThreadLocal 上(或者 Header,总之让它存到另一个地方),然后就可以比较干净的来写 Controller 了。
    taogen
        8
    taogen  
       2021-05-26 11:25:51 +08:00   1
    @Aliberter #3
    我觉得想办法去掉 @PathVariable 没必要,可能有很优雅的方法做到。但是会增加代码的复杂度,增加了一层 HTTP URL 到 controller URL 的转换关系。

    另外,我觉得可以这样实现,我没有验证,只是提供一个思路。
    1 )写一个 filter 。
    2 )在 filter 中 forward 请求,把 URL 中的参数放到 request 中。
    3 )写一个实体类 BaseParam 封装 apiVersion, userId, 和 clientName,controller 接口中用 @ModelAttribute BaseParam baseParam 接收参数。
    rd554259440
        9
    rd554259440  
       2021-05-26 11:27:55 +08:00
    楼上答的不是想要的吧..........用对象接收,把参数都放对象里,不就可以只写一个到处使用了........
    xiangyuecn
        10
    xiangyuecn  
       2021-05-26 11:28:26 +08:00   2
    脱离框架来思考,所有功能都异常简单,非常容易移植

    不然去研究 xx 框架有没有 xx 功能,如果以前会,那还好,不会?学习成本比自己手撸一个框架还高

    就你这个事,按我的脑回路 优先想到的就是写一个静态的类,每个参数都提供一个静态 get 方法,直接取当前请求上下文中的 url,提取对应变量和默认值,几十行代码半小时搞定,研究 spring 半天不一定搞得定,毕竟人家写的东西代码又多又看不懂
    agzou
        11
    agzou  
       2021-05-26 11:30:21 +08:00   1
    @Aspect
    @Component
    @RestController
    public class Demo {
    @RequestMapping()
    public void test() {
    //获取参数
    ParamsHolder.getApiVersion();
    }

    @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void pointCut() {

    }

    @Around("pointCut()")
    public Object process(ProceedingJoinPoint pjp) throws Throwable {
    try {
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest req = servletRequestAttributes.getRequest();
    ParamsHolder.setApiVersion(getApiVersion(req));
    return pjp.proceed();
    } finally {
    ParamsHolder.removeApiVersion();
    }

    }

    private String getApiVersion(HttpServletRequest request) {
    // TODO: 2021/5/26 获取需要的参数
    return null;
    }

    public static class ParamsHolder {
    private static final ThreadLocal<String> API_VERSION = new ThreadLocal<>();

    public static String getApiVersion() {
    return API_VERSION.get();
    }

    private static void removeApiVersion() {
    API_VERSION.remove();
    }

    private static void setApiVersion(String apiVersion) {
    API_VERSION.set(apiVersion);
    }
    }
    }
    huifer
        12
    huifer  
       2021-05-26 12:38:22 +08:00
    自己写 url 解析写 aop,写拦截器你在自己做类型转换等你写完这个就相当于实现了 spring-mvc 中的路由解析只是没有注解.
    huifer
        13
    huifer  
       2021-05-26 13:15:15 +08:00   1
    @Aliberter
    解决方案为开启一个独立的 servlet,具体在 springboot 中注入方式如下:

    ```
    @Component
    @ComponentScan("com.example.demo.*")
    public class Beans {
    @Autowired
    private ApplicationContext context;

    @Bean
    public ServletRegistrationBean viewRedisServlet() {
    ServletRegistrationBean<Servlet> servletServletRegistratiOnBean= new ServletRegistrationBean<>();
    CustomerServlet servlet = new CustomerServlet();
    servlet.setContext(context);

    servletServletRegistrationBean.setServlet(servlet);
    return servletServletRegistrationBean;
    }
    }
    ```

    第二步编写 servlet 具体代码如下:

    ```
    @WebServlet
    public class CustomerServlet extends HttpServlet {
    Gson gson = new Gson();
    private ApplicationContext context;

    public ApplicationContext getContext() {
    return context;
    }

    public void setContext(ApplicationContext context) {
    this.cOntext= context;
    }

    @SneakyThrows @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    // 从请求头中获取一个标记,用于确认是需要进行处理的

    String c = req.getHeader("is_c");
    if (Boolean.valueOf(c)) {

    String cOntextPath= req.getContextPath();
    String servletPath = req.getServletPath();

    // /{controller}/{action}/{apiVersion}/{userId}/{clientName}
    String requestURI = req.getRequestURI();
    String[] split = requestURI.split("/");

    String cOntroller= null;
    String action = null;
    String apiVersion = null;
    String userId = null;
    String clientName = null;
    if (split.length == 6) {
    cOntroller= split[1];
    action = split[2];
    apiVersion = split[3];
    userId = split[4];
    clientName = split[5];
    }

    // 通过 spring 上下文去搜索 controller + actiron 对应的方法

    ApplicationContext tuUse = this.context;
    if (StringUtils.hasText(controller)) {
    // 找到实例
    Object bean = tuUse.getBean(controller);
    // 找到执行方法
    Method[] methods = bean.getClass().getDeclaredMethods();

    Method toCall = null;
    for (Method method : methods) {
    boolean equals = method.getName().equals(action);
    if (equals) {
    toCall = method;
    break;
    }
    }
    // 获取方法参数类型, 你需要做转换
    Class<?>[] types = toCall.getParameterTypes();

    // 转换后进行参数使用调用方法
    Object invoke = toCall.invoke(bean, apiVersion, userId, clientName);

    resp.setContentType("application/json; charset=");
    resp.getWriter().write(gson.toJson(invoke));
    }
    System.out.println(contextPath);
    }
    }

    }
    ```

    上述代代码处理流程:

    1. 判断是需要进行处理的
    2. 将 url 中的 /{controller}/{action}/{apiVersion}/{userId}/{clientName}参数提取
    3. 通过成员变量 context 在 spring 中根据名字获取 bean 实例,名字是 controller,通过 spring 中 Component 注解的 value 进行赋值
    4. 在 bean 实例种搜索 action 对应的方法,这里要求方法名称和 action 强对应。
    5. 将上一步得到的方法提取方法参数,将 url 参数进行类型转换。
    6. 反射执行
    7. response 返回





    其他代码如下:

    ```
    @Data
    public class IndexResponse {
    private int code;
    private Object data;
    }
    ```



    ```
    @Service(value = "home")
    public class HomePageController {

    public IndexResponse index(
    String apiVersion,
    String userId,
    String clientName) {

    IndexResponse respOnse= new IndexResponse();
    response.setCode(1);
    response.setData(apiVersion + "-" +
    userId + "-" +
    clientName);
    return response;
    }

    }
    ```



    测试用例如下:

    GET http://localhost:8080/home/index/6.0.0/0/Any.

    HTTP/1.1 200
    Content-Type: application/json;charset=
    Content-Length: 32
    Date: Wed, 26 May 2021 05:14:26 GMT
    Keep-Alive: timeout=60
    Connection: keep-alive

    {
    "code": 1,
    "data": "6.0.0-0-Any."
    }
    ikas
        14
    ikas  
       2021-05-26 13:26:19 +08:00   4
    ......................................
    了解下?
    @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
    public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
    }
    zliea
        15
    zliea  
       2021-05-26 13:35:59 +08:00
    Map<String, String> pathVariableRequestMap = (Map<String, String>) nativeWebRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    FreeEx
        16
    FreeEx  
       2021-05-26 14:09:53 +08:00
    这个接口设计就很拉跨,安卓看了沉默,iOS 看了流泪,前端出来骂街。
    zliea
        17
    zliea  
       2021-05-26 14:25:16 +08:00   1
    详细说一下

    自定义一个注解,实现 HandlerMethodArgumentResolver 的 Bean,在 @EnableWebMvc 中配置并注入这个 Bean 。
    然后在 resolveArgument 使用
    Map<String, String> pathVariableRequestMap = (Map<String, String>) nativeWebRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    获取所有 url 参数,进行通用处理,如果接口需要这些数据的,可以返回一个类,里边包含这些通用参数。

    不需要参数的,可以在方法上加入这个注解。需要参数的,在方法参数上加入注解与返回类。
    Aliberter
        18
    Aliberter  
    OP
       2021-05-26 15:15:19 +08:00
    @actar 多谢大佬,采用了你的方法,如有需要留个支付宝我赞助杯咖啡喝,以后还要多请教
    Aliberter
        19
    Alibrter  
    OP
       2021-05-26 15:16:29 +08:00
    @agzou 多谢大佬,我学习学习
    Aliberter
        20
    Aliberter  
    OP
       2021-05-26 15:17:17 +08:00
    @huifer 多谢大佬!!学习下
    KotlinAmai
        21
    KotlinAmai  
       2021-05-26 16:42:37 +08:00
    MjEwMDY3MTgxMUBxcS5jb20=
    这是支付宝,也是邮箱。
    有问题可以多多交流。
    @Aliberter
    jorneyr
        22
    jorneyr  
       2021-05-26 16:56:49 +08:00
    这不就是另一个 ServletDispatcher 吗?用拦截器然后再处理吧。
    bringyou
        23
    bringyou  
       2021-05-27 18:18:03 +08:00
    可以用 #14 的 model attribute
    如果路径上的值跟变量名一样,可以省略 @PathVariable 的括号
    MarioLuo
        24
    MarioLuo  
       2021-05-28 07:45:43 +08:00 via Android   1
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2969 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 00:21 PVG 08:21 LAX 17:21 JFK 20:21
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86