Restful简介
第一印象
左侧是传统写法,右侧是RESTful写法
-
用url描述资源,而不是行为
-
用http方法描述行为,使用http状态码来表示不同的结果(200表示成功,500表示错误)
-
使用json交互数据
-
RESTful只是一种风格,并不是强制的标准
REST成熟度模型
编写第一个Restful API
通过用户查询,创建,删除,修改来学习怎么写一个Restful API
编写针对RestfullAPI的测试用例
UserController.java
@RestController@RequestMapping("user")public class UserController { private ListgetThreeEmptyUsers() { List userList = new ArrayList<>(); userList.add(new User()); userList.add(new User()); userList.add(new User()); return userList; } @RequestMapping(value = "query1", method = RequestMethod.GET) public List query() { return getThreeEmptyUsers(); }}
UserControllerTest.java
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest { @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test public void whenQuerySuccess() throws Exception { mockMvc.perform(get("/user/query1") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()) // 返回状态码为200 .andExpect(jsonPath("$.length()").value(3)); // 返回数据的集合长度是3 }}
jsonPath表达式
常用注解
@RestControlelr 标明此Controller提供RestAPI
@RequestMapping及其变体,映射http请求url到java方法
@RequestParam 映射请求参数到Java方法的参数
@PageableDefault 指定分页参数的默认值
@JsonView 控制json输出内容,使用步骤如下
- 使用接口来声明多个视图
- 在值对象的get方法上指定视图
- 在Controller方法上指定视图
@GetMapping @RequestMapping的变体
传递参数
@PathVariable 映射url片段到java方法的参数 用户详情服务
- 在url声明中使用正则表达式
@RequestBody 映射请求体到java方法的参数
日期类型参数的处理
- 传递时间戳,有利于前后台分离
@Valid注解和BindingResult验证请求参数的合法性并处理校验结果
参数校验
常用的验证注解
自定义消息
- message = ""
自定义校验注解
- 参照代码的MyConstraint
服务异常处理
工具:chrome插件Restlet Client测试Restful接口的插件
Spring Boot中默认的错误处理机制
分析源码:BasicErrorController
如果请求一个不存在的url,app发出的请求返回json格式,浏览器发出的请求返回 页面格式。
依据Content-Type来判断是请求的页面还是json
自定义异常处理
text/html 基于状态码处理
- 配置404 resources/resources/error/404.html
application/json
- 参照代码UserNotExistException
拦截REST服务
Filter 过滤器
自定义过滤器,TimeFilter,加Component注释
第三方过滤器,LogFilter,通过WebConfig添加
拿不到Controller方法
Interceptor 拦截器
TimeInterceptor
拿不到Controller方法的参数
分析源码:DispatcherServlet/doService/doDispatcher/ha.handle(参数的拼装是调用这个方法完成的)
Aspect 切片
when/where/do what
关系
使用REST方式处理文件服务
测试方法
@Testpublic void whenUploadSuccess() throws Exception{ String result = mockMvc.perform(fileUpload("/file") .file(new MockMultipartFile("file", "text.txt", "multipart/form-data", "hello update".getBytes("UTF-8")))) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); System.out.println(result);}
文件上传下载
@RestController@RequestMapping("file")public class FileController { @PostMapping public FileInfo upload(MultipartFile file) throws Exception { System.out.println("name: " + file.getName()); System.out.println("filename: " + file.getOriginalFilename()); String filePath = System.getProperty("user.home") + File.separator + new Date().getTime() + ".txt"; File localFile = new File(filePath); file.transferTo(localFile); return new FileInfo(filePath); } @GetMapping("{id}") public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception { String filePath = System.getProperty("user.home") + File.separator + id + ".txt"; File file = new File(filePath); if (!file.exists()) { System.out.println("文件不存在"); return; } try ( InputStream is = new FileInputStream(file); OutputStream os = response.getOutputStream(); ) { response.setContentType("application/x-download"); response.addHeader("Content-Disposition", "attachment;filename=test.txt"); IOUtils.copy(is, os); os.flush(); } }}