Spring源码学习笔记(6)——REST服务的拦截
一. 拦截REST服务的几种方式
- 拦截REST服务
在很多情况下,我们需要在REST服务核心逻辑的前后,加入一些通用的额外处理,比如权限控制,日志记录和方法统计等。这时,我们可以对REST服务进行拦截,并织入我们的通用逻辑。拦截REST服务的方式有一下几种:
- Filter:过滤器
- Interceptor:拦截器
- Aspect:切面
下面以记录方法执行时间为例,分别演示几种拦截方式。
二. Filter过滤器拦截
Filter是Web开发中一个十分常用的组件,一个Web应用可以注册多个Filter,它会按照配置的路径拦截Http请求,并进行相应的处理。
首先,编写TimeFilter类,实现过滤器逻辑:
代码语言:javascript复制/**
* @Auther: ZhangShenao
* @Date: 2018/9/26 14:56
* @Description:
*/
public class TimeFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.err.println("TimeFilter拦截Rest服务");
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
System.err.println("方法执行时间: " (endTime - startTime));
}
@Override
public void destroy() {
}
}
然后,注册该TimeFilter。在传统的JavaWeb开发中,一般是通过web.xml文件来配置Filter。而基于SpringBoot开发后,SpringBoot提供了FilterRegistrationBean来注册Filter,代码如下:
代码语言:javascript复制/**
* @Auther: ZhangShenao
* @Date: 2018/9/26 14:59
* @Description:Filter配置
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new TimeFilter());
String[] urlPatterns = {"/*"};
filterRegistrationBean.addUrlPatterns(urlPatterns);
return filterRegistrationBean;
}
}
访问服务,可以看到控制台输出如下:
代码语言:javascript复制TimeFilter拦截Rest服务
方法执行时间: 66
三. Interceptor拦截
Interceptor,顾名思义,是一种拦截器,SpringMVC提供了Interceptor拦截Http访问的执行,并在Controller处理前后增加自定义的逻辑。SpringMVC推荐使用HandlerInterceptor进行拦截。
HandlerInterceptor接口包含三个方法,具体见源码:
代码语言:javascript复制public interface HandlerInterceptor {
/**
* 在Handler的方法执行前置处理
* @param request
* @param response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return 如果返回true,则继续执行Handler的目标方法,否则直接返回。
* @throws Exception 如果抛出异常,则目标方法无法继续执行。
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* 目标方法执行后置处理
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 最终处理,无论目标方法执行成功还是失败,都会回调该方法
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
首先,开发TimeInterceptor,实现HandlerInterceptor接口:
代码语言:javascript复制/**
* @Auther: ZhangShenao
* @Date: 2018/9/26 15:50
* @Description:自定义Interceptor拦截器
*/
public class TimeInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.err.println("TimeInterceptor:拦截方法执行");
request.setAttribute("startTime",System.currentTimeMillis());
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod)handler;
System.err.println("拦截目标对象: " handlerMethod.getBean() ",目标方法: " handlerMethod.getMethod().getName());
}
System.err.println(handler);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Long startTime = (Long)request.getAttribute("startTime");
System.err.println("方法执行时间: " (System.currentTimeMillis() - startTime));
}
}
下面,注册TimeInterceptor:
代码语言:javascript复制/**
* @Auther: ZhangShenao
* @Date: 2018/9/26 16:10
* @Description:Interceptor配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeInterceptor());
}
}
访问REST服务,可看到控制台输出:
代码语言:javascript复制TimeInterceptor:拦截方法执行
拦截目标对象: william.security.demo.controller.UserController@5cb2fc03,目标方法: getById
public william.security.demo.dto.UserDto william.security.demo.controller.UserController.getById(long)
方法执行时间: 146
四. Aspect切面拦截
Aspect基于Spring提供的AOP功能,提供了强大的面向切面编程的支持。AOP的思想和Spring AOP的原理这里不展开叙述,仅演示下怎么使用Aspect拦截REST服务。
首先,开发Aspect切面类,并指定切入点表达式:
代码语言:javascript复制@Aspect
@Component
public class TimeAspect {
@Around("execution(* william.security.demo.controller.UserController.*(..))")
public Object interceptMethodRuntime(ProceedingJoinPoint joinPoint) throws Throwable {
System.err.println("TimeAspect:拦截方法执行,目标对象: " joinPoint.getTarget()
",目标方法: " joinPoint.getSignature().getName() ",方法参数: " joinPoint.getArgs());
long startTime = System.currentTimeMillis();
Object retVal = joinPoint.proceed();
System.err.println("方法执行时间: " (System.currentTimeMillis() - startTime));
return retVal;
}
}
这里使用了@Around环绕通知,可以在目标方法前后都织入我们自定义的处理。
访问REST服务,查看控制台:
代码语言:javascript复制TimeAspect:拦截方法执行,目标对象: william.security.demo.controller.UserController@3842f7e0,目标方法: getById,方法参数: [Ljava.lang.Object;@4641d47c
方法执行时间: 7
五. 总结
- 几种拦截方式的对比 以上介绍的几种拦截REST服务的方法,各有优劣,适合于不同的应用场景。现简单进行对比: Filter拦截 Interceptor拦截 Aspect拦截 可获取到的信息 HttpRequest、HttpResponse HttpRequest、HttpResponse、目标对象和目标方法 目标方法及参数 开发难易程度 易 较易 较难 局限 仅能对Controller的方法进行拦截,并且无法获取目标方法的信息,不易于结合Spring框架处理过多的逻辑。 仅能对Controller的方法进行拦截,可以获取目标方法信息,但无法拿到方法参数。
- 使用场景
- Filter和Interceptor适用与对Http响应进行简单拦截,并加入额外处理的场景,不适用于过于复杂的横切逻辑织入。
- Aspect使用与较复杂的拦截处理场景。