在〈增添行為〉中談過,如何使用 Java 動態代理,來達到為物件增加原本未定義的行為,在 Spring AOP 中,增加行為的這個動作稱為 Introduction,第一個動作同樣地是要定義介面:
package cc.openhome.aspect;
public interface Nullable {
void enable();
void disable();
boolean isEnabled();
}
接著要實作介面,例如:
package cc.openhome.aspect;
public class NullableIntroduction implements Nullable {
private boolean enabled;
@Override
public void enable() {
enabled = true;
}
@Override
public void disable() {
enabled = false;
}
public boolean isEnabled() {
return enabled;
}
}
嗯?就這樣?沒錯!就這樣,要導入目標物件的行為是分開設計的,如果要將這個行為導入至 AccountDAO
,可以如下設定:
package cc.openhome.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class NullableAspect {
@DeclareParents(
value = "cc.openhome.model.AccountDAO+",
defaultImpl = cc.openhome.aspect.NullableIntroduction.class
)
public Nullable nullableIntroduction; // configuration only
}
@DeclareParents
用來宣告導入行為,value
用以設定導入的目標,+
表示可以是其子類或實作類別,defaultImpl
表示實作的 Introduction 類別。
這麼一來,如果 accountDAO
變數參考了 Spring 注入的實例,該實例就會是實作了 Nullable
的代理物件,也就可以在 Cast 之後進行操作:
((Nullable) accountDAO).enable();
如果單純只是為了增加行為,不與目標物件上的資訊或方法互動,只寫上頭的程式碼當然沒問題,不過,若要進一步有〈增添行為〉中檢查參數值是否為 null
的功能,NullableAspect
就得多些設計,例如,結合 @Around
來進行 null
檢查:
package cc.openhome.aspect;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class NullableAspect {
@DeclareParents(
value = "cc.openhome.model.AccountDAO+",
defaultImpl = cc.openhome.aspect.NullableIntroduction.class
)
public Nullable nullableIntroduction; // configuration only
@Around("execution(* cc.openhome.model.AccountDAO.*(..)) && this(nullable)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, Nullable nullable) throws Throwable {
Object target = proceedingJoinPoint.getTarget();
Object[] args = proceedingJoinPoint.getArgs();
String methodName = proceedingJoinPoint.getSignature().getName();
if(!nullable.isEnabled() && Arrays.stream(args).anyMatch(arg -> arg == null)) {
throw new IllegalArgumentException(
String.format("argument(s) of %s.%s cannot be null",
target.getClass().getName(),
methodName
)
);
}
return proceedingJoinPoint.proceed();
}
}
在 @Around
的 Pointcut 設定中看到了 this(nullable)
,this
表示代理物件必須實作指定的介面,nullable
定義了方法中使用的參數名稱,從參數型態可得知要實作的介面,如此一來,代理物件就可以注入方法上定義的 Nullable nullable
參數,於是就可以檢查 nullable.isEnable()
傳回值,來決定要拋出例外,或者是執行目標物件上的方法。
你可以在 Introduction 中找到以上的範例專案。