6 различие между фильтрами и перехватчиками, не тупи, непонятно

программист

Маленький друг добавил меня на Wechat на выходных и задал мне вопрос: брат, в чем разница между фильтром и перехватчиком? Когда я услышал титул, мое первое впечатление было: просто!

Ведь эти два инструмента довольно часто используются в разработке, и они относительно просты в применении, но когда я собирался ему ответить, я не знал, с чего начать. долго, чтобы ответить на такой простой вопрос, как этот, и я потерял взрослого.

Я обычно думаю, что это просто вопрос знания, но я обычно не уделяю слишком много внимания деталям.Когда меня спрашивают другие, я не могу сказать, почему.

В конечном счете, я все еще недостаточно знаю об этих знаниях, и я застрял на той стадии, когда я могу их использовать, до сих пор.Как только вы это увидите, это будет пустой тратой времени!Это типичное отсутствие твердой основы, эй~, на самом деле, я тоже пухлая!

Зная стыд, а затем набравшись смелости, давайте объединим приведенную ниже практику, чтобы более интуитивно почувствовать разницу между ними.

Подготовка окружающей среды

В проекте настраиваем как перехватчики, так и фильтры.

1. Фильтр

Конфигурация фильтра относительно проста. Вы можете напрямую реализовать интерфейс фильтра. Вы также можете использовать аннотацию @WebFilter для перехвата определенных URL-адресов. Как видите, в интерфейсе фильтра определены три метода.

  • init() : этот метод вызывается, когда контейнер начинает инициализировать фильтр, и он будет вызываться только один раз за весь жизненный цикл фильтра. Примечание. Этот метод должен быть выполнен успешно, иначе фильтр не будет работать.
  • doFilter() : этот метод вызывается для каждого запроса в контейнере, а FilterChain используется для вызова следующего фильтра, Filter.
  • destroy (): этот метод вызывается, когда контейнер уничтожает экземпляр фильтра.Как правило, ресурс уничтожается или закрывается в методе, и он будет вызываться только один раз за весь жизненный цикл фильтра.
@Component
public class MyFilter implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 处理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

        System.out.println("Filter 后置");
    }
}

2. Перехватчик

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

Сначала напишем простой класс обработки перехватчика, перехват запроса реализован через HandlerInterceptor, а также определены три метода в интерфейсе HandlerInterceptor.

  • preHandle() : этот метод будет вызываться перед обработкой запроса. Примечание. Если возвращаемое значение этого метода равно false, это будет считаться окончанием текущего запроса, не только собственный перехватчик выйдет из строя, но и другие перехватчики больше не будут выполняться.
  • postHandle(): выполняется, только если метод preHandle() возвращает значение true. Будет вызываться после метода в контроллере и до того, как DispatcherServlet вернется для отображения представления. Интересно, что порядок вызова метода postHandle() противоположен порядку вызова метода preHandle(): метод preHandle() перехватчика, объявленного первым, выполняется первым, а метод postHandle() выполняется позже.
  • Послепроцентность (): будет выполняться только если метод Prehandle () возвращает true. После окончания всей запроса диспетченерВерсер открывает соответствующее представление и выполняет его.
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("Interceptor 处理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("Interceptor 后置");
    }
}

Пользовательский перехватчик хорошо зарегистрированный класс обработчика, и с помощью addPathPatterns, excludePathPatterns необходимо перехватить или другое свойство для исключения URL-адреса.

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

мы разные

И фильтр, и перехватчик воплощают в себе программную идею АОП, и оба могут реализовывать такие функции, как ведение журнала, аутентификация входа и т. д., но между ними есть много различий, которые будут объяснены ниже.

1. Принцип реализации другой

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

Сосредоточьтесь на фильтре здесь!

В нашем кастомном фильтре реализован метод DOFILTER(), этот метод имеет параметр filterchain и фактически является интерфейсом обратного вызова. ApplicationFilterchain — это его класс реализации, который также имеет метод DOFILTER() внутри класса — это метод обратного вызова.

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain xxxFilter может попасть внутрь нашего пользовательского класса, в своем внутреннем методе обратного вызова doFilter() вызвать в каждом xxxFilter пользовательские фильтры и выполнить метод doFilter().

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

Каждый xxxFilter сначала выполнит свою собственную логику фильтрации doFilter() и, наконец, выполнит filterChain.doFilter(servletRequest, servletResponse) перед окончанием выполнения, что означает обратный вызов метода doFilter() ApplicationFilterChain и выполнение обратного вызова функции в петля.

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(servletRequest, servletResponse);
    }

2. Различная сфера использования

Мы видим, что фильтр реализует интерфейс javax.servlet.Filter, который определен в спецификации Servlet, а это значит, что использование фильтра зависит от таких контейнеров, как Tomcat, поэтому его можно использовать только в веб-программах.

Перехватчик (Interceptor) является компонентом Spring и управляется контейнером Spring, не зависит от таких контейнеров, как Tomcat, и может использоваться отдельно. Его можно использовать не только в веб-программах, но и в таких приложениях, как Application и Swing.

3. Время триггера отличается

Время срабатывания фильтров и перехватчиков тоже разное, посмотрим на картинку ниже.

Фильтр Фильтр предварительно обрабатывается после того, как запрос входит в контейнер, но перед входом в сервлет, а окончание запроса — после обработки сервлета.

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

4. Объем перехватываемых запросов разный

В приведенном выше примере мы одновременно настроили фильтр и перехватчик, а затем создали контроллер для получения запроса на тестирование.

@Controller
@RequestMapping()
public class Test {

    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
        System.out.println("我是controller");
        return null;
    }
}

В процессе запуска проекта было обнаружено, что метод init() фильтра был инициализирован при запуске контейнера.

В это время браузер отправляет запрос, и F12 видит, что на самом деле есть два запроса, один — это наш пользовательский запрос контроллера, а другой — запрос на доступ к ресурсам статической иконки.

Смотрите журнал печати консоли следующим образом:

Порядок выполнения: Обработка фильтра -> Предварительная обработка перехватчика -> Я контролер -> Обработка перехватчика -> Обработка перехватчика

Filter 处理中
Interceptor 前置
Interceptor 处理中
Interceptor 后置
Filter 处理中

Фильтр Filter выполняется дважды, а перехватчик Interceptor — только один раз. Это связано с тем, что фильтр может действовать почти на все запросы, поступающие в контейнер, в то время как перехватчик может действовать только на запросы в контроллере или запросы на доступ к ресурсам в статическом каталоге.

5. Ситуация с инъекцией Bean отличается

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

Ниже мы внедряем сервис как в фильтр, так и в перехватчик, и видим, в чем разница?

@Component
public class TestServiceImpl implements TestService {

    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

Внедрите службу в фильтр, инициируйте запрос на ее проверку, и журнал обычно печатает «Я метод A».

@Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 处理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }


Filter 处理中
我是方法A
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor 后置

Внедрите сервис в перехватчик, инициируйте запрос на его тестирование, и даже ТМ сообщит об ошибке, отладьте и узнайте, как внедренный сервис является Null?

Это проблема, вызванная порядком загрузки: перехватчик загружается до контекста Spring, а bean-компонент управляется Spring.

Перехватчик: Сегодня я войду в брачный чертог;
Весна: Не ссорься, брат, я еще невестку твою не родила!

Решение также очень простое, мы вручную внедряем Interceptor перед регистрацией перехватчика. Примечание. Экземпляр getMyInterceptor() зарегистрирован в реестре.addInterceptor().

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

6, порядок выполнения управления отличается

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

Фильтр аннотирован @Order для управления порядком выполнения, а уровень фильтра контролируется @Order.Чем меньше значение, тем выше уровень, первый будет выполнен.

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

Порядок выполнения перехватчика по умолчанию — это его порядок регистрации, который также можно задать вручную и контролировать с помощью Order, чем меньше значение, тем выполняется первым.

 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

Глядя на результаты вывода, обнаруживается, что метод preHandle() перехватчика, объявленного первым, выполняется первым, а метод postHandle() будет выполняться позже.

Порядок вызова метода postHandle() противоположен порядку вызова preHandle()! Если порядок выполнения строго требуется в реальной разработке, на это следует обратить особое внимание.

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

Так почему же это так? Чтобы получить ответ, мы можем только посмотреть исходный код, нам нужно знать, что все запросы в контроллере должны быть маршрутизированы через основной компонент DispatcherServlet, и его метод doDispatch() будет выполняться, а перехватчик postHandle() и В нем вызываются методы preHandle().

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 
        try {
         ...........
            try {
 
                // 获取可以执行当前Handler的适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 执行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

Глядя на то, как вызываются два метода applyPreHandle() и applyPostHandle(), вы можете понять, почему порядок выполнения postHandle() и preHandle() обратный.

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }


void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

Было обнаружено, что при вызове массива перехватчиков HandlerInterceptor[] в двух методах порядок цикла был обратным. . . , вызывая обратное выполнение методов postHandle() и preHandle().

Суммировать

Я считаю, что большинство людей могут убить фильтр и перехватчики, но разница еще больше, в противном случае в разработке ненадлежащего развития, иногда существует странная виновата, приведенное выше содержание относительно просто, новичка Я надеюсь, что все активно дополнены. Если есть какая-то ошибка, он также надеется просветить меня.

Эта статья написана автором SegmentFault, который думает, что нет.


Нажмите «Подписаться», чтобы впервые узнать о новых технологиях HUAWEI CLOUD~