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