使用springboot开发restful Api

课程目录如下:

1:restfulApi的特点
2:使用sprintboot开发resufulApi
3:使用MockMvc对Api进行单元测试
4:使用filter对api进行过滤
5:使用interceptor对Api进行拦截
6:使用spring提供的切面对api进行日志记录
7:实现Api上传及下载功能
8:使用线程异步对api性能进行提升

restfulApi的特点

  • restfulApi使用Url描述资源
  • 使用http方法描述行为,使用http状态码来表示不同的结果
  • 使用json交换数据
  • restful只是一种风格,不是强制的标准

解释如下

例如对用户信息进行增删改查操作,传统做法
增:addUser()
删:deleteUser()
改:updateUser()
查:selectUser()
这四个操作,分别对于四个入口,对用户进行操作,在restful中,Url代表资源,增删改查均为对用户进行操作,即操作的为/user这个资源,无论增删改查,其url均保持一致,问题来了,url一致,如何区分增删改查? 在restful中,使用使用http方法描述行为,即增为post请求,删为delete请求,改为put请求,查为get请求,通过http的请求方法,即可实现同一url的请求分发。

代码演示如下:

User实体类:

public class User {
   
    private String id;
    private String username;
    private String sex;
    private String password;
    private Date birthday;
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

UserController

@RestController
@RequestMapping("/user")
public class UserController {

   private final  Logger logger = LoggerFactory.getLogger(getClass());


    //删除用户
    @DeleteMapping(value = "/{id:\\d+}")
    public void delete(@PathVariable String id) {
        System.out.println(id);
    }

    /**
     * 更新用户
     */
    @PutMapping(value = "/{id:\\d+}")
    public User update(@Valid @RequestBody User user, BindingResult errors) {
        if (errors.hasErrors()) {
            errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
        }
        System.out.println(user.getUsername());
        System.out.println(user.getBirthday());
        user.setId("1");
        return user;
    }

    //创建用户
    @PostMapping
    public User creat(@Valid @RequestBody User user, BindingResult errors) {
        if (errors.hasErrors()) {
            errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
        }
        System.out.println(user.getUsername());
        System.out.println(user.getBirthday());
        user.setId("1");
        return user;
    }

    //查询用户
    @GetMapping
    public List query(User user, @PageableDefault(page = 1, size = 10, sort = "username,asc") Pageable pageable) {
        logger.info(user.toString());
        logger.info(String.valueOf(pageable.getPageNumber()));
        logger.info(String.valueOf(pageable.getPageSize()));
        logger.info(String.valueOf(pageable.getSort()));
        List lists = new ArrayList<>();
        lists.add(new User("手机", "男", "123456"));
        lists.add(new User("电脑", "男", "123456"));
        lists.add(new User("平板", "男", "123456"));
        return lists;
    }

在类上标注@RestController标识,声明其为restfulApi,即可返回json数据,增删改查方法对应的url一致,根据其不同类型,分发至不同的HTTP方法处理器中。

使用MockMvc 执行测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    @Test
    public void whenGetInfoSuccess() throws Exception {
        String result = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);

    }
    @Test
    public void whenCreatSuccess() throws Exception {
        Date date = new Date();
        String content = "{\"username\":null,\"sex\":\"男\",\"password\":\"1234\",\"birthday\":\"" + date.getTime() + "\"}";
        String result = mockMvc.perform(MockMvcRequestBuilders.post("/user").
                contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

    @Test
    public void whenUpdateSuccess() throws Exception {
        Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        String content = "{\"username\":\"wumeng\",\"id\":\"2\",\"password\":\"1234\",\"birthday\":\"" + date.getTime() + "\"}";
        String result = mockMvc.perform(MockMvcRequestBuilders.put("/user/1").
                contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

    @Test
    public void whenDeleteSuccess() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.delete("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
}

使用过滤器对特定请求及进行拦截,例如记录一次访问的时间:

public class TimeFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器初始化。。。。");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long start = new Date().getTime();
        filterChain.doFilter(servletRequest, servletResponse);
        long end = new Date().getTime();
        System.out.println("访问耗时:" +(end - start));
    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁。。。");
    }
}

过滤器写好之后需要进行配置

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        TimeFilter timeFilter = new TimeFilter();
        filterRegistrationBean.setFilter(timeFilter);
        List urls = new ArrayList<>();
        urls.add("/*");
        filterRegistrationBean.setUrlPatterns(urls);
        return  filterRegistrationBean;
    }
}

使用Interceptor对请求进行拦截

@Component
public class TimeInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        System.out.println("preHandle。。。");
        System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
        System.out.println(((HandlerMethod)handler).getMethod().getName());
        httpServletRequest.setAttribute("start",new Date().getTime());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle。。。");
        long start = (long)httpServletRequest.getAttribute("start");
        System.out.println("耗时:"+(new Date().getTime()-start));

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("afterCompletion。。。");
        long start = (long)httpServletRequest.getAttribute("start");
        System.out.println("耗时:"+(new Date().getTime()-start));
        System.out.println("ex is"+e);
    }
}

对拦截器进行配置

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }

    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        TimeFilter timeFilter = new TimeFilter();
        filterRegistrationBean.setFilter(timeFilter);
        List urls = new ArrayList<>();
        urls.add("/*");
        filterRegistrationBean.setUrlPatterns(urls);
        return  filterRegistrationBean;
    }
}

使用切面对请求进行拦截,首先需要在类上声明@Aspect注解表明这是一个切面,其次需要在方法上声明切入点及通知方法,示例如下

@Aspect
@Component
public class TimeAspect {
    @Around("execution(* com.wiseweb.controller.UserController.*(..))")
    public Object handlerControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("进入切面");
        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            System.out.println("在切面中获取到的参数为" + arg);
        }
        long start = new Date().getTime();
        Object obj = pjp.proceed();
        System.out.println("切面执行的时间为:" + (new Date().getTime() - start));
        return obj;
    }
}

过滤器,拦截器,切面均可实现对请求时间的记录,那他们之间有什么区别?
过滤器:在过滤器中只可以拿到请求的url及访问的参数
拦截器:不仅可以拿到请求的url及访问参数,还可以得到具体访问的类名及方法名
切面:除了可以拿到参数之外,还可以拿到目标方法的返回对象
在实际应用中,可根据需求使用不同的实现方式。

上传及下载功能:

```
@RestController
@RequestMapping("/file")
public class FileController {

    private String folder = "D:/source/spring_security/security_demo/src/main/java/com/wise/web/controller";

    //文件上传
    @PostMapping
    public FileInfo upload(MultipartFile file) throws IOException {
        System.out.println(file.getName());
        System.out.println(file.getSize());
        System.out.println(file.getOriginalFilename());
        File localFile = new File(folder, new Date().getTime() + ".txt");
        file.transferTo(localFile);
        return new FileInfo(localFile.getAbsolutePath());
    }

    //处理文件下载
    @GetMapping("/{id}")
    public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
        try (
                InputStream inputStream = new FileInputStream(new File(folder, id + ".txt"));
                OutputStream outputStream = response.getOutputStream();
        ) {
                response.setContentType("application/x-download");
                response.addHeader("Content-Disposition", "attachment;filename=test.txt");
                IOUtils.copy(inputStream, outputStream);
                outputStream.flush();
        }
    }
}

使用异步方法对性能进行提升:

@RestController
public class AsyncController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    //传统rest处理模式
    @RequestMapping("/order")
    private String order() throws InterruptedException {
        logger.info("主线程开始");
        Thread.sleep(100);
        logger.info("主线程结束");
        return "success";
    }

    //异步rest处理模式
    @RequestMapping("/async/order")
    private Callable asyncOrder() throws InterruptedException {
        logger.info("主线程开始");
        Callable result = new Callable() {
            @Override
            public String call() throws Exception {
                logger.info("副线程开始");
                Thread.sleep(100);
                logger.info("副线程结束");
                return "SUCCESS";
            }
        };
        logger.info("主线程结束");
        return result;
    }
}

至此resutful风格的Api就告一段落,接下来会写一些关于权限的文章,例如如何通过SpringSecurity对Api进行保护,在学习过程中如有疑问,可将疑问发送至我的邮箱([email protected]),欢迎交流 .

你可能感兴趣的:(java)