SpringBoot 过滤器、拦截器、监听器对比及使用场景

关系图理解

在这里插入图片描述

区别

过滤器

(1) 过滤器是在web应用启动的时候初始化一次, 在web应用停止的时候销毁
(2) 可以对请求的URL进行过滤, 对敏感词过滤
(3) 挡在拦截器的外层
(4) 实现的是 javax.servlet.Filter 接口,是 Servlet 规范的一部分
(5) 在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后
(6) 依赖Web容器
(7) 会多次执行

HttpServletRequestWrapper

在请求到达之前对 request 进行修改

package com.mry.springboottools.filter;


import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 *  在请求到达之前对 request 进行修改
 *
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        log.info("RequestWrapper");
    }

    @Override
    public String getParameter(String name) {
        // 可以对请求参数进行过滤
        return super.getParameter(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        // 对请求参数值进行过滤
        // String[] values =super.getRequest().getParameterValues(name);
        // return super.getParameterValues(name);
        return "t e s t".split(" ");
    }

}

OncePerRequestFilter

OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter;

package com.mry.springboottools.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

/**
 * 请求过滤器
 * OncePerRequestFilter:
 * OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter.
 * 大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢,往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,
 * 也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,
 * 因此,为了兼容各种不同运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择。
 *
 */
@Slf4j
public class RequestFilter extends OncePerRequestFilter {


    @Override
    public void destroy() {
        super.destroy();
        log.info("RequestFilter destroy");
    }

    /*
     OncePerRequestFilter.doFilter方法中通过request.getAttribute判断当前过滤器是否已执行
     若未执行过,则调用doFilterInternal方法,交由其子类实现
    */
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        try {
            RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
            filterChain.doFilter(requestWrapper, httpServletResponse);
            log.info("RequestFilter");
            log.info(Arrays.toString(requestWrapper.getParameterValues("name")));
        } catch (Exception exception) {
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.write(exception.toString());
        }
    }

}

配置
package com.mry.springboottools.config;

import com.mry.springboottools.filter.RequestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 过滤器配置类
 *
 */
@Configuration
public class FilterConfig {

    @Bean
    public RequestFilter requestFilter(){
        return new RequestFilter();
    }

    @Bean
    public FilterRegistrationBean<RequestFilter> registrationBean() {
        FilterRegistrationBean<RequestFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(requestFilter());
        registrationBean.addUrlPatterns("/filter/*");
        registrationBean.setName("RequestFilter");
        //过滤器的级别,值越小级别越高越先执行
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

拦截器

(1) 实现 org.springframework.web.servlet.HandlerInterceptor 接口,动态代理
(2) 拦截器应用场景, 性能分析, 权限检查, 日志记录
(3) 是一个Spring组件,并由Spring容器管理
(4) 不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中
(5) 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束

登录拦截
package com.mry.springboottools.interceptor;

import com.mry.springboottools.entity.User;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录拦截
 *
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        User user = (User)request.getSession().getAttribute("user");
        if (!ObjectUtils.isEmpty(user)) {
            return true;
        } else {
            // 不管是转发还是重定向,必须返回false。否则出现多次提交响应的错误
            redirect(request, response);
            return false;
        }
    }

    /*
     * 对于请求是ajax请求重定向问题的处理方法
     * @param request
     * @param response
     *
     */
    public void redirect(HttpServletRequest request, HttpServletResponse response) throws IOException {

        if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){// ajax
            //获取当前请求的路径
            response.setHeader("Access-Control-Expose-Headers", "REDIRECT,CONTENT_PATH");
            //告诉ajax我是重定向
            response.setHeader("REDIRECT", "REDIRECT");
            //告诉ajax我重定向的路径
            StringBuffer url = request.getRequestURL();
            String contextPath = request.getContextPath();
            response.setHeader("CONTENT_PATH", url.replace(url.indexOf(contextPath) + contextPath.length(), url.length(), "/").toString());
        }else{// http
            response.sendRedirect( "/page/login");
        }

        response.getWriter().write(403);
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    }

}

配置
package com.mry.springboottools.config;

import com.mry.springboottools.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * mvc 控制器配置
 * MyWebMvcConfigurer: Springboot2.x以后版本使用
 *
 */
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    /*
     * 拦截器依赖于Spring容器,此处拦截了所有,需要对静态资源进行放行
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。
        // registry.addInterceptor(new PageInterceptor()).addPathPatterns("/**").order()
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/page/login", "/user/login","/page/ajax","/static/**");
    }


    /*
     * 不要要写控制器即可完成页面跳转访问
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/page/ajax").setViewName("ajax");
    }


    /*
     * 自定义静态资源映射
        Spring Boot 默认为我们提供了静态资源映射:
                classpath:/META-INF/resources
                classpath:/resources
                classpath:/static
                classpath:/public
              优先级:META-INF/resources > resources > static > public
     * @param registry
     *
     */
//    @Override
//    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
//        registry.addResourceHandler("/static/**").addResourceLocations("file:E:/static/");
//    }

}

监听器

实现 javax.servlet.ServletRequestListener, javax.servlet.http.HttpSessionListener, javax.servlet.ServletContextListener 等等接口

主要用来监听对象的创建与销毁的发生, 比如 session 的创建销毁, request 的创建销毁, ServletContext 创建销毁。

注意

静态资源问题

SpringBoot2.x以后版本拦截器也会拦截静态资源,在配置拦截器时需要将静态资源放行。

/*
 * 拦截器依赖于Spring容器,此处拦截了所有,需要对静态资源进行放行
 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new PageInterceptor()).addPathPatterns("/**")
            .excludePathPatterns("/page/login", "/user/login","/page/ajax","/static/**");
}

SpringBoot2.x 自定义静态资源映射

spring:
  mvc:
    static-path-pattern: /static/**

项目默认目录

classpath:/META-INF/resources
classpath:/resources
classpath:/static
classpath:/public

优先级:META-INF/resources > resources > static > public

登录拦截ajax重定向

由于ajax是异步的,还在当前页面进行的局部请求。当拦截到登录请求时,即使重定向也无法生效。需采用服务端给地址由前端进行跳转。详细见登录拦截器代码。

// 前端处理
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>AJAX</title>
    <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
</head>
<body>
    <button>USER</button>
</body>
</html>

<script>
    $.ajaxSetup({
        complete:function(xhr,status){
            //拦截器实现超时跳转到登录页面
            let win = window;
            // 通过xhr取得响应头
            let REDIRECT = xhr.getResponseHeader("REDIRECT");
            //如果响应头中包含 REDIRECT 则说明是拦截器返回的需要重定向的请求
            if (REDIRECT === "REDIRECT")
            {
                while (win !== win.top)
                {
                    win = win.top;
                }
                win.location.href = xhr.getResponseHeader("CONTEXTPATH");
            }
        }
    });
    $("button").click(function(){
        $.get("/page/user", function(result){
            $("div").html(result);
        });
    });
</script>

验证

代码地址:
https://gitee.com/mry6/springboot-filter-interceptor.git

拦截器测试

启动项目访问首页
http://localhost:8686/page/index

由于没有登录,点击LOGIN按钮直接重定向到了登录页
在这里插入图片描述

输入用户名密码完成登录,调转到用户页

由于后端没有对用户名和密码的合法性进行校验,所以用户名和密码可以随便输入。
在这里插入图片描述
在这里插入图片描述

此时再访问首页
在这里插入图片描述

退出登录

成功退出后,访问为授权的页面也相对会被重定向到登录页
在这里插入图片描述

ajax未授权访问测试

在这里插入图片描述

点击访问user ,由于未登录,没有全权访问。在前端进行了页面跳转,转到了登录页。
在这里插入图片描述

过滤器测试

在这里插入图片描述

在这里插入图片描述
可以看到过滤器进行了相对应的处理,重写的getParameterValues()也生效了。配合使用HttpServletRequestWrapper & OncePerRequestFilter 实现了对request的修改。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值