阅读(542) (0)

Micronaut Bean 自省

2023-02-23 10:58:42 更新

自 Micronaut 1.1 以来,已经包含了 JDK 的 Introspector 类的编译时替代品。

BeanIntrospector 和 BeanIntrospection 接口允许查找 bean 自省以实例化和读/写 bean 属性,而无需使用反射或缓存反射元数据,这会为大型 bean 消耗过多的内存。

使 Bean 可用于自省

与 JDK 的 Introspector 不同,每个类都不会自动进行内省。要使一个类可用于自省,您必须至少在构建中启用 Micronaut 的注释处理器(用于 Java 和 Kotlin 的 micronaut-inject-java 和用于 Groovy 的 micronaut-inject-groovy),并确保您对 micronaut-core 具有运行时依赖性.

 Gradle  Maven
annotationProcessor("io.micronaut:micronaut-inject-java:3.8.5")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-inject-java</artifactId>
        <version>3.8.5</version>
    </path>
</annotationProcessorPaths>

对于 Kotlin,在 kapt 范围内添加 micronaut-inject-java 依赖项,对于 Groovy,在 compileOnly 范围内添加 micronaut-inject-groovy。

 Gradle Maven 
runtimeOnly("io.micronaut:micronaut-core:3.8.5")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-core</artifactId>
    <version>3.8.5</version>
    <scope>runtime</scope>
</dependency>

配置构建后,您可以通过多种方式生成内省数据。

使用@Introspected 注解

@Introspected 注释可以用在任何类上以使其可用于自省。简单地用@Introspected 注释类:

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Introspected;

@Introspected
public class Person {

    private String name;
    private int age = 18;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
import groovy.transform.Canonical
import io.micronaut.core.annotation.Introspected

@Introspected
@Canonical
class Person {

    String name
    int age = 18

    Person(String name) {
        this.name = name
    }
}
import io.micronaut.core.annotation.Introspected

@Introspected
data class Person(var name : String) {
    var age : Int = 18
}

在编译时生成内省数据后,通过 BeanIntrospection API 检索它:

 Java Groovy  Kotlin 
final BeanIntrospection<Person> introspection = BeanIntrospection.getIntrospection(Person.class); // (1)
Person person = introspection.instantiate("John"); // (2)
System.out.println("Hello " + person.getName());

final BeanProperty<Person, String> property = introspection.getRequiredProperty("name", String.class); // (3)
property.set(person, "Fred"); // (4)
String name = property.get(person); // (5)
System.out.println("Hello " + person.getName());
def introspection = BeanIntrospection.getIntrospection(Person) // (1)
Person person = introspection.instantiate("John") // (2)
println("Hello $person.name")

BeanProperty<Person, String> property = introspection.getRequiredProperty("name", String) // (3)
property.set(person, "Fred") // (4)
String name = property.get(person) // (5)
println("Hello $person.name")
val introspection = BeanIntrospection.getIntrospection(Person::class.java) // (1)
val person : Person = introspection.instantiate("John") // (2)
print("Hello ${person.name}")

val property : BeanProperty<Person, String> = introspection.getRequiredProperty("name", String::class.java) // (3)
property.set(person, "Fred") // (4)
val name = property.get(person) // (5)
print("Hello ${person.name}")
  1. 您可以使用静态 getIntrospection 方法检索 BeanIntrospection

  2. 一旦有了 BeanIntrospection,就可以使用 instantiate 方法实例化一个 bean。

  3. 可以从内省中检索 BeanProperty

  4. 使用set方法设置属性值

  5. 使用get方法获取属性值

将@Introspected 与@AccessorsStyle 一起使用

可以将@AccessorsStyle 注释与@Introspected 一起使用:

import io.micronaut.core.annotation.AccessorsStyle;
import io.micronaut.core.annotation.Introspected;

@Introspected
@AccessorsStyle(readPrefixes = "", writePrefixes = "") (1)
public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { (2)
        return name;
    }

    public void name(String name) { (2)
        this.name = name;
    }

    public int age() { (2)
        return age;
    }

    public void age(int age) { (2)
        this.age = age;
    }
}
  1. 使用@AccessorsStyle 注释该类,为getter 和setter 定义空的读写前缀。

  2. 定义不带前缀的 getter 和 setter。

现在可以使用 BeanIntrospection API 检索编译时生成的内省:

BeanIntrospection<Person> introspection = BeanIntrospection.getIntrospection(Person.class);
Person person = introspection.instantiate("John", 42);

Assertions.assertEquals("John", person.name());
Assertions.assertEquals(42, person.age());

Bean Fields

默认情况下,Java 自省仅将 JavaBean getter/setter 或 Java 16 记录组件视为 bean 属性。但是,您可以使用 @Introspected 注释的 accessKind 成员在 Java 中定义具有公共或包保护字段的类:

 Java Groovy 
import io.micronaut.core.annotation.Introspected;

@Introspected(accessKind = Introspected.AccessKind.FIELD)
public class User {
    public final String name; // (1)
    public int age = 18; // (2)

    public User(String name) {
        this.name = name;
    }
}
import io.micronaut.core.annotation.Introspected

@Introspected(accessKind = Introspected.AccessKind.FIELD)
class User {
    public final String name // (1)
    public int age = 18 // (2)

    User(String name) {
        this.name = name
    }
}
  1. 最终字段被视为只读属性

  2. 可变字段被视为读写属性

accessKind 接受一个数组,因此可以允许两种类型的访问器,但根据它们在注释中出现的顺序更喜欢一种或另一种。列表中的第一个具有优先权。

在 Kotlin 中不可能对字段进行自省,因为无法直接声明字段。

构造方法

对于有多个构造函数的类,在构造函数上加上@Creator注解即可使用。

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;

import javax.annotation.concurrent.Immutable;

@Introspected
@Immutable
public class Vehicle {

    private final String make;
    private final String model;
    private final int axles;

    public Vehicle(String make, String model) {
        this(make, model, 2);
    }

    @Creator // (1)
    public Vehicle(String make, String model, int axles) {
        this.make = make;
        this.model = model;
        this.axles = axles;
    }

    public String getMake() {
        return make;
    }

    public String getModel() {
        return model;
    }

    public int getAxles() {
        return axles;
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Vehicle {

    final String make
    final String model
    final int axles

    Vehicle(String make, String model) {
        this(make, model, 2)
    }

    @Creator // (1)
    Vehicle(String make, String model, int axles) {
        this.make = make
        this.model = model
        this.axles = axles
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Vehicle @Creator constructor(val make: String, val model: String, val axles: Int) { // (1)

    constructor(make: String, model: String) : this(make, model, 2) {}
}
  1. @Creator 注释表示要使用哪个构造函数

此类没有默认构造函数,因此调用不带参数的实例化会抛出 InstantiationException。

静态创建者方法

@Creator 注释可以应用于创建类实例的静态方法。

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;

import javax.annotation.concurrent.Immutable;

@Introspected
@Immutable
public class Business {

    private final String name;

    private Business(String name) {
        this.name = name;
    }

    @Creator // (1)
    public static Business forName(String name) {
        return new Business(name);
    }

    public String getName() {
        return name;
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Business {

    final String name

    private Business(String name) {
        this.name = name
    }

    @Creator // (1)
    static Business forName(String name) {
        new Business(name)
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Business private constructor(val name: String) {
    companion object {

        @Creator // (1)
        fun forName(name: String): Business {
            return Business(name)
        }
    }

}
  1. @Creator注解应用于实例化类的静态方法

可以有多个注释的“创建者”方法。如果有一个没有参数,它将是默认的构造方法。第一个带参数的方法将用作主要构造方法。

Enums

也可以内省枚举。给枚举加上注解,可以通过标准的valueOf方法构造。

在配置类上使用@Introspected 注解

如果要内省的类已经编译并且不受您的控制,则另一种选择是使用 @Introspected 注释集的类成员定义配置类。

 Java Groovy  Kotlin 
import io.micronaut.core.annotation.Introspected;

@Introspected(classes = Person.class)
public class PersonConfiguration {
}
import io.micronaut.core.annotation.Introspected

@Introspected(classes = Person)
class PersonConfiguration {
}
import io.micronaut.core.annotation.Introspected

@Introspected(classes = [Person::class])
class PersonConfiguration

在上面的示例中,PersonConfiguration 类为 Person 类生成内省。

您还可以使用 @Introspected 的 packages 成员,该包在编译时扫描并为包中的所有类生成内省。但是请注意,此功能目前被视为实验性的。

编写一个 AnnotationMapper 来检查现有的注释

如果您希望默认内省现有注释,则可以编写一个 AnnotationMapper。

这方面的一个例子是 EntityIntrospectedAnnotationMapper,它确保所有用 javax.persistence.Entity 注释的 bean 在默认情况下都是可自省的。

AnnotationMapper 必须位于注释处理器类路径中。

The BeanWrapper API

BeanProperty 提供读取和写入给定类的属性值的原始访问,并且不提供任何自动类型转换。

您传递给 set 和 get 方法的值应该与底层属性类型相匹配,否则会发生异常。

为了提供额外的类型转换智能,BeanWrapper 接口允许包装现有的 bean 实例并设置和获取 bean 的属性,并在必要时执行类型转换。

 Java Groovy  Kotlin 
final BeanWrapper<Person> wrapper = BeanWrapper.getWrapper(new Person("Fred")); // (1)

wrapper.setProperty("age", "20"); // (2)
int newAge = wrapper.getRequiredProperty("age", int.class); // (3)

System.out.println("Person's age now " + newAge);
final BeanWrapper<Person> wrapper = BeanWrapper.getWrapper(new Person("Fred")) // (1)

wrapper.setProperty("age", "20") // (2)
int newAge = wrapper.getRequiredProperty("age", Integer) // (3)

println("Person's age now $newAge")
val wrapper = BeanWrapper.getWrapper(Person("Fred")) // (1)

wrapper.setProperty("age", "20") // (2)
val newAge = wrapper.getRequiredProperty("age", Int::class.java) // (3)

println("Person's age now $newAge")
  1. 使用静态 getWrapper 方法获取 bean 实例的 BeanWrapper。

  2. 您可以设置属性,BeanWrapper 将执行类型转换,或者如果无法转换则抛出 ConversionErrorException。

  3. 您可以使用 getRequiredProperty 检索属性并请求适当的类型。如果该属性不存在,则抛出 IntrospectionException,如果无法转换,则抛出 ConversionErrorException。

Jackson and Bean Introspection

Jackson 配置为使用 BeanIntrospection API 来读取和写入属性值并构造对象,从而实现无反射序列化/反序列化。这从性能的角度来看是有益的,并且需要更少的配置来正确运行运行时,例如 GraalVM native。

默认情况下启用此功能;通过将 jackson.bean-introspection-module 配置设置为 false 来禁用它。

当前仅支持 bean 属性(具有公共 getter/setter 的私有字段),不支持使用公共字段。

此功能目前处于试验阶段,将来可能会发生变化。