SeamFramework.orgCommunity Documentation

章 7. 攔截器(Interceptor)

7.1. 攔截器綁定
7.2. 實做攔截器
7.3. 啟用攔截器
7.4. 與成員綁定的攔截器
7.5. 多重攔截器綁定標記
7.6. 攔截器綁定類型的 inheritance
7.7. 使用 @Interceptors

Web Bean 會重新使用 EJB 3.0 的基本攔截器架構,並朝兩個方向來延伸該功能:

EJB 格式定義了兩種類型的攔截點:

business method 攔截器可適用於來自 Web Bean 客戶端的 Web Bean 的 method 調用:

public class TransactionInterceptor {

    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

lifecycle callback 攔截器可適用於來自 container 的 lifecycle callback 的調用:

public class DependencyInjectionInterceptor {

    @PostConstruct public void injectDependencies(InvocationContext ctx) { ... }
}

攔截器 class 可攔截 lifecycle callback 以及 business method。

假設我們希望宣告我們有些 Web Bean 屬於交易性的 Web Bean。我們首先需要的就是一個 攔截器綁定標記(interceptor binding annotation)來明確指定我們要專注的 Web Bean 是哪個:

@InterceptorBindingType

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

現在我們能輕易地指定我們的 ShoppingCart 屬於一個交易性的物件:

@Transactional

public class ShoppingCart { ... }

或是,若我們想要的話,我們能夠指定只有一個 method 屬於交易性:

public class ShoppingCart {

    @Transactional public void checkout() { ... }
}

我們必須在某個階段實際地實做提供此交易管理功能的攔截器。我們只需要建立一個標準的 EJB 攔截器並將它標記為 @Interceptor@Transactional

@Transactional @Interceptor

public class TransactionInterceptor {
    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

所有的 Web Bean 攔截器皆為基本的 Web Bean,並且能夠有效利用依賴注入(dependency injection)以及 contextual lifecycle 管理。

@ApplicationScoped @Transactional @Interceptor

public class TransactionInterceptor {
    @Resource Transaction transaction;
    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
    
}

多個攔截器可能會使用相同的攔截器綁定類型。

最後,我們需要在 web-beans.xml啟用我們的攔截器。


<Interceptors>
    <tx:TransactionInterceptor/>
</Interceptors
>

為何會有尖括弧?

XML 宣告可解決兩項問題:

比方說,我們能夠指定我們的安全性攔截器能比我們的 TransactionInterceptor 還要早執行。


<Interceptors>
    <sx:SecurityInterceptor/>
    <tx:TransactionInterceptor/>
</Interceptors
>

或是我們可在測試環境中將它們兩者同時關閉!

假設我們希望新增一些額外的資訊至我們的 @Transactional 標記中:

@InterceptorBindingType

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {
    boolean requiresNew() default false;
}

Web Bean 將會使用 requiresNew 的值來在兩個不同的攔截器(TransactionInterceptorRequiresNewTransactionInterceptor)之間作選擇。

@Transactional(requiresNew=true) @Interceptor

public class RequiresNewTransactionInterceptor {
    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

現在我們可如下使用 RequiresNewTransactionInterceptor

@Transactional(requiresNew=true)

public class ShoppingCart { ... }

當綁定攔截器時,若我們只有一個攔截器而我們希望管理員忽略 requiresNew 的值時該怎麼辦呢?這時我們可使用 @NonBinding 標記:

@InterceptorBindingType

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Secure {
    @NonBinding String[] rolesAllowed() default {};
}

通常,我們會使用不同攔截器綁定類型的組合來將多重攔截器綁定至 Web Bean。比方說,下列宣告會被用來將 TransactionInterceptorSecurityInterceptor 綁定至相同的 Web Bean:

@Secure(rolesAllowed="admin") @Transactional

public class ShoppingCart { ... }

不過,在非常複雜的情況下,攔截器本身會指定一些攔截器綁定類型的組合:

@Transactional @Secure @Interceptor

public class TransactionalSecureInterceptor { ... }

然後這個攔截器便可透過使用下列任何一個組合來綁定至 checkout() 這個 method:

public class ShoppingCart {

    @Transactional @Secure public void checkout() { ... }
}
@Secure

public class ShoppingCart {
    @Transactional public void checkout() { ... }
}
@Transactionl

public class ShoppingCart {
    @Secure public void checkout() { ... }
}
@Transactional @Secure

public class ShoppingCart {
    public void checkout() { ... }
}

Java 語言對於標記上的支援有個限制,那就是缺少了標記的 inheritance。實際上,標記應該要內建 reuse,以便允許這類型的功能有效:

public @interface Action extends Transactional, Secure { ... }

幸運的是,Web Bean 有方法來解決 Java 所缺少的這項功能上的問題。我們可利用其它攔截器綁定類型來標記某個攔截器綁定類型。攔截器綁定為可轉變的 — 任何含有第一個攔截器綁定的 Web Bean 都會繼承宣告為 meta-annotation 的攔截器綁定。

@Transactional @Secure

@InterceptorBindingType
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action { ... }

任何標記為 @Action 的 Web Bean 都會被綁定至 TransactionInterceptorSecurityInterceptor。(甚至是 TransactionalSecureInterceptor,若它存在的話。)

企業級與基本的 Web Bean 皆支援 EJB 規格所定義的 @Interceptors 標記,例如:

@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class})

public class ShoppingCart {
    public void checkout() { ... }
}

不過,這個方式有下列缺點:

因此,我們建議使用 Web Bean 形式的攔截器綁定。