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;
    }
}
相关推荐
程序员爱钓鱼16 分钟前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__21 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
uzong6 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程7 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
一只爱撸猫的程序猿8 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋8 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端