Как происходит последовательное выполнение цепочки фильтров в Java
Что проверяют
Понимаете ли вы, как фильтр передаёт управление следующему и почему код после вызова цепочки выполняется в обратном порядке. Следом спрашивают, как задать порядок фильтров в Spring и чем фильтр отличается от HandlerInterceptor.
Как отвечать
Контракт jakarta.servlet.Filter:
doFilter(request, response, chain): фильтр получает запрос, ответ и объект цепочки. Код доchain.doFilter(...)работает на входе, код после него на выходе, когда сервлет уже отработал;- передача управления: реализация цепочки в контейнере, например в Tomcat, хранит массив фильтров и текущую позицию. Каждый
chain.doFilterсдвигает позицию и вызывает следующий фильтр, а после последнего вызывается сервлет. Получается вложенность: фильтры A, B, C входят в порядке A, B, C и выходят в порядке C, B, A; - прерывание: если фильтр не вызвал
chain.doFilter, запрос дальше не пойдёт. Так фильтр аутентификации отдаёт 401, не доходя до контроллера; - обёртки: дальше можно передать
HttpServletRequestWrapperвместо исходного запроса, например чтобы прочитать тело дважды.
Порядок в web.xml задаётся порядком filter-mapping, у фильтров с @WebFilter он не гарантирован. В Spring Boot его задают через FilterRegistrationBean.setOrder() или @Order, а базовым классом берут OncePerRequestFilter, чтобы фильтр не срабатывал повторно на внутренних dispatch.
Spring Security устроен как цепочка внутри цепочки: в контейнере зарегистрирован DelegatingFilterProxy, он передаёт запрос в FilterChainProxy, а тот выбирает подходящий SecurityFilterChain и прогоняет его фильтры. Свои фильтры туда добавляют через addFilterBefore и addFilterAfter.
Где спотыкаются
Показывают цикл for по списку фильтров как реализацию цепочки. В нём фильтр не может ни остановить запрос, ни выполнить код после обработки, а в этом и смысл паттерна.
Путают фильтр и HandlerInterceptor. Фильтр работает на уровне контейнера до DispatcherServlet, интерцептор живёт внутри Spring MVC и знает, какой обработчик выбран.
Забывают, что исключение из сервлета идёт обратно через все фильтры, и пишут пост-обработку без try/finally: при ошибке она просто не выполняется.