SpringBoot之拦截器与过滤器解读
作者:LZHH_2008
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 { } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。