SpringBoot (fifteen) interceptor

interceptor

1. Introduction to interceptors

Interceptor (Interceptor) is the same as Filter filter, both of them are aspect-oriented programming - the specific implementation of AOP (AOP aspect programming is just a programming idea).

You can use Interceptors to perform certain tasks, such as writing logs, adding or updating configuration, before the Controller processes the request...

In Spring, when a request is sent to the Controller, it must go through Interceptors (0 or more) before being processed by the Controller.

Spring Interceptor is a concept very similar to Servlet Filter.

The interceptor in Java is an object that dynamically intercepts action calls, and then provides some operations that can be added before and after the execution of the action, and can also stop the operation before the execution of the action. In fact, the interceptor can also do the same operation as the filter,

The following are common scenarios for interceptors.

  1. Login authentication: In some simple applications, an interceptor may be used to verify the user's login status. If there is no login or the login fails, the user will be given a friendly prompt or returned to the login page.

  2. Record system logs: In web applications, it is usually necessary to record user request information, such as requested IP, method execution time, etc. Through these records, the status of the system can be monitored, so as to facilitate information monitoring, information statistics, and calculation of PV ( PageView) and performance tuning, etc.

  3. General processing: There may be information that all methods must return in the application. At this time, interceptors can be used to save the redundant and repeated code implementation of each method.

  4. Log record: record the log of request information for information monitoring, information statistics, calculation of PV (Page View), etc.;

  5. Permission check: such as login detection, enter the processor to detect whether to log in;

  6. Performance monitoring: The interceptor records the start time before entering the processor, and records the end time after processing, so as to obtain the processing time of the request. (Reverse proxies such as Apache can also log automatically)

  7. General behavior: read the cookie to get user information and put the user object into the request, so as to facilitate the use of subsequent processes, as well as extract Locale, Theme information, etc., as long as it is required by multiple processors, it can be implemented with an interceptor.

2. Interceptor life cycle

Here, using the Spring interceptor as an example, you need to implement the HandlerInterceptor class on the class , and rewrite the three methods in the class , which are:

preHandle: Executed before the controller method request , called before the business processor processes the request, and the method is called before the request is processed. This method is executed first in the Interceptor class , and is used to perform some pre-initialization operations or preprocess the current request, and also make some judgments to determine whether the request should continue. The return of this method is of Boolean type. When it returns false, it means that the request is over, and the subsequent Interceptor and Controller will not be executed; when it returns true, it will continue to call the preHandle method of the next Interceptor, if it is the last When an Interceptor is called, the Controller method of the current request will be called.

postHandle: Executed after the execution of the controller method request is completed , after the execution of the business processor processing the request is completed, and before the view is generated. The method is executed after the current request is processed, that is, after the Controller method is called, but it will be called before the DispatcherServlet renders the view back, so we can operate on the ModelAndView object processed by the Controller in this method.

afterCompletion: After processing the view and model data, it is executed after the rendering of the view is completed . It is called after the DispatcherServlet has completely processed the request. It is usually used to record the time consumed, and can also perform some resource processing operations. The method will only be executed when the return value of the postHandler method of the corresponding Interceptor class is true. As the name implies, this method will be executed after the entire request ends, that is, after the DispatcherServlet renders the corresponding view. This method is mainly used for resource cleanup.

3. Custom interceptor case - performance monitoring

There are two ways to customize interceptors:

Implement the org.springframework.web.servlet.HandlerInterceptor interface and inherit the org.springframework.web.servlet.handler.HandlerInterceptorAdapter class. For example, record the processing time of the request and get some slow requests (such as the processing time exceeds 500 milliseconds), so as to improve performance. General reverse proxy servers such as apache have this function, but here we demonstrate how to use interceptors.

3.1 Implementation analysis:

1. Record the start time before entering the processor, that is, record the start time in the preHandle of the interceptor;

2. Record the end time after the end of request processing, that is, record the end implementation in the afterCompletion of the interceptor, and use the end time-start time to get the processing time of this request. 3. When testing, you need to put stopWatchHandlerInterceptor at the first of the interceptor chain, so that the time obtained is more accurate.

3.2 Questions:

Our interceptor is a singleton, so no matter how many times the user requests, there is only one interceptor implementation, that is, the thread is not safe, so how should we record the time?

For example, when A sends a request, the browser starts timing at 1:31. There are many methods of operation in this controller method, and the execution of the method will be slower. Then, the time B sends the request has reached 1:32 and starts timing. His controller method has relatively few operations, and it will be executed quickly. It takes 1 minute, so the start time of A has been covered by 32 minutes, so the time after A is executed is wrong.

3.3 Solutions

The solution is to use ThreadLocal, which is a thread-bound variable that provides thread local variables (a thread has one ThreadLocal, the ThreadLocal of A thread can only see the ThreadLocal of A thread, but not the ThreadLocal of B thread), and a request creates a Objects don't affect other requests, so timing doesn't get messed up

3.4 Interceptor code implementation:

Implement the HandlerInterceptor interface

/**
 * 配置拦截器
 */
​
public class MyInterceptor implements HandlerInterceptor {
    //NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。线程安全的,每进来一个请求都会创建一个单独的对象,其他用户不会收到影响
    //ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个 ThreadLocal,A线程的ThreadLocal 只能看到A线程的 ThreadLocal,不能看到B线程的 ThreadLocal)
    //一个请求创建一个对象不会影响到其他请求,所以时间不会混乱
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");
    //日志对象,
    private Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
​
    //控制器方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
        return true;//继续流程
    }
​
    //控制器方法执行以后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
​
    //处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long endTime = System.currentTimeMillis();//2、结束时间
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
        long consumeTime = endTime - beginTime;//3、消耗的时间
​
        System.out.println("开始时间:"+beginTime);
        System.out.println("结束时间:"+endTime);
        System.out.println("消耗时间:"+consumeTime);
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
            //TODO 记录到日志文件 设置日志
            logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        } else {
            // 测试的时候由于请求时间未超过500,所以启用该代码
            //request.getRequestURI():请求路径:/test/filter
            //consumeTime:销毁时间
            // 日志打印:/test/filter consume 13 millis
            logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
​
    }
}

3.5 Interceptor configuration class code implementation

Implement WebMvcConfigurer

/**
 * 拦截器配置
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(拦截器类).addPathPatterns("路径路径");
       registry.addInterceptor(new MyInterceptor()).addPathPatterns("/test/*");
    }
}
@RestController
@RequestMapping("/test")
public class FilterController {
​
    @RequestMapping(value = "filter")
    public String filter1(){
        return "成功";
    }
​
}

Guess you like

Origin blog.csdn.net/m0_65992672/article/details/130450661