Подробное объяснение перехватчика Spring Boot (Interceptor)

Spring Boot

Введение в перехватчикФункция перехватчикаПользовательский перехватчикЗапустите программу и проверьте эффектприменениеМониторинг производительностиОбнаружение входаиспользованная литература

Введение в перехватчик

Перехватчик такой жеКак и Filter, они оба являются аспектно-ориентированным программированием — конкретной реализацией АОП (аспектное программирование АОП — это просто идея программирования).

Вы можете использовать Interceptor для выполнения определенных задач, например, вControllerПишите журналы, добавляйте или обновляйте конфигурацию перед обработкой запросов...

существуетВесна, когда запрос отправляется наControllerбудучиControllerПеред обработкой он должен пройтиInterceptors(0 и более).

Spring Interceptorочень похожеServlet FilterКонцепция чего-либо .

Функция перехватчика

  1. Ведение журнала: запись журнала запроса информации для мониторинга информации, статистики информации, расчета PV (Page View) и т.д.;
  2. Проверка разрешений: например, обнаружение входа в систему, введите процессор, чтобы определить, следует ли войти в систему;
  3. Мониторинг производительности: перехватчик записывает время начала перед входом в процессор и записывает время окончания после обработки, чтобы получить время обработки запроса. (Обратные прокси, такие как Apache, также могут вести автоматический журнал)
  4. Общее поведение: прочитать cookie, чтобы получить информацию о пользователе и поместить объект пользователя в запрос, чтобы облегчить использование последующих процессов, а также извлечь информацию о локали, теме и т. д., если это требуется для нескольких процессоров. можно реализовать с помощью перехватчиков.

Пользовательский перехватчик

если вам нужно настроитьInterceptorдолжны быть реализованыorg.springframework.web.servlet.HandlerInterceptorинтерфейс или наследованиеorg.springframework.web.servlet.handler.HandlerInterceptorAdapterкласс, и необходимо переопределить следующие 3 метода:

  1. preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)Метод вызывается перед обработкой запроса. Этот метод выполняется первым в классе Interceptor и используется для выполнения некоторых операций предварительной инициализации или предварительной обработки текущего запроса.Он также может выносить некоторые суждения, чтобы определить, должен ли запрос продолжаться. Возврат этого метода имеет тип Boolean, когда он возвращает false, это означает, что запрос завершен, и последующие Interceptors и Controller не будут выполняться, когда он возвращает true, он продолжит вызывать метод preHandle следующего Interceptor , если он последний. При вызове Interceptor будет вызываться метод Controller текущего запроса.
  2. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)Метод выполняется после завершения обработки текущего запроса, то есть после вызова метода Controller, но он будет вызван до того, как DispatcherServlet вернет и отобразит представление, поэтому мы можем оперировать объектом ModelAndView, обрабатываемым контроллером в этом метод.
  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)Метод необходимо выполнить, когда метод preHandle соответствующего класса Interceptor возвращает значение true. Как следует из названия, этот метод будет выполнен после завершения всего запроса, то есть после того, как DispatcherServlet отобразит соответствующее представление. Этот метод в основном используется для очистки ресурсов.

Затем изучите фактический код.

Класс ЛогИнтерцептор:

public class LogInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println("\n-------- LogInterception.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Start Time: " + System.currentTimeMillis());

        request.setAttribute("startTime", startTime);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("\n-------- LogInterception.postHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("\n-------- LogInterception.afterCompletion --- ");

        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("End Time: " + endTime);

        System.out.println("Time Taken: " + (endTime - startTime));
    }
}

Класс OldLoginInterceptor:

public class OldLoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");

        response.sendRedirect(request.getContextPath()+ "/admin/login");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("\n-------- OldLoginInterceptor.afterCompletion --- ");
    }
}

Настройте перехватчик:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());

        registry.addInterceptor(new OldLoginInterceptor()).addPathPatterns("/admin/oldLogin");

        registry.addInterceptor(new AdminInterceptor()).addPathPatterns("/admin/*").excludePathPatterns("/admin/oldLogin");
    }
}

LogInterceptorПерехватчик используется для перехвата всех запросов;OldLoginInterceptorиспользуется для блокировки ссылок"/admin/старый логин", он будет перенаправлен на новый"/админ/логин".;AdminInterceptorиспользуется для блокировки ссылок"/админ/*", в дополнение к ссылке"/admin/старый логин".

Пользовательский перехватчик проверки контроллера

@Controller
public class LoginController {

    @RequestMapping("/index")
    public String index(Model model){
        return "index";
    }

    @RequestMapping(value = "/admin/login")
    public String login(Model model){
        return "login";
    }
}

также зависит отшаблон листа тимьянаСоздайте две страницы.

index.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8" />
    <title>Spring Boot Mvc Interceptor example</title>
</head>

<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
    <a th:href="@{/}">Home</a>
    &nbsp;&nbsp; | &nbsp;&nbsp;
    <a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>

<h3>Spring Boot Mvc Interceptor</h3>

<span style="color:blue;">Testing LogInterceptor</span>
<br/><br/>

See Log in Console..

</body>
</html>

login.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Spring Boot Mvc Interceptor example</title>
</head>
<body>

<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
    <a th:href="@{/}">Home</a>
    &nbsp;&nbsp; | &nbsp;&nbsp;
    <a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>

<h3>This is Login Page</h3>

<span style="color:blue">Testing OldLoginInterceptor &amp; AdminInterceptor</span>
<br/><br/>
See more info in the Console.

</body>

</html>

Запустите программу и проверьте эффект

Все готово для запуска проекта. Открытый URL: http://localhost:8080/index

Графически показан процесс выполнения запроса в фоновом режиме:

Если вы нажмете в это время/admin/oldLogin (OLD URL)Или введите в строке URL: http://localhost:8080/admin/oldLogin

Консоль выводит результат:

-------- LogInterception.preHandle --- 
Request URL: http://localhost:8080/admin/oldLogin
Start Time: 1576329730709

-------- OldLoginInterceptor.preHandle --- 
Request URL: http://localhost:8080/admin/oldLogin
Sorry! This URL is no longer used, Redirect to /admin/login

-------- LogInterception.afterCompletion --- 
Request URL: http://localhost:8080/admin/oldLogin
End Time: 1576329730709
Time Taken: 0

-------- LogInterception.preHandle --- 
Request URL: http://localhost:8080/admin/login
Start Time: 1576329730716

-------- AdminInterceptor.preHandle --- 

-------- AdminInterceptor.postHandle --- 

-------- LogInterception.postHandle --- 
Request URL: http://localhost:8080/admin/login

-------- AdminInterceptor.afterCompletion --- 

-------- LogInterception.afterCompletion --- 
Request URL: http://localhost:8080/admin/login
End Time: 1576329730718
Time Taken: 2

Мы также используем графическую форму для анализа:

применение

мониторинг производительности

Например, запишите время обработки запроса и получите несколько медленных запросов (например, время обработки превышает 500 миллисекунд), чтобы повысить производительность.Общий обратный прокси-сервер, такой как apache, имеет эту функцию, но здесь мы демонстрируем как пользоваться перехватчиком.

Анализ реализации:

1. Записать время старта перед входом в процессор, то есть записать время старта в preHandle перехватчика;

2. Запишите время окончания после окончания обработки запроса, то есть зафиксируйте конец реализации в записи afterCompletion перехватчика, и используйте время окончания-время начала, чтобы получить время обработки этого запроса.

проблема:

Наш перехватчик является синглтоном, поэтому независимо от того, сколько раз пользователь запрашивает, реализуется только один перехватчик, то есть поток небезопасен, так как же нам записывать время?

Решение состоит в использовании ThreadLocal, которая является связанной с потоком переменной и предоставляет локальные переменные потока (один ThreadLocal для одного потока, ThreadLocal потока A может видеть только ThreadLocal потока A, но не ThreadLocal потока B).

Код:

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");
    private Logger logger = LoggerFactory.getLogger(StopWatchHandlerInterceptor.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 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、消耗的时间
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
            //TODO 记录到日志文件
            logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
        //测试的时候由于请求时间未超过500,所以启用该代码
//        logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));

    }
}

NamedThreadLocal: именованная реализация ThreadLocal, предоставляемая Spring.

При тестировании нужно поставить stopWatchHandlerInterceptor первым в цепочке перехватчиков, чтобы полученное время было более точным.

Класс конфигурации перехватчика

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new StopWatchHandlerInterceptor());

        registry.addInterceptor(new OldLoginInterceptor()).addPathPatterns("/admin/oldLogin");

    }
}

В соответствии с приведенными выше шагами результат печати консоли будет следующим:

2019-12-14 21:51:43.881  INFO 4616 --- [nio-8080-exec-3] c.e.i.StopWatchHandlerInterceptor        : /index consume 14 millis

-------- OldLoginInterceptor.preHandle --- 
Request URL: http://localhost:8080/admin/oldLogin
Sorry! This URL is no longer used, Redirect to /admin/login
2019-12-14 21:51:54.055  INFO 4616 --- [nio-8080-exec-5] c.e.i.StopWatchHandlerInterceptor        : /admin/oldLogin consume 1 millis
2019-12-14 21:51:54.070  INFO 4616 --- [nio-8080-exec-6] c.e.i.StopWatchHandlerInterceptor        : /admin/login consume 9 millis

Обнаружение входа

При доступе к определенным ресурсам (например, страницам заказов) пользователям необходимо войти в систему, чтобы просмотреть их, поэтому требуется обнаружение входа в систему.

Процесс:

1. При доступе к ресурсам, требующим входа в систему, перехватчик перенаправляет на страницу входа;

2. При доступе к странице входа перехватчик не должен перехватывать;

3. После того, как пользователь успешно войдет в систему, добавьте идентификатор успешного входа (например, номер пользователя) в файл cookie/сеанс;

4. При следующем запросе перехватчик решает, продолжать ли процесс или перейти на страницу входа, определяя, есть ли у cookie/сеанса этот идентификатор;

5. Перехватчик также должен разрешать посетителям доступ к ресурсам.

Код перехватчика выглядит так:

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean flag = true;
        String ip = request.getRemoteAddr();
        long startTime = System.currentTimeMillis();
        request.setAttribute("requestStartTime", startTime);
        if (handler instanceof ResourceHttpRequestHandler) {
            System.out.println("preHandle这是一个静态资源方法!");
        } else if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            System.out.println("用户:" + ip + ",访问目标:" + method.getDeclaringClass().getName() + "." + method.getName());
        }

        //如果用户未登录
        User user = (User) request.getSession().getAttribute("user");
        if (null == user) {
            //重定向到登录页面
            response.sendRedirect("toLogin");
            flag = false;
        }
        return flag;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (handler instanceof ResourceHttpRequestHandler) {
            System.out.println("postHandle这是一个静态资源方法!");
        } else if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            long startTime = (long) request.getAttribute("requestStartTime");
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - startTime;

            int time = 1000;
            //打印方法执行时间
            if (executeTime > time) {
                System.out.println("[" + method.getDeclaringClass().getName() + "." + method.getName() + "] 执行耗时 : "
                        + executeTime + "ms");
            } else {
                System.out.println("[" + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "] 执行耗时 : "
                        + executeTime + "ms");
            }
        }
    }

}

использованная литература

https://snailclimb.gitee.io/springboot-guide/#/./docs/basis/springboot-interceptor

https://www.cnblogs.com/junzi2099/p/8260137.html#_label3_0