Micronaut Around Advice

2023-03-01 17:38 更新

您可能想要應(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
  1. 注解的保留策略應(yīng)該是RUNTIME

  2. 通常,您希望能夠在類或方法級別應(yīng)用建議,因此目標(biāo)類型是 TYPE 和 METHOD

  3. 添加 @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)
        }
    }
}
  1. @InterceptorBean 注解用于指示攔截器與什么注解相關(guān)聯(lián)。請注意,@InterceptorBean 是使用 @Singleton 的默認(rèn)范圍進(jìn)行元注釋的,因此如果您想要創(chuàng)建一個新的攔截器并將其與每個攔截的 bean 相關(guān)聯(lián),您應(yīng)該使用 @Prototype 對攔截器進(jìn)行注釋。

  2. 攔截器實(shí)現(xiàn) MethodInterceptor 接口。

  3. 傳遞的 MethodInvocationContext 用于查找第一個為 null 的參數(shù)

  4. 如果發(fā)現(xiàn)空參數(shù),則拋出異常

  5. 否則調(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ī)則:

  1. 在 @Factory bean 的類級別應(yīng)用的 AOP 建議將建議應(yīng)用于工廠本身,而不應(yīng)用于使用 @Bean 注釋定義的任何 bean。

  2. 應(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)用了建議。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號