解决

如果我们遇到的问题相同,那你看完这段代码就可以知道解决方法。

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

@RestController
@RequestMapping("/api/user")
public class UserController {
    @GetMapping(path = {"/{userId}", "/"})
    public void user(@PathVariable(value = "userId", required = false) String uid) {
        if (uid == null) {
            uid = "默认的id值";
        }
        
        String id = uid;    // 获取到值
        // do something.
    }
}

核心的两步:
@RequestMapping注解的path属性值可以是字符串数组,即多个URL模式。
@PathVariable注解的required属性表示该路由变量是否是必须的,需要设置为false

Intro

首先,依赖详情:org.springframework.spring-web:5.3.4 注解路径:org.springframework.web.bind.annotation.PathVariable

其次:HTTP 客户端向服务端传递参数的4种方式 SpringMVC的对应接收方式

设计RESTful风格的API如下
get /api/user/{userId}

get /api/user/1001 表示获取id为1001的用户信息
get /api/user/1003 表示获取id为1003的用户信息
get /api/user/1004 表示获取id为1004的用户信息

如果接口的调用方(前段/或其他消费端)未传入userId,能否在服务的提供段设置默认值?即:
get /api/user 后台会获取一个默认的用户信息(如id为1000的用户信息)
可以。

解决

  • V1
@RequestMapping(path = "/api/user/{userId}")
public void user(@PathVariable(value = "userId") String uid) {
    String id = uid;    // 获取到值
    // do something.
}

V1的缺点是: 只能处理 /api/user/xxx这样的请求,而对于/api/user这样的请求,他根本不处理(非己之责)

  • V2
@RequestMapping(path = {"/api/user/{userId}", "/api/user"})
public void user(@PathVariable(value = "userId", required = false) String uid) {
    if (uid == null) {
        uid = "默认的id值";
    }
    String id = uid;    // 获取到值
    // do something.
}

首先,@RequestMapping注解的path属性是这样定义的:

@AliasFor("value")
String[] path() default {};

即可以设置请求映射规则为对多个path值进行拦截处理。

只改path还不够。
还需要修改@PathVariablerequired属性为false
这样即使不传入这个路由变量。也可以被该方法拦截。

  • V3

通常,在Controller类上使用注解@RequestMapping指定基本路径,
而对于该控制器中的具体方法,可以使用以下几种指定了请求方法的、带有请求映射功能的注解去标记。

@PostMapping 增
@DeleteMapping 删
@PutMapping 改
@GetMapping 查
@PatchMapping 部分字段修改

修改后:

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

@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping(path = {"/{userId}", "/"})
    public void user(@PathVariable(value = "userId", required = false) String uid) {
        if (uid == null) {
            uid = "默认的id值";
        }
        String id = uid;    // 获取到值
        // do something.
    }

}

这样,当请求格式为get /api/user/xxx/api/user时,都会进入该方法,执行一套逻辑。
相当于设置了路由变量{userId}的默认值。

思考

对处于路由路径path末端的路由单节路由变量可设置默认值,
那么对于路由路径path中,中间部分的某节路由变量,能否也设置默认值?

@RequestMapping(path = {"/api/user/{userId}/profile", "/api/user/profile"})
public void profile(@PathVariable(value = "userId", required = false) String uid) {
	System.out.println("拿到的路由变量:" + uid);
	if (uid == null) {
		uid = "默认的id值";
	}
    String id = uid;
}

经测试可以。

get /api/user/id2002/profile可以返回userId为id2002的用户profile。
get /api/user/profile也可以返回一个userId为所设置默认值的用户profile。

源码

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
 * Annotation which indicates that a method parameter should be bound to a URI template
 * variable. Supported for {@link RequestMapping} annotated handler methods.
 *
 * <p>If the method parameter is {@link java.util.Map Map<String, String>}
 * then the map is populated with all path variable names and values.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 * @see RequestMapping
 * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {

	/**
	 * Alias for {@link #name}.
	 */
	@AliasFor("name")
	String value() default "";

	/**
	 * The name of the path variable to bind to.
	 * @since 4.3.3
	 */
	@AliasFor("value")
	String name() default "";

	/**
	 * Whether the path variable is required.
	 * <p>Defaults to {@code true}, leading to an exception being thrown if the path
	 * variable is missing in the incoming request. Switch this to {@code false} if
	 * you prefer a {@code null} or Java 8 {@code java.util.Optional} in this case.
	 * e.g. on a {@code ModelAttribute} method which serves for different requests.
	 * @since 4.3.3
	 */
	boolean required() default true;

}

可以看到@PathVariable注解有三个属性值,其中namevalue互为同义词/别名。
required就是我们利用到的属性。