spring boot 之 拦截器、过滤器

拦截器、过滤器区别

出身不同

拦截器实现的是HandlerInterceptor接口,拦截器是属于Spring技术,它是Spring的一个组件,并由Spring容器创建管理,并不依赖Tomcat服务器,是可以单独使用的,拦截器不仅能应用在web程序中,也可以用于ApplicationSwing等程序中;

过滤器实现是javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat服务器,Filter是在Tomcat服务器创建的对象,导致它只能在web程序中使用。

使用场景不同

拦截器侧重于验证请求、截断请求,对用户请求做预先的判断处理、可以修改ModelAndView对象中的数据和视图,影响控制器方法最终的执行结果的,拦截器主要应用于登陆校验、日志记录、权限验证、性能监控等功能:

过滤器侧重于对请求参数进行过滤的, 比如敏感词过滤、字符集编码设置CharacterEncodingFilter、修改请求头和响应头的信息;

触发时机不同

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

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

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

实现不同

拦截器是基于动态代理(底层是反射)实现的。

过滤器是基于方法回调实现的,当我们要执行下一个过滤器或下一个流程时,需要调用FilterChain对象的doFilter方法进行回调执行。

部分应用场景示例

拦截器:

复制代码
1、登录验证,判断用户是否登录;
2、权限验证,判断用户是否有权限访问资源,如校验token;
3、日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量;
4、处理 cookie、本地化、国际化、主题等;
5、性能监控,监控请求处理时长等;
6、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现;

过滤器

复制代码
1、过滤敏感词汇(防止sql注入);
2、设置字符编码;
3、URL级别的权限访问控制;
4、压缩响应信息;

拦截器的使用

新建拦截器

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class ServletInterceptor implements HandlerInterceptor {

    /**
     * 在请求处理之前进行调用(Controller方法调用之前)
     * 预处理回调方法,实现处理器的预处理
     * 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token
        String token = request.getHeader("token");
        //如果请求头中没有token,抛出异常
        if (StrUtil.isBlankIfStr(token)) {
            // 返回false 或者 自定义封装的异常
            throw new MySelfException("请求头缺少token");
        }
        return true;
    }

    /**
     * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
     * 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前
     * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
     * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,
     * 如性能监控中我们可以在此记录结束时间并输出消耗时间,
     * 还可以进行一些资源清理,类似于try-catch-finally中的finally,
     * 但仅调用处理器执行链中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

配置拦截器

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private ServletInterceptor servletInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(servletInterceptor)
                // 配置拦截的路径
                .addPathPatterns("/**")
                // 配置不需拦截的路径
                .excludePathPatterns("/admin/**")
                // 配置拦截器顺序,数字越大执行越靠后
                .order(1);

    }
}

过滤器的使用

新建过滤器

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;

@Slf4j
public class ServletStreamFilter implements Filter {

    @Override //可不重写
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        log.info("ServletStreamFilter 过滤器成功初始化");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        String contentType = req.getContentType();
        String method = "multipart/form-data";
        if (contentType != null && contentType.contains(method)) {
            // 将转化后的 request 放入过滤链中
            request = new StandardServletMultipartResolver().resolveMultipart(request);
        }
        // 若后续不在使用request中的东西,直接放行即可
        // chain.doFilter(req, resp);
        
        // 若后续还会用到request中的东西,使用该方法操作
        // 扩展request,使其能够能够重复读取requestBody
        ServletRequest requestWrapper = new RequestWrapper(request);
        // 这里需要放行,但是要注意放行的 request是requestWrapper
        chain.doFilter(requestWrapper, resp);
    }

    @Override //可不重写
    public void destroy() {
        Filter.super.destroy();
        log.info("ServletStreamFilter 过滤器被销毁");
    }

    // 内部类
    static class RequestWrapper extends HttpServletRequestWrapper {
        /**
         * 存储body数据的容器
         */
        private final byte[] body;

        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            // 将body数据存储起来
            String bodyStr = getBodyString(request);
            body = bodyStr.getBytes(Charset.defaultCharset());
        }

        /**
         * 获取请求Body
         *
         * @param request request
         * @return String
         */
        public String getBodyString(final ServletRequest request) throws IOException {
            return inputStream2String(request.getInputStream());
        }

        /**
         * 获取请求Body
         *
         * @return String
         */
        public String getBodyString() throws IOException {
            final InputStream inputStream = new ByteArrayInputStream(body);
            return inputStream2String(inputStream);
        }

        /**
         * 将inputStream里的数据读取出来并转换成字符串
         *
         * @param inputStream inputStream
         * @return String
         */
        private String inputStream2String(InputStream inputStream) throws IOException {
            StringBuilder sb = new StringBuilder();
            BufferedReader reader = null;
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
                @Override
                public int read() throws IOException {
                    return inputStream.read();
                }

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                }
            };
        }
    }

}

配置过滤器

java 复制代码
import com.company.filter.ServletStreamFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyFilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        // 创建一个注册过滤器对象
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new ServletStreamFilter());
        // 设置过滤拦截匹配规则,/*是匹配所有
        // registrationBean.addUrlPatterns("/*");
        // 只拦截test下面的接口
        registrationBean.addUrlPatterns("/test/*");
        // 给过滤器起名字
        registrationBean.setName("servletStreamFilter-");
        // 存在多个过滤器时,设置执行顺序,值越大,执行顺序越靠后
        registrationBean.setOrder(2);
        // 返回这个注册过滤器对象
        return registrationBean;
    }
}
相关推荐
用户6757049885029 分钟前
告别数据库瓶颈!用这个技巧让你的程序跑得飞快!
后端
鳄鱼杆21 分钟前
服务器 | Centos 9 系统中,如何部署SpringBoot后端项目?
服务器·spring boot·centos
千|寻27 分钟前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
程序员岳焱40 分钟前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
龚思凯1 小时前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响1 小时前
枚举在实际开发中的使用小Tips
后端
wuhunyu1 小时前
基于 langchain4j 的简易 RAG
后端
techzhi1 小时前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
酷爱码1 小时前
Spring Boot 整合 Apache Flink 的详细过程
spring boot·flink·apache
cacyiol_Z2 小时前
在SpringBoot中使用AWS SDK实现邮箱验证码服务
java·spring boot·spring