前言
本篇文章,我们接着来学习spring-web对应jar包中的注解,Spring版本:4.3.14。本篇要学习的注解包括:MatrixVariable
,SessionAttribute
,SessionAttributes
,ResponseStatus
,RequestAttribute
,RequestHeader
,CookieValue
,RequestPart
,CrossOrigin
。
一、Spring-web相关注解
1. MatrixVariable注解
在 applicationContext.xml-mvc标签 这篇文章中,我们已经了解过什么是Matrix-variable,其实说白了就是URL中使用键值对的这种特性。matrix变量可以出现在任何路径中,使用分号进行分割,而对应的该注解用于参数上来绑定URL中的matrix变量,比如:"/cars;color=red;year=2012"
,而多个值是使用逗号进行分割,比如:"/color=red,green,blue"
,或者也可以使用重复的变量名,如:color=red;color=green;color=blue
。
首先来简单看下它的几个属性:
name
,这个很简单,matrix变量的名称;value
,和name功能一样,是name的别名;pathVar
,如果需要进行消除歧义(例如在多个路径中存在相同名称的matrix变量),可以设置该URI路径变量的名称;required
,是否必须,默认是true;defaultValue
,默认值,不多说了;
不过默认情况下Spring MVC是没有开启该功能的,需要我们去配置下,如果我们使用XML的话,那么对应的配置是enable-matrix-variables,该参数默认配置为false:
<mvc:annotation-driven enable-matrix-variables="true" />
而如果我们使用纯注解开发的话,必须设置 RequestMappingHandlerMapping 中的 removeSemicolonContent 为 false,默认情况下该设置是true。由于我的配置类SpringConfig是继承自WebMvcConfigurerAdapter
,所以只需要覆盖它的方法configurePathMatch
即可,这里RequestMappingHandlerMapping会委托给UrlPathHelper去查找相应的path,我们可以按照如下配置:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
接下来我们来看下官网提供的几个例子:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
根据传入的参数/pets/42;q=11;r=22
,我们可以得到对应的q的值是11。
在复杂的情况下,由于路径的每部分都可能包含matrix变量,在某些情况下,您需要更具体地确定变量的位置,这时候就可以借助 pathVar 变量:
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name = "q", pathVar = "ownerId") int q1,
@MatrixVariable(name = "q", pathVar = "petId") int q2) {
// q1 == 11
// q2 == 22
System.out.println(q1);
System.out.println(q2);
}
我们可以通过pathVar参数,也就是占位符的名称 来区分URL参数中两个q变量的值。
同样,matrix变量也可以定义为可选的,也可以指定默认值:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
System.out.println(q);
}
当然,matrix变量的接收参数也可以是Map,如果是Map并且没有指定matrix的变量名,那么该map将包含所有的matrix变量和对应的值:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar = "petId") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 11, "s" : 23]
System.out.println(matrixVars);
System.out.println(petMatrixVars);
}
这里简单说下,MultiValueMap是Spring提供的一个接口,功能是一个key可以对应多个value,从上面的例子可以看出,属性matrixVars的一个key是q,对应的值有多个。我们可以看下MultiValueMap的继承结构就可以看出来了:
public interface MultiValueMap<K, V> extends Map<K, List<V>> {
}
因为一般情况下,Map中的key是不允许重复的,我们将上面例子中的MultiValueMap修改为Map,然后传入相同的参数(有两个q参数),来看一下结果:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(@MatrixVariable Map<String, String> matrixVars,
@MatrixVariable(pathVar = "ownerId") Map<String, String> petMatrixVars) {
System.out.println(matrixVars);
System.out.println(petMatrixVars);
}
看一下打印结果:
{q=11, r=12, s=23}
{q=11, r=12}
可以看出map对象matrixVars中的q的值保存的是第一个,这里可能需要我们注意,可以使用pathVar
来区分,从而避免出现这种情况。
如需有关matrix-variable的更多内容,可以查看官网文档:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/mvc.html
有关matrix的使用可参考这篇文章:https://blog.csdn.net/csdnfanguyinheng/article/details/51586633
2. SessionAttribute,SessionAttributes注解
SessionAttributes
默认情况下,ModelMap中属性的作用域是request级别,也就是说,当本次请求结束后,Model中的属性将被销毁,如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求能在 ModelMap 的属性列表中访问到这些属性。而这一功能的实现则是通过@SessionAttributes和SessionAttribute注解来实现的。
先来看下SessionAttributes注解的几个参数:
names
和value
,存储于session中的Model属性的名称,数组格式;types
,存储于session中的Model属性的类型,数组格式;
接下来,通过简单的例子来学习一下它们的用法。
@RestController
@RequestMapping("/world")
@SessionAttributes("currUser")
public class WorldController {
@RequestMapping(value = "/test.html", method = RequestMethod.GET)
public String test(User user, ModelMap modelMap) {
System.out.println(user);
// 添加属性名为currUser的对象
modelMap.addAttribute("currUser", user);
return "test";
}
}
我们将ModelMap中属性名为currUser的属性放到Session属性列表中,以便这个属性可以跨请求访问。然后我们在另一个控制器方法中查看session中对应的该属性。
@RequestMapping(value = "/test2.html", method = RequestMethod.GET)
public String test2(HttpServletRequest request, HttpSession session) {
System.out.println("currUser:" + session.getAttribute("currUser"));
return "test2";
}
首先,我们发送第一个方法对应的请求:http://localhost:8080/world/test.html?id=123&name=zhangsan
,然后再发送第二个方法对应的请求:http://localhost:8080/world/test2.html
,然后看一下打印出的对象:
User{name='zhangsan', id='123', aDouble=null}
currUser:User{name='zhangsan', id='123', aDouble=null}
可以看到session中已经保存了我们的对象。而如果要清除session中的对象,可以使用
结合文档和上面的例子,我们可以看到:
- 这里我们仅将一个 ModelMap 的属性放入 Session 中,其实 @SessionAttributes 允许指定多个属性。你可以通过字符串数组的方式指定多个属性,如 @SessionAttributes({“attr1”,”attr2”});
- 此外,@SessionAttributes 还可以通过属性类型指定要存到 session 的 ModelMap 属性,如
@SessionAttributes(types = User.class)
,当然也可以指定多个类,如@SessionAttributes(types = {User.class,Dept.class})
,还可以联合使用属性名和属性类型指定:@SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})
;
- 此外,@SessionAttributes 还可以通过属性类型指定要存到 session 的 ModelMap 属性,如
- 我们还可以配合@ModelAttribute注解来实现将传入的参数自动添加到session中;
- 而如果要清除session中的对象,可以通过
SessionStatus
的setComplete()方法来完成,但它只清除的是@SessionAttributes的session,不会清除HttpSession的数据;
- 而如果要清除session中的对象,可以通过
- 根据官方文档,对于永久会话属性(如:用户身份验证对象),请使用传统的session.setAttribute方法,另外,也可考虑使用WebRequest来实现。
SessionAttribute
接下来再说下SessionAttribute这个注解。根据文档,我们知道,这个注解用于将全局session中已存在的属性绑定到参数上,和@SessionAttributes
定义在类上不同,@SessionAttribute
是定义在方法上的,并且需要注意下:
@SessionAttribute是Spring4.3之后引入的;
If you need access to pre-existing session attributes that are managed globally, i.e. outside the controller (e.g. by a filter), and may or may not be present use the @SessionAttribute annotation on a method parameter:
也就是说,通过该注解,我们可以将session(也就是javax.servlet.http.HttpSession)中已经存在的属性绑定到对应的参数上,不过要注意,是已经存在的属性。与其直接使用HttpSession,使用该注解还可以做一些类型的自动转换和可选/必需的校验,下面我们来简单看一下参数:
name
和value
,session中存在的属性的名称,默认是根据方法中参数的名称进行推断出来的;required
,session中是否存在该属性,默认是true。如果属性不在session中,那么Spring将会抛出异常,不过在设置为false的情况下,可以接收值为null或者是 Java 8 中的 java.util.Optional。
该注解可用于在全局范围内管理已存在的会话属性,例如在控制器中,例如在过滤器、拦截器等中,适用于存储/访问用户身份验证信息等相关操作。
我们来看一个简单的例子:
我们先在session中存储一个user属性:
@RestController
@RequestMapping("/world")
@SessionAttributes("user")
public class WorldController {
@RequestMapping(value = "/test2.html", method = RequestMethod.GET)
public String test2(HttpServletRequest request, HttpSession session) {
System.out.println("test");
System.out.println(session.getAttribute("user"));
return "test2";
}
@ModelAttribute("user")
public User getUser() {
User user = new User();
user.setId("id");
user.setName("name");
return user;
}
}
然后,再另一个控制器中使用@SessionAttribute注解来获取session中已经存在的user对象:
@RestController
public class HelloController {
@RequestMapping(value = "/test2.html", method = RequestMethod.GET)
public String test2(@SessionAttribute(name = "user", required = false) User user) {
System.out.println(user);
return "test2";
}
}
我们先发送请求,用于向session中保存user对象:/world/test2.html
,再发送请求:test2.html
,来获取session,并绑定到对象user上,然后查看下打印结果:
User{name='name', id='id', aDouble=null}
可以看到,数据已经成功绑定到我们所需要的对象上了。当然这时候如果从客户端传参数绑定到对象上的话,也是绑不上去的。
这里参考了如下文章:
Spring MVC - Accessing pre-existing session attributes using @SessionAttribute
spring学习之@SessionAttributes
https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/mvc.html
3. ResponseStatus 注解
ResponseStatus注解是用于标记一个方法或异常类在返回时响应的http状态,用于方法和异常类上,也可以再Controller上使用此注解,这样所有的RequestMapping都会继承,还可以结合ResponseBody 注解一起使用。
大致了解下它的参数:
code
,请求响应的状态码,默认是HttpStatus.INTERNAL_SERVER_ERROR
,也就是500;value
,code 别名;reason
,对应的错误信息;
我们来看一个简单的例子,先定义我们的异常类:
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "用户相关异常")
public class UserException extends RuntimeException {
}
然后在Controller中抛出该异常:
@RestController
public class HelloController {
@RequestMapping(value = "/test2.html", method = RequestMethod.GET)
public String test2(User user) {
if (user.getId() == null) {
throw new UserException();
}
return "test2";
}
}
最终结果:
我们再来看下该注解修饰方法的情况:
@RestController
public class HelloController {
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "找不到用户信息喽")
@RequestMapping(value = "/test2.html", method = RequestMethod.GET)
public String test2(User user) {
if (user.getId() == null) {
throw new UserException();
}
return "test2";
}
}
然后,我们访问该地址,先不传参数id,然后错误页面和上面一样,因为进了UserException异常。然后我们传id参数,然后可以看到:
但方法的整体流程还是执行完成了,也就是说,在方法上使用该注解,并不影响程序的流程,只是在最终返回的时候会根据状态码和错误信息显示错误页面。
4. RequestAttribute注解
Spring4.3之后添加的注解,用于方法参数上,用于将Web请求(request attribute)中的属性绑定到方法参数上,也可以用于将由过滤器或拦截器创建的属性绑定到方法参数上。和@SessionAttribute类似,也是用于绑定已经存在的属性。
参数简单说下,value
和 name
是要绑定的request attribute的名称,required
表示该请求属性是否必需,默认是true,这种情况下如果该请求属性不存在的话将会抛出异常。
简单看下例子:
@RestController
public class HelloController {
@RequestMapping(value = "/test1.html", method = RequestMethod.GET)
public String test2(@RequestAttribute User user) {
System.out.println(user);
return "test1";
}
@ModelAttribute
public void user(HttpServletRequest request) {
User user = new User();
user.setName("name");
user.setId("id");
request.setAttribute("user", user);
}
}
访问下该接口:/test1.html
,查看下我们获取到的数据:
User{name='name', id='id', aDouble=null}
5. RequestHeader注解
RequestHeader注解用于方法参数,目的是为了将Http的请求头绑定到方法参数上,和HttpServletRequest.getHeader(String)是一样的。该注解的属性很简单,就不看了,直接来看一个简单的例子即可:
@RequestMapping(value = "/test1.html", method = RequestMethod.GET)
public String test2(@RequestHeader("Accept-Language") String language) {
System.out.println(language);
return "test1";
}
打印结果:
zh-CN,zh;q=0.9
如果有兴趣的话,可以了解下Http请求头各个属性的具体含义,我们也可以在发送请求的时候添加一些请求头,然后在后台接收一下。
6. CookieValue注解
CookieValue注解也是用于方法参数,目的是为了将Http的某个cookie值绑定到方法参数上,而要绑定的方法参数类型可以是Cookie对象,字符串,整数类型等;而该注解的几个属性和RequestHeader一样,不多说了,也是来看个简单的例子:
@RequestMapping(value = "/test1.html", method = RequestMethod.GET)
public String test2(@CookieValue("JSESSIONID") String jessionId) {
System.out.println(jessionId);
return "test1";
}
最终会打印出我们cookie中的JSESSIONID
值:
97F942850C6E27905758DCA4E1608CF0
7. RequestPart注解
RequestPart注解也是用于方法参数,目的是为了将文件之类的multipart,也就是multipart/form-data
表单提交的绑定到方法参数上。
而RequestParams也是完全支持这种类型的,它和RequestPart主要的区别,就是请求方法的参数类型不是字符串类型的时候,RequestParam通过注册的转换器或PropertyEditor进行类型转换,而RequestPart则依赖于HttpMessageConverters和请求中的Content-Type
。@RequestParam适用于name-value类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)等。
Note that @RequestParam annotation can also be used to associate the part of a "multipart/form-data" request with a method argument supporting the same method argument types. The main difference is that when the method argument is not a String, @RequestParam relies on type conversion via a registered Converter or PropertyEditor while @RequestPart relies on HttpMessageConverters taking into consideration the 'Content-Type' header of the request part. @RequestParam is likely to be used with name-value form fields while @RequestPart is likely to be used with parts containing more complex content (e.g. JSON, XML).
该注解的参数很简单,就不多说了,下面来简单看下,先看下RequestParam:
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
再来看下RequestPart:
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") Part file) {
InputStream inputStream = file.getInputStream();
// store bytes from uploaded file somewhere
return "redirect:uploadSuccess";
}
}
代码摘自官方文档,地址:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/mvc.html#mvc-multipart
其实,RequestPart和RequestParams在multipart/form-data
方面的功能很相似,但RequestParams一般用于简单的name-value这种形式的参数绑定,而RequestPart则更多的是用于比较复杂的类型,比如JSON,XML等。
8. CrossOrigin注解
CrossOrigin注解是用于处理跨域请求的注解,用于类级别和方法级别上。跨域,通俗点说,就是浏览器不能在当前网站执行其他网站的脚本,这是由于浏览器的同源策略造成的,是浏览器对JS做的安全限制。所谓 同源,是指域名,协议,端口均相同,只要有一个不同就算是跨域。
该注解是Spring4.2之后引入的。
下面我们来看一下它的一些参数(已过时的就不说了):
-
origins
和value
,允许跨域访问的来源列表,数组格式,比如http://domain1.com
,或者*
允许所有来源,默认情况下,所有请求的域都支持。而这些值将都展示在请求头中的Access-Control-Allow-Origin
中,value则是 origins的别名。
-
-
allowedHeaders
,在请求的时候允许的请求头列表,数组格式,*
表示允许所有请求头,默认情况下,所有请求头都支持。同样,这些值都将展示在请求头Access-Control-Allow-Headers
中,如果请求头名称是以下列表中的其中之一,则不必列出来:Cache-Control, Content-Language, Expires, Last-Modified, or Pragma as per the CORS spec。
-
-
exposedHeaders
,响应头中允许的headler,即:Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, or Pragma等,默认情况下是空,同样,这些值都将展示在Access-Control-Expose-Headers
列表中;
-
-
methods
,支持的HTTP请求方法列表,例如"{RequestMethod.GET, RequestMethod.POST}"},默认情况下所支持的方法与控制器映射的方法相同。
-
-
allowCredentials
,是否允许跨域请求的cookie随请求发送(不单单是Cookie),配置的值展示在响应头Access-Control-Allow-Credentials
中。
-
-
maxAge
,返回结果可以用于缓存的最长时间,单位是秒,默认是1800秒,也就是30分钟,展示在响应头Access-Control-Max-Age
中,将此设置为一个合理的值可以减少浏览器所需的预请求/响应交互的数量。
-
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin(origins = "http://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
加在类上,表示整个Controller都支持跨域访问;加到方法上,则是针对具体的方法。而如果要使整个application都支持的话,可以进行如下配置:
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
当然,我们也可以根据需要修改对应属性,以及配置适用于特定的路径模式的跨域请求:
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
这里推荐查看官网文档,文档上有更详细的例子:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/cors.html
有关跨域,还可以参考以下链接:
Cross-Origin Resource Sharing
Spring中@CrossOrigin的作用