Spring 架构篇——使用 Spring Boot 开发 RESTful API
来源:互联网 发布:知世故而不世故, 编辑:程序博客网 时间:2024/05/16 15:01
转载请注明出处:http://blog.csdn.net/smartbetter/article/details/73441323
Restful 本质上是一个优雅的 URI 表达方式,基于 Restful 设计的软件可以更简洁,更有层次,更易于实现缓存等机制。资源的状态和状态转移。下面来看一组 传统API 和 RESTful API 写法的对比:
Restful API 用 URL 描述资源,用 HTTP 方法描述行为,使用 HTTP 状态码来表示不同的结果,使用 json 交互数据(/ 模块 / 资源 / {标示} / 集合1 / …)。
开发 RESTful API 常用的 Spring MVC 注解:
除此之外,Hibernate Validator 库还提供了一些对参数校验的注解,如下:
1.基于Restful设计的增删改查
测试用例这里使用 MockMvc 结合 PerfTest 进行编写,PerfTest 需要添加 Maven 依赖:
<dependency> <groupId>org.databene</groupId> <artifactId>contiperf</artifactId> <version>2.3.4</version></dependency>
1.查询请求API
public class User { //使用接口来声明多个视图 public interface UserSimpleView {}; public interface UserDetailView extends UserSimpleView {}; private Long id; private String username; private String password; private Date gmtCreate; private Date gmtModified; //在值对象的get方法上指定视图,然后在Controller方法上指定视图,就可以达到使用UserSimpleView视图隐藏password的效果 @JsonView(UserSimpleView.class) public Long getId() { return id; } @JsonView(UserSimpleView.class) public String getUsername() { return username; } @JsonView(UserDetailView.class) public String getPassword() { return password; } @JsonView(UserSimpleView.class) public Date getGmtCreate() { return gmtCreate; } @JsonView(UserSimpleView.class) public Date getGmtModified() { return gmtModified; } //省略setter请求}
@RestControllerpublic class UserController { private Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @GetMapping("/user") @JsonView(User.UserSimpleView.class) //指定视图 public List<User> queryUser(@RequestParam String username) { return userService.queryUser(username); } //添加正则表达式 \d+ 表示id只能为数字 @GetMapping("/user/{id:\\d+}") @JsonView(User.UserSimpleView.class) public User getUserInfo(@PathVariable String id) { return userService.getUserInfo(id); }}
编写测试用例:
@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest { @Autowired private UserController userController; private MockMvc mvc; private Logger logger = LoggerFactory.getLogger(UserControllerTest.class); @Rule public ContiPerfRule i = new ContiPerfRule(); @Before public void setup() { mvc = MockMvcBuilders.standaloneSetup(userController).build(); } @Test @PerfTest(invocations = 100000, threads = 1000) public void whenQueryUserSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.get("/user") .param("username", "tom") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3)) .andReturn(); logger.info(result.getResponse().getContentAsString()); } @Test @PerfTest(invocations = 100000, threads = 1000) public void whenGetUserInfoSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.get("/user/1") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom")) .andReturn(); logger.info(result.getResponse().getContentAsString()); } @Test @PerfTest(invocations = 100000, threads = 1000) public void whenGetUserInfoFail() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/user/a") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().is4xxClientError()); }}
2.创建请求API
在 UserController 增加创建请求的接口:
/** * @param user @Valid:校验数据,约束需要在pojo中定义,校验结果会通过BindingResult返回,可以写成: * public User createUserInfo(@Valid @RequestBody User user, BindingResult erros) { * if (erros.hasErrors()) { * erros.getAllErrors().stream().forEach(error -> logger.error(error.getDefaultMessage())); * } * Date date = new Date(); * user.setGmtCreate(date); * user.setGmtModified(date); * return userService.createUserInfo(user); * } * 如果不写BindingResult,将会通过Spring Boot中默认的错误处理机制返回给客户端 */@PostMapping("/user")@JsonView(User.UserSimpleView.class)public User createUserInfo(@Valid @RequestBody User user) { Date date = new Date(); user.setGmtCreate(date); user.setGmtModified(date); return userService.createUserInfo(user);}
在 User 中添加校验约束:
@NotBlank(message = "用户名不能为空") //校验约束private String username;@NotBlank(message = "密码不能为空")private String password;
在 UserControllerTest 增加创建请求的测试用例:
@Test@PerfTest(invocations = 100000, threads = 1000)public void whenCreateUserSuccess() throws Exception { String content = "{\"username\":\"bob\",\"password\":\"123\"}"; MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user") .content(content) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn(); logger.info(result.getResponse().getContentAsString());}
3.修改请求API
在 UserController 增加修改请求的接口:
@PutMapping("/user/{id:\\d+}")@JsonView(User.UserSimpleView.class)public User updateUserInfo(@Valid @RequestBody User user) { Date date = new Date(); user.setGmtCreate(date); user.setGmtModified(date); return userService.updateUserInfo(user);}
在 UserControllerTest 增加修改请求的测试用例:
@Test@PerfTest(invocations = 100000, threads = 1000)public void whenUpdateUserSuccess() throws Exception { String content = "{\"id\":\"1\",\"username\":\"bob\",\"password\":\"123\"}"; MvcResult result = mvc.perform(MockMvcRequestBuilders.put("/user/1") .content(content) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn(); logger.info(result.getResponse().getContentAsString());}
4.删除请求API
在 UserController 增加删除请求的接口:
@DeleteMapping("/user/{id:\\d+}")@JsonView(User.UserSimpleView.class)public void deleteUserInfo(@PathVariable String id) { userService.deleteUserInfo(id);}
在 UserControllerTest 增加删除请求的测试用例:
@Test@PerfTest(invocations = 100000, threads = 1000)public void whenDeleteUserSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.delete("/user/1") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); logger.info(result.getResponse().getContentAsString());}
2.Restful API错误处理
1.Spring Boot 中默认的错误处理机制
Spring Boot 提供默认的错误处理机制,浏览器发送请求遇到错误返回 html 错误网页,APP 发送请求遇到错误返回 json 错误代码。
这种错误机制的具体实现可以查看 org.springframework.boot.autoconfigure.web.BasicErrorController 源码:
@Controller@RequestMapping({"${server.error.path:${error.path:/error}}"})public class BasicErrorController extends AbstractErrorController { @RequestMapping(produces = {"text/html"}) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { } @RequestMapping @ResponseBody public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { }}
可以看出,其实就是根据 produces 参数来实现的。
2.自定义异常处理
大部分业务我们使用 Spring Boot 默认提供的错误处理机制即可。如果我们需要单独处理某些浏览器响应错误,例如 404,就需要自定义异常。新建 resources/resources/error 目录,在其中新建 404.html:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>404</title></head><body> 您所访问的页面不存在</body></html>
运行项目,此时从浏览器访问 API 接口,如果发生 404 错误则不再返回 Spring Boot 提供的 html,而是返回上面自定义的 html(这种自定义只对浏览器生效,不会影响 APP)。下面我们自定义 APP 异常:
public class UserNotExistException extends RuntimeException { private String id; public UserNotExistException(String id) { super("user not exist"); this.id = id; } public String getId() { return id; }}
新建控制器的错误处理器,抛出的 UserNotExistException 都会到这里处理:
@ControllerAdvicepublic class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String, Object> handleUserNotExistException(UserNotExistException ex) { Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); return result; }}
此时,如果代码发生错误,我们只需要手动 抛出 UserNotExistException(id) 即可。例如获得错误响应为:
{ "id": "1", "message": "user not exist"}
3.Restful API的拦截机制
三者的顺序是:过滤器 -> 拦截器 -> ControllerAdvice -> 切片 -> Controller
1.过滤器Filter
@Component //添加该注解即可生效public class TimeFilter implements javax.servlet.Filter { //初始化 @Override public void init(FilterConfig filterConfig) throws ServletException { } //处理过滤器逻辑 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long start = System.currentTimeMillis(); chain.doFilter(request, response); System.out.println("Time Filter 耗时:" + (System.currentTimeMillis()-start)); } //销毁 @Override public void destroy() { }}
2.添加第三方Filter到自己的项目
新建配置类注册 bean 即可(这里假设是第三方的TimeFilter):
@Configurationpublic class WebConfig { @Bean public FilterRegistrationBean timeFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); TimeFilter timeFilter = new TimeFilter(); registrationBean.setFilter(timeFilter); List<String> urls = new ArrayList<>(); //所有路径都起作用 urls.add("/*"); registrationBean.setUrlPatterns(urls); return registrationBean; }}
3.拦截器Interceptor
拦截器是指通过统一拦截从浏览器发往服务器的请求来完成功能的增强,处理所有请求的共性问题。如解决乱码问题(web.xml 中配置 filter)、权限验证问题、验证是否登录等。拦截器的工作原理和过滤器非常相似。
@Componentpublic class TimeInterceptor implements HandlerInterceptor { /** * 执行步骤1: 在控制器处理请求之前被调用 * @param handler 被拦截请求对象实例 * @return false:表示拦截当前请求, 请求被终止, true:表示不拦截当前请求, 请求被继续 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.setAttribute("startTime", System.currentTimeMillis()); return true; } /** * 执行步骤2: 在控制器处理请求之后被调用, 生成视图之前执行的动作 * @param handler 被拦截请求对象实例 * @param modelAndView 可通过modelAndView改变显示的视图或修改发往视图的方法, 比如当前时间 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 执行步骤3: 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等 * 注意: 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion方法 * @param handler 被拦截请求对象实例 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception { Long start = (Long) request.getAttribute("startTime"); System.out.println("Time Interceptor 耗时:" + (System.currentTimeMillis()-start)); }}
在配置类中添加拦截器:
@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter{ @Autowired private TimeInterceptor timeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); }}
另外多个拦截器协同时工作流程图如下:
3.切片Aspect
@Aspect@Componentpublic class TimeAspect { private final static Logger logger = LoggerFactory.getLogger(TimeAspect.class); @Around("execution(public * com.example.security.web.controller.UserController.*(..))") public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object[] args = pjp.getArgs(); for (Object arg : args) { System.out.println("Time Aspect 参数:" + arg); } Object object = pjp.proceed(); System.out.println("Time Aspect 耗时:" + (System.currentTimeMillis()-start)); return object; }}
4.使用REST方式处理文件上传
新建文件上传的控制器 UploadController:
@RestControllerpublic class UploadController { @PostMapping("/upload") public FileInfo upload(MultipartFile file) throws IOException { String folder = "/Users/guochao/Documents"; String _fileName = file.getOriginalFilename(); String suffix = _fileName.substring(_fileName.lastIndexOf(".")); File localFile = new File(folder, UUID.randomUUID().toString() + suffix); file.transferTo(localFile); return new FileInfo(localFile.getAbsolutePath()); }}
新建 UploadControllerTest 添加文件上传的测试用例:
@RunWith(SpringRunner.class)@SpringBootTestpublic class UploadControllerTest { @Autowired private UploadController uploadController; private MockMvc mvc; private Logger logger = LoggerFactory.getLogger(UserControllerTest.class); @Rule public ContiPerfRule i = new ContiPerfRule(); @Before public void setup() { mvc = MockMvcBuilders.standaloneSetup(uploadController).build(); } @Test @PerfTest(invocations = 100000, threads = 1000) public void whenUploadSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.fileUpload("/upload") .file(new MockMultipartFile("file", "test.txt", "multipart/form-data", "hello upload".getBytes("UTF-8")))) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); logger.info(result.getResponse().getContentAsString()); }}
5.异步处理REST服务
@RestControllerpublic class AsyncController { private Logger logger = LoggerFactory.getLogger(AsyncController.class); /** * 同步处理 */ @RequestMapping("/sync") public String sync() throws InterruptedException { long start = System.currentTimeMillis(); logger.info("主线程开始"); Thread.sleep(1000); logger.info("主线程结束,耗时" + (System.currentTimeMillis()-start)); return "success"; } /** * 异步处理 */ @RequestMapping("/async") public Callable<String> async() { long start = System.currentTimeMillis(); logger.info("主线程开始"); Callable<String> result = () -> { long start2 = System.currentTimeMillis(); logger.info("副线程开始"); Thread.sleep(1000); logger.info("副线程返回,耗时" + (System.currentTimeMillis()-start2)); return "success"; }; logger.info("主线程结束,耗时" + (System.currentTimeMillis()-start)); return result; }}
- Spring 架构篇——使用 Spring Boot 开发 RESTful API
- 使用 JSONDoc 记录 Spring Boot RESTful API
- 使用Spring Boot开发Restful程序
- 使用Spring Boot开发Restful程序
- spring boot 实现Restful API
- Spring boot restful api demo
- spring boot restful API风格
- Spring boot构建RESTFul API+使用Swagger2构建API文档
- RESTful API开发神器swagger与spring-boot的快速整合使用
- Spring boot 中使用swagger-ui实现 restful-api
- Spring Boot 使用Swagger2自动生成RESTful API文档
- Spring Boot中使用Swagger2构建RESTful API文档
- Spring Boot系列 - 6. spring boot 实现Restful API
- Spring Boot 基础篇之 整合Mybatis 实现 RESTful API
- Spring Boot构建RESTful API与单元测试
- spring-boot 集成 Swagger 搭建RESTful API
- Spring Boot构建RESTful API与单元测试
- 三、Spring Boot构建RESTful API
- ios 移除UIView上所有的subViews
- 漫谈并发编程:用MPI进行分布式内存编程(入门篇)
- Java实现-旋转图像
- [CodeM初赛A轮]D
- 漫谈并发编程:Future模型(Java、Clojure、Scala多语言角度分析)
- Spring 架构篇——使用 Spring Boot 开发 RESTful API
- mySQL5.6的optimizer_trace
- 路由选择协议调研
- 配置jdk环境的配置方式及说明
- Hibernate继承映射【标准+全】
- interactions_anova
- 解决react-native-swiper在安卓上与TabNavigator共用时不显示内容问题
- 计算语言学之隐马尔可夫模型
- link和import的区别