微服务: 学习几个容易混淆的URL注解

了解 URL

首先,大家需要知道下面这三个东西的定义:

URI,Universal Resource Identifier,统一资源标志符

URL,Universal Resource Locator,统一资源定位符

URN,Universal Resource Name,统一资源名称

在 WWW 上,每一信息资源都有统一的且在网上唯一的地址,该地址就叫 URL,它是 WWW 的统一资源定位标志,就是指网络地址。

URL 是 URI 的子集,所有的 URL 都是 URI,但不是每个 URI 都是 URL,还有可能是 URN,他们的关系可以用下面的图来表示:

换句话说,URI 分为三种,URL 或者 URN 或者是 URL 和 URI 的组合。

不管怎么说,大家平时使用HTTP请求的地址基本都是称之为 URL,所以暂时不必要纠结于三者的区别之中而无法自拔,搞懂 URL 的组成部分和规范才是重点。

URL 由三部分组成即资源类型、存放资源的主机域名和资源文件名。

URL 的一般语法格式为:

1
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

具体参数释义如下:

  • scheme:传送协议有些地方也写作protocol,如http、https、ftp等

  • user:password:访问资源需要的凭证信息,可省略

  • host:服务器地址,通常为域名,有时为IP地址

  • port:端口号,以数字方式表示,一般使用端口默认值80,可省略

  • path:路径,以“/”字符区别路径中的每一个目录名称

  • query:查询,GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题

  • fragment:片段,以“#”字符为起点

常用的 URL 如下:

1
2
3
http://www.veryitman.com/article/name=itman&pwd=123
http://www.veryitman.com/article/1/2

大部分的编程语言都会有针对 URL 处理的系统函数库,最常用的如 URLEncodeURLDecode 针对 URL 编解码的类。

下面跟大家分享一下在 SpringBoot 中经常使用的 RequestParamQueryParamPathVariablePathParam 这三个注解。

@RequestParam

注解 @RequestParam 可以从每个 HTTP Request 中获取参数,该注解支持下面四种参数:

  • defaultValue 如果本次请求没有携带这个参数,或者参数为空,那么就会启用默认值
  • name 绑定本次参数的名称,要跟 URL 上面的一样,否则会请求失败
  • required 用来标示对应的参数是不是必须的,默认是 true,如果对某个参数做可选值可以设置该值为 false
  • value 是 name 属性的一个别名,value 和 name 只能出现一个否则会请求报错

下面是注解 @RequestParam 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

这里重点说一下 value 这个值,举个例子。

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@RequestParam(value = "page") String page, @RequestParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

该方法有两个参数,分别是 pagepageSize,注意这里的参数 pageSize 是大写的,在注解 @RequestParam 中对应的 value 值也是大写的 pageSize,现在启动工程后,请求一下下面的地址。

1
http://localhost:8080/friends?page=1&pageSize=2

这个时候可以正常访问且能返回正确的结果,假如我们将 @RequestParam 中对应的 value 值改为小写的 pagesize 再来使用上面的地址访问呢?

就会报下面的错误,如图所示:

所以需要修改访问地址,即将 pageSize 改为 pagesize

1
http://localhost:8080/friends?page=1&pagesize=2

那我们再思考另外一个问题,是否可以修改注解 @RequestParam 中对应的value值的 page 和 pageSize 为其他的比如 xx、yy?

动手试试就知道了!

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@RequestParam(value = "xx") String page, @RequestParam(value = "yy") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

函数本身的参数名称 page 和 pageSize 没有做任何修改,只是将注解中的修改为 xxyy 了,再次访问下面的URL:

1
http://localhost:8080/friends?xx=1&yy=2

一如既往的正常~ 所以,对于注解 @RequestParam 的参数,URL中对应的参数一定要和value对应。

@QueryParam

这个跟 @RequestParam 基本一致,都是以键值对的方式来或者参数。

只不过,该注解需要在 pom 文件中导入依赖,如下:

1
2
3
4
5
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>

然后在工程类中需要导入包,如下:

1
import javax.ws.rs.QueryParam;

注解 @QueryParam 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryParam {
/**
* Defines the name of the HTTP query parameter whose value will be used
* to initialize the value of the annotated method argument, class field or
* bean property. The name is specified in decoded form, any percent encoded
* literals within the value will not be decoded and will instead be
* treated as literal text. E.g. if the parameter name is "a b" then the
* value of the annotation is "a b", <i>not</i> "a+b" or "a%20b".
*/
String value();
}

可以看到 @QueryParam 只有 value 一个属性,value 对应的值要和函数参数保持一致,否则可能请求会得不到预期的响应结果,这个跟注解 @RequestParam 是不一样的。下面给个示例,如下:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@QueryParam(value = "page") String page, @QueryParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器请求下面的地址即可。

1
http://localhost:8080/friends?page=1&pageSize=2

@PathVariable

注解 @PathVariable 与上面的刚说的注解 @RequestParam 是不同的,@PathVariable 需要从 URI 中获取参数,比如下面的例子,如下:

1
2
3
4
@RequestMapping(value = "/friends/{page}/{pagesize}", method = RequestMethod.GET)
public String getFrieds(@PathVariable(value = "page") String page, @PathVariable(value = "pagesize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pagesize: " + pageSize;
}

在 @RequestMapping 中写了 {page}/{pagesize},二者带上了 {} 这样 @PathVariable 才能识别。

请求下面的地址,可以正常访问。

1
http://localhost:8080/friends/1/2

相当于 page=1,pagesize=2,所以注解 @PathVariable 是从URI 中匹配参数的,他不是以键值对方式获取对应的值。

@PathVariable 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}

有三个属性 value、name 和 required,其中 value 也是 name 的别名。

@PathParam

注解 @PathParam 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package javax.websocket.server;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface PathParam {
String value();
}

可以看出,@PathVariable 和 @RequestParam 是 org.springframework.x 包下面的即属于spring框架,而 @PathParam 属于 javax.websocket.x 包下面的。

注解 @PathParam 只有一个属性 value,没有其他属性了。@PathParam 里面的value对应的值一定要和函数参数名称一致(包括大小写),示例:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@PathParam(value = "page") String page, @PathParam(value = "pageSize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器中请求下面的地址,请求和响应都是正常的。

1
http://localhost:8080/friends?page=1&pageSize=2

如果 @PathParam 中 value 对应的值和函数参数不一致,请求得不到预期的响应。现在修改 value 的 pageSizepagesize,如下:

1
2
3
4
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public String getFrieds(@PathParam(value = "page") String page, @PathParam(value = "pagesize") String pageSize) {
return "Get friend list. " + "page: " + page + " - pageSize: " + pageSize;
}

在浏览器中请求下面的地址:

1
http://localhost:8080/friends?page=1&pagesize=2

就会得到下面的结果,如下图所示:

但是你使用 http://localhost:8080/friends?page=1&pageSize=2 对于上面的代码请求和响应也是正常的,这就说明注解 @PathParam 中请求的 URL 参数是以函数参数为主的,他和注解 @QueryParam 、@RequestParam 都是以键值对方式获取对应的值。

所以,注解 @PathParam 中 value 对应的值和函数参数尽量保持一致。


学海无涯苦作舟~

坚持原创技术分享!