Як Spring Boot налаштовує та виконує перехоплювачі

pic

Джерело зображення

Spring Boot робить роботу з веб-додатками більш зручною, а HandlerInterceptor є технічною особливістю, яку часто недооцінюють. У цій статті пояснюється, як Spring Boot керує налаштуваннями перехоплювачів, охоплюючи їх реєстрацію, порядок та використання для обробки HTTP запитів і відповідей.

Перехоплювачі в Spring Boot

Що таке HandlerInterceptor?

HandlerInterceptor — це механізм у Spring Boot, який дозволяє розробникам вставляти власну логіку в життєвий цикл запиту на певних етапах. Він діє як компонент, схожий на фільтр, і розташовується між DispatcherServlet та контролером, перехоплюючи запити, які проходять через додаток. Перехоплювачі дозволяють виконувати такі дії, як модифікація запитів до того, як вони потраплять до контролера, або виконання задач після обробки запиту контролером.

Перехоплювачі відрізняються від фільтрів сервлетів тим, що вони прив’язані до фреймворку обробки запитів Spring і мають доступ до специфічних деталей обробки запитів. Це робить їх більш підходящими для завдань, що вимагають взаємодії з внутрішнім конвеєром обробки запитів Spring.

Типові сценарії, де корисні перехоплювачі, включають:

  • Перевірка запиту: Перевірка, чи має вхідний запит правильні параметри або заголовки перед його обробкою.
  • Аутентифікація та авторизація: Перевірка, чи відповідає запит вимогам безпеки, наприклад, чи має він дійсні токени або облікові дані користувача.
  • Моніторинг продуктивності: Вимірювання часу, який потрібно для обробки запиту, шляхом вимірювання часу до та після обробки.
  • Користувацькі заголовки або метадані: Додавання інформації до відповідей, наприклад, користувацьких заголовків для налагодження або відстеження.

Як працюють перехоплювачі

Коли запит надходить до додатка на базі Spring Boot, DispatcherServlet відповідає за його маршрутизацію до відповідного контролера. Перехоплювачі викликаються під час цього процесу маршрутизації, що дозволяє вам втручатись на трьох етапах:

  • Перед виконанням контролера (preHandle)
    Метод preHandle() виконується перед тим, як запит досягне контролера. Це дає можливість змінювати запит або зупиняти подальшу обробку, повернувши false. Зверніть увагу, що повернення false запобігає подальшій обробці перехоплювачів і контролера, але все ж викликає метод afterCompletion().

Приклад:

@Override  
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {  
 String apiKey = request.getHeader("X-API-KEY");  
 if (apiKey == null || !isValidApiKey(apiKey)) {  
 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  
 return false; // Зупинити подальшу обробку запиту  
 }  
 return true; // Продовжити обробку  
}

Цей етап зазвичай використовується для таких завдань, як перевірка запиту або налаштування даних для контексту.

  • Після виконання контролера, але до рендерингу представлення (postHandle)
    Метод postHandle() виконується після того, як контролер обробить запит і поверне відповідь, але до того, як буде виконано рендеринг представлення. Це підходящий етап для модифікації об'єкта ModelAndView або додавання даних до відповіді.

Приклад:

@Override  
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {  
 if (modelAndView != null) {  
 modelAndView.addObject("trackingId", UUID.randomUUID().toString());  
 }  
}

Цей етап дозволяє вносити корективи на основі результату логіки контролера.

  • Після завершення запиту (afterCompletion)
    Метод afterCompletion() викликається після завершення всього запиту, включаючи рендеринг представлення.
    Цей етап зазвичай використовується для задач з очищення, таких як закриття ресурсів або логування остаточних деталей про життєвий цикл запиту.

Приклад:

@Override  
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {  
 if (ex != null) {  
 System.err.println("Запит призвів до винятку: " + ex.getMessage());  
 }  
}

На відміну від preHandle() і postHandle(), цей етап гарантовано виконується незалежно від того, чи були винятки під час обробки запиту.

Переваги перехоплювачів перед фільтрами

Хоча як перехоплювачі, так і фільтри сервлетів можна використовувати для керування життєвим циклом запиту, перехоплювачі дають більш точний контроль, працюючи в межах потоку обробки запитів Spring. Наприклад:

  • Доступ до інформації про обробник: Перехоплювачі можуть отримати доступ до об'єкта handler, який представляє обрану методику контролера, дозволяючи логіку, що залежить від конкретного контролера або методу, який виконується.
  • Легша інтеграція з можливостями Spring: Перехоплювачі безшовно працюють з такими функціями Spring, як впровадження залежностей і обробка винятків.

Приклад використання — Відстеження часу запиту

Типове використання перехоплювачів — вимірювання часу, який витрачається на обробку запиту. Методи preHandle та afterCompletion можуть бути використані разом для досягнення цієї мети.

public class PerformanceInterceptor implements HandlerInterceptor {  
 private static final String START_TIME = "startTime";  

 @Override  
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {  
 request.setAttribute(START_TIME, System.currentTimeMillis());  
 return true;  
 }  

 @Override  
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {  
 Long startTime = (Long) request.getAttribute(START_TIME);  
 long duration = System.currentTimeMillis() - startTime;  
 System.out.println("Запит до " + request.getRequestURI() + " тривав " + duration + " мс");  
 }  
}

Цей приклад показує, як перехоплювачі можуть бути використані для відстеження продуктивності в реальному часі без зміни логіки контролера.

Огляд фаз виконання перехоплювачів

  1. preHandle
    Тригериться до того, як контролер обробить запит. Цей етап використовується для перевірки вхідних запитів, модифікації їх атрибутів або зупинки подальшої обробки, якщо це необхідно.
  2. postHandle
    Виконується після того, як контролер обробить запит, але до того, як буде виконано рендеринг представлення. Він корисний для коригування об'єкта ModelAndView або модифікації атрибутів відповіді на основі логіки контролера.
  3. afterCompletion
    Виконується після завершення всього запиту, включаючи рендеринг представлення. Цей етап часто використовується для очищення ресурсів або логування остаточних деталей про життєвий цикл запиту.

Реєстрація перехоплювачів у Spring Boot

Перехоплювачі в Spring Boot потрібно явно реєструвати, щоб вони стали частиною життєвого циклу обробки запитів додатка. Цей процес включає асоціювання перехоплювачів із конкретними відображеннями запитів і налаштування їх відповідно до вимог вашого додатка.

Використання інтерфейсу WebMvcConfigurer

У Spring Boot рекомендованим методом реєстрації перехоплювачів є реалізація інтерфейсу WebMvcConfigurer. Цей інтерфейс дозволяє розробникам налаштовувати конфігурацію фреймворку Spring MVC, включаючи додавання перехоплювачів до конвеєра обробки запитів.

Для реєстрації перехоплювача потрібно перевизначити метод addInterceptors(), наданий WebMvcConfigurer.
Ось базовий приклад:

@Configuration  
public class WebConfig implements WebMvcConfigurer {  

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

У цьому прикладі:

  • Клас WebConfig позначено анотацією @Configuration, що вказує на те, що це конфігураційний клас, який керується контейнером Spring.
  • Метод addInterceptors() перевизначено для реєстрації LoggingInterceptor.

Об'єкт InterceptorRegistry, який передається як аргумент до addInterceptors(), використовується для додавання перехоплювачів і налаштування їх поведінки. Кожен перехоплювач, зареєстрований в registry, буде викликаний під час обробки запиту.

Перехоплювач як компонент

Хоча можна безпосередньо створити екземпляр класу перехоплювача при додаванні його до реєстру, зазвичай використовується практика визначення перехоплювача як компонента, який керується Spring. Це дозволяє перехоплювачу скористатися можливістю впровадження залежностей, що спрощує доступ до сервісів або компонентів, необхідних для його логіки.

Приклад:

@Component  
public class AuthenticationInterceptor implements HandlerInterceptor {  
 // Логіка перехоплювача  
}  

@Configuration  
public class WebConfig implements WebMvcConfigurer {  

 private final AuthenticationInterceptor authenticationInterceptor;  

 public WebConfig(AuthenticationInterceptor authenticationInterceptor) {  
 this.authenticationInterceptor = authenticationInterceptor;  
 }  

 @Override  
 public void addInterceptors(InterceptorRegistry registry) {  
 registry.addInterceptor(authenticationInterceptor);  
 }  
}

У цьому налаштуванні:

  • Клас AuthenticationInterceptor позначено анотацією @Component, що робить його компонентом Spring.
  • Клас WebConfig використовує ін'єкцію через конструктор для отримання біну перехоплювача.

Такий підхід дозволяє перехоплювачу отримувати доступ до інших бінів у додатку, таких як сервіси аутентифікації або утиліти для логування.

Практичний приклад — кілька перехоплювачів

Зазвичай додатки потребують кількох перехоплювачів для вирішення різних завдань, таких як логування, перевірка безпеки та аналітика. Кожен перехоплювач має бути зареєстрований окремо в методі addInterceptors().

Приклад:

@Configuration  
public class WebConfig implements WebMvcConfigurer {  

 @Override  
 public void addInterceptors(InterceptorRegistry registry) {  
 registry.addInterceptor(new LoggingInterceptor());  
 registry.addInterceptor(new AuthenticationInterceptor());  
 registry.addInterceptor(new MetricsInterceptor());  
 }  
}

У цьому випадку:

  • Зареєстровано три перехоплювачі (LoggingInterceptor, AuthenticationInterceptor та MetricsInterceptor).
  • Вони будуть застосовані до вхідних запитів, якщо не налаштовано інакше.

Життєвий цикл перехоплювачів під час реєстрації

Коли перехоплювач додається до InterceptorRegistry, він стає частиною життєвого циклу обробки запитів Spring MVC. Порядок, у якому перехоплювачі реєструються в методі addInterceptors(), визначає послідовність їх виконання. Процес реєстрації також асоціює перехоплювачів з відображеннями запитів додатка, що дозволяє розробникам точно налаштовувати їх поведінку.

Сумісність перехоплювачів з іншими можливостями Spring MVC

Перехоплювачі безшовно працюють з іншими функціями Spring MVC, такими як поради для контролерів і обробники винятків. Однак процес налаштування повинен враховувати наступне:

  • Користувацькі вирішувачі аргументів: Якщо перехоплювач потребує доступу до користувацьких об'єктів в параметрі handler, може знадобитися налаштувати вирішувачі аргументів.
  • Асинхронні запити: Перехоплювачі мають бути сумісними з асинхронною обробкою запитів.
    Наприклад, довготривалі задачі, що обробляються з використанням @Async або DeferredResult, вимагають уважного підходу до того, як перехоплювачі обробляють такі випадки.

Ось приклад перехоплювача, розробленого для асинхронних запитів:

@Override  
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {  
 if (request.isAsyncStarted()) {  
 System.out.println("Виявлений асинхронний запит");  
 }  
 return true;  
}

Цей метод перевіряє, чи є запит асинхронним, і додає логіку, специфічну для таких випадків.

Тестування реєстрації перехоплювачів

Після того, як перехоплювачі зареєстровані, їх поведінку слід протестувати, щоб підтвердити, що вони застосовуються коректно. Юніт-тестування реалізації WebMvcConfigurer здійснюється за допомогою фреймворку MockMvc від Spring.

Приклад:

@RunWith(SpringRunner.class)  
@WebMvcTest  
public class WebConfigTest {  

 @Autowired  
 private MockMvc mockMvc;  

 @Test  
 public void testInterceptorRegistration() throws Exception {  
 mockMvc.perform(get("/api/test"))  
 .andExpect(status().isOk());  
 // Додаткові перевірки для валідації логіки перехоплювачів  
 }  
}

Цей приклад демонструє, як використовувати MockMvc для симуляції запиту та перевірки, що перехоплювачі поводяться так, як очікується. Такі тести є корисними для перевірки, що реєстрація працює коректно для різних сценаріїв запитів.

Порядок і відповідність шляхів

Перехоплювачі в Spring Boot не лише реєструються, але й можуть бути впорядковані та налаштовані на відповідність певним шляхам. Тут ми зосередимося на тому, як Spring Boot обробляє ці налаштування, надаючи гнучкість для контролю як за порядком виконання перехоплювачів, так і за шляхами, до яких вони застосовуються.

Порядок виконання перехоплювачів

Порядок, у якому виконуються перехоплювачі, визначається послідовністю їх реєстрації в InterceptorRegistry. Перехоплювачі обробляються в тому порядку, в якому вони були зареєстровані для вхідних запитів. Для afterCompletion() порядок є зворотнім, що дозволяє очищення виконуватися в протилежній послідовності. Варто зазначити, що postHandle() виконується в тому ж порядку, що й preHandle().

Розглянемо ще раз цей приклад з попереднього розділу:

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
 registry.addInterceptor(new LoggingInterceptor());  
 registry.addInterceptor(new AuthenticationInterceptor());  
 registry.addInterceptor(new MetricsInterceptor());  
}
  • Для запиту LoggingInterceptor виконуватиметься першим, потім AuthenticationInterceptor, і в кінці — MetricsInterceptor.
  • Під час afterCompletion() порядок буде зворотнім: спочатку виконається MetricsInterceptor, потім AuthenticationInterceptor, і в кінці LoggingInterceptor.

Цей механізм упорядкування корисний, коли деякі перехоплювачі залежать від результатів або побічних ефектів попередніх перехоплювачів.

Основи відповідності шляхів

Відповідність шляхів дозволяє застосовувати перехоплювачі вибірково до певних шаблонів запитів.
Це робиться за допомогою методів addPathPatterns() і excludePathPatterns(), наданих класом InterceptorRegistry.

Приклад:

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
 registry.addInterceptor(new LoggingInterceptor())  
 .addPathPatterns("/api/**")  
 .excludePathPatterns("/api/health", "/api/docs");  
}

У цій конфігурації:

  • LoggingInterceptor застосовується до всіх запитів, що починаються з шляху /api/.
  • Однак він пропускає запити до /api/health і /api/docs.

Це вибіркове застосування дозволяє звузити область дії перехоплювача, щоб уникнути непотрібних накладних витрат або ненавмисних побічних ефектів.

Просунуті шаблони відповідності шляхів

Spring Boot підтримує різні шаблони для відповідності шляхам:

  • Точне співпадіння: Співпадає з конкретним шляхом точно.
.addPathPatterns("/users/profile")

Це застосовується лише до запитів до /users/profile.

  • Шаблон з підстановкою: Співпадає з будь-яким шляхом, що починається з певного префікса.
.addPathPatterns("/api/*")

Це співпадає з /api/user і /api/admin, але не з /api/user/details.

  • Шаблони в стилі Ant: Підтримує складні шаблони, наприклад, рекурсивне співпадіння.
.addPathPatterns("/api/**")

Це співпадає з /api/, /api/user, /api/user/details і т.д.

  • Розширення файлів: Співпадає з певними типами файлів.
.addPathPatterns("/files/*.pdf")

Це співпадає з /files/document.pdf, але не з /files/document.txt.

  • Змінні в шляху: Співпадає з шляхами з динамічними змінними.
.addPathPatterns("/products/{id}")

Це співпадає з /products/123 і /products/abc.

За замовчуванням відповідність шляхів чутлива до регістру, тобто /api/User розглядається як інший шлях, ніж /api/user.

Комбінування відповідності шляхів і виключень

В деяких випадках вам може знадобитися комбінувати шаблони шляхів для створення складної логіки включення та виключення. Наприклад, ви можете захотіти, щоб перехоплювач застосовувався до всіх API кінцевих точок, окрім кількох адміністративних шляхів.

Приклад:

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
 registry.addInterceptor(new AuthenticationInterceptor())  
 .addPathPatterns("/api/**")  
 .excludePathPatterns("/api/admin/**", "/api/public/**");  
}

У цій конфігурації:

  • AuthenticationInterceptor застосовується до всіх шляхів під /api/.
  • Він пропускає шляхи, що починаються з /api/admin/ і /api/public/.

Тестування логіки відповідності шляхів

Під час налаштування відповідності шляхів важливо перевірити, що перехоплювачі застосовуються до призначених шляхів. Це можна зробити за допомогою інтеграційних тестів з фреймворком MockMvc від Spring.

Приклад:

@RunWith(SpringRunner.class)  
@WebMvcTest  
public class PathMatchingTest {  

 @Autowired  
 private MockMvc mockMvc;  

 @Test  
 public void testInterceptorApplied() throws Exception {  
 mockMvc.perform(get("/api/user"))  
 .andExpect(status().isOk());  
 // Перевірки для підтвердження поведінки перехоплювачів  
 }  

 @Test  
 public void testInterceptorExcluded() throws Exception {  
 mockMvc.perform(get("/api/admin/settings"))  
 .andExpect(status().isOk());  
 // Перевірки для підтвердження виключення  
 }  
}

Цей приклад демонструє, як симулювати запити до включених і виключених шляхів, перевіряючи, що логіка відповідності шляхів працює так, як очікується.

Перехоплювачі та механізм налаштування відповідності шляхів

Відповідність шляхів обробляється всередині класу MappedInterceptor в Spring. Коли перехоплювач реєструється за допомогою addPathPatterns() або excludePathPatterns(), Spring створює екземпляр MappedInterceptor, який асоціює перехоплювач з його шаблонами шляхів.

Під час обробки запиту:

  1. DispatcherServlet оцінює шлях вхідного запиту.
  2. MappedInterceptor перевіряє шлях за допомогою своїх включених та виключених шаблонів.
    3.
    Якщо шлях співпадає з включеним шаблоном і не співпадає з жодним виключеним шаблоном, перехоплювач (Interceptor) виконується.

Цей механізм забезпечує виклик лише тих перехоплювачів, які є релевантними для кожного запиту, оптимізуючи процес обробки та зберігаючи чіткість у поведінці перехоплювачів.

Висновок

Spring Boot надає структурований та ефективний спосіб обробки перехоплювачів через інтерфейс HandlerInterceptor, дозволяючи точно контролювати обробку запитів та відповідей. Ми розглянули, як перехоплювачі інтегруються в життєвий цикл запиту, як їх реєструвати через WebMvcConfigurer, а також як їх порядок і відповідність шляхам впливають на їхню поведінку. Уважно керуючи цими конфігураціями, ви можете створювати додатки, які ефективно обробляють запити і застосовують логіку лише там, де це необхідно. Такий підхід до механіки перехоплювачів дає основу для створення модульних та організованих веб-додатків у Spring Boot.

  1. Офіційна документація Spring Boot — HandlerInterceptor
  2. Документація Spring Boot WebMvcConfigurer
  3. Тестування перехоплювачів Spring за допомогою MockMvc

Дякую за прочитання! Якщо ця стаття була корисною, будь ласка, подумайте про те, щоб підкреслити, підтримати, відповісти або зв'язатися зі мною на Twitter/X, це дуже цінується і допомагає зберігати такі матеріали безкоштовними!

pic

Іконка Spring Boot від Icons8

Перекладено з: How Spring Boot Configures and Executes Interceptors

Leave a Reply

Your email address will not be published. Required fields are marked *