【项目地址】 从零开始学习springmvc
SpringMVC常用注解使用
三、SpringMVC常用注解使用
3.1 @RequestMapping
之前我们使用RequestMapping
最多的是使用value
属性指定URL与Controller方法上的映射,但是如果我们有一个查询用户GET /user/{id}
接口和删除用户接口DELETE /user/{id}
,那该怎么区分呢?下面就来具体介绍一下RequestMapping各种属性来解决上诉类似问题
3.1.1 属性
-
value
指定请求路径上的url,符合AntMatcher匹配规则
-
path
同value
-
method
指定方法的请求方式
RequestMethod
GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS、TRACE
-
params
指定限制请求参数的条件
- 要求请求映射所匹配的请求必须携带param请求参数 param
- 要求请求映射所匹配的请求必须不能携带params请求参数 !param
- 要求请求映射所匹配的请求必须携带param请求参数且param=value param=value
- 要求请求映射所匹配的请求必须携带param请求参数但是param!=value param!=value
-
headers
发送的请求中必须包含的请求头
类似于params的四种条件
-
consumes
指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
MediaType.APPLICATION_JSON_VALUE、MediaType.TEXT_HTML_VALUE
-
produces
指定返回的内容类型
取值为
MediaType
3.1.2 请求路径Ant Path匹配规则
RequestMapping
的value属性指定的请求路径满足ant路径匹配规则,且Spring还提供了一个AntPathMatcher
工具类。下面介绍一下ant路径匹配规则。
特殊符号 | 作用 |
---|---|
? | 匹配任意单个字符 |
* | 匹配当前路径下(以/作为一层路径)的0个或任意个字符 |
** | 匹配0个或任意个路径 |
{id: [a-z0-9A-Z_-]+} | 将正则表达式[a-z0-9A-Z_-]+匹配到的值,赋值给名为id 的路径参数,且必须完全匹配 |
可以在工程下建立测试类,使用AntPathMatcher
进行测试,新建一个AntPathMatcherTest测试类
编写测试用例如下:
package org.numb.common.util;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.util.AntPathMatcher;
public class AntPathMatcherTest {
@Test
public void testAntPath() {
AntPathMatcher matcher = new AntPathMatcher();
boolean result;
String path = "/user/00112233";
result = matcher.match("/user/{id}", path);
Assert.assertTrue(result);
path = "/user/00112233";
result = matcher.match("/user/?", path);
Assert.assertFalse(result);
path = "/user/00112233";
result = matcher.match("/user/*", path);
Assert.assertTrue(result);
path = "/user/00112233/ZhangSan";
result = matcher.match("/user/*", path);
Assert.assertFalse(result);
path = "/user/00112233/ZhangSan";
result = matcher.match("/user/**", path);
Assert.assertTrue(result);
path = "/user/00112233";
result = matcher.match("/user/{id:[a-z0-9A-Z_-]+}", path);
Assert.assertTrue(result);
path = "/user/00112233$";
result = matcher.match("/user/{id:[a-z0-9A-Z_-]+}", path);
Assert.assertFalse(result);
}
}
注意AntPathMatcher
的match(String pattern, String path)
方法,要匹配的路径在前,请求的路径在后,换言之RequestMapping
的value属性类比于这里的pattern参数。分析一下上面的测试用例
请求路径 | 匹配路径 | 结果 | 说明 |
---|---|---|---|
/user/00112233 | /user/{id} | true | 一般的请求路径映射 |
/user/00112233 | /user/? | false | ?只能匹配一个字符,00112233有多个字符 |
/user/00112233 | /user/* | true | *匹配当前路径下的任意字符 |
/user/00112233/ZhangSan | /user/* | false | *只能匹配一层路径,/00112233/ZhangSan是两层路径,所以无法匹配 |
/user/00112233/ZhangSan | /user/** | true | **匹配任意层路径 |
/user/00112233 | /user/{id:[a-z0-9A-Z_-]+} | true | 满足[a-z0-9A-Z_-]+正则表达式 |
/user/00112233$ | /user/{id:[a-z0-9A-Z_-]+} | false | 不满足[a-z0-9A-Z_-]+正则表达式 |
通过上面例子可以更好的理解RequestMapping
的请求路径匹配规则。
3.1.3 指定请求方法
现在来解决如何区分相同请求路径参数的不同http方法,即类似查询用户GET /user/{id}
接口和删除用户接口DELETE /user/{id}
的区分。可以用RequestMapping
的method属性指定请求方法,从而完成请求的映射。
可以改写上述UserController的接口如下,满足Restful风格:
@Controller
public class UserController {
@Resource
private UserService userService;
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.GET)
@ResponseBody
public String getUser(@PathVariable(value = "user_id") String id) {
return userService.getUserName(id);
}
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.DELETE)
@ResponseBody
public String deleteUser(@PathVariable(value = "user_id") String userId) {
System.out.println(userId);
return "success";
}
}
3.1.4 一般的RequestMapping格式
一般常用的value
、method
、consumes
和produces
来确定一个接口,如下所示
package org.numb.controller;
import org.numb.model.User;
import org.numb.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@Controller
public class UserController {
@Resource
private UserService userService;
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody public String getUser(@PathVariable(value = "user_id") String id) {
return userService.getUserName(id);
}
@RequestMapping(value = "/users", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody public String getUser(@RequestBody List<User> users) {
System.out.println(users);
return "success";
}
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String deleteUser(@PathVariable(value = "user_id") String userId) {
System.out.println(userId);
return "success";
}
}
3.2 @ResponseBody
3.2.1 作用
将函数返回值装入responsebody中返回到前端,也就是将return返回值作为请求返回值,return的数据不会解析成返回跳转路径。
3.2.2 示例
当使用@ResponseBody标注时,如果返回的是一个对象,则直接将这个对象序列化成字符串返回。这里注意必须依赖序列化组件,由于在2.2章节中已经引入jackson,在这里直接引用。增加UserService中的getUser方法,并用Map模拟数据库操作,返回一个User对象
UserService
public User getUser(String userId) {
String userName = getUserName(userId);
User user = new User();
user.setId(userId);
user.setName(userName);
return user;
}
UserController
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public User getUser(@PathVariable(value = "user_id") String id) {
return userService.getUser(id);
}
测试:
注意要增加请求头Content-Type:application/json
,否则由于我们指定了RequestMapping的consumes属性,会报415不支持的媒体类型(Unsupported media type)错误。
3.3 @ResponseStatus
用应返回的状态代码和原因消息标记方法或异常类。 调用处理程序方法时或抛出指定的异常时,状态代码将应用于 HTTP 响应。 它会覆盖通过其他方式设置的状态信息,例如ResponseEntity
或redirect:
。这里我们简单讲一下controller方法利用@ResponseStatus返回http状态码。
3.3.1常见的HTTP状态码
状态码 | 状态 | 说明 |
---|---|---|
200 | OK | 客户端请求成功 |
202 | Accepted | 已接受处理请求,但处理尚未完成 |
204 | No Content | 服务器已成功完成请求,并且不会返回响应体 |
302 | Found | 临时跳转,跳转的地址通过Location指定 |
400 | Bad Requet | 客户端请求有语法错误,不能被服务器识别 |
401 | Unauthorized | 客户端尝试在受保护资源上运行而未提供适当的授权 |
403 | Forbidden | 服务器收到请求,但是拒绝提供服务 |
404 | Not Found | 请求的资源不存在 |
405 | Method Not Allowed | 表示客户端尝试使用资源不允许的HTTP方法 |
415 | Unsupported Media Type | 表明无法处理客户端提供的媒体类型 |
500 | Internal Server Error | 服务器发生不可预期的错误 |
509 | Bandwidth Limit Exceeded | 服务器达到带宽限制。当前访问太频繁被限流 |
3.3.2 @ResponseStatus使用
这里希望post:/users
请求返回202,但是默认返回200,此时可以用@ResponseStatus返回202。
@RequestMapping(value = "/users", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@ResponseStatus(code = HttpStatus.ACCEPTED)
public String getUser(@RequestBody List<User> users) {
System.out.println(users);
return "success";
}
测试:
3.4 @Mapping相关注解
@Mapping
、@RequestMapping
和@GetMapping
等相关注解的关系。
从@RequestMapping的源码可以看出之间的关系:
-
@Mapping:表明请求映射的元注解
-
@RequestMapping:实际表示将一个 Web 请求映射到Controller类中方法的注解,可以理解为@Mapping的实现者。
-
@GetMapping:在@GetMapping注解上加了
@RequestMapping(method = RequestMethod.GET)
,即指定了get的请求映射注解,其余类似。 -
@PostMapping:表明post请求映射的注解。
-
@PutMapping:表明put请求映射的注解。
-
@DeleteMapping:表明delete请求映射的注解。
-
@PatchMapping:表明patch请求映射的注解。
关系图为:
3.5 @RequestAttribute
@RequestAttribute注解的作用是获取请求中的属性值(attribute),属性(attribute)和参数(parameter)容易混淆,特在此说明。
3.5.1 http请求中的属性(attribute)和参数(parameter)的区别
-
来源不同:
参数(parameter)是从客户端(浏览器)中由用户提供的,是直接由前端传到后端来的值。
属性(attribute)是服务器端的组件(JSP或者Servlet)利用requst.setAttribute()
设置的,在SpringMVC体系中一般在**过滤器(filter)或拦截器(interceptor)**中进行设置。 -
操作不同:
参数(parameter)的值只能读取不能修改,读取可以使用
request.getParameter()
读取;
属性(attribute)的值既可以读取也可以修改,读取可以使用request.getAttribute()
,设置可使用request.setAttribute()
。 -
数据类型不同:
参数(parameter)不管前台传来的值语义是什么,在服务器获取时都以String类型看待,并且客户端的参数值只能是简单类型的值,不能是复杂类型,比如一个对象。
属性(attribute)的值可以是任意一个Object类型。
从ServletRequest
接口源码中便可以看出
参考链接:request中参数(parameter)和属性(Attribute)的区别
3.5.2 @RequestAttribute的使用
可以用filter验证@RequestAttribute的使用。在成熟项目的中,会对一个用户的登录信息进行身份验证,此时会引入token信息来作为验证手段。本节中可以在filter中设置一个token的attribute,然后在controller中获取到token信息并进行验证,这里仅实现filter中request.setAttribute("token", token)
来做@RequestAttribute使用的演示说明。
-
添加一个自定义filter
-
自定义AuthenticationFilter
package org.numb.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import org.springframework.stereotype.Component; // 生成一个AuthenticationFilter的bean,被spring托管 @Component public class AuthenticationFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { throw new ServletException("AuthenticationFilter can handle http requests"); } request.setAttribute("token", "I am root"); chain.doFilter(request, response); } @Override public void destroy() { } }
-
在web.xml中配置filter
<filter> <filter-name>authenticationFilter</filter-name> <filter-class>org.numb.filter.AuthenticationFilter</filter-class> </filter> <filter-mapping> <filter-name>authenticationFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
-
测试@RequestAttribute的使用
-
在UserController中添加@RequestAttribute
@RequestMapping(value = "/user/{user_id}", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @ResponseStatus(code = HttpStatus.OK) public User getUser(@PathVariable(value = "user_id") String id, @RequestAttribute(value = "token") String tokenInfo) { System.out.println(tokenInfo); return userService.getUser(id); }
-
postman测试
-
3.5.3 扩展:<url-pattern>/</url-pattern>
与<url-pattern>/*</url-pattern>
的区别
/
:表示匹配所有请求,其中包含除.jsp和.jspx外的所有后缀。
/*
:会覆盖其他所有的servlet,包括servlet容器提供的所有servlet。即无论你发出什么请求,都会在该servlet拦截处理,当然也包括.jsp和.html等静态资源。
/**
:什么请求都不能拦截处理,相当于没有设置。
3.6 @ModelAttribute
@ModelAttribute,用来将请求参数绑定到 Model 对象。
3.6.1 Model对象
模型(Mode)对象的作用主要是保存数据,可以借助它们将数据带到前端。前端可以获取model数据,并且实现相应的业务逻辑
3.6.2 @ModelAttribute使用
-
应用于Controller内普通方法上
被
@ModelAttribute
标注的方法会在Controller
的每个请求映射方法(带@RequestMapping的方法)执行之前都执行,因此对于一个Controller
中包含多个请求映射方法的时候,要谨慎使用。方法一:方法参数上带Model参数,使用 model.addAttribute()设置。
/** * ModelAttribute的使用 <br/> * 方法一:方法参数上带Model参数,使用 model.addAttribute()设置 * * @param model */ @ModelAttribute public void addModel(Model model) { model.addAttribute("requestId", UUID.randomUUID().toString()); }
方法二:使用返回值,返回值会被自动调用设置进隐含的Model。
/** * ModelAttribute的使用 <br/> * 方法二:使用返回值,返回值会被自动调用设置进隐含的Model。 * 在Model中的key为返回值首字母小写,value为返回的值 */ @ModelAttribute public String addModel() { return UUID.randomUUID().toString(); }
-
应用于方法参数上
可以获取Model中的attribute,这一点有点类似于
@RequestAttribute
@ModelAttribute public void addModel(Model model) { model.addAttribute("requestId", UUID.randomUUID().toString()); } @RequestMapping("/hello") @ResponseBody public String helloUser(@RequestParam(value = "user_name", required = false) String userName, @ModelAttribute(value = "request_id") String requestId) { System.out.println(requestId); return "Hi, " + userName; }
测试:
-
应用于Controller内请求映射方法(带@RequestMapping的方法)上
表明请求映射方法的返回值不再是视图,而是将返回值放入到
Model
中
3.7 @RestController
@RestController
的作用于一个Controller类上,表明这个类是一个Controller,且会给每个请求映射方法都会追加@ResponseBody
注解,表明方法返回值不是一个视图,直接将结果返回至前台处理。
@RestController的源码如下:
被@RestController注解的Controller类,可以不用在每个方法上再加@ResponseBody,但是注意一点,如果是重定向方法,可能会导致重定向失效。因为表明了方法返回值是直接返回至前端的,所以就不再重定向方法,返回值被当作字符串返回到前端