Spring源码学习笔记(6)——REST服务的拦截

时间:2022-07-24
本文章向大家介绍Spring源码学习笔记(6)——REST服务的拦截,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Spring源码学习笔记(6)——REST服务的拦截

一. 拦截REST服务的几种方式

  1. 拦截REST服务 在很多情况下,我们需要在REST服务核心逻辑的前后,加入一些通用的额外处理,比如权限控制,日志记录和方法统计等。这时,我们可以对REST服务进行拦截,并织入我们的通用逻辑。拦截REST服务的方式有一下几种:
    1. Filter:过滤器
    2. Interceptor:拦截器
    3. Aspect:切面

    下面以记录方法执行时间为例,分别演示几种拦截方式。

二. Filter过滤器拦截

Filter是Web开发中一个十分常用的组件,一个Web应用可以注册多个Filter,它会按照配置的路径拦截Http请求,并进行相应的处理。

首先,编写TimeFilter类,实现过滤器逻辑:

/**
 * @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,代码如下:

/**
 * @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;
    }
}

访问服务,可以看到控制台输出如下:

TimeFilter拦截Rest服务
方法执行时间: 66

三. Interceptor拦截

Interceptor,顾名思义,是一种拦截器,SpringMVC提供了Interceptor拦截Http访问的执行,并在Controller处理前后增加自定义的逻辑。SpringMVC推荐使用HandlerInterceptor进行拦截。

HandlerInterceptor接口包含三个方法,具体见源码:

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接口:

/**
 * @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:

/**
 * @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服务,可看到控制台输出:

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切面类,并指定切入点表达式:

@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服务,查看控制台:

TimeAspect:拦截方法执行,目标对象: william.security.demo.controller.UserController@3842f7e0,目标方法: getById,方法参数: [Ljava.lang.Object;@4641d47c
方法执行时间: 7

五. 总结

  1. 几种拦截方式的对比 以上介绍的几种拦截REST服务的方法,各有优劣,适合于不同的应用场景。现简单进行对比: Filter拦截 Interceptor拦截 Aspect拦截 可获取到的信息 HttpRequest、HttpResponse HttpRequest、HttpResponse、目标对象和目标方法 目标方法及参数 开发难易程度 易 较易 较难 局限 仅能对Controller的方法进行拦截,并且无法获取目标方法的信息,不易于结合Spring框架处理过多的逻辑。 仅能对Controller的方法进行拦截,可以获取目标方法信息,但无法拿到方法参数。
  2. 使用场景
    • Filter和Interceptor适用与对Http响应进行简单拦截,并加入额外处理的场景,不适用于过于复杂的横切逻辑织入。
    • Aspect使用与较复杂的拦截处理场景。