您可能想要應(yīng)用的最常見類型的通知是“Around”通知,它可以讓您修飾方法的行為。
Writing Around Advice
第一步是定義一個將觸發(fā) MethodInterceptor 的注解:
Around Advice 注釋示例
Java |
Groovy |
Kotlin |
import io.micronaut.aop.Around;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME) // (1)
@Target({TYPE, METHOD}) // (2)
@Around // (3)
public @interface NotNull {
}
|
import io.micronaut.aop.Around
import java.lang.annotation.*
import static java.lang.annotation.ElementType.*
import static java.lang.annotation.RetentionPolicy.RUNTIME
@Documented
@Retention(RUNTIME) // (1)
@Target([TYPE, METHOD]) // (2)
@Around // (3)
@interface NotNull {
}
|
import io.micronaut.aop.Around
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FILE
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
@MustBeDocumented
@Retention(RUNTIME) // (1)
@Target(CLASS, FILE, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) // (2)
@Around // (3)
annotation class NotNull
|
注解的保留策略應(yīng)該是RUNTIME
通常,您希望能夠在類或方法級別應(yīng)用建議,因此目標(biāo)類型是 TYPE 和 METHOD
添加 @Around 注解是為了告訴 Micronaut 這個注解是 Around advice
定義 Around 建議的下一步是實(shí)現(xiàn) MethodInterceptor。例如,以下攔截器不允許具有空值的參數(shù):
方法攔截器示例
Java |
Groovy |
Kotlin |
import io.micronaut.aop.InterceptorBean;
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.type.MutableArgumentValue;
import jakarta.inject.Singleton;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@Singleton
@InterceptorBean(NotNull.class) // (1)
public class NotNullInterceptor implements MethodInterceptor<Object, Object> { // (2)
@Nullable
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.getParameters()
.entrySet()
.stream()
.filter(entry -> {
MutableArgumentValue<?> argumentValue = entry.getValue();
return Objects.isNull(argumentValue.getValue());
})
.findFirst(); // (3)
if (nullParam.isPresent()) {
throw new IllegalArgumentException("Null parameter [" + nullParam.get().getKey() + "] not allowed"); // (4)
}
return context.proceed(); // (5)
}
}
|
import io.micronaut.aop.InterceptorBean
import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import io.micronaut.core.annotation.Nullable
import io.micronaut.core.type.MutableArgumentValue
import jakarta.inject.Singleton
@Singleton
@InterceptorBean(NotNull) // (1)
class NotNullInterceptor implements MethodInterceptor<Object, Object> { // (2)
@Nullable
@Override
Object intercept(MethodInvocationContext<Object, Object> context) {
Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.parameters
.entrySet()
.stream()
.filter({entry ->
MutableArgumentValue<?> argumentValue = entry.value
return Objects.isNull(argumentValue.value)
})
.findFirst() // (3)
if (nullParam.present) {
throw new IllegalArgumentException("Null parameter [${nullParam.get().key}] not allowed") // (4)
}
return context.proceed() // (5)
}
}
|
import io.micronaut.aop.InterceptorBean
import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import java.util.Objects
import jakarta.inject.Singleton
@Singleton
@InterceptorBean(NotNull::class) // (1)
class NotNullInterceptor : MethodInterceptor<Any, Any> { // (2)
override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
val nullParam = context.parameters
.entries
.stream()
.filter { entry ->
val argumentValue = entry.value
Objects.isNull(argumentValue.value)
}
.findFirst() // (3)
return if (nullParam.isPresent) {
throw IllegalArgumentException("Null parameter [${nullParam.get().key}] not allowed") // (4)
} else {
context.proceed() // (5)
}
}
}
|
@InterceptorBean 注解用于指示攔截器與什么注解相關(guān)聯(lián)。請注意,@InterceptorBean 是使用 @Singleton 的默認(rèn)范圍進(jìn)行元注釋的,因此如果您想要創(chuàng)建一個新的攔截器并將其與每個攔截的 bean 相關(guān)聯(lián),您應(yīng)該使用 @Prototype 對攔截器進(jìn)行注釋。
攔截器實(shí)現(xiàn) MethodInterceptor 接口。
傳遞的 MethodInvocationContext 用于查找第一個為 null 的參數(shù)
如果發(fā)現(xiàn)空參數(shù),則拋出異常
否則調(diào)用 proceed() 以繼續(xù)方法調(diào)用。
Micronaut AOP 攔截器不使用反射來提高性能并減少堆棧跟蹤大小,從而改進(jìn)調(diào)試。
將注釋應(yīng)用于目標(biāo)類以使新的 MethodInterceptor 起作用:
環(huán)繞建議用法示例
Java |
Groovy |
Kotlin |
import jakarta.inject.Singleton;
@Singleton
public class NotNullExample {
@NotNull
void doWork(String taskName) {
System.out.println("Doing job: " + taskName);
}
}
|
import jakarta.inject.Singleton
@Singleton
class NotNullExample {
@NotNull
void doWork(String taskName) {
println "Doing job: $taskName"
}
}
|
import jakarta.inject.Singleton
@Singleton
open class NotNullExample {
@NotNull
open fun doWork(taskName: String?) {
println("Doing job: $taskName")
}
}
|
每當(dāng)將 NotNullExample 類型注入到類中時,就會注入一個編譯時生成的代理,該代理會使用之前定義的 @NotNull 通知來裝飾方法調(diào)用。您可以通過編寫測試來驗(yàn)證該建議是否有效。以下測試驗(yàn)證當(dāng)參數(shù)為 null 時是否拋出了預(yù)期的異常:
Around Advice Test
Java |
Groovy |
Kotlin |
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testNotNull() {
try (ApplicationContext applicationContext = ApplicationContext.run()) {
NotNullExample exampleBean = applicationContext.getBean(NotNullExample.class);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Null parameter [taskName] not allowed");
exampleBean.doWork(null);
}
}
|
void "test not null"() {
when:
def applicationContext = ApplicationContext.run()
def exampleBean = applicationContext.getBean(NotNullExample)
exampleBean.doWork(null)
then:
IllegalArgumentException e = thrown()
e.message == 'Null parameter [taskName] not allowed'
cleanup:
applicationContext.close()
}
|
@Test
fun testNotNull() {
val applicationContext = ApplicationContext.run()
val exampleBean = applicationContext.getBean(NotNullExample::class.java)
val exception = shouldThrow<IllegalArgumentException> {
exampleBean.doWork(null)
}
exception.message shouldBe "Null parameter [taskName] not allowed"
applicationContext.close()
}
|
由于 Micronaut 注入發(fā)生在編譯時,通常在編譯上述測試時,建議應(yīng)打包在類路徑上的依賴 JAR 文件中。它不應(yīng)該在同一個代碼庫中,因?yàn)槟幌M诰幾g建議本身之前編譯測試。
自定義代理生成
Around 注釋的默認(rèn)行為是在編譯時生成一個代理,該代理是被代理類的子類。換句話說,在前面的示例中,將生成 NotNullExample 類的編譯時子類,其中代理方法裝飾有攔截器處理,并且通過調(diào)用 super 來調(diào)用原始行為。
這種行為更有效,因?yàn)橹恍枰粋€ bean 實(shí)例,但是根據(jù)用例,您可能希望改變這種行為。 @Around 注釋支持各種允許您更改此行為的屬性,包括:
proxyTarget(默認(rèn)為 false)- 如果設(shè)置為 true,代理將委托給原始 bean 實(shí)例,而不是調(diào)用 super 的子類
hotswap(默認(rèn)為 false)- 與 proxyTarget=true 相同,但另外代理實(shí)現(xiàn) HotSwappableInterceptedProxy,它將每個方法調(diào)用包裝在 ReentrantReadWriteLock 中,并允許在運(yùn)行時交換目標(biāo)實(shí)例。
lazy(默認(rèn)為 false)- 默認(rèn)情況下,Micronaut 在創(chuàng)建代理時急切地初始化代理目標(biāo)。如果設(shè)置為 true,則代理目標(biāo)將針對每個方法調(diào)用延遲解析。
@Factory Beans 上的 AOP 建議
當(dāng)應(yīng)用于 Bean 工廠時,AOP 建議的語義與常規(guī) bean 不同,應(yīng)用以下規(guī)則:
在 @Factory bean 的類級別應(yīng)用的 AOP 建議將建議應(yīng)用于工廠本身,而不應(yīng)用于使用 @Bean 注釋定義的任何 bean。
應(yīng)用于用 bean 范圍注釋的方法的 AOP 建議將 AOP 建議應(yīng)用于工廠生產(chǎn)的 bean。
考慮以下兩個示例:
AOP Advice at the type level of a @Factory
Java |
Groovy |
Kotlin |
@Timed
@Factory
public class MyFactory {
@Prototype
public MyBean myBean() {
return new MyBean();
}
}
|
@Timed
@Factory
class MyFactory {
@Prototype
MyBean myBean() {
new MyBean()
}
}
|
@Timed
@Factory
open class MyFactory {
@Prototype
open fun myBean(): MyBean {
return MyBean()
}
}
|
上面的示例記錄了創(chuàng)建 MyBean bean 所花費(fèi)的時間。
現(xiàn)在考慮這個例子:
AOP Advice at the method level of a @Factory
Java |
Groovy |
Kotlin |
@Factory
public class MyFactory {
@Prototype
@Timed
public MyBean myBean() {
return new MyBean();
}
}
|
@Factory
class MyFactory {
@Prototype
@Timed
MyBean myBean() {
new MyBean()
}
}
|
@Factory
open class MyFactory {
@Prototype
@Timed
open fun myBean(): MyBean {
return MyBean()
}
}
|
上面的示例記錄了執(zhí)行 MyBean bean 的公共方法所花費(fèi)的時間,而不是創(chuàng)建 bean 所花費(fèi)的時間。
這種行為的基本原理是您有時可能希望將建議應(yīng)用于工廠,而在其他時候?qū)⒔ㄗh應(yīng)用于工廠生產(chǎn)的 bean。
請注意,目前無法將方法級別的建議應(yīng)用于 @Factory bean,并且工廠的所有建議都必須在類型級別應(yīng)用。您可以通過將未應(yīng)用建議的方法定義為非公開的方法來控制哪些方法應(yīng)用了建議。
更多建議: