Filter、Interceptor、Aspect

作者: 花神子 | 来源:发表于2019-09-16 11:25 被阅读0次

filter、interceptor、aspect

  1. Filter过滤器
  2. Interceptor拦截器
  3. Aspect切片

Filter过滤器

  • 过滤器依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,一个过滤器实例只能在容器初始化时调用一次。

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

  • 使用过滤器的目的是用来做一些过滤操作,对请求的数据进行获取,或者替换请求数据,或者权限拦截检查,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,过滤关键字:过滤低俗文字、危险字符等。

切面的方法说明

  • @Aspect 作用是把当前类标识为一个切面供容器读取

  • @Before 标识一个前置增强方法,相当于BeforeAdvice的功能

  • @AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行

  • @AfterThrowing 异常抛出增强,相当于ThrowsAdvice

  • @After final增强,不管是抛出异常或者正常退出都会执行

  • @Around 环绕增强,相当于MethodInterceptor

  • 除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX"

切面PointCut的切入点

execution切点函数

  • execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))

例如:
execution(* com.imooc.controller.UserController.*(..))  
第一个  * 代表的是所有的返回值类型,
com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController类的所有方法,
(..)代表的是所有的参数

Filter代码实例

@Component
public class TimeFilter implements Filter {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(TimeFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        String uri = request.getRequestURI();

        long start = System.currentTimeMillis();
        
        filterChain.doFilter(servletRequest,servletResponse);

        long end = System.currentTimeMillis();

        LOGGER.info("请求地址 : [{}], 耗时 : [{}]",uri, (end-start)/3600);
    }

    @Override
    public void destroy() {
    }
}
  • 实现javax.servlet.Filter接口,并重写接口的三个方法,其中doFilter方法是必须实现的,其它两个方法是接口的默认方法;
  • doFilter方法 提供请求的ServletRequest和响应的ServletResponse对象
  • Filter中无法获取其它对象,例如Controller 即Filter中不支持对象注入(@AutoWired). 在Spring中 web容器的加载顺序是:Listener -> Filter -> Servlet, 先初始化Listener,然后进行Filter初始化,再接口进行Servlet的初始化过程。然后我们的Controller对象即在Servlet容器初始化后进行初始化的,所以在Filter初始化时,注解获取Bean的还没有进行初始化,即没法注入。
public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

Filter配置

Filter过滤器实例已经书写完毕,但是使其生效需要进行配置,一般有两种方式进行配置:

  • 第一个方案在Filter上面加上@Component
@Component 
public class TimeFilter implements Filter {
  ...
}
  • 第二个方案配置化注册过滤器
    该方案的特点就是可以细化到过滤哪些规则的URL
@Configuration
public class FilterConfig {

    @Bean
    FilterRegistrationBean<TimeFilter> filterRegistrationBean() {
        FilterRegistrationBean<TimeFilter> registrationBean = new FilterRegistrationBean();
        TimeFilter timeFilter = new TimeFilter();
        registrationBean.setFilter(timeFilter);
        registrationBean.setUrlPatterns(Arrays.asList("/*"));
        return registrationBean;
    }
}

容器启动输出:

com.ams.admin.filters.TimeFilter         : init...

调用请求输出

2019-09-04 14:11:27.424  INFO 66854 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single], 耗时 : [10334] ms
2019-09-04 14:11:34.236  INFO 66854 --- [io-10001-exec-3] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single], 耗时 : [14] ms
2019-09-04 14:11:35.442  INFO 66854 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single], 耗时 : [14] ms

容器销毁输出

2019-09-04 14:19:16.148  INFO 66854 --- [      Thread-31] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2019-09-04 14:19:16.149  INFO 66854 --- [      Thread-31] com.alibaba.druid.pool.DruidDataSource   : {dataSource-3} closed
2019-09-04 14:19:16.158  INFO 66854 --- [      Thread-31] com.ams.admin.filters.TimeFilter         : destroy...

Filter 总结

  • 过滤器(Filter)是基于函数回调;
  • Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁;
  • 启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
  • 每一次请求时都只调用方法doFilter()进行处理
  • 停止服务器时调用destroy()方法,销毁实例。

Interceptor拦截器

Interceptor基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在一个方法前,调用一个方法,或者在方法后,调用一个方法。其实我们所了解的代理机制其实就是一种拦截器的实现。而我们此处讨论的拦截器是针对web框架所说的,所以依赖于web框架。

  • 在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等。在spring中拦截器有两种,第一种是HandlerInterceptor,第二种是MethodInterceptor。HandlerInterceptor是SpringMVC中的拦截器,它拦截的是Http请求的信息,优先于MethodInterceptor。而MethodInterceptor是springAOP的。前者拦截的是请求的地址,而后者是拦截controller中的方法,因为下面要将Aspect,就不详细讲述MethodInterceptor

代码实例

public class AmsInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AmsInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod method = (HandlerMethod) handler;
        String cn = method.getBean().getClass().getName();
        String mn = method.getMethod().getName();
        LOGGER.info("preHandle -> [{}]#[{}]#[{}]", cn, mn, readRaw(request.getInputStream()));
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LOGGER.info("postHandle -> [{}]#[{}]", request.getRequestURI(), request.getPathInfo());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LOGGER.info("afterCompletion -> [{}]#[{}]", request.getRequestURI(), request.getPathInfo());
    }


    public static String readRaw(InputStream inputStream) {

        String result = "";
        try {
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];

            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }

            outSteam.close();
            inputStream.close();

            result = new String(outSteam.toByteArray(), "UTF-8");

        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }
}

Interceptor配置

  • 继承WebMvcConfigurationSupport
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    @Autowired
    AmsInterceptor amsInterceptor;

    /**
     * Override this method to add Spring MVC interceptors for
     * pre- and post-processing of controller invocation.
     *
     * @param registry
     * @see InterceptorRegistry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(amsInterceptor).addPathPatterns(Arrays.asList("/**"));
    }
}

输出

2019-09-05 11:27:29.585  INFO 77598 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single]
2019-09-05 11:27:29.586  INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor   : preHandle : [com.ams.admin.controller.AmsGroupController]#[operateEntity]#[{   "name":"三部",    "groupAmsid":"0003",    "remark":"三部",  "systemId": 1,  "operator":"maozw"}]
2019-09-05 11:27:29.586  INFO 77598 --- [io-10001-exec-4] c.a.admin.controller.AmsGroupController  : controller
2019-09-05 11:27:29.603  INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor   : postHandle : [/group/single]
2019-09-05 11:27:29.603  INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor   : afterCompletion : [/group/single]
2019-09-05 11:27:29.603  INFO 77598 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single], 耗时 : [18] ms

执行流程解释

  • 简要说明一下Interceptor执行流程,
  • 我们通过日志可以看出,执行流程 TimeFilter -> preHandle -> controller -> postHandle -> afterCompletion
    我们看下SpringMVC的核心类 org.springframework.web.servlet.DispatcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
    try {
        ...
        try {
            ...
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            ...
        }
    finally {
        ...
        if (asyncManager.isConcurrentHandlingStarted()) {
           // Instead of postHandle and afterCompletion
          if (mappedHandler != null) {
              mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
          }
        }
    }
}   

以上是删除一些无关代码之后的:

  • mappedHandler.applyPreHandle(processedRequest, response) 这个方法执行,就是执行的拦截器的preHandler方法

  • mappedHandler.applyPostHandle(processedRequest, response, mv); postHandler方法的执行,当controller内部有异常,posthandler方法是不会执行的。

  • mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); ****afterCompletion方法,不管controller内部是否有异常,都会执行此方法;此方法还会有个Exception ex这个参数;如果有异常,ex会有异常值;没有异常 此值为null

  • 根据上面的流程我们知道ha.handle(processedRequest, response, mappedHandler.getHandler());方法才会对请求参数进行组装方法参数,所以我们是在拦截器中可以拿到Http原始请求和响应信息,也可以获取到ioc的bean(controller)但是这个这个方法的参数是没法通过haner对象去获取。

  • 值得一提的事情,因为我在Interceptor中获取了ServletRequest的InputStream 这个操作会导致后续在Controller中获取不到流,解决办法我会在另一篇幅中进行说明:详见【】

Aspect切片

AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理。常见使用日志,事务,请求参数安全验证等。

  • 可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。

代码实例

@Aspect
@Component
@Slf4j
public class ControllerAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class);

    @Around("execution(* com.ams.admin.controller.*.*(..))")
    public Object handlerControlerMethod(ProceedingJoinPoint joinPoint) throws Throwable {

        Object[] args = joinPoint.getArgs();
        LOGGER.info("aop before... [{}]", args);
        Object proceed = joinPoint.proceed();
        LOGGER.info("aop after...");
        return proceed;
    }
}

配置使其生效

@EnableTransactionManagement(proxyTargetClass = true)

输出

2019-09-05 15:18:06.748  INFO 80313 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single]
2019-09-05 15:18:06.750  INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor   : preHandle : [com.ams.admin.controller.AmsGroupController$$EnhancerBySpringCGLIB$$7f16fb78]#[operateEntity]#[{  "name":"三部",    "groupAmsid":"0003",    "remark":"三部",  "systemId": 1,  "operator":"maozw"}]
2019-09-05 15:18:06.752  INFO 80313 --- [io-10001-exec-2] com.ams.admin.aspect.ControllerAspect    : aop before... [AmsGroup(id=null, groupAmsid=0003, name=三部, systemId=1, remark=三部, createDate=null, modifyDate=null, operator=maozw)]
2019-09-05 15:18:06.755  INFO 80313 --- [io-10001-exec-2] c.a.admin.controller.AmsGroupController  : controller
2019-09-05 15:18:16.909  INFO 80313 --- [io-10001-exec-2] com.ams.admin.aspect.ControllerAspect    : aop after...
2019-09-05 15:18:16.910  INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor   : postHandle : [/group/single]
2019-09-05 15:18:16.910  INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor   : afterCompletion : [/group/single]
2019-09-05 15:18:16.911  INFO 80313 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter         : 请求地址 : [/group/single], 耗时 : [10163] ms

小结

Filter
参数:ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
作用:可以获取原始的http请求和响应对象,无法获取请求控制器

Interceptor
参数:HttpServletRequest request, HttpServletResponse response, Object handler
作用:可以获取原始的http请求和响应对象,可以获取到控制器和方法,但获取不到方法参数

Aspect
参数:ProceedingJoinPoint joinPoint
作用:无法获取原始的http请求和响应对象,可以获取到控制器和方法,也可以获取方法参数

相关文章

网友评论

    本文标题:Filter、Interceptor、Aspect

    本文链接:https://www.haomeiwen.com/subject/yrbdyctx.html