spring security入门
1. Restful API和传统API的区别
- 用URL描述资源
- 用http描述方法行为,用http状态码描述结果
- 使用json交互数据
- RESTful是一种风格,不是强制的标准
2. 使用spring mvc开发Restful API
2.1 请求参数校验
1)实体类
import com.fasterxml.jackson.annotation.JsonView; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.Range; import javax.validation.constraints.NotNull; import java.util.Date; public class User { public interface UserSimpleView{} public interface UserDetailView extends UserSimpleView{} @JsonView(UserSimpleView.class) private Integer id; @JsonView(UserSimpleView.class) @NotBlank private String username; @JsonView(UserDetailView.class) private String password; @JsonView(UserSimpleView.class) @Range(max = 200, min = 0) @NotNull private Integer age; @JsonView(UserSimpleView.class) private Date birthday; }
2)控制器,如果不加BindingResult参数,并且出异常则不进入方法,直接将错误信息返回
@PostMapping public User create(@Valid @RequestBody User user, BindingResult errors) { if (errors.hasErrors()) { errors.getAllErrors().forEach(error -> { System.out.println("error ==> "+error.getDefaultMessage()); }); } System.out.println(user); user.setBirthday(new Date()); return user; }
2.2 选择性的返回实体类的属性,JsonView
1)实体类
public class User { public interface UserSimpleView{} public interface UserDetailView extends UserSimpleView{} @JsonView(UserSimpleView.class) private Integer id; @JsonView(UserSimpleView.class) @NotBlank private String username; @JsonView(UserDetailView.class) private String password; @JsonView(UserSimpleView.class) @Range(max = 200, min = 0) @NotNull private Integer age; @JsonView(UserSimpleView.class) private Date birthday; }
2)控制器
@GetMapping @JsonView(User.UserSimpleView.class) public List<User> query(UserQueryCondition condition, @PageableDefault(page = 1, size = 10, sort = "age") Pageable pageable) { System.out.println(condition); List<User> users = new ArrayList<>(); users.add(new User() {{ setId(1); setUsername("admin1"); setPassword("123"); setAge(20); }}); users.add(new User() {{ setId(2); setUsername("admin2"); setPassword("123"); setAge(22); }}); return users; }
2.3 请求URL参数变量
使用正则表达式对请求URL参数做限制
@GetMapping("/{id:\\d+}")
2.2 服务异常处理
在controller中发生异常,怎样处理?
1)自定义异常类
public class UserNotExistException extends RuntimeException { private static final long serialVersionId = Long.MIN_VALUE; private int id; public UserNotExistException(int id){ super("user not exists"); this.id = id; } }
2)处理控制器抛出的异常
@ControllerAdvice public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String, Object> handlerUserNotExistException(UserNotExistException ex){ Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); return result; } }
3 RESTful API拦截
3.1 过滤器(Filter)
定义过滤器,注册为spring组件
import javax.servlet.*; import java.io.IOException; import java.util.Date; @Component // 让过滤器生效, 默认拦截所有请求(/*) public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("time filter init..."); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("time filter start"); long start = new Date().getTime(); filterChain.doFilter(servletRequest, servletResponse); long finish = new Date().getTime(); System.out.println("time filter 耗时:"+(finish-start)); System.out.println("time filter finish"); } @Override public void destroy() { System.out.println("time filter destroy..."); } }
将第三方过滤器(没有@Component)注册到项目中,将TimeFilter的@Component去掉:
@Configuration public class WebConfig { @Bean public FilterRegistrationBean timeFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); TimeFilter timeFilter = new TimeFilter(); registrationBean.setFilter(timeFilter); // 配置拦截的URL List<String> urls = new ArrayList<>(); urls.add("/*"); registrationBean.setUrlPatterns(urls); return registrationBean; } }
在过滤器中无法获取控制器中的信息,可以使用拦截器获取spring中的控制器信息
3.2 拦截器(Interceptor)
定义拦截器:
@Component public class TimeInterceptor implements HandlerInterceptor { // 控制器方法被调用之前 @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("prehandler..."); System.out.println(((HandlerMethod)o).getBean().getClass().getName());//o为控制器方法 System.out.println(((HandlerMethod)o).getMethod().getName());//o为控制器方法 return true; } // 控制器方法被调用之后,如果控制器中抛出异常,则不会调用该方法 @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("postHandler..."); } // 处理请求后调用该方法,无论控制器方法是否抛出异常 @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("after completion..."); } }
如果配置了控制器异常处理,那么拦截器的异常对象为空。
配置拦截器:
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private TimeInterceptor timeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); } }
3.3 切片(Aspect)
只需定义切面,不需要其他配置即可生效
//比较细粒度的切片,一定能获取到切入点方法的参数信息 @Aspect @Component public class TimeAspect { //切入点定义 @Pointcut("execution(* com.getword.web.controller.UserController.*(..))") public void myPointcut(){} @Around("myPointcut()") public Object handlerControllerMethod(ProceedingJoinPoint pjp) throws Throwable { // 参数pjp包含控制器方法信息 System.out.println("time aspect start..."); //获取控制器方法的参数 Object[] args = pjp.getArgs(); for(Object arg : args){ System.out.println("arg is :"+arg); } Object object = pjp.proceed(); System.out.println("time aspect finish..."); return object; } }
4 文件上传下载
@RestController @RequestMapping("/file") public class FileController { @PostMapping public FileInfo upload(MultipartFile file){ System.out.println(file.getName()); System.out.println(file.getOriginalFilename()); System.out.println(file.getSize()); try { InputStream inputStream = file.getInputStream();
file.transferTo(new File("")); } catch (IOException e) { e.printStackTrace(); } FileInfo fileInfo = new FileInfo(""); return fileInfo; } @GetMapping("/{id}") public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception { //获取file文件 try(InputStream inputStream = new FileInputStream(new File("")); OutputStream out = response.getOutputStream(); ){ response.setContentType("application/x-download"); response.setHeader("Content-Disposition","attachment;filename=test.txt"); IOUtils.copy(inputStream, out); out.flush(); } } }
5 使用多线程提高RESTful性能
5.1 使用Callable,Runnable
@RequestMapping("/order") public Callable<String> orderAsync(){ logger.info("主线程开始..."); Callable<String> result = new Callable<String>() { @Override public String call() throws Exception { logger.info("副线程开始。。。"); Thread.sleep(1000); logger.info("副线程返回。。。"); return "success"; } }; logger.info("主线程返回..."); return result; }
5.2 异步处理RESTful服务,DeferredResult
1)控制器
@RequestMapping("/order3") public DeferredResult<String> deferredResult(){ logger.info("主线程开始"); // 生成订单号,相当于线程号 String orderNumber = RandomStringUtils.randomNumeric(8); // 处理订单,开启新的线程,同时监听是否处理完毕,如果处理完毕就对客户端响应 mockQueue.setPlaceOrder(orderNumber); // 该对象可以完成向 对应客户端 做出响应 DeferredResult<String> deferredResult = new DeferredResult<>(); deferredResultHolder.getMap().put(orderNumber, deferredResult); System.out.println("主线程返回"); return deferredResult; }
2)订单队列
@Component public class MockQueue { private Logger logger = LoggerFactory.getLogger(getClass()); private String placeOrder; private String completeOrder; public String getPlaceOrder() { return placeOrder; } public void setPlaceOrder(String placeOrder) { // 处理订单业务 new Thread(()->{ logger.info("接到下单请求..."+placeOrder); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.placeOrder = placeOrder; this.completeOrder = placeOrder; logger.info("下单请求处理完毕..."+placeOrder); }).start(); } public String getCompleteOrder() { return completeOrder; } public void setCompleteOrder(String completeOrder) { this.completeOrder = completeOrder; } }
3)DeferredResultHolder存放<订单序号,DeferredResult>键值对
@Component public class DeferredResultHolder { // 订单号,订单结果 private Map<String, DeferredResult<String>> map = new HashMap<>(); public Map<String, DeferredResult<String>> getMap() { return map; } public void setMap(Map<String, DeferredResult<String>> map) { this.map = map; } }
4)订单完成监听器
@Component public class QueueListener implements ApplicationListener<ContextRefreshedEvent> { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private MockQueue mockQueue; @Autowired private DeferredResultHolder deferredResultHolder; @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { // 开启新的线程监听队列中是否有完成的订单 new Thread(()->{ while (true) { if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) { // 监听到有订单完成了,结束异步请求,返回结果 String orderNumber = mockQueue.getCompleteOrder(); logger.info("返回订单处理结果:" + orderNumber); deferredResultHolder.getMap().get(orderNumber).setResult("order completed success..."); mockQueue.setCompleteOrder(null); } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
6 与前端并行开发
6.1 使用swagger自动生成HTML文档
1)引入maven依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> <exclusions> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> </exclusion> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.21</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>1.5.21</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
2)在spring boot启动类添加模块
@SpringBootApplication @EnableSwagger2 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
3)api描述:
方法描述
模型参数描述:
参数描述:
6.2 使用WireMock快速伪造RESTful服务
1)下载WireMock的jar包
2)运行WireMock服务器端
java -jar wiremock-standalone-2.20.0.jar --port 8000
3)使用Java代码,描述规则
public class MockServer { public static void main(String[] args) throws IOException { WireMock.configureFor(8000); WireMock.removeAllMappings(); //清空urlMapping mock("/order", "01.json"); } public static void mock(String url, String file) throws IOException { ClassPathResource classPathResource = new ClassPathResource("mock/response/"+file); String content = FileUtils.readFileToString(classPathResource.getFile(), "UTF-8"); System.out.println(content); WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(url)) .willReturn(WireMock.aResponse().withBody(content) .withStatus(200)));
WireMock.saveMappings(); } }
乱码?
withHeader("content-type", "application/json;charset=utf-8")
将mapping保存起来,下次启动WireMock时,这些mapping还存在
end