網頁

2021/1/12

Spring Web @RequestMapping optional @PathVariable API

Spring Web在Controller使用@RequestMapping設定API路徑時可選擇是否填入路徑參數(path variable)的作法如下。

範例環境:

  • Spring Boot 2.3.2
  • Spring Web

Spring Web @RequestMapping若設定了路徑參數@PathVariable,則一定要填入參數才能正確呼叫API。例如下面DemoController.api()必須/api/後接任意字才能正確呼叫,例如/api/helloworld;而/api則返回404找不到結果。

DemoController

package com.abc.demo.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @RequestMapping(value = "/api/{text}", method = RequestMethod.GET)
    public void api(@PathVariable("text") String text) {
        System.out.println(text);
    }
}

若希望/api也能正確呼叫DemoController.api()則改為設定如下。@RequestMapping.value多設一條無路徑參數的API路徑並將@PathVariable.required設為false。

DemoController

package com.abc.demo.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @RequestMapping(value = {"/api", "/api/{text}"}, method = RequestMethod.GET)
    public void api(@PathVariable(value = "text", required = false) String text) {
        System.out.println(text);
    }
}


但如果有兩個連續的路徑參數,且兩個都設為非必填時,則只能全不填或全填。

例如下面路徑參數arg1arg1@PathVariable.required接設為false。

DemoController

package com.abc.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping(value = {"/api", "/api/{arg1}/{arg2}"})
    public String api(
            @PathVariable(value = "arg1", required = false) String arg1,
            @PathVariable(value = "arg2", required = false) String arg2
    ) {
        if (arg1 == null) {
            arg1 = "";
        }
        if (arg2 == null) {
            arg2 = "";
        }
        return arg1 + arg2;
    }
}

/api/api/a/b才能正確呼叫,/api/a/api//b則會404 Not Found找不到資源。

測試如下。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest
public class DemoControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void api_arg1AndArg2_ok() throws Exception {
        mockMvc.perform(get("/api/a/b"))
                .andExpect(status().isOk())
                .andExpect(content().string("ab"));
    }

    @Test
    public void api_noArgs_ok() throws Exception {
        mockMvc.perform(get("/api"))
                .andExpect(status().isOk())
                .andExpect(content().string(""));
    }

    @Test
    public void api_arg1_notFound() throws Exception {
        mockMvc.perform(get("/api/a"))
                .andExpect(status().isNotFound());
    }

    @Test
    public void api_arg2_notFound() throws Exception {
        mockMvc.perform(get("/api//b"))
                .andExpect(status().isNotFound());
    }

}


若改用Map<String, String>來接連續的路徑參數,一樣只能全不填或全填。否則會404 Not Found。

DemoController

package com.abc.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class DemoController {

    @GetMapping(value = {"/api", "/api/{arg1}/{arg2}"})
    public String api(@PathVariable Map<String, String> args) {
        String arg1 = args.get("arg1");
        String arg2 = args.get("arg2");
        if (arg1 == null) {
            arg1 = "";
        }
        if (arg2 == null) {
            arg2 = "";
        }

        return arg1 + arg2;
    }
}

/api/api/a/b才能正確呼叫,/api/a/api/a//api//b會404 Not Found。


測試如下。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest
public class DemoControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void api_arg1AndArg2_ok() throws Exception {
        mockMvc.perform(get("/api/a/b"))
                .andExpect(status().isOk())
                .andExpect(content().string("ab"));
    }

    @Test
    public void api_noArgs_ok() throws Exception {
        mockMvc.perform(get("/api"))
                .andExpect(status().isOk())
                .andExpect(content().string(""));
    }

    @Test
    public void api_arg1_notFound() throws Exception {
        mockMvc.perform(get("/api/a"))
                .andExpect(status().isNotFound());
    }

    @Test
    public void api_arg2_notFound() throws Exception {
        mockMvc.perform(get("/api//b"))
                .andExpect(status().isNotFound());
    }

}


沒有留言:

張貼留言