Spring Boot的过滤器

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

1、使用传统的过滤器

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


image

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


image

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

@Component
public 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,方便测试

@RestController
public 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
,具体代码如下

@Configuration
public 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.

@Component
public 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
@Component
public 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,实际开发可能要综合使用,以便达到我们需要的效果。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容