SpringBoot之拦截器与过滤器解读

news2024/11/24 20:09:11

目录

一、SpringBoot 拦截器 过滤器

二、实现HandleInterceptor接口

三、继承HandleInterceptorAdapter类

四、实现RequestInterceptor接口 

 五、粉丝福利


一、SpringBoot 拦截器 过滤器

1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。

2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。

3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射

4、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。

5、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。

6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

过滤器和拦截器非常相似,但是它们有很大的区别最简单明了的区别就是**过滤器可以修改request,而拦截器不能过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境拦截器可以调用IOC容器中的各种依赖,而过滤器不能过滤器只能在请求的前后使用,而拦截器可以详细到每个方法**区别很多,大家可以去查下

总的来说过滤器就是筛选出你要的东西,比如requeset中你要的那部分拦截器在做安全方面用的比较多,比如 权限验证

下面是拦截器的例子:

拦截器定义:

二、实现HandleInterceptor接口

自定义拦截器类实现HandleInterceptor接口,并使用@Component注解标注为一个组件。

@Component注解 是为了 注入spring其他组件方便, 如果没有这个注解,自动注入为空

@Autowired   

UserService userService;

public class MySelfInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在业务处理器处理请求之前被调用");
       //可以进行权限校验,安全控制
        MyRequestWrapper requestWrapper = new MyRequestWrapper (request);
        // 读取请求内容
        BufferedReader br = requestWrapper.getReader();
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        // 将json字符串转换为json对象
        JSONObject body = JSONObject.parseObject(sb.toString());
        //业务处理
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在业务处理器处理请求执行完成后,生成视图之前执行");
        //可以对返回来的ModelAndView进行处理,这个时候还未渲染视图
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在DispatcherServlet完全处理完请求后被调用");
        //请求已经完成,页面已经渲染,数据已经返回。这个时候可以做一些资源清理,或者记录请求调用时间,做性能监控
    }
}

三、继承HandleInterceptorAdapter类

自定义拦截器类继承HandleInterceptor接口的实现类HandleInterceptorAdapter来定义,并使用@Component注解标注为一个组件。

可以根据需要覆盖一些方法

@Component
public class MyInterceptor extends HandlerInterceptorAdapter {
    public SingleLoginInterceptor() {
        super();
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return super.preHandle(request, response, handler);
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
    }
}

可以看到HandlerInterceptorAdapter类底层是实现了HandlerInterceptor接口,多了两个方法,要比实现HandlerInterceptor接口的方式功能强大。

这两个方法都是HandlerInterceptorAdapter类实现的org.springframework.web.servlet.AsyncHandlerInterceptor接口提供的,而AsyncHandlerInterceptor接口又继承了HandlerInterceptor接口,所以HandlerInterceptorAdapter底层是实现类HandlerInterceptor接口。

自定义拦截器类实现WebRequestInterceptor接口,并使用@Component注解标注为一个组件。

@Component
public class MyInterceptor implements WebRequestInterceptor {
    @Override
    public void preHandle(WebRequest webRequest) throws Exception {
    }
    @Override
    public void postHandle(WebRequest webRequest, ModelMap modelMap) throws Exception {
    }
    @Override
    public void afterCompletion(WebRequest webRequest, Exception e) throws Exception {
    }
}

两个实现接口方式的异同点 相同点 都可以实现controller层的拦截请求 不同点

  • 1.WebRequestInterceptor的入参WebRequest是包装了HttpServletRequest 和HttpServletResponse的,通过WebRequest获取Request中的信息更简便。
  • 2.WebRequestInterceptor的preHandle是没有返回值的,说明该方法中的逻辑并不影响后续的方法执行,所以这个接口实现就是为了获取Request中的信息,或者预设一些参数供后续流程使用。
  • 3.HandlerInterceptor的功能更强大也更基础,可以在preHandle方法中就直接拒绝请求进入controller方法。

四、实现RequestInterceptor接口 

此方式为微服务Feign调用的自定义拦截器,实现各个微服务之间的参数传递。

@Configuration
public class CenterinsRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
    }
}

拦截器的注册

创建一个自定义类,继承WebMvcConfigurerAdapter类重写addInterceptors方法。

@Configuration
public class WebCofiguration extends WebMvcConfigurerAdapter {
     @Bean
	MyInterceptor getMyInterceptor (){
		return new MyInterceptor ();
	}
    public void addInterceptors(InterceptorRegistry registry) {
            // 将自己定义的拦截器注入进来进行拦截操作
        //registry.addInterceptor(new MySelfInterceptor ()) // 如果是new 出来的对象 会导致 拦截器中自动装配为空
       registry.addInterceptor(getMyInterceptor ())
                .addPathPatterns("/**")
                .excludePathPatterns("/logout");
                //过滤器可以添加多个,这里的addPathPatterns的/**是对所有的请求都做拦截。
                //excludePathPatterns代表排除url的拦截路径,即不拦截
    }
}

此类在SpringBoot2.0以后已经废除,但仍可使用。

推荐使用以下两种方式来代替此方式。

1. 创建一个自定义类继承WebMvcConfigurationSupport类,实现addInterceptors。

@Configuration
public class MyInterceptorConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

此方式会导致默认的静态资源被拦截,这就需要我们手动将静态资源放开。

除了重写方法外还需要重写addResourceHandlers方法来释放静态资源

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    super.addResourceHandlers(registry);
}

此方式:一个容器内只能有一个WebMvcConfigurationSupport的实现类,也就是说不能有多个继承类,否则只有一个生效,会造成未知的错误,如果想在已有实现类的基础上(基础jar包中存在webConfig)还想继续添加拦截器,可以选择继承WebConfig,但是要super.addInterceptors,避免丢失注册

原因:在WebMvcAutoConfiguration 中 WebMvcConfigurationSupport 是  @ConditionalOnMissingBean 原来SpringBoot做了这个限制,只有当WebMvcConfigurationSupport类不存在的时候才会生效WebMvc自动化配置

2. 实现WebMvcConfigurer接口

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 实现WebMvcConfigurer不会导致静态资源被拦截
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
}

 另外可以写个配置注解,根据注解拦截需要的方法

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogData {
}

在controller中需要拦截的方法上加上 @LogData

@LogData
public ResponseMessage getUserList(@RequestParam Long id) {
        return ResponseMessage.ok();
    }

可以在拦截器中

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
        log.info("进入到拦截器中:preHandle() 方法");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        LogData loginVerify = handlerMethod.getMethodAnnotation(LogData .class);
        if (loginVerify == null) {
            log.info("不需要对该路径 进行拦截");
            return true;
        }else {
            log.info("对该路径 进行拦截");
            log.info("业务操作...");
            return true;
        }
    }

拦截器中request请求被读取一次后,controller获取为空

继承HandleInterceptorAdapter类  和 实现HandleInterceptor接口  实现WebRequestInterceptor接口  自定义类实现RequestInterceptor接口

HttpServletRequest的输入流只能读取一次的原因当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承与InputStream。

InputStream的read()方法内部有一个position,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()返回-1,表示已经读取完了,如果想要重新读取,则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所有就能重头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset(),并且markSupported()默认也是返回false

我们可以把流读取出来后用容器存起来,后面就可以多次利用了。JavaEE提供了一个HttpServletRequestWrapper类,它是一个http请求包装器,基于装饰者模式实现类HttpServletRequest界面。

继承HttpServletRequestWrapper,将请求体中的流copy一份,可以重写getinputStream()和getReader()方法,或自定义方法供外部使用

 
import dm.jdbc.e.e;
import dm.jdbc.util.StreamUtil;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
  * 重写 HttpServletRequestWrapper 
  * 
  */
@Slf4j
public class MyRequestWrapper extends HttpServletRequestWrapper {
	private byte[]  body; //用于保存读取body中数据
    public MyRequestWrapper (HttpServletRequest request) throws IOException {    
        super(request);
        //读取请求的数据保存到本类当中
        //body = StreamUtil.readBytes(request.getReader(), "UTF-8");
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader bufferedReader = null;
		InputStream inputStream = null;
		try{
			inputStream = request.getInputStream();
			if(inputStream != null){
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
				char[] charBuffer = new char[128];
				int bytesRead =-1;
				while((bytesRead = bufferedReader.read(charBuffer)) >0){
					stringBuilder.append(charBuffer,0, bytesRead);
				}
			}else{
				stringBuilder.append("");
			}
		}catch (Exception e){
		}finally {
			if(inputStream != null){
				inputStream.close();
			}
			if(bufferedReader != null){
				bufferedReader.close();
			}
		}
		body = stringBuilder.toString().getBytes();
    }
    //覆盖(重写)父类的方法
    @SuppressFBWarnings("DM_DEFAULT_ENCODING")
	@Override
    public BufferedReader getReader() throws IOException {    
        return new BufferedReader(new InputStreamReader(getInputStream()));    
    }    
    //覆盖(重写)父类的方法
    @Override    
    public ServletInputStream getInputStream() throws IOException {    
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {    
            @Override    
            public int read() throws IOException {    
                return bais.read();    
            }
			@Override
			public boolean isFinished() {
				// TODO Auto-generated method stub
				return false;
			}
			@Override
			public boolean isReady() {
				// TODO Auto-generated method stub
				return false;
			}
			@Override
			public void setReadListener(ReadListener arg0) {
				// TODO Auto-generated method stub
			} 
        };    
    }
    /**
     * 获取body中的数据
     * @return
     */
	public byte[] getBody() {
		return body;
	}
	/**
	 * 把处理后的参数放到body里面
	 * @param body
	 */
	public void setBody(byte[] body) {
		this.body = body;
	}
}

定义过滤器

import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
 * 过滤器
 * 
 */
@Slf4j
@WebFilter(urlPatterns = "/*", filterName = "logSignDataFilter")
public class LogSignDataFilter implements Filter {
	@Override
	public void destroy() {
	}
	@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		ServletRequest requestWrapper = null;
		if(request instanceof HttpServletRequest){
			requestWrapper = new MyRequestWrapper ((HttpServletRequest) request);
		}
		if(requestWrapper == null){
			filterChain.doFilter(request, response);
		}else{
			filterChain.doFilter(requestWrapper, response);
		}
    }
	@Override
	public void init(FilterConfig config) throws ServletException {
	}
}

 五、粉丝福利

最近很多同学问我有没有java学习资料,我根据我从小白到架构师多年的学习经验整理出来了一份50W字面试解析文档、简历模板、学习路线图、java必看学习书籍 、 需要的小伙伴 可以关注我
公众号:“ 灰灰聊架构 ”, 回复暗号:“ 321 ”即可获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1393948.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python系列28:fastapi部署应用

1. 介绍与安装 FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,类似flask,Django,webpy 在部署时可能需要用到下面的库: Uvicorn 或者 Hypercorn负责ASGI 服务器。 Starlette 负责 web 部分…

C语言从入门到实战——动态内存管理

动态内存管理 前言一、 为什么要有动态内存分配二、 malloc和free2.1 malloc2.2 free 三、calloc和realloc3.1 calloc3.2 realloc 四、常见的动态内存的错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释放一块动态开…

自动化测试总结

1.什么是自动化测试 以程序测试程序,以代码代替思维,以脚本的运行代替手工测试。自动化的测试涵盖了:功能(黑盒)自动化测试,功能(白盒)自动化测试,性能测试,…

Mac版VsCode快捷键大全

1 对应关系 标志 键名 ⌘ command ⇧ shift ↩ 回车 ↑ 上 ↓ 下 ⌃ control ⌥ option 高亮标记的是常用的快捷键。 2 编辑 按键 功能 ⇧ ⌥ 鼠标左键 ( Left) 按住鼠标左键下拉可以批量将鼠标键放在指定位置 ⇧ ⌥ ↑ 向上复制整行或者整段 ⇧ ⌥ ↓ 向下复制整行或…

掌上单片机实验室 – 低分辨率编码器测速方式完善(24)

一、背景 本以为“掌上单片机实验室”这一主题已告一段落,可最近在测试一批新做的“轮式驱动单元”时,发现原来的测速算法存在问题。 起因是:由于轮式驱动单元的连线较长,PCB体积也小,导致脉冲信号有干扰,加…

组件v-model(.sync)记录使用(vue3)

示例(演示地址) 以下是Vue3中使用v-model实现组件的双向数据绑定的示例代码: 首先,让我们来了解一下Vue3中v-model的用法。在Vue3中,v-model 指令可以用于自定义组件上,用于实现组件的双向数据绑定。与Vue2…

一键式Excel分词统计工具:如何轻松打包Python脚本为EXE

一键式Excel分词统计工具:如何轻松打包Python脚本为EXE 写在最前面需求分析直接用Python打包为什么大?为什么要使用conda环境? 将Python脚本打包为一个独立的应用程序1. 编写Python脚本:初步功能实现2. 初步图形用户界面&#xff…

基于Python flask的猫眼电影票房数据分析可视化系统,可以定制可视化

技术方案 猫眼电影票房数据分析可视化系统是基于Python Flask框架开发的一款用于分析和展示猫眼电影票房数据的Web应用程序。该系统利用Flask提供了一个简单而强大的后端框架,结合Request库进行网络爬虫获取猫眼电影票房数据,并使用Pyecharts进行可视化…

【AI的未来 - AI Agent系列】【MetaGPT】4.1 细说我在ActionNode实战中踩的那些坑

文章目录 1. MetaGPT 0.5.2 版本的坑1.1 坑一:cannot import name "ActionNode" from "metagpt.actions.action"1.2 坑二:simple_fill 没有参数 schema1.3 坑三:ActionNode一直在循环执行, 2. 升级成 MetaGP…

高精度算法笔记·····························

目录 加法 减法 乘法 除法 高精度加法的步骤&#xff1a; 1.高精度数字利用字符串读入 2.把字符串翻转存入两个整型数组A、B 3.从低位到高位&#xff0c;逐位求和&#xff0c;进位&#xff0c;存余 4.把数组C从高位到低位依次输出 1.2为准备 vector<int> A, B, …

模拟实现简单的shell

目录 1.实现交互界面 2.子串分割的问题&#xff0c;解决命令行 3.指令的判断 1.实现交互界面 我们模仿打印出来就好了&#xff1a; 现在已经有初步的形状了。 2.子串分割的问题&#xff0c;解决命令行 3.指令的判断 看上面有一行内建命令中的export&#xff0c;其实不对的&a…

C++初入(四)

1.万能头文件 #include <bits/stdc.h> 里面包含了大量我们日常所需的头文件&#xff0c;如果使用它&#xff0c;我们就可以减少大量时间去写头文件&#xff0c;但是其实在平常练习和实际运用中&#xff0c;该头文件几乎没有实际价值&#xff0c;原因&#xff1a;1.里面…

真实体验|微信新推出的工作流表现如何?

以小程序公众号消息推送为例&#xff0c;全面真实体验微信工作流新能力。详解到底能不能用&#xff1f;谁适合用&#xff1f; 上周微信云开发上线了「工作流」能力。支持通过拖拉拽的方式搭建流程&#xff0c;支持调用、时间、消息通知、支付等多种驱动场景的业务计算&#xf…

开源图床LightPicture搭建本地图片管理系统并实现无公网IP远程访问

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

工具 | WSL | WSL动不动就卡死

工具 | WSL | WSL动不动就卡死 PS C: Users flyer> ws1 由于连接方在一段时间后没有正确答复或连接的主机没有反应&#xff0c;连接尝试失败 Error code: Ws1/Service/0x8007274c wsl动不动就卡住 Error code: Wsl/Service/0x8007274c

文心一言 VS 讯飞星火 VS chatgpt (181)-- 算法导论13.4 4题

四、用go语言&#xff0c;在 RB-DELETE-FIXUP 代码的哪些行中&#xff0c;可能会检查或修改哨兵 T.nil &#xff1f; 文心一言&#xff1a; RB-DELETE-FIXUP 是红黑树中的一个操作&#xff0c;用于在删除一个节点后进行必要的调整以保持红黑树的性质。在这个过程中&#xff0…

Alinx ZYNQ 7020 LED调试--in RAM

设置拨码开关为JTAG方式 烧写LED bit stream a. 点击“Program device”烧录程序到FPGA中&#xff08;重新上电程序就丢失了&#xff09; b. /01_led/led.runs/impl_1/led.bit 程序烧录到Flash中 ZYNQ与以往的直接烧录Flash不同&#xff0c;首先必须PS&#xff0c;然后烧…

新定义51单片机(RD8G37)实现测距测速仪

本文描述用新定义51单片机&#xff08;RD8G37&#xff09;超声波一体测距传感器实现简单的测距测速仪。 测距仪演示效果 新定义RD8G37Q48RJ开发板 超声波测距模块&#xff1a; 8位并口屏 1、main.c unsigned short timeConsuming0; unsigned int oldDistance;void rectClearS…

Linux基础命令(超详细)

1、Linux命令入门 命令: 告诉我们要做什么事 选项: 规定做事的方式参数: 规定对谁做这件事 ls命令 # ls 展示目录中的文件信息 ls # -a 展示所有文件内容,包括隐藏文件(以点开头的文件内容) # -l 以列表形式详细展示文件内容 # -h 以合适的单位展示文件大小, 配合-l进行使用 #…

Google推广之关键字匹配类型

做过线上推广的小伙伴们应该都知道&#xff0c;关键字有肯定和否定形式&#xff0c;今天我们主要跟大家分享肯定式关键字的四种匹配类型。不同匹配面向的客户群体不尽相同&#xff0c;比如&#xff0c;我们可以使用“广泛匹配”类型&#xff0c;向广泛的受众群体展示广告&#…