过滤器监听器拦截器AOP

过滤器、监听器、拦截器、AOP的实现

一、过滤器 Filter

​ 在传统的Servlet容器中,可以使用过滤器和监听器,在Java框架中还可以使用拦截器。

​ 过滤器,这里指的是Servlet过滤器,它是在Java Servlet中定义的,能够对Servlet容器中的请求和响应对象进行检查和修改,只起到过滤作用,不会生成Request和Response对象。

​ 过滤器特点:(1)过滤器是基于回调函数实现;(2)过滤器是Servlet规范规定的,只能用于Web程序中;(3)过滤器只在Servlet启动前后起作用,作用范围较窄。

1.1 Java web中实现Filter

Web三大组件:Servlet、Filter、 Listener

什么是过滤器:当浏览器向服务器发送请求的时候,过滤器可以将请求拦截下来,完成一些特殊的功能(我们可以将这种操作理解成方法层面的增强)。

过滤器通常可以做什么操作:编码过滤、权限校验、日志记录等。

1.1.1、 创建一个java类实现Filter接口,重写接口中的方法

@WebFilter("/demo1") // 当前过滤器拦截/demo1的请求
//@WebFilter(value = "/user/demo2",dispatcherTypes = DispatcherType.FORWARD) // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}//真正执行过滤业务的方法@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {System.out.println("FilterDemo1 is running...");filterChain.doFilter(request,response);//过滤器要放行 只有放行之后,过滤器过滤的资源才会执行System.out.println("write log to database...");}@Overridepublic void destroy() {}
}
  • 使用过滤器需要注意的事项:
  1. 过滤器必须实现Filter接口。
  2. 过滤器拦截的请求执行完毕之后,必须要放行,否则我们的请求就不会被执行

filterChain.doFilter(request,response); //过滤器放行

  • 可以看到里面只有三个方法,它们的作用分别如下所示:
  1. init(),该方法在Servlet容器启动初始化过滤器时被调用,即它在Filter整个的生命周期中只会被调用一次。此方法必须调用成功,否则Filter无法初始化,那么后续将无法提供服务。
  2. doFilter(),Servlet容器中每一次请求都会调用该方法,FilterChain 用于调用下一个过滤器。
  3. destroy(),当Servlet容器销毁该Filter实例时被调用,通常在该方法中书写销毁或者关闭资源的逻辑。同样在Filter整个的生命周期中只会被调用一次。

1.1.2、我们可以使用@WebFilter来配置过滤器要拦截的资源,当然我们也可以通过xml的方式配置过滤器。

<!--配置过滤器2 -->
<filter><filter-name>demo2</filter-name><filter-class>com.qf.filter.FilterDemo2</filter-class>
</filter><filter-mapping><filter-name>demo2</filter-name><url-pattern>/user/demo1</url-pattern>
</filter-mapping><!--配置过滤器1 -->
<filter><filter-name>demo1</filter-name><filter-class>com.qf.filter.FilterDemo1</filter-class>
</filter><filter-mapping><filter-name>demo1</filter-name><url-pattern>/user/demo1</url-pattern>
</filter-mapping>

​ 在存在多个过滤器时,过滤器链中,在默认的情况下,过滤器会按照名称的自然顺序进行执行。我们在web.xml里面定义过滤器的配置,谁定义在上面谁就先执行。

  • Filter过滤器的生命周期
    • 当加载我们的web应用的时候,首先会初始化过滤器的实例对象,然后执行1次init方法。
    • 当我们发送请求的时候,会将doFilter方法执行。
    • 当我们重新部署项目或者停止web服务器的时候,会执行destroy方法。如果重新部署项目,也会初始化过滤器实例对象,还会执行init方法。

1.2 SpringBoot中实现Filter三种方式

  • 无路径无顺序方式实现过滤器

    无路径无顺序,即默认过滤所有的路径,且多个过滤器之间不存在执行顺序。这种方式最简单,只需自定义类并实现 Filter 接口,并在自定义类上使用 @Component 注解将其交由Spring管理。

  • 有路径无顺序方式实现过滤器

    即指定过滤的路径,但是多个过滤器之间不存在执行顺序。在自定义类上使用 @WebFilter 注解,通过 @WebFilter 注解来设置过滤器的匹配路径,同时还需要在启动类中添加@ServletComponentScan 注解,@ServletComponentScan 注解用于扫描添加 @WebFilter 、@WebServlet 、@WebListener 注解的Bean,并在使用的时候自动注入。

  • 有路径有顺序方式实现过滤器

    即指定过滤的路径,同时多个过滤器之间存在执行顺序。这种方式配置比较复杂,只能通过配置类来实现,且只针对实现过滤器的接口,它不需要通过注解来注入Spring容器,只通过配置类实现。

    //配置类示例:
    @Configuration
    public class IndexFilterConfig {@Beanpublic FilterRegistrationBean filterA(){FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();FilterA filterA = new FilterA();filterRegistrationBean.setFilter(filterA);filterRegistrationBean.addUrlPatterns("*");filterRegistrationBean.setName("filterA");filterRegistrationBean.setOrder(1);return filterRegistrationBean;}@Beanpublic FilterRegistrationBean filterB(){FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();FilterB filterB = new FilterB();filterRegistrationBean.setFilter(filterB);filterRegistrationBean.addUrlPatterns("*");filterRegistrationBean.setName("filterB");filterRegistrationBean.setOrder(2);return filterRegistrationBean;}
    }
    

在spring中是实现Filter同样是需要实现Filter接口并且重写三个方法:

  1. init 方法:在容器中创建当前过滤器的时候自动调用
  2. destory 方法:在容器中销毁当前过滤器的时候自动调用
  3. doFilter 方法:过滤的具体操作

1.2.1、实现Filter接口

过滤器1:

package com.example.spring_test_handler.filterConfig;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author: AD_Liu* @Description: 过滤器实现测试类* @ClassName: MyFilterTest* @author AD*/
@Slf4j
@Component
public class MyFilterTest implements Filter {/**开始时间*/private static  long startTime;/**结束时间*/private static  long endTime;/*** 声明一个本地线程对象(为了数据的安全,保存到该对象中的数据只能当前线程访问* */private static  ThreadLocal<Long> threadLocal = new ThreadLocal<>();/*** 过滤器的英文名称为 Filter, 是 Servlet 技术中最实用的技术。* 如同它的名字一样,过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请求,通常用作 Session 校验,判断用户权限,如果不符合设定条件,则会被拦截到特殊的地址或者基于特殊的响应。** 首先需要实现 Filter接口然后重写它的三个方法* init 方法:在容器中创建当前过滤器的时候自动调用* destory 方法:在容器中销毁当前过滤器的时候自动调用* doFilter 方法:过滤的具体操作* */@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Filter.init():初始化过滤器,在项目启动的时候会初始化对应的Filter过滤器到容器中!");Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {startTime = System.currentTimeMillis();threadLocal.set(startTime);HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse responseWrapper = (HttpServletResponse) servletResponse;String requestURI = request.getRequestURI();log.info("[Filter:doFilter] 请求地址 requestURI ="+requestURI);if (requestURI.contains("/add")|| requestURI.contains("/delete")|| requestURI.contains("/update")|| requestURI.contains("/testFilter")){responseWrapper.sendRedirect("/index.html");endTime = System.currentTimeMillis();log.info("【Filter111】response.sendRedirect:执行doFilter1 处理总耗时: "+ (endTime - threadLocal.get()) );}else {//Filter过滤器放行该请求filterChain.doFilter(servletRequest,servletResponse);}//try {//    //线程睡眠3秒//    Thread.sleep(3000);//} catch (InterruptedException e) {//    throw new RuntimeException(e);//}endTime = System.currentTimeMillis();log.info("【Filter111】执行doFilter1 处理总耗时: "+ (endTime - threadLocal.get()) );}@Overridepublic void destroy() {log.info("Filter.destroy():销毁过滤器,当Filter被移除或服务器正常关闭时,会销毁对应的 Filter 过滤器!");Filter.super.destroy();}
}

过滤器2:

package com.example.spring_test_handler.filterConfig;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import javax.servlet.*;
import java.io.IOException;/*** @author: AD_Liu* @Description: 测试拦截器2* @ClassName: TestFilter2*/@Slf4j
@Component
public class TestFilter2 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Filter2 执行初始化!");Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {long startTime = System.currentTimeMillis();filterChain.doFilter(servletRequest,servletResponse);long endTime = System.currentTimeMillis();log.info("【Filter222】 执行耗时: {} ms", endTime - startTime);}@Overridepublic void destroy() {log.info("Filter2 执行销毁!");Filter.super.destroy();}
}

1.2.2、配置类中注册过滤器Bean

​ 通过实现 WebMvcConfigurer接口,使用 @Bean 注解实现Filter过滤器的注册, 这一步可有可无。因为我们在spring中可以通过 @Component注解实现了Bean对象的注入。

配置类如下:

package com.example.spring_test_handler.config;import com.example.spring_test_handler.filterConfig.TestFilter2;
import com.example.spring_test_handler.interceptorConfig.MyHandlerInterceptorTest;
import com.example.spring_test_handler.listenerConfig.MyListenerTest1_sessionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.Filter;
import javax.servlet.FilterRegistration;/*** @author: AD_Liu* @Description: 初始化监听器Listener 配置类* @ClassName: WebListenerConfig*/@Configuration
public class WebAllHandlerConfig implements WebMvcConfigurer {/***@Description:注册过滤器,也可通过@Component注解+@Autowired注解注入,也可以通过 new 在通过@Bean进行注入到系统中去*@Param []*@return org.springframework.boot.web.servlet.FilterRegistrationBean*/@Beanpublic FilterRegistrationBean filterRegistrationBean(){FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new TestFilter2());filterRegistrationBean.addUrlPatterns("./*");return filterRegistrationBean;}}

二、监听器 Listener

​ 监听器,它也是Servlet层面的内容,可用于监听Web应用中某些对象或者信息的创建、修改和销毁等动作发生,并作出相应的响应处理。

  • 监听器大概分为以下几种:

1、ServletContextListener:用来监听 ServletContext (Web应用)对象的生命周期,接口主要有两个方法,一个在当Servlet容器启动web应用时调用(contextInitialized),另一个是在Servlet容器终止web应用时调用(contextDestroyed)。

2、HttpSessionListener:用来监听 Web 应用中的 Session (Session会话)对象生命周期。接口主要有两个方法,一个在Session会话创建时调用(sessionCreated),另一个在Session会话销毁时调用(sessionDestroyed)

3、ServletRequestListener:用来监听 ServletRequest 对象的生命周期。主要有两个方法,一个在ServletRequest对象初始化时调用(requestInitialized),另一个在ServletRequest对象销毁时调用(requestDestroyed)

  • 监听器的使用

我们通过 HttpSessionListener来统计当前在线人数、ip等信息,为了避免并发问题我们使用原子int来计数。
ServletContext,是一个全局的储存信息的空间,它的生命周期与Servlet容器也就是服务器保持一致,服务器关闭才销毁。
request,一个用户可有多个;
session,一个用户一个;
而servletContext,所有用户共用一个。
所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。因此我们这里用ServletContext来存储在线人数sessionCount最为合适。

2.1 JavaWeb中实现Listener

​ 简单演示介绍一个监听器:ServletContextListener。这个监听器负责监听ServletContext容器的创建和销毁。

2.1.1、创建 ServletListener监听器类,通过实现 ServletContenxListener接口实现接口的两个抽象方法(创建、销毁)。

@WebListener
public class MyListener implements ServletContextListener {//监听Servlet上下文对象创建的方法@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {System.out.println("contextInitialized is running");}//监听Servlet上下文对象销毁的方法@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {System.out.println("contextDestroyed is running");}
}

2.1.2、配置监听器,可以通过@WebListener来进行配置。也可以通过XML配置文件来配置

<!--配置监听器-->
<listener><listener-class>com.qf.listener.MyListener</listener-class>
</listener>

2.2 Spring中实现Listener

2.2.1、创建监 HttpSessionListener 监听器的实现类

package com.example.spring_test_handler.listenerConfig;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.concurrent.atomic.AtomicInteger;/*** 监听器通常用于监听 Web 应用程序中对象的创建、销毁等动作的发送,同时对监听的情况作出相应的处理,最常用于统计网站的在线人数、访问量等。* 监听器大概分为以下几种:*      1.ServletContextListener:用来监听 ServletContext 属性的操作,比如新增、修改、删除等。*      2.HttpSessionListener:用来监听 Web 应用中的 Session 对象,通常用于统计在线情况。*      3.ServletRequestListener:用来监听 Request 对象的属性操作。** 监听器的使用:*      我们通过 HttpSessionListener来统计当前在线人数、ip等信息,为了避免并发问题我们使用原子int来计数。* ServletContext,是一个全局的储存信息的空间,它的生命周期与Servlet容器也就是服务器保持一致,服务器关闭才销毁。* request,一个用户可有多个;* session,一个用户一个;* 而servletContext,所有用户共用一个。* 所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。因此我们这里用ServletContext来存储在线人数sessionCount最为合适。* *//*** @author: AD_Liu* @Description:监听器Listener的实现及功能___该测试类通过‘一个session一个用户的原则’利HttpSessionListener监听器的触发机制来实现统计在线人数!* @ClassName: MyListenerTest*/
@Slf4j
@Component
public class MyListenerTest1_sessionListener implements HttpSessionListener /*ServletContextListener*/{public  static AtomicInteger userCount = new AtomicInteger(0);@Overridepublic synchronized void sessionCreated(HttpSessionEvent se) {//以原子方式将当前值递增 1userCount.getAndIncrement();se.getSession().getServletContext().setAttribute("sessionCount",userCount.get());log.info("【HttpSessionListener】在线人数人数增加为:{}",userCount.get());//此处可以在ServletContext域对象中为访问量计数,然后传入过滤器的销毁方法//在销毁方法中调用数据库入库,因为过滤器生命周期与容器一致}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {//原子递减当前值 1userCount.getAndDecrement();se.getSession().getServletContext().setAttribute("sessionCount",userCount.get());log.info("【HttpSessionListener】在线人数人数减少为:{}",userCount.get());}
}

2.2.2、创建 ServletRequestListener 监听器

package com.example.spring_test_handler.listenerConfig;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;/*** @author: AD_Liu* @Description: ServletRequestListener Rquest监听器测试* @ClassName: MyServletRequestListener_test2*/
@Slf4j
@Component
public class MyServletRequestListener_test2 implements ServletRequestListener {/***@Description:ServletRequestListener监听器创建时执行*@Param [sre]*@return void*/@Overridepublic void requestInitialized(ServletRequestEvent sre) {HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();String requestURI = request.getRequestURI();log.info("【ServletRequestListener】Request监听器Listener被创建:被调用路径{}",requestURI);}/***@Description:ServletRequestListener监听器销毁时执行*@Param [sre]*@return void*/@Overridepublic void requestDestroyed(ServletRequestEvent sre) {log.info("【ServletRequestListener】Request监听器Listener被销毁:MyServletRequestListener_test2 requestDestroyedP");}
}

2.2.3、初始化监听器

​ 同样在初始化监听器的方式上,我们可以在监听器实现类上通过@Component注解来实现监听器Bean 的注入,也可以通过 WebMvcConfigurer配置类 来实现监听器Bean的注入。

package com.example.spring_test_handler.config;import com.example.spring_test_handler.filterConfig.TestFilter2;
import com.example.spring_test_handler.interceptorConfig.MyHandlerInterceptorTest;
import com.example.spring_test_handler.listenerConfig.MyListenerTest1_sessionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.Filter;
import javax.servlet.FilterRegistration;/*** @author: AD_Liu* @Description: 初始化监听器Listener 配置类* @ClassName: WebListenerConfig*/@Configuration
public class WebAllHandlerConfig implements WebMvcConfigurer {/***@Description:注册监听器————也可通过@Component注解+@Autowired注解注入,也可以通过 new 在通过@Bean进行注入到系统中去*@Param []*@return org.springframework.boot.web.servlet.ServletListenerRegistrationBean*/@Beanpublic ServletListenerRegistrationBean ListenerRegistrationBean(){ServletListenerRegistrationBean servletListener = new ServletListenerRegistrationBean();servletListener.setListener(new MyListenerTest1_sessionListener());return servletListener;}}

三、拦截器 Interceptor

​ 主要用于拦截用户请求并做相应的处理。拦截器和过滤器、监听器不同,它不依赖于Servlet容器,依赖于Spring框架,是AOP的一种体现,底层基于Java的动态代理实现。

​ 过滤器可以拦截请求,对请求进行相关处理,但是过滤器对请求的处理很有限,比如我们如果要在过滤器中进行数据验证,只能验证数据是否为空(不能获取到目标方法的各种信息),如果要实现数据的合法性验证就无法实现了。

​ 拦截器是基本上所有项目都会使用到技术,所谓的拦截器就是用户和控制器之间的一个屏障,这个屏障可以对用户的请求进行拦截,拦截之后可以进行数据的验证,保证到了控制器的数据都是合法的、安全的,当然还有其他很多应用场景,比如性能检测(可以统计出一个请求耗费的时间)等。

在这里插入图片描述

  • 拦截器的使用

我们需要实现 HandlerInterceptor 类,并且重写三个方法:

1、boolean preHandle (HttpServletRequest request,HttpServletResponse response,Object handler):在 Controoler 处理请求之前被调用,返回值是 boolean类型,如果是true就进行下一步操作;若返回false,则证明不符合拦截条件,在失败的时候不会包含任何响应,此时需要调用对应的response返回对应响应。

2、public void postHandle(HttpServletRequest reques, HttpServletResponse response, Object handler,ModelAndView modleAndView)throws Exceptio:在 Controoler
处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。

3、public void afterCompletion(HttpServletRequest request , HttpServletResponse response,Object handler ,Exception ex):在 DispatcherServlet 完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。

3.1 拦截器实现

3.1.1、创建拦截器实现类

package com.example.spring_test_handler.interceptorConfig;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Java中的拦截器是动态拦截 action 调用的对象,然后提供了可以在 action 执行前后增加一些操作,也可以在 action 执行前停止操作,功能与过滤器类似,但是标准和实现方式不同。*  1.登录认证:在一些应用中,可能会通过拦截器来验证用户的登录状态,如果没有登录或者登录失败,就会给用户一个友好的提示或者返回登录页面,当然大型项目中都不采用这种方式,都是调单点登录系统接口来验证用户。*  2.记录系统日志:我们在常见应用中,通常要记录用户的请求信息,比如请求ip,方法执行时间等,通过这些记录可以监控系统的状况,以便于对系统进行信息监控、信息统计、计算 PV、性能调优等。*  3.通用处理:在应用程序中可能存在所有方法都要返回的信息,这是可以利用拦截器来实现,省去每个方法冗余重复的代码实现。**拦截器的使用:我们需要实现 HandlerInterceptor 类,并且重写三个方法:*  1.preHandle:在 Controoler 处理请求之前被调用,返回值是 boolean类型,如果是true就进行下一步操作;若返回false,则证明不符合拦截条件,在失败的时候不会包含任何响应,此时需要调用对应的response返回对应响应。*  2.postHandler:在 Controoler 处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。*  3.afterCompletion:在 DispatcherServlet 完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。* *//*** @author: AD_Liu* @Description: 拦截器测试* @ClassName: MyHandlerInterceptorTest*/
@Slf4j
@Component
public class MyHandlerInterceptorTest implements HandlerInterceptor {private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();private static Long startTime;private static Long endTime;/***@Description:在Controller处理请求之前执行*@Param [request, response, handler]*@return boolean*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {startTime = System.currentTimeMillis();threadLocal.set(startTime);//获取目标方法对象及名称String methodName =null;if (handler != null && handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;methodName = handlerMethod.getMethod().getName();}log.info("【HandlerInterceptor:preHandle】==》 调用了路径:{} \t 目标方法:{}",request.getRequestURI(),methodName);return true;}/***@Description:Controller执行完目标方法,响应数据之前*@Param [request, response, handler, modelAndView]*@return void*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//获取目标方法中的 ModelAndView 试图对象if (modelAndView != null){modelAndView.addObject("msg","HandlerInterceptor拦截器追加数据!");}String methodName =null;if (handler != null && handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;methodName = handlerMethod.getMethod().getName();}log.info("【HandlerInterceptor:postHandle】==》 Controller执行完毕对应方法:{},即将响应视图层!",methodName);}/***@Description:渲染(响应)结束之后触发(请求完全执行完毕)*@Param [request, response, handler, ex]*@return void*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {endTime = System.currentTimeMillis();log.info("【HandlerInterceptor:afterCompletion】==> 请求执行完毕!总耗时:{}ms",endTime-threadLocal.get());}
}

3.1.2、拦截器初始化配置

package com.example.spring_test_handler.config;import com.example.spring_test_handler.filterConfig.TestFilter2;
import com.example.spring_test_handler.interceptorConfig.MyHandlerInterceptorTest;
import com.example.spring_test_handler.listenerConfig.MyListenerTest1_sessionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.Filter;
import javax.servlet.FilterRegistration;/*** @author: AD_Liu* @Description: 初始化监听器Listener 配置类* @ClassName: WebListenerConfig*/@Configuration
public class WebAllHandlerConfig implements WebMvcConfigurer {/** 注入拦截器HandlerInterceptor */@Autowiredprivate MyHandlerInterceptorTest handlerInterceptorTest;/***@Description:配置拦截器,拦截器属于springframework,需要通过配置类进行添加!!!!*  1.通过@Component注解无效,还需要通过InterceptorRegistry注册: addInterceptor(xx)添加拦截器Interceptor*  2.通过addPathPatterns(xxx)添加拦截路径*@Param [registry]*@return void*/@Override //重写该方法,通过 InterceptorRegistry 拦截器注册实例进行 addInterceptor(xx)添加拦截器Interceptorpublic void addInterceptors(InterceptorRegistry registry) {//配置拦截路径registry.addInterceptor(/*this.getInterceptor()*/ handlerInterceptorTest).addPathPatterns("/handler/testFilter","/**/**","/**");}/*** 通过@Bean注入 或者 通过@Component注解+@Autowired注解注入* */@Beanpublic MyHandlerInterceptorTest getInterceptor(){return new MyHandlerInterceptorTest();}
}

3.2 HandlerMethod类型介绍

preHandle 方法中的handler参数:HandlerMethod 类型介绍

1、public Object getBean():取得目标控制器对象

2、public Class<?> getBeanType() :取得目标控制器的Class类类型

3、public Method getMethod():取得目标控制器中要访问的方法,就是目方法

​ 3.1、 method.getAnnotation(xxxx.class) :获取注解信息

4、public MethodParameter[] getMethodParamenters():取得目标方法的参数列表信息,返回值是“MethodParameter”类型的数组,在该类型中要关注的方法如下:

​ 4.1、public String getParameterName():取得的是参数的名称

​ 4.2、public Class<?> getParameterType() :取得参数的类型的Class类对象

3.3 sign、token、ts认证

​ 服务端与前端对接的API接口,如果被第三方抓包并进行恶意篡改参数,可能会导致数据泄露和篡改数据,下面主要围绕token,签名,时间戳,三个部分来保证API接口的安全性。

3.3.1 签名Sign

​ 为了防止API接口中的数据被篡改,很多时候我们需要对API接口做签名。

​ 接口请求方将请求参数+ 时间戳+ 密钥拼接成一个字符串,然后通过md5等hash算法,生成一个前面sign。然后在请求参数或者请求头中,增加sign参数,传递给API接口。

​ API接口的网关服务,获取到该sign值,然后用相同的请求参数 + 时间戳 + 密钥拼接成一个字符串,用相同的m5算法生成另外一个sign,对比两个sign值是否相等。

​ 如果两个sign相等,则认为是有效请求,API接口的网关服务会将给请求转发给相应的业务系统。如果两个sign不相等,则API接口的网关服务会直接返回签名错误。

​ 为了安全性考虑,防止同一次请求被反复利用,增加了密钥没破解的可能性,我们必须要对每次请求都设置一个合理的过期时间,比如:15分钟。这样一次请求,在15分钟之内是有效的,超过15分钟,API接口的网关服务会返回超过有效期的异常提示。

3.3.2 请求身份合法性Token认证参数

​ 用户登录成功后,会获取一个ticket值,接下去任何接口的访问都需要这个参数。我们把它放置在redis内,有效期为10分钟,在ticket即将超时,无感知续命。延长使用时间,如果用户在一段时间内没进行任何操作,就需要重新登录系统。

​ 请求携带参数TokenSign,只有拥有合法的身份Token和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到SecretKey仅作本地加密使用,不参与网络传输),无法伪造合法的请求。

3.3.3 timestamp(ts)参数

​ 引入一个时间戳参数,保证接口仅在一分钟内有效,需要和客户端时间保持一致。

​ 在请求中增加 timestamp 参数要来表示请求时间戳,服务方端接收该请求后,根据当前时间生成一个接收时间戳,然后根据两个时间戳的差值进行请求判定,如果差值大于指定的阈值,则认为请求无效,否则请求通过。关于阈值的选定,可以根据接口的响应速度进行适当的调整,一般默认为 60 秒。

​ 缺点:通过上面的判定逻辑可以发现,在小于阈值的时间段内是可以进行重复请求的,该方案不能保证请求仅一次有效。

3.3.4 接口安全拦截器的实现

package com.example.spring_test_handler.interceptorConfig;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;/*** @author: AD_Liu* @Description: 每次请求都带有这三个参数,我们都需要进行验证,只有在三个参数都满足我们的要求,才允许数据返回或被操作。* 1.请求身份合法性:Token认证参数* 2.防止篡改:Sign / Signature 签名参数* 3.重放攻击:ts / Timestamp参数时间戳参数** @ClassName: LoginInterceptor*///@Component
public class LoginInterceptor implements HandlerInterceptor {//注入Redis,实现缓存操作@Autowiredprivate RedisTemplate redisTemplate;/*** 封装签名 Signature 时用到的密钥key* */private static final String secretKeyOfWxh = "dffkdgdfgdsss";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果不是映射到方法直接通过if (handler instanceof HandlerMethod){return true;}//方法注解级拦截器HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//判断接口是否需要验证,验证接口为标注了指定类型的自定义注解的Annotation annotation = method.getAnnotation(Annotation.class);if (annotation ==null){System.out.println("LoginInterceptor 拦截器执行了==> 无需鉴权直接通过");//还可以做细致化操作:通过获取注解中的参数数据进行判断该接口是否需要进行鉴权处理!return true;}JSONObject jsonObject = new JSONObject();/** 1.请求身份合法性:Token认证参数:在用户登录创建一个票根传给前端,后端将该票根存入 redis 中,下次需要验证的时候通过redis获取验证*  String ticket = UUID.randomUUID().toString();*  icket = ticket.replace("-","");*  redisTemplate.opsForValue().set(ticket,personEntity.getLoginName(),10L, TimeUnit.MINUTES);* */String ticket = request.getParameter("token/ticket");String sign = request.getParameter("sign/signature");String ts = request.getParameter("ts");if (StringUtils.isEmpty(ticket) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts)){jsonObject.put("message","args is isEmpty");response.getWriter().write(jsonObject.toJSONString());return false;}//如果redis存在ticket就认为是合法的请求if (redisTemplate.hasKey(ticket)){String values = (String) redisTemplate.opsForValue().get(ticket);//判断ticket是否即将过期,进行续命操作if (redisTemplate.opsForValue().getOperations().getExpire(ticket) != -2 && redisTemplate.opsForValue().getOperations().getExpire(ticket) < 20){redisTemplate.opsForValue().set(ticket,values,10L, TimeUnit.MINUTES);}//判断是否重复访问,存在重放攻击的时间窗口期if (getTimestamp() - Long.valueOf(ts) > 600){jsonObject.put("message","Overtime to connect to server");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());return false;}//验证签名if (checkSign(request,sign)){jsonObject.put("message","sign is invalid");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());return false;}return true;}else {jsonObject.put("message","ticket is invalid,Relogin.");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());}return false;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}/***@Description:引入一个时间戳参数,保证接口仅在一分钟内有效,需要和客户端时间保持一致。*@Param []*@return long*/public static long getTimestamp(){long timestampLong = System.currentTimeMillis();long timestampsStr = timestampLong / 1000;return timestampsStr;}/***@Description:验证权限的方法,在HandlerInterceptor拦截器中的proHandle()方法中进行鉴权、验签操作!*@Param [request, response]*@return java.lang.Boolean*/private Boolean checkSign(HttpServletRequest request,String sign){System.out.println("HandlerInterceptorTest2:variableOauth ==> 进行验签操作");Boolean flag = false;Enumeration<String> parameterNames = request.getParameterNames();HashMap<String, String> parameterMap = new HashMap<>();String old_Signature = null;while (parameterNames.hasMoreElements()) {String pName  = parameterNames.nextElement();if ("signature".equals(pName)){old_Signature = request.getParameter(pName);continue;}String pValue = request.getParameter(pName);parameterMap.put(pName,pValue);}if (old_Signature != null && old_Signature.equals( getSignature(parameterMap ,secretKeyOfWxh))){flag = true;}return flag;}/***@Description:TODO---通过该方法,生成相关参数的[signature]相关数据信息---设计到加密解密技术相关操作!!*@Param [parameterMap, secretKeyOfWxh]*@return java.lang.String*/private String getSignature(HashMap<String, String> parameterMap,String secretKeyOfWxh){return "根据参数、密钥等信息生成签名信息数据";}
}

四、切面编程AOP

​ AOP(Aspect Oriented Programming)称为面向切面编程,AOP是一种编程思想,并不是某种具体实现。一般在提到AOP实现时,会有过滤器和代理模式这两种,过滤器基于函数回调实现,而代理模式基于java反射机制。代理模式分为静态代理和动态代理,动态代理就是拦截器的简单实现。

​ 为了解决重复的代码,提出了一种开发思想:面向切面的编程,叫做AOP编程(AspectOrientedPropramming),就是将那些辅助的操作(打开连接、关闭连接、权限验证…)放到一个专门的类中实现,这个类可以横切需要操作数据的类的方法。简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

​ 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

在这里插入图片描述

4.1 相关技术术语

  • AOP的相关概念

    • 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
    • Aspect(切面):通常是一个类,里面可以定义切入点和通知
    • JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
    • Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)
    • Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
    • weave(织入):将切面应用到目标对象并导致代理对象创建的过程
    • introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
    • AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
    • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

  • Advice通知类型介绍

    • @Aspect,使用在类上,用于将普通Java类定义为切面类;
    • @Pointcut,用于定义一个切入点,可以是一个规则表达式,也可以是注解;
    • @Before,用于在切入点开始处切入内容;
    • @After,用于在切入点结尾处切入内容;
    • @AfterReturning,用于在切入点返回内容之后处理逻辑;
    • @Around,用于在切入点前后切入内容,并控制何时执行切入点的内容。可以理解是 @Before 和 @After注解的组合使用;
    • @AfterThrowing,用于当切入内容部分抛出异常之后的处理逻辑。(
    • @Order AOP切面的执行顺序。注意 @Before 数值越小越先执行,@After 和@AfterReturning 则是数值越大越先执行。
  • AOP使用场景

    Authentication 权限
    Caching 缓存
    Context passing 内容传递
    Error handling 错误处理
    Lazy loading 懒加载
    Debugging  调试
    logging, tracing, profiling and monitoring 记录跟踪 优化 校准
    Performance optimization 性能优化
    Persistence  持久化
    Resource pooling 资源池
    Synchronization 同步
    Transactions 事务

Spring中实现AOP操作

​ spring的aop本质就是动态代理设计,spring默认使用jdk动态代理,可以手工配置cglib的动态代理。

4.2 AOP实现操作日志记录

​ 通过自定义方法注解,实现接口操作日志的记录。在调用带有自定义注解的接口时,会触发Aop实现类接口,后续进调用接口的相关信息进行记录持久化到数据库。

4.2.1、自定义注解类

package cn.zhidasifang.common.annotation;import cn.zhidasifang.common.enums.BusinessType;
import lombok.extern.java.Log;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author: AD_Liu* @Description: 系统日志生成注解* @ClassName: SysLogAnnotation*/@Target(ElementType.METHOD) //注解范围在方法上
@Retention(RetentionPolicy.RUNTIME)  //注解运行时生效
public @interface SysLogAnnotation {String desc() default "接口操作描述!";BusinessType type() default BusinessType.OTHER;
}

4.2.2、切面类的封装

package cn.zhidasifang.common.utils;import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.zhidasifang.common.entity.LogInfo;
import cn.zhidasifang.common.annotation.SysLogAnnotation;
import cn.zhidasifang.common.enums.BusinessType;
import cn.zhidasifang.common.service.ISysLogService;
import lombok.extern.flogger.Flogger;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** @author: AD_Liu* @Description: 系统日志Aspect切面类* @ClassName: SysLogAspect*/
@Aspect  //切面类
@Component  //注入bean到Spring-ioc容器中
@Slf4j
public class SysLogAspect {/*** 注入日志操作Service层*/@AutowiredISysLogService sysLogService;/*** 注入HttpServletRequest,request对象已经内置到了Spring的ioc中*/@AutowiredHttpServletRequest httpServletRequest;/*** 本地线程*/private final ThreadLocal<Date> THREAD_LOCAL = new ThreadLocal<>();/*** @return void* @Description:--切点方法的配置信息* @Param []* @desc--之前的配置方式是@execution("类路径.类名.方法名"),现在方式@annotation("注解类路径"):实现使用该注解方法都会进行AOP操作*/@Pointcut("@annotation(cn.zhidasifang.common.annotation.SysLogAnnotation)")public void aspectPointcutMethod_SysLog() {}@Before(value = "aspectPointcutMethod_SysLog()")public void sysLogDoBefore(JoinPoint joinPoint) throws InterruptedException {THREAD_LOCAL.set(new Date());//Thread.sleep(5000);}@After(value = "aspectPointcutMethod_SysLog() && @annotation(sysLogAnnotation)", argNames = "joinPoint,sysLogAnnotation")//携带参数annotation:syslogAnnotationpublic void sysLogDoAfter(JoinPoint joinPoint, SysLogAnnotation sysLogAnnotation) throws ClassNotFoundException, NoSuchMethodException {//获取注解中的信息【可以忽略掉,能直接获取注解信息的话!】//Map<String, Object> annotationInfos = getAnnotationInfos(joinPoint,sysLogAnnotation);//创建日志实体 SysLogLogInfo logInfo = new LogInfo()//忽略使用mybatis-Plus 主键策略Assign.UUID来完成!//.setLid(RandomUtil.randomLong(0,1000000000000000000L))//.setUserName("AOP中获取系统管理员").setRequestUrl(httpServletRequest.getHeader("host"))//.setRequestType((String) annotationInfos.get("type")).setRequestType((String) sysLogAnnotation.type().toString())//.setDesc((String) annotationInfos.get("desc")).setDesc((String) sysLogAnnotation.desc()).setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(THREAD_LOCAL.get()));//持久化日志对象操作log.info("【AOP:后置操作,操作日志持久化操作】");sysLogService.addSysLog(logInfo);}/*** @return java.util.HashMap* @Description:获取方法上注解中的信息--【废弃】* @Param [joinPoint]*/public Map<String, Object> getAnnotationInfos(JoinPoint joinPoint, SysLogAnnotation sysLogAnnotation) throws ClassNotFoundException, NoSuchMethodException {HashMap<String, Object> annotationInfo = new HashMap<>();//获取方法所在类名String className = joinPoint.getTarget().getClass().getName();//获取方法名String methodName = joinPoint.getSignature().getName();System.out.println("sysLogAnnotation.type() = " + sysLogAnnotation.type());System.out.println("sysLogAnnotation.desc() = " + sysLogAnnotation.desc());/*//获取方法所有参数Object[] args = joinPoint.getArgs();//获取该方法的类中的所有方法Method[] methods = Class.forName(className).getMethods();*//*** todo--带参数方法,通过反射获取时,需要添加参数相关的信息,否则获取为null,比如方法的重载。 获取带参方法失败!!!* todo--废弃该方法,直接将注解相关信息通过参数的方式传入到切面类中来,不在通过反射的方式在单独的去获取对应的方法,在通过方法实例在获取注解信息!*Object[] args = joinPoint.getArgs();System.out.println("args.toString() = " + args.toString());Method method = null;if (ObjectUtil.isNull(args)){method = Class.forName(className).getMethod(methodName);}else {method= Class.forName(className).getMethod(methodName,args.getClass());}*///通过反射的机制来获取方法中的注解信息Method method = Class.forName(className).getMethod(methodName);String desc = method.getAnnotation(SysLogAnnotation.class).desc();String type = method.getAnnotation(SysLogAnnotation.class).type().toString();annotationInfo.put("desc", desc);annotationInfo.put("type", type);return annotationInfo;}
}

五、过滤器和拦截器的区别

​ 其实过滤器和拦截器都是AOP(面向切面编程)的具体实现,都可以实现诸如登录鉴权、日志记录等功能,但是也存在一些不同点。

5.1 实现原理不同

(1)过滤器(Filter)基于函数回调。
其中实现接口中的 doFilter( ServletRequest , ServletResponse , FilterChain ) 方法,其中的FilterChain类对象就是一个回调接口,该类中的doFilter方法就是回调方法。

(2)拦截器(Interceptor)基于Java反射机制(动态代理)实现。

在这里插入图片描述

5.2 使用范围不同

(1)自定义的过滤器需要实现javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,或者说过滤器需要依赖于Servlet容器,因此它只能在Web程序中使用。

(2)自定义的拦截器需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,它存在于SpringMVC中,由Spring容器来管理,不依赖于Servlet容器,是可以单独使用的,因此不仅可以用在Web程序中,也可以用在Application、Swing等程序中。

5.3 触发时机不同

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

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

在这里插入图片描述

5.4 拦截的请求范围不同

​ 过滤器的init和destory方法只会调用一次,且分别在容器初始化和销毁时调用。而拦截器中的三个方法和过滤器中的doFilter方法则会在每次请求的时候都会调用。

拦截器可以在方法执行前调用(preHandle),方法执行后调用(postHandle),视图页面渲染后调用(afterCompletion)。

​ 实际上过滤器会对几乎所有进入容器的请求都起作用,但是拦截器只会对Controller中的请求或者访问static目录下的静态资源起作用(目标执行方法起作用)。

在这里插入图片描述

5.5 注入Bean的时机不同

​ 如果需要在分别在过滤器和拦截器中都注入Service层。

​ 在Interceptor的配置类中(实现 **WebMvcConfigurer ** 接口)的类中的 addInterceptors( ) 中的代码,在调用 addInterceptor 方法的时候如果手动new了一个 Interceptor 自定义拦截器对象。

@Configuration
public class WebAllHandlerConfig implements WebMvcConfigurer {/** 注入拦截器HandlerInterceptor */@Autowiredprivate MyHandlerInterceptorTest handlerInterceptorTest;/***@Description:配置拦截器,拦截器属于springframework,需要通过配置类进行添加!!!!*  1.通过@Component注解无效,还需要通过InterceptorRegistry注册: addInterceptor(xx)添加拦截器Interceptor*  2.通过addPathPatterns(xxx)添加拦截路径*@Param [registry]*@return void*/@Override //重写该方法,通过 InterceptorRegistry 拦截器注册实例进行 addInterceptor(xx)添加拦截器Interceptorpublic void addInterceptors(InterceptorRegistry registry) {//配置拦截路径registry.addInterceptor(/*this.getInterceptor()*/ handlerInterceptorTest).addPathPatterns("/handler/testFilter","/**/**","/**");}
}

​ 如果在 Interceptor 自定义拦截器类上又添加了 @Component注解,表示让Spring容器来管理并生成 Interceptor对象。

@Slf4j
@Component
public class MyHandlerInterceptorTest implements HandlerInterceptor {.....
}

​ 很明显这样注册的拦截器和Spring容器中存在的不是同一个,所以才会导致Spring无法管理进而出现空指针。解决办法就是去掉 Interceptor 自定义拦截器类上的 @Component 注解,同时在 InterceptorConfig 类中手动定义一个 Interceptor 对象,并将该拦截器注册一下。

5.6 执行流程不同

​ 实际开发过程中经常出现同时存在多个拦截器或者过滤器,通过不同的方式来控制它们执行的先后顺序。

(1)过滤器控制执行顺序

​ 可以在类上使用 @Order 注解来控制多个过滤器的执行顺序,其中 @Order 注解中的数字越小,那么优先级越高,越先执行。

(2)过滤器执行流程

​ 客户端发起的请求首先是经过了过滤器并处理了Request请求,之后去访问Servlet资源,当Servlet执行完毕后,Response响应也会经过过滤器,即也是经过了过滤器处理。

在这里插入图片描述

(3)拦截器控制执行顺序

​ 注册顺序(在实现 **WebMvcConfigurer ** 接口的配置类中的 addInterceptors() 方法)就是默认的执行顺序,当然也可以通过Order方法来进行控制, Order 方法中的数字越小,那么优先级越高,越先执行。

​ 对于多个拦截器来说,先声明的拦截器的 preHandle 方法先执行,但是它的postHandle 方法后执行,也就是说 postHandle 方法的执行顺序和 preHandle 方法的执行顺序是相反的。

(4)拦截器执行流程

程序先执行 preHandle() 方法,如果该方法返回值为true,那么程序将继续往下执行处理器中的方法,否则认为当前请求结束,不再往下执行;

​ 在Controller类(业务处理器)处理完请求后会执行 postHandle() 方法,之后通过DispatcherServlet 向客户端返回响应;

​ DispatcherServlet 处理完请求后执行 afterCompletion() 方法。

在这里插入图片描述

结束!

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

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

相关文章

EasyExcelFactory 导入导出功能的实战使用

EasyExcelFactory 导入导出功能的实战使用分享&#xff1a; 1、jar包引入 <!-- 阿里巴巴Excel处理--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.6</version></dependen…

1、Seaborn可视化库

你的数据可视化编程初体验! Seaborn是一个基于matplotlib的图形可视化Python库,它提供了一种高级的API接口,使得制作统计图形更加容易。 Seaborn的目标是使可视化成为探索和理解数据的核心部分,它面向数据集的绘图功能对整个数据集进行操作,并在内部执行必要的语义映射和统…

Python - 【Socket】消息粘包处理demo(一)

一. 前言 在网络编程中&#xff0c;粘包是指TCP协议在传输过程中&#xff0c;多条数据被合并成一条数据发送或者一条数据被拆分成多个数据发送的现象。 二. 粘包问题的常规处理方法&#xff1a; 使用固定长度的包头 可以在发送数据前先发送一个固定长度的包头&#xff0c;包…

个人云服务器docker搭建部署前后端应用-myos

var code "87c5235c-b551-45bb-a5e4-9593cb104663" mysql、redis、nginx、java应用、前端应用部署 本文以单台云服务器为例&#xff1a; 1. 使用腾讯云服务器 阿里或其他云服务器皆可&#xff0c;类似 安装系统&#xff0c;现在服务器系统都集成安装了docker镜像&a…

[ACM学习]自上而下树形dp

问题引入 设置dp状态&#xff0c;相比于更容易出错的贪心更...不易出错。 状态设计 如果选择父结点&#xff0c;就会使孩子结点不能被选择&#xff0c;我们会多开一维的dp&#xff0c;用来标记该点是否被标记过。 以1点举例&#xff0c;f[1][0]为不选它的状态&#xff0c;那么…

FOR XML PATH 函数与同一分组下的字符串拼接

FOR XML PATH 简单介绍 FOR XML PATH 语句是将查询结果集以XML形式展现&#xff0c;通常情况下最常见的用法就是将多行的结果&#xff0c;拼接展示在同一行。 首先新建一张测试表并插入数据&#xff1a; CREATE TABLE #Test (Name varchar(70),Hobby varchar(70) );insert #T…

芯驰E3340软件编译以及更新步骤

打开已有工程File->Open Solution: 东南项目&#xff1a;e3340\boards\e3_324_ref_display\proj\jetour-t1n-fl3\sf\SES 编译&#xff1a;build->build sf 增加头文件和宏定义&#xff1a; 编译完成sf后&#xff0c;进行编译bootloader 东南项目&#xff1a;e3340\boa…

IaC基础设施即代码:Terraform 创建ACK集群 与部署应用

目录 一、实验 1.环境 2.Terraform 创建网络资源 3. 阿里云给RAM添加权限 4.Terraform 创建 ACK集群 5.在ACK集群中部署应用 6.销毁资源 二、问题 1.Terraform 验证失败 2.Terraform申请资源失败 一、实验 1.环境 &#xff08;1&#xff09;主机 表1-1 主机 主机系…

火山引擎ByteHouse:“专用向量数据库”与“数据库+向量扩展”,怎么选?

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 背景 随着LLM&#xff08;Large Language Model&#xff09;的不断发展&#xff0c;向量检索也逐渐成为关注的焦点。LLM通过处理大量的文本数据&#xff0c;获取丰…

第1章-计算机网络基础

目录 1. 计算机网络与计算机 2. 计算机网络的定义和基本功能 2.1. 定义&#xff1a;计算机网络是一组自治计算机互连的集合 2.2. 基本功能 2.3. 计算机网络的演进 2.4. 广域网(Wide Area Network&#xff0c;WAN) 2.5. 网络的拓扑结构 2.6. 数据交换方式 2.7. 衡量计算…

图灵日记之java奇妙历险记--异常包装类泛型

目录 异常概念与体系结构异常的分类异常的处理防御式编程异常的抛出异常的捕获异常声明throwstry-catch捕获并处理 自定义异常类 包装类基本数据类型及其对应包装类装箱和拆箱 泛型泛型使用类型推导 裸类型说明 泛型的编译机制泛型的上界语法 异常概念与体系结构 在java中,将程…

VisualODX——ODX数据自动转换工具 加快开发进度

在创建ODX数据库的过程中&#xff0c;我们需要录入大量的数据以及应对多种数据格式。这不仅费时费力&#xff0c;而且还需很高的人力成本&#xff0c;且其错误率也非常高&#xff0c;从而导致开发速度缓慢、效率低下。基于多年的汽车行业诊断经验&#xff0c;我们开发了VisualO…

org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException

异常信息&#xff1a; org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout 开发环境描述&#xff1a; rocketMq 版本是 4.9.0&#xff0c;部署在服务器主机上&#xff0c; Broker 部署为单节点&#xff08;单机&#xff…

jdk的安装和Tomcat的安装

jdk的安装 双击jdk&#xff0c;然后一路下一步 公共JRE可以关闭&#xff0c;没多大用&#xff0c;反而会占用内存 计算机–>属性–>高级系统设置–>环境变量 系统变量–新建 JAVA_HOMEjdk的存放路径 修改path 在path的最后面添加&#xff08;&#xff1b;%JAVA_H…

Python算法题集_移动零

本文为Python算法题集之一的代码示例 题目283&#xff1a;移动零 说明&#xff1a;给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序 注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作 本文给出四…

ChatGPT与生成式AI在教育领域的应用前景光明,但也伴随着挑战

随着ChatGPT和其他生成式AI技术&#xff0c;如GPT-3.5、GPT-4的出现&#xff0c;我们正见证教育领域一场前所未有的变革浪潮。这些技术不仅推动了教育方式的进步&#xff0c;也为学习者带来了全新的机遇和挑战。 NO.1 教育变革的新浪潮 生成式AI技术&#xff0c;特别是ChatGPT…

力扣每日一题 --- 972. 相等的有理数

本题中的一个难点是怎么判断是否相等&#xff0c;如果自己写判断的话是不是很麻烦&#xff0c;判断整数之后再去判断小数部分&#xff0c;那么我们这题的另一个难点就要登场了&#xff0c;第一个难点让本题的情况变得复杂&#xff0c;第二个难点让本题变得很难想到怎么判断&…

如何测试你的 Golang 代码

文章目录 简单概述最易想到的方法一个快速体验案例学会使用 go testing测试的编写规则灵活记忆 API 的使用 实践一个案例简洁紧凑的表组测试详细的日志输出灵活控制运行哪些测试总结参考 不论是开源项目&#xff0c;还是日常程序的开发&#xff0c;测试都是必不可少的一个环节。…

STM32-GPIO输出(HAL库)

STM32-GPIO 介绍 什么是GPIO&#xff1f; GPIO&#xff08;通用输入/输出&#xff09;是一种用于与外部设备进行数字通信的通用硬件接口。它允许微控制器或其他数字电路的引脚以灵活的方式配置为输入或输出&#xff0c;并在运行时进行动态控制。GPIO可用于连接和控制各种外围…

C# CefSharp 根据输入日期段自动选择日期

1&#xff0c;前言 搞这个Demo整整搞几天通宵&#xff0c;爆肝了。后做的效果出来&#xff0c;还是不错的。给小伙伴看看效果图。 2, 遇到的问题 日期之间相差多少个月数。开始时间框点击对应月份要点击多少次&#xff0c;结束时间框点击对应月份要点击多少次Xpath获取问题。…