知識庫 / Spring RSS 訂閱

CDI攔截器 vs Spring AspectJ

Jakarta EE,Spring
HongKong
4
02:48 PM · Dec 06 ,2025

1. 引言

攔截器模式通常用於在應用程序中添加新的、橫斷式功能或邏輯,並且在大量的庫中得到了廣泛的支持。

在本文中,我們將探討並對比這兩個主要庫:CDI 攔截器和 Spring AspectJ。

2. CDI 攔截器項目設置

CDI 官方支持 Jakarta EE,但某些實現也提供在 Java SE 環境中使用 CDI 的支持。 Weld 可以被視為一個在 Java SE 中支持的 CDI 實現的例子。

為了使用 CDI,我們需要在 POM 中導入 Weld 庫:

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-core</artifactId>
    <version>3.1.6.Final</version>
</dependency>

最新的 Weld 庫可以在 Maven 倉庫中找到。

現在,讓我們創建一個簡單的攔截器。

3. 介紹 CDI 攔截器

為了指定需要攔截的類,我們創建攔截器綁定:

@InterceptorBinding
@Target( { METHOD, TYPE } )
@Retention( RUNTIME )
public @interface Audited {
}

我們已經定義了攔截器綁定,現在需要定義實際的攔截器實現:

@Audited
@Interceptor
public class AuditedInterceptor {
    public static boolean calledBefore = false;
    public static boolean calledAfter = false;

    @AroundInvoke
    public Object auditMethod(InvocationContext ctx) throws Exception {
        calledBefore = true;
        Object result = ctx.proceed();
        calledAfter = true;
        return result;
    }
}

每個 @AroundInvoke 方法都接受一個 javax.interceptor.InvocationContext 參數,返回一個 java.lang.Object,並且可以拋出一個 Exception

因此,當我們用新的 @Audit 接口註解一個方法時,auditMethod 會首先被調用,然後目標方法才會繼續執行。

4. 應用 CDI 攔截器

讓我們將創建的攔截器應用於一些業務邏輯:

public class SuperService {
    @Audited
    public String deliverService(String uid) {
        return uid;
    }
}

我們創建了這個簡單的服務,並使用 @Audited 註解標記了我們想要攔截的方法。

為了啓用 CDI 中斷器,需要在 beans.xml 文件中指定完整的類名,該文件位於 META-INF 目錄下:

<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_2.xsd">
    <interceptors>
        <class>com.baeldung.interceptor.AuditedInterceptor</class>
    </interceptors>
</beans>

為了驗證攔截器是否確實生效,我們現在運行以下測試:

public class TestInterceptor {
    Weld weld;
    WeldContainer container;

    @Before
    public void init() {
        weld = new Weld();
        container = weld.initialize();
    }

    @After
    public void shutdown() {
        weld.shutdown();
    }

    @Test
    public void givenTheService_whenMethodAndInterceptorExecuted_thenOK() {
        SuperService superService = container.select(SuperService.class).get();
        String code = "123456";
        superService.deliverService(code);
        
        Assert.assertTrue(AuditedInterceptor.calledBefore);
        Assert.assertTrue(AuditedInterceptor.calledAfter);
    }
}

在本次快速測試中,我們首先從容器中獲取 Bean SuperService,然後調用其業務方法 deliverService,並驗證攔截器 AuditedInterceptor 是否被實際調用,通過驗證其狀態變量。

此外,我們有使用 @Before@After 註解的方法,分別用於初始化和關閉 Weld 容器。

5. CDI 考量

我們可以指出 CDI 攔截器的以下優勢:

  • 它是 Jakarta EE 規範的標準特性
  • 某些 CDI 實現庫可用於 Java SE
  • 可以在我們的項目在第三方庫方面存在嚴格限制時使用

CDI 攔截器的劣勢如下:

  • 業務邏輯類與攔截器之間存在緊耦合
  • 難以確定項目中哪些類被攔截
  • 缺乏靈活的機制來將攔截器應用於一組方法

6. Spring AspectJ

Spring 支持使用 AspectJ 語法類似的攔截器功能實現。

首先,我們需要將以下 Spring 和 AspectJ 依賴添加到 POM 中:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.2.1</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.20.1</version>
</dependency>

最新版本的 Spring contextaspectjweaver 可在 Maven 倉庫中找到。

現在,我們可以使用 AspectJ 註解語法創建一個簡單的方面:

@Aspect
public class SpringTestAspect {
    @Autowired
    private List accumulator;

    @Around("execution(* com.baeldung.spring.service.SpringSuperService.*(..))")
    public Object auditMethod(ProceedingJoinPoint jp) throws Throwable {
        String methodName = jp.getSignature().getName();
        accumulator.add("Call to " + methodName);
        Object obj = jp.proceed();
        accumulator.add("Method called successfully: " + methodName);
        return obj;
    }
}

我們創建了一個方面,該方面應用於 SpringSuperService 類中的所有方法,為了簡化起見,其結構如下:

public class SpringSuperService {
    public String getInfoFromService(String code) {
        return code;
    }
}

7. Spring AspectJ 方面應用

為了驗證該方面確實應用於服務,讓我們編寫以下單元測試:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { AppConfig.class })
public class TestSpringInterceptor {
    @Autowired
    SpringSuperService springSuperService;

    @Autowired
    private List accumulator;

    @Test
    public void givenService_whenServiceAndAspectExecuted_thenOk() {
        String code = "123456";
        String result = springSuperService.getInfoFromService(code);
        
        Assert.assertThat(accumulator.size(), is(2));
        Assert.assertThat(accumulator.get(0), is("Call to getInfoFromService"));
        Assert.assertThat(accumulator.get(1), is("Method called successfully: getInfoFromService"));
    }
}

在本次測試中,我們注入我們的服務,調用方法並檢查結果。

以下是配置的樣貌:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public SpringSuperService springSuperService() {
        return new SpringSuperService();
    }

    @Bean
    public SpringTestAspect springTestAspect() {
        return new SpringTestAspect();
    }

    @Bean
    public List getAccumulator() {
        return new ArrayList();
    }
}

@EnableAspectJAutoProxy 標註中,一個重要的方面是它支持處理帶有 AspectJ 的 @Aspect 標註的組件,類似於 Spring XML 元素中找到的功能。

8. Spring AspectJ 考慮因素

讓我們指出使用 Spring AspectJ 的一些優勢:

  • 攔截器與業務邏輯解耦
  • 攔截器可以受益於依賴注入
  • 攔截器本身包含所有配置信息
  • 添加新的攔截器不需要增強現有代碼
  • 攔截器具有靈活的機制來選擇要攔截的方法
  • 可以在不使用 Jakarta EE 的情況下使用

當然,還有一些缺點:

  • 您需要了解 AspectJ 語法才能開發攔截器
  • AspectJ 攔截器的學習曲線高於 CDI 攔截器

9. CDI 攔截器 vs Spring AspectJ

如果您的當前項目使用 Spring,那麼考慮使用 CDI 攔截器是一個不錯的選擇。

如果您正在使用大型應用程序服務器,或者您的項目不使用 Spring(或其它框架,例如 Google Guice),並且嚴格遵循 Jakarta EE 標準,那麼唯一的選擇就是使用 CDI 攔截器。

10. 結論在本文中,我們介紹了攔截器模式的兩種實現:CDI攔截器和Spring AspectJ。我們還討論了它們各自的優缺點。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.