springboot工程中有多种切入的方式,如aop、filter、interceptor、listener、resolver。下面以白名单校验的业务场景进行简单的分析。
1、aop方式
aop是springboot提供的面向切面编程,只需在方法前添加切点,然后再对切点进行处理即可。
首先定义一个注解Whitelist,然后使用@Aspect注解声明一个切面类WhitelistAspect,最后在执行被注解的方法时才会进行切面类的处理方法中。
@Aspect
@Component
public class WhitelistAspect {
@Pointcut("@annotation(com.***.Whitelist)")
public void whitelistPointcut() {}
@Pointcut("execution(* com.***..*.*(..))")
public void pointcut(){}
@Before(value = "whitelistPointcut() && @annotation(whitelist)")
public void checkWhitelist(JoinPoint joinPoint, Whitelist whitelist) {
System.out.println("checkWhitelist");
// 可使用 joinPoint.getArgs() 获取Controller方法的参数
// 可使用 whitelist 变量获取注解参数
}
@Before(value = "@annotation(whitelist)")
public void checkWhitelist2(JoinPoint joinPoint, Whitelist whitelist) {
System.out.println("checkWhitelist2");
// 可使用 joinPoint.getArgs() 获取Controller方法的参数
// 可使用 whitelist 变量获取注解参数
}
// 须去掉, Whitelist whitelist,否则报错
@Before(value = "pointcut()")
public void checkWhitelist3(JoinPoint joinPoint) {
System.out.println("checkWhitelist3");
// 可使用 joinPoint.getArgs() 获取Controller方法的参数
}
}
2、filter方式
filter并不是spring提供的,是在servlet规范中定义的,对指定的url进行切入,被过滤的请求不会派发到spring容器中。
public class WhitelistFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化后被调用一次
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 判断是否需要拦截
System.out.println("doFilter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 被销毁时调用一次
}
}
3、interceptor方式
interceptor拦截器用在在controller内action被执行前进行切入校验释放要执行。
@Component
public class WhitelistInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Whitelist whitelist = ((HandlerMethod) handler).getMethodAnnotation(Whitelist.class);
// whitelist.values(); 通过 request 获取请求参数,通过 whitelist 变量获取注解参数
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 方法在Controller方法执行结束后执行
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在view视图渲染完成后执行
}
}
4、listener方式
listener监听器实现ServletRequestListener 接口,对请求进行切入。
public class MyHttpRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
String requestUri = request.getRequestURI();
log.info("进入监听器的请求地址是:{}", requestUri);
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
log.info("监听器被销毁");
}
}
5、resolver方式
参数解析器是 Spring 提供的用于解析自定义参数的工具,Spring 会维护一个 ResolverList, 在请求到达时,Spring 发现有自定义类型参数(非基本类型), 会依次尝试这些 Resolver,直到有一个 Resolver 能解析需要的参数。要实现一个参数解析器,需要实现 HandlerMethodArgumentResolver 接口。
首先定义自定义参数类型 AuthParam,然后定义 AuthParamResolver 并实现 HandlerMethodArgumentResolver 接口,最后在 Controller Action 方法上签名内添加 AuthParam 参数以启用此 Resolver;
public class AuthParam implements Serializable {
private static final long serialVersionUID = 1L;
private String name = "hello2";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Component
public class AuthParamResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Whitelist whitelist = parameter.getMethodAnnotation(Whitelist.class);
// 通过 webRequest 和 whitelist 校验白名单
System.out.println("resolveArgument");
return new AuthParam();
}
}
通过配置的方式将上面注入spring
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WhitelistInterceptor()).addPathPatterns("/*").order(1);
// 这里可以配置拦截器启用的 path 的顺序,在有多个拦截器存在时,任一拦截器返回 false 都会使后续的请求方法不再执行
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthParamResolver());
}
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new WhitelistFilter());
registration.addUrlPatterns("/*");
registration.setName("whitelistFilter");
registration.setOrder(1);
return registration;
}
}
测试示例
@Whitelist
@RequestMapping("/hello")
public Object hello() {
return "hello";
}
@RequestMapping("/hello2")
public Object hello2(AuthParam authParam) {
return "hello2";
}
通过测试可知不同切入方式的执行顺序如下,filter是servlet实现的,自然是最先被调用,后续被调用的是interceptor被拦截了自然不需要后续再进行处理,然后是resolver,最后才是aop。在使用的过程中还需根据具体的业务需求来选择。