1:restfulApi的特点
2:使用sprintboot开发resufulApi
3:使用MockMvc对Api进行单元测试
4:使用filter对api进行过滤
5:使用interceptor对Api进行拦截
6:使用spring提供的切面对api进行日志记录
7:实现Api上传及下载功能
8:使用线程异步对api性能进行提升
解释如下:
例如对用户信息进行增删改查操作,传统做法
增: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]),欢迎交流 .