前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot的过滤器

Spring Boot的过滤器

作者头像
breezedancer
发布2018-09-12 15:46:49
2.2K0
发布2018-09-12 15:46:49
举报
文章被收录于专栏:技术与生活

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

1、使用传统的过滤器

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

image

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

image

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

代码语言:javascript
复制
@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,方便测试

代码语言:javascript
复制
@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 ,具体代码如下

代码语言:javascript
复制
@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.

代码语言:javascript
复制
@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注入进来,

代码语言:javascript
复制
@Autowired private TimerInterceptor timerInterceptor;@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timerInterceptor);
    }

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

image

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

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

代码语言:javascript
复制
    @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行

代码语言:javascript
复制
                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

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

代码语言:javascript
复制
@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

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

修改下控制器方法的代码

代码语言:javascript
复制
@GetMapping("/filter/{id:\\d+}")    public String testFilter(@PathVariable String id){        //throw new RuntimeException();
        return "filter is ok";
    }

然后测试

image

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术与生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、使用传统的过滤器
    • 第三方过滤器的使用
    • 2、使用Interceptor
    • 3、切片 Aspect
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档