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

Java
6 отличий между фильтрами и перехватчиками, хватит глупить

Эта статья размещена в личном блоге:www.chengxy-nds.top, делитесь техническими ресурсами и добивайтесь прогресса вместе

Маленький друг добавил меня в WeChat на выходных и задал мне вопрос: Брат,"фильтр (Filter) и перехватчики (Interceptor) Какая разница?"Мое первое впечатление, когда я услышал название, было:"Простой"!

Ведь эти два инструмента довольно часто используются в разработке, и они относительно просты в применении, но когда я собирался ему ответить, я не знал, с чего начать. долго, чтобы ответить на такой простой вопрос, как этот, и я потерял взрослого.自导自演,别太当真过,哈哈哈Я обычно думаю, что это просто вопрос знания, но я обычно не уделяю слишком много внимания деталям.Когда меня спрашивают другие, я не могу сказать, почему.

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

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

Подготовьте среду

Настраиваем оба в проекте拦截器и过滤器.

1. Фильтр

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

  • init(): Этот метод вызывается, когда контейнер начинает инициализировать фильтр, который находится вFilterбудет вызываться только один раз за все время существования ."Уведомление": Этот метод должен быть выполнен успешно, иначе фильтр не будет работать.

  • doFilter(): этот метод вызывается для каждого запроса в контейнере,FilterChainиспользуется для вызова следующего фильтраFilter.

  • destroy(): этот метод вызывается, когда контейнер уничтожает экземпляр фильтра, обычно уничтожая или закрывая ресурсы в методе, в фильтреFilterВесь жизненный цикл будет вызываться только один раз

@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. Перехватчик

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

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

  • preHandle(): этот метод будет вызываться перед обработкой запроса."Уведомление": Если возвращаемое значение методаfalse, это будет расцениваться как окончание текущего запроса, не только его собственный перехватчик выйдет из строя, но и другие перехватчики больше не будут выполняться.

  • postHandle():только вpreHandle()Возвращаемое значение методаtrueбудет казнен. Будет вызываться после метода в контроллере и до того, как DispatcherServlet вернется для отображения представления."что интересно":postHandle()Порядок вызова методов следующий:preHandle()наоборот, перехватчик объявил первымpreHandle()метод выполняется первым, иpostHandle()Вместо этого метод будет выполнен позже.

  • afterCompletion():только вpreHandle()Возвращаемое значение методаtrueбудет казнен. После завершения всего запроса DispatcherServlet выполняется после рендеринга соответствующего представления.

@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("/**");
    }
}

мы разные

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

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), то есть обратный вызовApplicationFilterChainизdoFilter()метод, этот цикл выполняет обратный вызов функции.

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

        filterChain.doFilter(servletRequest, servletResponse);
    }

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

Мы видим, что фильтр реализуетjavax.servlet.Filterинтерфейс, и этот интерфейс находится вServletопределенный в спецификации, то есть фильтрFilterиспользование зависит отTomcatи т. д. контейнер, в результате чего он работает только вwebиспользуется в программе.在这里插入图片描述в то время как перехватчик (Interceptor) этоSpringкомпонентов, а поSpringУправление контейнером, не зависит отTomcatи другие контейнеры, можно использовать отдельно. Его можно применять не толькоwebпрограмма, также может быть использована дляApplication,SwingЖду программу.在这里插入图片描述

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

过滤器и拦截器Время срабатывания тоже разное, давайте посмотрим на картинку ниже.在这里插入图片描述

фильтрFilterпроисходит после того, как запрос входит в контейнер, но после вводаservletПеред предварительной обработкой конец запроса находится вservletПосле обработки.

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

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

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

@Controller
@RequestMapping()
public class Test {

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

Процесс запуска проекта обнаружил, что фильтрinit()метод, который инициализируется при запуске контейнера.在这里插入图片描述В это время браузер отправляет запрос, а F12 видит, что запросов на самом деле два, один наш кастомныйControllerзапрос, другой — запрос на доступ к ресурсу статического значка.在这里插入图片描述Смотрите журнал печати консоли следующим образом:

Порядок выполнения:Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

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

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

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

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

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

@Component
public class TestServiceImpl implements TestService {

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

ввести в фильтрservice, инициировать запрос на проверку, журнал печатается нормально“我是方法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 后置

вводить в перехватчикservice, инициировать запрос на его проверку, но TM сообщает об ошибке,debugСледите за открытием инъекцииserviceкакNullА?在这里插入图片描述Это из-за проблем с порядком загрузки,拦截器Время загрузки вspringcontextраньше, покаBeanснова поspringсправляться.

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

Решение тоже очень простое, прежде чем мы зарегистрируем перехватчик, мы сначалаInterceptorВводить вручную."Уведомление":существуетregistry.addInterceptor()зарегистрированgetMyInterceptor()пример.

@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 处理后

"Так почему же это так?"Чтобы получить ответ, мы можем только посмотреть исходный код, нам нужно знатьcontrollerВсе запросы в основном компоненте проходят через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()Порядок выполнения методов обратный.在这里插入图片描述

Суммировать

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


Оригинальность не так проста,"сжигание волос", если у вас немного урожая, закажите"отличный" Поощряйте это!

Разобраны и разосланы друзьям сотни различных технических электронных книг. Подпишитесь на официальный аккаунт, чтобы ответить【"666"] Самовывоз. Мы создали группу технического обмена с друзьями, чтобы обсуждать технологии и делиться технической информацией, стремясь вместе учиться и развиваться. Если вам интересно, присоединяйтесь к нам!