天天看點

優雅處理HTTP請求:過濾器攔截器、ControllerAdvice和自定義AOP

作者:馬士兵老師
優雅處理HTTP請求:過濾器攔截器、ControllerAdvice和自定義AOP

我們在開發Spring Boot應用程式時,經常會遇到需要對HTTP請求進行一些處理的情況,例如鑒權、資料校驗、請求日志記錄等等。在處理HTTP請求時,我們可以使用四種不同的技術來實作這些功能:過濾器、攔截器、ControllerAdvice和自定義AOP。

在本文中,我們将分别介紹這四種技術的概念、用法和差別,并舉例說明如何在Spring Boot應用程式中使用它們來實作對HTTP請求的統一處理。

基本概念及攔截執行順序

優雅處理HTTP請求:過濾器攔截器、ControllerAdvice和自定義AOP
  1. 過濾器(Filter):過濾器是Java Web中的一種技術,它在請求到達Servlet之前或之後進行處理。是以,過濾器是最先執行的攔截器。在過濾器中可以進行一些通用的業務處理,例如鑒權、資料校驗、請求日志記錄等等。Spring Boot應用程式中注冊的過濾器按照注冊的順序依次執行。
  2. 攔截器(Interceptor):攔截器是Spring MVC架構提供的一種技術,它在請求到達Controller之前或之後進行處理。是以,攔截器在過濾器之後執行,但在請求到達Controller之前。在攔截器中可以進行請求的修改或者一些通用的業務邏輯處理。Spring Boot應用程式中注冊的攔截器按照注冊的順序依次執行。
  3. ControllerAdvice:ControllerAdvice是Spring MVC提供的一個注解,用于定義一個全局的異常處理器、資料綁定器和模型處理器。它可以被用于處理所有的Controller中抛出的異常和響應,對于多個Controller中重複的異常處理可以進行統一管理。ControllerAdvice在請求到達Controller之後執行,可以對Controller傳回的資料進行統一處理,例如添加通用的響應頭、設定統一的傳回值格式等等。
  4. 自定義AOP(Aspect-Oriented Programming):自定義AOP是一種程式設計範式,它可以用于在方法調用前、後或抛出異常時添加一些橫切邏輯。在Spring Boot應用程式中,自定義AOP切面可以用于對HTTP請求進行統一處理,例如擷取和解析請求頭資訊、請求日志記錄、鑒權等等。自定義AOP切面的執行順序在攔截器之後,在請求到達Controller之前執行。

總的來說,四種攔截方式的執行順序是過濾器->攔截器->ControllerAdvice->自定義AOP。

但是需要注意的是,這并不是絕對的順序,具體的執行順序還會受到過濾器鍊、攔截器鍊、AOP切入位置等因素的影響,例如

  • 如果在過濾器或攔截器中調用了chain.doFilter(request, response)或handlerInterceptor.preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)方法,則會依次調用過濾器鍊和攔截器鍊的下一個過濾器或攔截器。
  • 自定義AOP的執行順序在ControllerAdvice之後,是因為一般情況下我們會将切入點選在Controller的方法上

四種攔截方法的使用case

過濾器

在Spring Boot項目中,可以使用過濾器(Filter)來統一處理HTTP請求,并擷取和解析請求頭中的内容。過濾器可以在請求到達Servlet之前或之後進行一些處理操作。

以下例子展示如何在Spring Boot項目中實作一個過濾器來擷取和解析請求頭中的内容:

  1. 建立一個過濾器類,實作Filter接口。在該過濾器類中,可以重寫doFilter方法,在該方法中擷取并解析請求頭資訊。
Java複制代碼public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 擷取請求頭資訊
        String headerInfo = httpRequest.getHeader("Header-Info");
        // 解析請求頭資訊
        // ...
        chain.doFilter(request, response); // 繼續執行請求處理
    }

    // 可以重寫init和destroy方法進行初始化和銷毀操作
}
           
  1. 在Spring Boot應用程式中注冊該過濾器類。可以通過建立一個配置類并繼承WebMvcConfigurerAdapter類,然後重寫addFilters方法來注冊過濾器。
Java複制代碼public class MyFilterConfig extends WebMvcConfigurerAdapter {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new MyFilter());
        registrationBean.addUrlPatterns("/*"); // 指定過濾的URL
        return registrationBean;
    }
}
           

這樣,當請求到達時,就會先進入過濾器的doFilter方法進行處理,并擷取和解析請求頭資訊。過濾器可以處理任何類型的請求,而不僅僅是Web請求,是以需要在過濾器中進行類型判斷。

攔截器

在Spring Boot項目中,可以使用攔截器(Interceptor)來對HTTP請求進行統一轉換和處理請求頭資訊。攔截器可以在請求處理前和處理後進行一些處理操作。

以下例子展示如何在Spring Boot項目中實作一個攔截器來擷取和解析請求頭中的資訊:

建立一個攔截器類,實作HandlerInterceptor接口。在該攔截器類中,可以重寫preHandle方法,在該方法中擷取并解析請求頭資訊。

Java複制代碼public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 擷取請求頭資訊
        String headerInfo = request.getHeader("Header-Info");
        // 解析請求頭資訊
        // ...
        return true; // 傳回true繼續執行請求處理,傳回false則不執行請求處理
    }

    // 可以重寫 afterCompletion 和 postHandle 方法進行請求處理後的操作
}
           

在Spring Boot應用程式中注冊該攔截器類。可以通過建立一個配置類并繼承WebMvcConfigurerAdapter類,然後重寫addInterceptors方法來注冊攔截器。

Java複制代碼@Configuration
public class MyInterceptorConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
}
           

這樣,當請求到達時,就會先進入攔截器的preHandle方法進行處理,并擷取和解析請求頭資訊。注意,攔截器隻能處理@Controller注解或@RequestMapping注解的處理器方法,并且隻對Web請求有效,對于非Web請求(如靜态資源)不會進行攔截。

除preHandle(目标方法執行前執行), HandlerInterceptor還有兩外兩個方法:

  • postHandle 目标方法執行後執行
  • afterCompletion 請求完成時執行

ControllerAdvice

  • @ControllerAdvice是Spring MVC提供的一個注解,用于定義一個全局的異常處理器、資料綁定器和模型處理器。它可以被用于處理所有的Controller中抛出的異常和響應,對于多個Controller中重複的異常處理可以進行統一管理。
  • @ControllerAdvice注解對于應用程式中所有的@Controller注解的類中的方法進行全局處理,可以捕獲Controller中的異常并傳回相應的響應資訊,也可以在Controller中傳回資料之前對資料進行統一處理。
  • @ControllerAdvice注解所标注的類中,可以定義多個方法,每個方法可以處理不同的異常或傳回不同類型的資料。在方法上可以使用@ExceptionHandler、@InitBinder或@ModelAttribute注解來指定要處理的異常類型、資料綁定規則和模型處理規則。

下面是一個@ControllerAdvice注解的示例:

Java複制代碼@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
}
           

上面的代碼定義了一個全局異常處理器,用于處理所有Controller中抛出的Exception異常,如果出現異常則傳回一個HTTP 500錯誤響應,并将異常資訊作為響應體傳回。

另外,@ControllerAdvice注解中還可以添加basePackages屬性或value屬性來指定需要處理的Controller所在的包路徑或類。例如:

Java複制代碼@ControllerAdvice(basePackages = "com.example.controller")
public class GlobalExceptionHandler {
    // ...
}
           

這樣就隻會處理com.example.controller包下的Controller中抛出的異常。

總之,@ControllerAdvice是一個很有用的注解,可以讓我們在應用程式中統一處理異常和響應,避免重複的代碼,提高代碼的可維護性和可讀性。

自定義AOP

在Spring Boot項目中,可以使用自定義AOP的方式來統一處理HTTP請求,并擷取和解析請求頭中的内容。通過自定義AOP切面,可以在請求處理前和處理後添加相應的切面邏輯。

以下例子展示如何在Spring Boot項目中實作自定義AOP切面來擷取和解析請求頭中的内容:

建立一個注解,用于标注需要進行切面處理的方法。在該注解中添加一個String類型的屬性,用于指定需要擷取的請求頭資訊。

Java複制代碼@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value();
}
           

建立一個切面類,實作Aspect接口,并在該類中定義一個切點方法和一個通知方法。在切點方法中,使用@Pointcut注解來指定需要進行切面處理的方法;在通知方法中,通過JoinPoint參數擷取請求對象,并從請求對象中擷取并解析請求頭資訊。

Java複制代碼@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(com.example.demo.MyAnnotation)")
    public void myPointcut() {}

    @Around("myPointcut()")
    public Object myAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 擷取請求對象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 擷取注解中指定的請求頭資訊
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        MyAnnotation annotation = methodSignature.getMethod().getAnnotation(MyAnnotation.class);
        String headerInfo = request.getHeader(annotation.value());

        // 解析請求頭資訊
        // ...

        // 繼續執行請求處理
        Object result = joinPoint.proceed();

        // 處理請求處理結果
        // ...

        return result;
    }
}
           

在需要進行切面處理的方法上添加自定義注解,并指定需要擷取的請求頭資訊。

Java複制代碼@RestController
public class MyController {

    @MyAnnotation("Header-Info")
    @GetMapping("/hello")
    public String hello() {
        return "Hello World!";
    }
}
           

這樣,在請求到達需要進行切面處理的方法時,就會先進入自定義AOP切面的通知方法進行處理,并擷取和解析請求頭資訊。注意,自定義AOP切面可以處理任何類型的方法,而不僅僅是@Controller注解或@RequestMapping注解的處理器方法,它也可以處理非Web請求(如異步方法或定時任務等)。

繼續閱讀