Джерело зображення
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 + " мс");
}
}
Цей приклад показує, як перехоплювачі можуть бути використані для відстеження продуктивності в реальному часі без зміни логіки контролера.
Огляд фаз виконання перехоплювачів
- preHandle
Тригериться до того, як контролер обробить запит. Цей етап використовується для перевірки вхідних запитів, модифікації їх атрибутів або зупинки подальшої обробки, якщо це необхідно. - postHandle
Виконується після того, як контролер обробить запит, але до того, як буде виконано рендеринг представлення. Він корисний для коригування об'єктаModelAndView
або модифікації атрибутів відповіді на основі логіки контролера. - 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
, який асоціює перехоплювач з його шаблонами шляхів.
Під час обробки запиту:
- DispatcherServlet оцінює шлях вхідного запиту.
- MappedInterceptor перевіряє шлях за допомогою своїх включених та виключених шаблонів.
3.
Якщо шлях співпадає з включеним шаблоном і не співпадає з жодним виключеним шаблоном, перехоплювач (Interceptor) виконується.
Цей механізм забезпечує виклик лише тих перехоплювачів, які є релевантними для кожного запиту, оптимізуючи процес обробки та зберігаючи чіткість у поведінці перехоплювачів.
Висновок
Spring Boot надає структурований та ефективний спосіб обробки перехоплювачів через інтерфейс HandlerInterceptor, дозволяючи точно контролювати обробку запитів та відповідей. Ми розглянули, як перехоплювачі інтегруються в життєвий цикл запиту, як їх реєструвати через WebMvcConfigurer
, а також як їх порядок і відповідність шляхам впливають на їхню поведінку. Уважно керуючи цими конфігураціями, ви можете створювати додатки, які ефективно обробляють запити і застосовують логіку лише там, де це необхідно. Такий підхід до механіки перехоплювачів дає основу для створення модульних та організованих веб-додатків у Spring Boot.
- Офіційна документація Spring Boot — HandlerInterceptor
- Документація Spring Boot WebMvcConfigurer
- Тестування перехоплювачів Spring за допомогою MockMvc
Дякую за прочитання! Якщо ця стаття була корисною, будь ласка, подумайте про те, щоб підкреслити, підтримати, відповісти або зв'язатися зі мною на Twitter/X, це дуже цінується і допомагає зберігати такі матеріали безкоштовними!
Іконка Spring Boot від Icons8
Перекладено з: How Spring Boot Configures and Executes Interceptors