【Spring学习】过滤器和拦截器

 1、认识过滤器(Filter)

1.1、过滤器的定义

过滤器是JavaWeb的三大组件之一,是实现Filter接口的Java类。

过滤器是实现对请求资源(jsp、servlet、html)的过滤功能,是一个运行在服务器的程序,优先于请求资源(jsp、servlet、html)之前执行。

当浏览器发送请求给服务器的时候,先执⾏过滤器,然后才访问Web的资源。服务器响应Response,从Web资源抵达浏览器之前,也会途径过滤器。

在很多Web开发中,都会用到过滤器(Filter),如参数过滤、防止SQL注入、防止页面攻击、过滤敏感字符、解决网站乱码、空参数矫正、Token验证、Session验证、点击率统计等。


1.2、为什么要使用过滤器

在Web开发中,经常会有这样的需求:在所有接口中去除用户输入的非法字符,以防止引起业务异常。要实现这个功能,可以有很多方法,如:

在前端参数传入时进行校验,先过滤非法字符,然后返回用户界面提示用户重新输入。

后端接收前端没有过滤的数据,然后过滤非法字符。

利用filter处理项目中所有非法字符。

很明显,前两种实现方法会存在重复代码,因为每个前端页面或后端都需要处理,这样会导致代码很难维护。如果用过滤器来实现,则只需要用过滤器对所有接口进行过滤处理。这样非常方便,同时不会出现冗余代码。


1.3、使用Filter的步骤(以SpringBoot项目为例)

(1)新建类,实现Filter抽类类。

(2)重写init、doFilter、destroy方法。

(3)在Spring Boot入口类中添加注解@ServletComponentScan,以注册Filter。


init():该方法在容器启动初始化过滤器时被调用,它在Filter的整个生命周期只会被调用一次,这个方法必须执行成功,否则过滤器会不起作用。

doFilter():容器中的每一次请求都会调用该方法,FilterChain用来调用下一个过滤器Filter。

destroy():容器销毁时被调用。一般在方法中销毁或关闭资源,也只会被调用一次。


注意的是doFilter()方法:

@Override

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

  //请求(request)处理逻辑

  //请求(request)封装逻辑

  //chain重新写回request和response

}


上面的FilterChain是一个接口,里面又定义了doFilter()方法,这是因为在Java中使⽤了链式结构。把所有的过滤器都放在FilterChain⾥边,如果符合条件,就执⾏下⼀个过滤器(如果没有过滤器了,就执⾏⽬标资源)。



1.4、SpringBoot实现一个简单的过滤器

(1)首先随便写一个控制器Controller

@RestController

@Slf4j

@RequestMapping("/api/filter")

public class FilterUserController {

    @GetMapping("/getUserList")

    public List<String> getUser() {

        log.info("开始业务逻辑处理。");

        List<String> list = new ArrayList<>();

        list.add("张三");

        list.add("李四");

        list.add("王五");

        log.info("业务逻辑处理结束。");

        return list;

    }

}


(2)在启动类添加一个注解,找到定义的拦截器。

@ServletComponentScan(basePackages = "com.binlog.study.filter")


(3)写一个过滤器,实现Filter

@Slf4j

@Order(1)  //如果有多个Filter,则序号越小,越早被执行

//@Component//无需添加此注解,在启动类添加@ServletComponentScan注解后,会自动将带有@WebFilter的注解进行注入!

//这里的urlPatterns为接口里的路径过滤条件

@WebFilter(filterName = "timeFilter", urlPatterns = "/api/filter/*")

public class TimeFilter implements Filter {

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

        log.info("初始化过滤器:{}", filterConfig.getFilterName());

    }

    @Override

    public void destroy() {

        log.info("销毁过滤器");

    }

    @Override

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        log.info("开始执行");

        long startTime = System.currentTimeMillis();

        filterChain.doFilter(servletRequest, servletResponse);

        long endTime = System.currentTimeMillis();

        log.info("请求:{},耗时:{}ms", getUrlFrom(servletRequest), (endTime - startTime));

        log.info("结束执行");

    }

    private String getUrlFrom(ServletRequest servletRequest) {

        if (servletRequest instanceof HttpServletRequest) {

            return ((HttpServletRequest) servletRequest).getRequestURL().toString();

        }

        return "";

    }

}


(4)项目启动后,控制台输出结果为:

项目启动后,就已经初始化过滤器了。


浏览器页面调用一下接口:http://localhost:8060/api/filter/getUserList


关闭项目后,过滤器也销毁了。



2、认识拦截器(Interceptor)

2.1、拦截器的定义

SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。

拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序依次执行。


2.2、拦截器的核心API

SpringMVC拦截器提供三个方法分别是preHandle、postHandle、afterCompletion,我们就是通过重写这几个方法来对用户的请求进行拦截处理的。

preHandle() :这个方法将在请求处理之前进行调用。「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。

postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。「有意思的是」:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行,在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。


2.3、SpringBoot实现一个登录拦截器

预想:用户在访问首页接口,先判断一下session,如果session中有user的信息,说明用户已经登录过了,能正常访问首页接口,否则跳转到登录页面,让用户进行登录。

(1)首先随便定义一个实体类

@Data

public class InterceptorUserEntity {

    private Integer id;

    private String name;

}

(2)在controller里写3个接口

@RestController

@Slf4j

@RequestMapping("/api/interceptor")

public class InterceptorUserController {

    @GetMapping("/setSession")

    @ResponseBody

    public Object setSession(HttpServletRequest request) {

        //将用户信息存放到session中

        InterceptorUserEntity user = new InterceptorUserEntity();

        user.setId(001);

        user.setName("张三");

        request.getSession().setAttribute("user", user);

        return "已进行登录!";

    }

    /**

    * 用户登录后跳转到首页

    *

    * @return

    */

    @GetMapping("/index")

    public Object index() {

        return "这里是首页!";

    }

    /**

    * 登录页面

    *

    * @return

    */

    @GetMapping("/login")

    public Object login() {

        return "请进行登录!";

    }

}


(3)编写拦截器,可以通过要定义的Interceptor类实现handlerInterceptor接口。

@Component

@Slf4j

public class UserInterceptor implements HandlerInterceptor {

    @Override

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

        //业务拦截相关规则

        //从session中获取用户的信息

        InterceptorUserEntity user = (InterceptorUserEntity) request.getSession().getAttribute("user");

        //判断用户是否登录

        if (null == user) {

            response.sendRedirect(request.getContextPath() + "/api/interceptor/login");

            return false;

        }

        //需要返回true,否则请求不会被控制器处理

        return true;

    }

    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        log.info("请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后),如果异常发生,则该方法不会被调用");

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        log.info("在整个请求结束之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行(主要是用于进行资源清理工作)");

    }

}


(4)使用@Configuration注解写一个拦截器的配置文件。

@Configuration

public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired

    private UserInterceptor userInterceptor;

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(userInterceptor).addPathPatterns("/api/interceptor/**").excludePathPatterns("/**/login", "/**/setSession");

    }

}

.addPathPatterns表示作用范围。(只在这个interceptor下的所有接口进行拦截)

.excludePathPatterns表示放行。这里把登录页面和已登录完成(setSession)放行。

(其它接口都会被拦截,然后跳转到login页面)


在用户没有请求过 /interceptor/setsession的时候,如果用户请求了 /interceptor/拦截器就会发挥作用, 把它跳转到/user/login的接口上去,如果用户请求过/interceptor/setsession的话, 再去请求/user/index拦截器就会放行,请求到相应的结果。


3、过滤器与拦截器的区别

相同点:

过滤器与拦截器都体现了AOP的编程思想,都可以实现例如日志、登录鉴权等功能。

不同点:

①:拦截器是基于java的反射机制(动态代理)的实现,而过滤器是基于函数的回调。

②:拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。

③:拦截器只对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。

④:拦截器可以访问Controller上下文、值、栈里面的对象,而过滤器不可以。

⑤:在spring容器的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

⑥:拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。


触发机制不同

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

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

推荐阅读更多精彩内容