专栏首页技术与生活Spring Boot的过滤器

Spring Boot的过滤器

在开发 Web 项目的时候,经常需要过滤器来处理一些请求,包括字符集转换什么的,记录请求日志什么的等等。在之前的 Web 开发中,我们习惯把过滤器配置到 web.xml 中,但是在 SpringBoot 中,兵没有这个配置文件,该如何操作呢?其实在 Spingboot 中存在3种形式进行过滤操作。

1、使用传统的过滤器

首先构建一个包,该包需要在项目启动下面,如下图

image

其中1代表是微服务启动类,2代表在启动类下面构建一个包,再在堡垒新建一个过滤器类,并且实现 Filter 接口

image

接下来实现里面的方法,这里我们仅仅是记录方法调用锁花费的时间。当然为了 SpringBoot 能够识别这个组件,需要注解@Component

@Componentpublic class TimerFilter implements Filter{    @Override
    public void destroy() {
        System.out.println("timer Filter is destoried");
    }    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("timer Filter begin");        long start=new Date().getTime();
        chain.doFilter(request, response);        long end=new Date().getTime();
        System.out.println("timer Filter end,cost time:"+(end-start));
    }    @Override
    public void init(FilterConfig arg0) throws ServletException {
        System.out.println("timer Filter is inited");
    }

}

构建一个测试 Controller,方便测试

@RestControllerpublic class UserController {    
    @GetMapping("/filter")    public String testFilter(){        return "filter is ok";
    }
}

启动项目,进行测试。 项目启动后,首先看到过滤器里面的初始化方法被执行了

image

接着访问http://localhost:8888/filter,控制台打印出如下内容,表示过滤器正常调用

image

第三方过滤器的使用

有时候,我们使用的是第三方的过滤器,并不是在我们项目启动类注解可扫描的部分,也没法配置到 web.xml 里面,这个时候该怎么办?

我们可以使用 SpringBoot 的配置类进行配置。 首先构建一个包,再新建一个配置类,然后添加注解为@Configuration

image

接下来,我们就开始注入 bean,这个 bean 是FilterRegistrationBean ,具体代码如下

@Configurationpublic class ProjectConfig {    @Bean
    public FilterRegistrationBean timerFilter(){
        FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();
        
        filterRegistrationBean.setFilter(new TimerFilter());
        List<String>urls=Lists.newArrayList();
        urls.add("/*");
        filterRegistrationBean.setUrlPatterns(urls);        
        return filterRegistrationBean;
    }
}

这里可以添加过滤器锁拦截的 URL,拦截更加精准。

重新运行项目,不出意外,将会得到同样的结论。

2、使用Interceptor

由于上面的过滤器的过来方法里面是使用的ServletRequest request, ServletResponse response,所以和 Spring 相关的上下文就很难获得,也不知道是从哪个 Controller 来的,所以,就出现了 SpringBoot 框架自带的过滤器interceptor.

首先构建包cn.ts.demo.interceptor,并且新建TimerInterceptor类,该类需要实现HandlerInterceptor

image

可以看到有三个需要实现的方法,从方法名称可以得知每个方法的具体作用。 preHandle:表示在调用某个方法之前,会调用 postHandle:表示控制器的方法调用之后,该方法用调用;如果控制器的方法跑出异常,那么这个方法不会被执行。 afterCompletion:表示无论控制器方法如何处理,该方法都会调用。

现在来完善方法里面的内容,以及对象 Object 是什么。当然也是需要标注为 @Component.

@Componentpublic class TimerInterceptor implements HandlerInterceptor{    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse arg1, Object arg2, Exception exception)
            throws Exception {
        System.out.println("afterCompletion exe");
        Long start=(Long)request.getAttribute("startTimer");
        System.out.println("afterCompletion花费时间:"+(new Date().getTime()-start));
        System.out.println("异常:"+exception);
    }    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
            throws Exception {
        System.out.println("postHandle exe");
        Long start=(Long)request.getAttribute("startTimer");
        System.out.println("postHandle花费时间:"+(new Date().getTime()-start));
    }    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse arg1, Object obj) throws Exception {
        System.out.println("preHandle exe");        //向 request 里面放入一个属性
        request.setAttribute("startTimer", new Date().getTime());        //查看这里的 obj 是什么
        System.out.println("类名称:"+((HandlerMethod)obj).getBean().getClass().getName());
        System.out.println("方法名称:"+((HandlerMethod)obj).getMethod().getName());        return true;
    }

}

如此操作之后,并不能直接使用,需要在配置类里面进行配置,同时修改配置继承WebMvcConfigurerAdapter,然后覆盖addInterceptors方法。这个方法需要把刚才做好的TimerInterceptor增加进来。当然需要把TimerInterceptor注入进来,

@Autowired private TimerInterceptor timerInterceptor;@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timerInterceptor);
    }

现在可以重启项目,开始验证。不出意外,就得到如下结果

image

确实能够得到相关的类和方法名称。

如果我们的控制器方法跑出异常,再来看下,修改下控制器的方法。

    @GetMapping("/filter")    public String testFilter(){        throw new RuntimeException();        //return "filter is ok";
    }

继续重启,再运行 得到的结论:

image

postHandle不会执行了,直接跳到afterCompletion。需要注意的是,如果有异常处理机制,也不会再afterCompletion捕获到异常。

3、切片 Aspect

虽然 Interceptor 能够拿到类和方法名称,但是不能够拿到方法的参数和他的值。 查看下 Spring 的源码,找到 DispatcherServlet,这个是用来分发请求的,找 doService方法,再找到doDispatch(request, response);,大概在901行,进入这个方法,找到962-967行

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {                    return;
                }                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

其中mappedHandler.applyPreHandle就是调用我们拦截器的pre部分。如果为 false,就不再执行下面内容。接下来才是真正执行,也就是ha.handle这个方法,里面包含了参数的拼装,也就是说控制器的参数对象是有这个方法通过 request 里面的参数来构造出来的。所以在 interceptor的 prehandle 方法里面是不知道参数是什么样的。 所以如果需要知道具体的参数,就得使用切片来处理。

Spring AOP 简介 一个切片需要切入点和最强两个部分。

image

大概了解了切片之后,我们需要立马实现他。 首先还是先建立个 aspect 包,然后新建一个切片类TimerAspect。需要增加注解。

image

接下来,需要创建切入点,时间点的说明 Before 雷同 interceptor 的 preHandle; After 雷同 interceptor 的 postHandle; AfterThrow 雷同 interceptor 的 afterCompletion; Around 是包围全部,也就是覆盖上面3个,一般用这个。

哪些方法上执行是一些正则表达式,在上述注解里面,比如 @Around("execution(* cn.ts.demo.controller.UserController.*(..))") 第一个*表示任何的返回值; UserController后面的点星代表该类里面的所有方法; 括号点点表示方法参数任意。 关羽如何编写这样的表达式,可以参考[AOP参考]https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts

接下来继续完善该切片代码

@Aspect@Componentpublic class TimerAspect {    @Around("execution(* cn.ts.demo.controller.UserController.*(..))")    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{
        
        System.out.println("time aspect start");        //方法参数
        Object[] args = pjp.getArgs();        for (Object arg:args) {
            System.out.println("参数:"+arg);
        }
        
        Long start=new Date().getTime();
        Object obj=pjp.proceed();
        System.out.println("time aspect花费时间:"+(new Date().getTime()-start));
        System.out.println("time aspect end");        
        return obj;
    }
}

重启服务,进行测试,

image

由于我们的测试方法没有参数,所以参数打印不存在。

修改下控制器方法的代码

@GetMapping("/filter/{id:\\d+}")    public String testFilter(@PathVariable String id){        //throw new RuntimeException();
        return "filter is ok";
    }

然后测试

image

不出意外,参数应该可以正常打印出来。

这样我们把三种过滤器的方法做了说明,也能看得出默认的顺序是过滤器,interceptor,aspect,实际开发可能要综合使用,以便达到我们需要的效果。

本文分享自微信公众号 - 技术与生活(technology_life),作者:唐顺

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 设计模式-状态模式

    一个对象的行为取决于一个或者多个动态变化的属性,这些属性叫做状态,比如订单的支付状态;而这些订单状态的值是预先知道的,已支付、未支付;当订单在客户操作过程中可能...

    breezedancer
  • 设计模式-观察者模式

    软件系统很多时候需要这样的情况,一旦发生故障,就通知对应的系统或模块,使之能够及时处理。

    breezedancer
  • 深度学习之卷积

    今日休假,把卷积神经网络梳理下。先从一些基本概念入手,什么是卷积?为什么叫这么个名字? 搜索了一遍,网上有很多人已经表述的非常好了,这里用自己理解的语言重述下。

    breezedancer
  • 马上Java14要来了,你还不知道Java8的新特性?

    Lambda表达式, 也可以称为闭包,它是Java8这个版本最重要的新特性.Lambda允许把函数作为一个方法的参数, 可以使代码变得更加简洁.

    故里
  • Java基础知识笔记五(详细)(1024即将到来,那就文末留言送书吧)

    1,因为面向对象思想是基于面向过程思想。回想一下之前,在做一些练习的时候,我首先得清楚,我需要做什么操作,然后在一步步分析要怎么做,最后用代码实现。需要面对每一...

    用户7656790
  • JavaSE(三)之static、final、abstract修饰符

    一、static修饰符 1.1、static变量         在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。         ...

    用户1195962
  • Java 版 C 语言经典 100 例(6 - 10)

    国际象棋棋盘由 64 个黑白相间的格子组成,分为 8 行 * 8 列,用双重循环控制输出即可

    村雨遥
  • 004.多线程-线程的三种创建方式

    当前主流的就是面向接口开发, 因为实现了接口后还可以根据业务需要再继承其他的类。 实现Runnable接库与实现Callable接口对比来看, Calla...

    qubianzhong
  • 16(03)总结增强for循环,静态导入,可变参数

    3:增强for循环(掌握) (1)是for循环的一种 (2)格式: for(元素的数据类型 变量名 : 数组或者Collection集合的对象) { 使...

    Java帮帮
  • Java 反射

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对...

    赵哥窟

扫码关注云+社区

领取腾讯云代金券