Micronaut Bean 注释元数据
Java 的 AnnotatedElement API 提供的方法通常不提供在不加载注释本身的情况下内省注释的能力。它们也不提供任何内省注释构造型的能力(通常称为元注释;注释构造型是用另一个注释对一个注释进行注释的地方,本质上是继承它的行为)。
为了解决这个问题,许多框架生成运行时元数据或执行昂贵的反射来分析类的注释。
Micronaut 改为在编译时生成此注解元数据,从而避免了昂贵的反射并节省了内存。
BeanContext API 可用于获取对实现 AnnotationMetadata 接口的 BeanDefinition 的引用。
例如,以下代码获取所有带有特定构造型注释的 bean 定义:
按构造型查找 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 是直接使用还是通过注释构造型继承。
请注意,getAnnotation 方法和该方法的变体返回 AnnotationValue 类型而不是 Java 注释。这是设计使然,您通常应该在读取注释值时尝试使用此 API,因为从性能和内存消耗的角度来看,合成代理实现更糟糕。
如果您需要对注释实例的引用,您可以使用 synthesize 方法,它创建一个实现注释接口的运行时代理:
合成注解实例
Controller controllerAnn = definition.synthesize(Controller.class);
但是,不推荐使用这种方法,因为它需要反射并由于使用运行时生成的代理而增加内存消耗,并且应该作为最后的手段使用,例如,如果您需要注释的实例来与第三方集成图书馆。
注解继承
Micronaut 将遵守 Java 的 AnnotatedElement API 中定义的关于注解继承的规则:
使用 Inherited 进行元注释的注释将通过 AnnotationMetadata API 的 getAnnotation* 方法提供,而直接声明的注释则通过 getDeclaredAnnotation* 方法提供。
未使用 Inherited 进行元注释的注释将不会包含在元数据中
Micronaut 与 AnnotatedElement API 的不同之处在于它将这些规则扩展到方法和方法参数,以便:
任何用 Inherited 注释并出现在被子接口或类 B 覆盖的接口或超类 A 的方法上的注释都将继承到可通过 ExecutableMethod API 从 BeanDefinition 或 AOP 拦截器检索的 AnnotationMetadata 中。
任何用 Inherited 注释并出现在被子接口或类 B 覆盖的接口或超类 A 的方法参数上的注释都将继承到可通过 Argument 接口从 ExecutableMethod API 的 getArguments 方法检索的 AnnotationMetadata 中。
通常,您可能希望覆盖的一般行为默认情况下不会继承,包括 Bean 范围、Bean 限定符、Bean 条件、验证规则等。
如果您希望在子类化时继承特定的范围、限定符或一组要求,那么您可以定义一个用@Inherited 注释的元注释。例如:
定义继承的元注解
Java | Groovy | Kotlin |
|
|
|
注解声明为@Inherited
Bean Conditions 将被子类继承
Bean 限定符将由子类继承
Bean Scopes 将被子类继承
你也可以给注解起别名,它们会被继承
使用此元注释,您可以将注释添加到超类:
在超类上使用继承的元注释
Java | Groovy | Kotlin |
|
|
|
然后子类将继承所有注释:
在子类中继承注解
Java | Groovy | Kotlin |
|
|
|
子类必须至少有一个 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 "";
}
value 成员也设置了 id 成员
id 成员也设置了 value 成员
有了这些别名,无论您定义@Client("foo") 还是@Client(id="foo"),value 和 id 成员都将被设置,从而更容易解析和使用注释。
如果您无法控制注释,另一种方法是使用 AnnotationMapper。要创建 AnnotationMapper,请执行以下操作:
实现 AnnotationMapper 接口
定义一个 META-INF/services/io.micronaut.inject.annotation.AnnotationMapper 文件引用实现类
将包含实现的 JAR 文件添加到 annotationProcessor 类路径(Kotlin 的 kapt)
因为 AnnotationMapper 实现必须在注释处理器类路径上,所以它们通常应该在一个包含很少外部依赖项的项目中,以避免污染注释处理器类路径。
以下是改进 JPA 实体的内省功能的示例 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()
);
}
}
map 方法接收带有注释值的 AnnotationValue。
可以返回一个或多个注释,在本例中为@Transient。
上面的示例实现了 NamedAnnotationMapper 接口,该接口允许将注释与运行时代码混合。要针对具体注释类型进行操作,请改用 TypedAnnotationMapper,但请注意,它需要注释类本身位于注释处理器类路径中。
更多建议: