Micronaut Bean 注釋元數(shù)據(jù)

2023-03-07 13:39 更新

Java 的 AnnotatedElement API 提供的方法通常不提供在不加載注釋本身的情況下內(nèi)省注釋的能力。它們也不提供任何內(nèi)省注釋構(gòu)造型的能力(通常稱為元注釋;注釋構(gòu)造型是用另一個注釋對一個注釋進(jìn)行注釋的地方,本質(zhì)上是繼承它的行為)。

為了解決這個問題,許多框架生成運行時元數(shù)據(jù)或執(zhí)行昂貴的反射來分析類的注釋。

Micronaut 改為在編譯時生成此注解元數(shù)據(jù),從而避免了昂貴的反射并節(jié)省了內(nèi)存。

BeanContext API 可用于獲取對實現(xiàn) AnnotationMetadata 接口的 BeanDefinition 的引用。

例如,以下代碼獲取所有帶有特定構(gòu)造型注釋的 bean 定義:

按構(gòu)造型查找 Bean 定義

BeanContext beanContext = ... // obtain the bean context
Collection<BeanDefinition> definitions =
    beanContext.getBeanDefinitions(Qualifiers.byStereotype(Controller.class))

for (BeanDefinition definition : definitions) {
    AnnotationValue<Controller> controllerAnn = definition.getAnnotation(Controller.class);
    // do something with the annotation
}

上面的示例找到所有用 @Controller 注釋的 BeanDefinition 實例,無論 @Controller 是直接使用還是通過注釋構(gòu)造型繼承。

請注意,getAnnotation 方法和該方法的變體返回 AnnotationValue 類型而不是 Java 注釋。這是設(shè)計使然,您通常應(yīng)該在讀取注釋值時嘗試使用此 API,因為從性能和內(nèi)存消耗的角度來看,合成代理實現(xiàn)更糟糕。

如果您需要對注釋實例的引用,您可以使用 synthesize 方法,它創(chuàng)建一個實現(xiàn)注釋接口的運行時代理:

合成注解實例

Controller controllerAnn = definition.synthesize(Controller.class);

但是,不推薦使用這種方法,因為它需要反射并由于使用運行時生成的代理而增加內(nèi)存消耗,并且應(yīng)該作為最后的手段使用,例如,如果您需要注釋的實例來與第三方集成圖書館。

注解繼承

Micronaut 將遵守 Java 的 AnnotatedElement API 中定義的關(guān)于注解繼承的規(guī)則:

  • 使用 Inherited 進(jìn)行元注釋的注釋將通過 AnnotationMetadata API 的 getAnnotation* 方法提供,而直接聲明的注釋則通過 getDeclaredAnnotation* 方法提供。

  • 未使用 Inherited 進(jìn)行元注釋的注釋將不會包含在元數(shù)據(jù)中

Micronaut 與 AnnotatedElement API 的不同之處在于它將這些規(guī)則擴展到方法和方法參數(shù),以便:

  • 任何用 Inherited 注釋并出現(xiàn)在被子接口或類 B 覆蓋的接口或超類 A 的方法上的注釋都將繼承到可通過 ExecutableMethod API 從 BeanDefinition 或 AOP 攔截器檢索的 AnnotationMetadata 中。

  • 任何用 Inherited 注釋并出現(xiàn)在被子接口或類 B 覆蓋的接口或超類 A 的方法參數(shù)上的注釋都將繼承到可通過 Argument 接口從 ExecutableMethod API 的 getArguments 方法檢索的 AnnotationMetadata 中。

通常,您可能希望覆蓋的一般行為默認(rèn)情況下不會繼承,包括 Bean 范圍、Bean 限定符、Bean 條件、驗證規(guī)則等。

如果您希望在子類化時繼承特定的范圍、限定符或一組要求,那么您可以定義一個用@Inherited 注釋的元注釋。例如:

定義繼承的元注解

 Java Groovy  Kotlin 
import io.micronaut.context.annotation.AliasFor;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.AnnotationMetadata;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited // (1)
@Retention(RetentionPolicy.RUNTIME)
@Requires(property = "datasource.url") // (2)
@Named // (3)
@Singleton // (4)
public @interface SqlRepository {
    @AliasFor(annotation = Named.class, member = AnnotationMetadata.VALUE_MEMBER) // (5)
    String value() default "";
}
import io.micronaut.context.annotation.AliasFor
import io.micronaut.context.annotation.Requires
import io.micronaut.core.annotation.AnnotationMetadata
import jakarta.inject.Named
import jakarta.inject.Singleton

import java.lang.annotation.Inherited
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy

@Inherited // (1)
@Retention(RetentionPolicy.RUNTIME)
@Requires(property = "datasource.url") // (2)
@Named // (3)
@Singleton // (4)
@interface SqlRepository {
    @AliasFor(annotation = Named.class, member = AnnotationMetadata.VALUE_MEMBER) // (5)
    String value() default "";
}
import io.micronaut.context.annotation.Requires
import jakarta.inject.Named
import jakarta.inject.Singleton
import java.lang.annotation.Inherited

@Inherited // (1)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@Requires(property = "datasource.url") // (2)
@Named // (3)
@Singleton // (4)
annotation class SqlRepository(
    val value: String = ""
)
  1. 注解聲明為@Inherited

  2. Bean Conditions 將被子類繼承

  3. Bean 限定符將由子類繼承

  4. Bean Scopes 將被子類繼承

  5. 你也可以給注解起別名,它們會被繼承

使用此元注釋,您可以將注釋添加到超類:

在超類上使用繼承的元注釋

 Java Groovy  Kotlin 
@SqlRepository
public abstract class BaseSqlRepository {
}
@SqlRepository
abstract class BaseSqlRepository {
}
@SqlRepository
abstract class BaseSqlRepository

然后子類將繼承所有注釋:

在子類中繼承注解

 Java Groovy  Kotlin 
import jakarta.inject.Named;
import javax.sql.DataSource;

@Named("bookRepository")
public class BookRepository extends BaseSqlRepository {
    private final DataSource dataSource;

    public BookRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}
import jakarta.inject.Named
import javax.sql.DataSource

@Named("bookRepository")
class BookRepository extends BaseSqlRepository {
    private final DataSource dataSource

    BookRepository(DataSource dataSource) {
        this.dataSource = dataSource
    }
}
import jakarta.inject.Named
import javax.sql.DataSource

@Named("bookRepository")
class BookRepository(private val dataSource: DataSource) : BaseSqlRepository()

子類必須至少有一個 bean 定義注釋,例如范圍或限定符。

別名/映射注釋

有時您可能希望將注釋成員的值作為另一個注釋成員的值的別名。為此,請使用@AliasFor 注釋。

例如,一個常見的用例是注釋定義了 value() 成員,但也支持其他成員。例如 @Client 注解:

@Client 注解

public @interface Client {

    /**
     * @return The URL or service ID of the remote service
     */
    @AliasFor(member = "id") (1)
    String value() default "";

    /**
     * @return The ID of the client
     */
    @AliasFor(member = "value") (2)
    String id() default "";
}
  1. value 成員也設(shè)置了 id 成員

  2. id 成員也設(shè)置了 value 成員

有了這些別名,無論您定義@Client("foo") 還是@Client(id="foo"),value 和 id 成員都將被設(shè)置,從而更容易解析和使用注釋。

如果您無法控制注釋,另一種方法是使用 AnnotationMapper。要創(chuàng)建 AnnotationMapper,請執(zhí)行以下操作:

  • 實現(xiàn) AnnotationMapper 接口

  • 定義一個 META-INF/services/io.micronaut.inject.annotation.AnnotationMapper 文件引用實現(xiàn)類

  • 將包含實現(xiàn)的 JAR 文件添加到 annotationProcessor 類路徑(Kotlin 的 kapt)

因為 AnnotationMapper 實現(xiàn)必須在注釋處理器類路徑上,所以它們通常應(yīng)該在一個包含很少外部依賴項的項目中,以避免污染注釋處理器類路徑。

以下是改進(jìn) JPA 實體的內(nèi)省功能的示例 AnnotationMapper。

EntityIntrospectedAnnotationMapper 映射器示例

public class EntityIntrospectedAnnotationMapper implements NamedAnnotationMapper {
    @NonNull
    @Override
    public String getName() {
        return "javax.persistence.Entity";
    }

    @Override
    public List<AnnotationValue<?>> map(AnnotationValue<Annotation> annotation, VisitorContext visitorContext) { (1)
        final AnnotationValueBuilder<Introspected> builder = AnnotationValue.builder(Introspected.class)
                // don't bother with transients properties
                .member("excludedAnnotations", "javax.persistence.Transient"); (2)
        return Arrays.asList(
                builder.build(),
                AnnotationValue.builder(ReflectiveAccess.class).build()
        );
    }
}
  1. map 方法接收帶有注釋值的 AnnotationValue。

  2. 可以返回一個或多個注釋,在本例中為@Transient。

上面的示例實現(xiàn)了 NamedAnnotationMapper 接口,該接口允許將注釋與運行時代碼混合。要針對具體注釋類型進(jìn)行操作,請改用 TypedAnnotationMapper,但請注意,它需要注釋類本身位于注釋處理器類路徑中。


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號