Seis diferenças principais entre filtros e interceptores

        Normalmente penso que os pontos de conhecimento são simples, mas normalmente não presto muita atenção aos detalhes. Uma vez questionados por outras pessoas, não consigo explicar porquê. É realmente algo que será descartado assim que você olhar para ele. Vamos reunir todos para distinguir filtros e interceptadores com base na prática ~

Entendimento popular :
(1) Filtro: Quando você tem um monte de coisas, você só deseja selecionar algo que atenda às suas necessidades. A ferramenta para definir esses requisitos é o filtro. (Entendimento: Basta escolher um B entre um monte de letras)
(2) Interceptador: Quando um processo está em andamento, você deseja interferir no seu progresso ou até mesmo encerrá-lo. É isso que o interceptador faz. (Entendimento: É só um monte de cartas, intervenha nelas, menos delas passam na verificação e fazem outras coisas pelo caminho)

1. Trabalho de preparação

        Configuramos os filtros e interceptores no projeto da seguinte forma:

1.Filtro

A configuração do filtro é relativamente simples. Você pode implementar diretamente a interface Filter. Você também pode interceptar URLs específicos por meio da anotação @WebFilter. Você pode ver que a interface Filter define três métodos:

(1) init() : Este método é chamado quando o contêiner começa a inicializar o filtro. Ele só será chamado uma vez em todo o ciclo de vida do Filtro. Nota: Este método deve ser executado com sucesso, caso contrário o filtro não funcionará.

(2) doFilter() : Este método será chamado para cada solicitação no contêiner e FilterChain é usado para chamar o próximo filtro.

(3) destroy() : Este método é chamado quando o contêiner destrói a instância do filtro. Geralmente, o recurso é destruído ou fechado no método. Ele só será chamado uma vez em todo o ciclo de vida do filtro.

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;


/**
 * @author: tangbingbing
 * @date: 2023/8/7 15:52
 */
@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 (ideia do Interceptor AOP)

        Interceptores são chamadas em cadeia. Vários interceptores podem existir em uma aplicação ao mesmo tempo. Uma solicitação também pode acionar vários interceptadores, e as chamadas de cada interceptador serão executadas em sequência de acordo com a ordem em que são declaradas.

Primeiro, escreva uma classe de processamento de interceptador simples. A interceptação de solicitações é implementada por meio de HandlerInterceptor.

A interface também define três métodos como segue:

(1) preHandle() : Este método será chamado antes que a solicitação seja processada. Nota: Se o valor de retorno deste método for falso, a solicitação atual será considerada encerrada. Não apenas seu próprio interceptador se tornará inválido, mas também fará com que outros interceptadores não sejam mais executados.

(2) postHandle() : Só será executado quando o valor de retorno do método preHandle() for verdadeiro. Ele será chamado após a chamada do método no Controller e antes do DispatcherServlet retornar à visualização renderizada. O interessante é que o método postHandle() é chamado na ordem oposta a preHandle(). O método preHandle() do interceptor declarado primeiro é executado primeiro, enquanto o método postHandle() é executado mais tarde.

(3) afterCompletion() : Só será executado quando o valor de retorno do método preHandle() for verdadeiro. Após a conclusão de toda a solicitação, o DispatcherServlet é executado após renderizar a visualização correspondente.

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:05
 */
@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 后置");
	}
}

Registrar interceptador

Registre a classe de processamento do interceptor customizado e defina as URLs que precisam ser interceptadas ou excluídas por meio de atributos como addPathPatterns e excludePathPatterns.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:12
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
	}
}

2. Seis diferenças principais entre filtros e interceptadores

1. Diferentes princípios de implementação

Os métodos subjacentes de implementação de filtros e interceptores são bastante diferentes. Os filtros são baseados em retornos de chamada de função, enquanto os interceptores são implementados com base no mecanismo de reflexão do Java (proxy dinâmico).

Aqui nos concentramos na análise de filtros

Em nossos filtros customizados, implementaremos um método doFilter(), que possui um parâmetro FilterChain, mas na verdade é uma interface de retorno de chamada. ApplicationFilterChain é sua classe de implementação.Esta classe de implementação também possui um método doFilter() dentro do qual está o método de retorno de chamada.

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

Podemos obter nossa classe xxxFilter personalizada em ApplicationFilterChain, chamar cada filtro xxxFilter personalizado em seu método de retorno de chamada interno doFilter() e executar o método 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);
        }
    }
 
}

      Cada xxxFilter executará primeiro sua própria lógica de filtragem doFilter() e, finalmente, executará filterChain.doFilter(servletRequest, servletResponse) antes do final da execução, que é chamar de volta o método doFilter() de ApplicationFilterChain, executando assim ciclicamente o retorno de chamada da função .

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

        filterChain.doFilter(servletRequest, servletResponse);
    }

2. Diferentes escopos de uso

        Vemos que o filtro implementa a interface javax.servlet.Filter, e esta interface é definida na especificação do Servlet, o que significa que o uso do filtro depende de containers como o Tomcat, fazendo com que ele seja utilizado apenas em programas web.

        O Interceptor é um componente Spring gerenciado pelo contêiner Spring, não depende de contêineres como o Tomcat e pode ser usado sozinho. Pode ser usado não apenas em programas web, mas também em Application, Swing e outros programas.

3. Diferentes tempos de ativação

O tempo de acionamento de filtros e interceptores também é diferente, vejamos a figura abaixo.

 Filtro O filtro é pré-processado após a solicitação entrar no contêiner, mas antes de entrar no servlet. A solicitação termina após o processamento do servlet. O interceptor é pré-processado após a solicitação entrar no servlet e antes de entrar no Controlador. A solicitação termina após a visualização correspondente ser renderizada no Controlador.

4. O escopo das solicitações interceptadas é diferente

Crie um novo controlador da seguinte maneira

@RestController
public class TestController {

	@RequestMapping("/test")
	public String test(){
		//System.out.println("源码环境构建成功...");
		System.out.println("Controller Method...");
		return "源码环境构建成功";
	}
}

Durante o processo de inicialização do projeto, descobriu-se que o init()método de filtro foi inicializado com a inicialização do contêiner.

 Solicite o console da interface do controlador da seguinte maneira

Ordem de execução: Processamento de filtro -> Pré-processamento do interceptor -> Método do controlador... -> Processamento do interceptor -> Pós-processamento do interceptor

Os filtros Filterpodem ser executados múltiplas vezes, os interceptores Interceptorsão executados apenas uma vez. Isso porque o filtro pode atuar em quase todas as requisições que entram no container, enquanto o interceptador atuará apenas nas Controllerrequisições intermediárias ou acessando staticrecursos no diretório.

5. A situação de injetar feijão é diferente

Em cenários reais de negócios, ao aplicar filtros ou interceptadores, é inevitável que alguns serviceserviços sejam introduzidos para processar a lógica de negócios.

A seguir, vamos injetá-lo no filtro e no interceptor servicepara ver qual é a diferença?

/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
public interface TestService {
	void a();
}
/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
@Component
public class TestServiceImpl implements TestService {

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

Injetar serviço no filtro

@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);
}

O console da interface de acesso é o seguinte

 Injete o serviço no interceptor, inicie uma solicitação e teste-o, e um erro será relatado.Após a depuração, descobri que o serviço injetado é Nulo.

 Isso se deve a um problema causado pela ordem de carregamento: o interceptor é carregado antes do springcontext e os beans são gerenciados pelo spring.

ps: Como resolver esse problema? continue descendo

A solução também é muito simples: antes de registrar o interceptor, primeiro injetamos o interceptor manualmente. Nota: O que está registrado em Registry.addInterceptor() é a instância 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(new MyInterceptor()).addPathPatterns("/**");
		registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
	}
}

Acesse a interface novamente e verifique o console, está normal.

 6. A ordem de execução do controle é diferente

No processo de desenvolvimento real, podem existir vários filtros ou interceptores ao mesmo tempo, mas às vezes queremos que um determinado filtro ou interceptor seja executado primeiro, o que envolve sua ordem de execução.

A ordem de execução padrão do interceptor é a sua ordem de registro. Também pode ser @Orderdefinida e controlada manualmente através de anotações. Quanto menor o valor, primeiro ele será executado.

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
}
 @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);
    }

Vendo os resultados de saída, descobrimos que o método preHandle() do interceptor declarado primeiro é executado primeiro, enquanto o método postHandle() é executado posteriormente. A ordem em que o método postHandle() é chamado é na verdade oposta à de preHandle()! Se a sequência de execução for estritamente necessária no desenvolvimento real, atenção especial deverá ser dada a este ponto.

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
Controller Method...
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

Então, por que isso está acontecendo? Para obter a resposta, só podemos olhar o código-fonte. Precisamos saber que todas as solicitações no controlador devem ser roteadas através do componente principal DispatcherServlet, e seu método doDispatch() será executado, e o interceptor postHandle() e Os métodos preHandle() são chamados nele.

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);
            }
        }
        ...........
    }

Se você observar como os dois métodos applyPreHandle() e applyPostHandle() são chamados, entenderá por que a ordem de execução de postHandle() e preHandle() é invertida.

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);
            }
        }
    }

Verificou-se que ao chamar o array interceptador HandlerInterceptor[] nos dois métodos, a ordem do loop foi realmente invertida, fazendo com que os métodos postHandle() e preHandle() fossem executados na ordem oposta.

Acho que você gosta

Origin blog.csdn.net/qq_45443475/article/details/132147949
Recomendado
Clasificación