Interceptor、Filter、Aspect在某种程度上都可以起到截断拦截的作用,但是它们之间有什么区别和联系呢?本文就来介绍下他们之间的区别和联系。

本文主要以一个简单的SpringBoot应用程序为例进行说明。

在此之前先写一个Controller,代码如下:


@RestController
public class DimpleController {

    @GetMapping("/user")
    public List getUserList(String name) {
        List userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setAge(i);
            user.setName(UUID.randomUUID().toString().substring(0, 2));
            user.setAddress(UUID.randomUUID().toString());
            userList.add(user);
        }
        return userList;
    }
}


Filter过滤器

Filter依赖于Servlet容器。在实现上基于函数回调。几乎可以对所有的请求进行过滤。一个过滤器实例只会在容器启动的时候初始化一次。

过滤器可以拦截到方法的请求和响应(ServletRequest,ServletResponse),并对响应做出过滤操作。


使用过滤器的目的是用于做一些过滤操作,比如:在过滤器中修改字符编码;修改HTTPServletRequest中的一些参数,包括低俗文字、危险字符等。


@Slf4j
@Component
public class DimpleFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("DimpleFilter Init...............");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("DimpleFilter Start:");
        long start = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long end = System.currentTimeMillis();
        log.info("DimpleFilter End,Execute Time:" + (end - start));
    }

    @Override
    public void destroy() {
        log.info("DimpleFilter Destroy...............");
    }
}

在启动应用之前需要配置Filter,配置Filter有两种方案:

1. 在Filter类上添加@Component注解,如上代码。

2. 在config类中进行注册:

    

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean dimpleFilterFilterRegistrationBean() {
        FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new DimpleFilter());
        Set urls = new HashSet<>();
        urls.add("/*");
        filterFilterRegistrationBean.setUrlPatterns(urls);
        return filterFilterRegistrationBean;
    }
}

使用Config类进行配置的好处是可以对需要过滤的url进行细化。

启动项目可以看到如下信息:

可以看到应用在启动的时候就初始化了Filter,而在应用关闭的时候才销毁。

另外,可以在Request类中可到请求的方法和地址,但是无法看到具体处理这个请求的方法。

Interceptor拦截器

Interceptor依赖于Web框架,在SpringMVC中就是依赖于Spring框架,在实现上,基于Java的反射机制,属于面向切面编程的一种应用。


@Slf4j
@Component
public class DimpleInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("DimpleInterceptor preHandle");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        log.info(handlerMethod.getBean().getClass().getName());
        log.info(handlerMethod.getMethod().getName());
        request.setAttribute("start", System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("DimpleInterceptor postHandle");
        Long start = (Long) request.getAttribute("start");
        log.info(" PostHandle Execute time :" + (System.currentTimeMillis() - start));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("DimpleInterceptor afterCompletion");
        Long start = (Long) request.getAttribute("start");
        log.info(" afterCompletion Execute time :" + (System.currentTimeMillis() - start));
    }
}

Interceptor配置才能生效:


@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Autowired
    DimpleInterceptor dimpleInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dimpleInterceptor).addPathPatterns("/*");
    }
}

结果如下:


在postHandler方法中,如果Controller内部出现异常,该方法是不会执行的。

afterCompletion方法,不管Controller中是否存在异常,都会执行此方法;注意到该方法还有一个Exception 参数,如果发生了异常,该参数不为null。

另外还需要注意一点:如果Controller内部有异常,但是异常被@ControllerAdvice或者@RestControllerAdvice统一捕获的话,该参数也会为null。

Aspect切片

AOP操作可以实现横向的拦截,最大的优势在于它可以获取执行方法的参数,对方法进行统一处理,常见的使用场景为:日志记录、事务、请求参数安全校验等。

ps:这里只用于演示,正常业务中不应该这么写的。


@Aspect
@Slf4j
@Component
public class DimpleAspect {

    @Around("execution(* com.dimple.filterinterceptoraspect.controller.DimpleController.*(..))")
    public Object handlerController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("DimpleAspect handlerController start");
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            log.info("arg is {} ", arg);
        }
        long start = System.currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        log.info("Execute Time {}", System.currentTimeMillis() - start);
        log.info("DimpleAspect handlerController end");
        return proceed;
    }
}

执行该方法后的结果为:


总结

Filter、Interceptor、Aspect三者同时使用的执行顺序是什么呢?

Filter->Interceptor->ControllerAdvice->aspect->controller

返回顺序:

controller->aspect->controllerAdvice->interceptor->Filter