Micronaut Bean 自省

2023-02-28 15:25 更新

自 Micronaut 1.1 以來(lái),已經(jīng)包含了 JDK 的 Introspector 類(lèi)的編譯時(shí)替代品。

BeanIntrospector 和 BeanIntrospection 接口允許查找 bean 自省以實(shí)例化和讀/寫(xiě) bean 屬性,而無(wú)需使用反射或緩存反射元數(shù)據(jù),這會(huì)為大型 bean 消耗過(guò)多的內(nèi)存。

使 Bean 可用于自省

與 JDK 的 Introspector 不同,每個(gè)類(lèi)都不會(huì)自動(dòng)進(jìn)行內(nèi)省。要使一個(gè)類(lèi)可用于自省,您必須至少在構(gòu)建中啟用 Micronaut 的注釋處理器(用于 Java 和 Kotlin 的 micronaut-inject-java 和用于 Groovy 的 micronaut-inject-groovy),并確保您對(duì) micronaut-core 具有運(yùn)行時(shí)依賴性.

 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>

對(duì)于 Kotlin,在 kapt 范圍內(nèi)添加 micronaut-inject-java 依賴項(xiàng),對(duì)于 Groovy,在 compileOnly 范圍內(nèi)添加 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>

配置構(gòu)建后,您可以通過(guò)多種方式生成內(nèi)省數(shù)據(jù)。

使用@Introspected 注解

@Introspected 注釋可以用在任何類(lèi)上以使其可用于自省。簡(jiǎn)單地用@Introspected 注釋類(lèi):

 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
}

在編譯時(shí)生成內(nèi)省數(shù)據(jù)后,通過(guò) 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. 您可以使用靜態(tài) getIntrospection 方法檢索 BeanIntrospection

  2. 一旦有了 BeanIntrospection,就可以使用 instantiate 方法實(shí)例化一個(gè) bean。

  3. 可以從內(nèi)省中檢索 BeanProperty

  4. 使用set方法設(shè)置屬性值

  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 注釋該類(lèi),為getter 和setter 定義空的讀寫(xiě)前綴。

  2. 定義不帶前綴的 getter 和 setter。

現(xiàn)在可以使用 BeanIntrospection API 檢索編譯時(shí)生成的內(nèi)?。?/p>

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

默認(rèn)情況下,Java 自省僅將 JavaBean getter/setter 或 Java 16 記錄組件視為 bean 屬性。但是,您可以使用 @Introspected 注釋的 accessKind 成員在 Java 中定義具有公共或包保護(hù)字段的類(lèi):

 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. 可變字段被視為讀寫(xiě)屬性

accessKind 接受一個(gè)數(shù)組,因此可以允許兩種類(lèi)型的訪問(wèn)器,但根據(jù)它們?cè)谧⑨屩谐霈F(xiàn)的順序更喜歡一種或另一種。列表中的第一個(gè)具有優(yōu)先權(quán)。

在 Kotlin 中不可能對(duì)字段進(jìn)行自省,因?yàn)闊o(wú)法直接聲明字段。

構(gòu)造方法

對(duì)于有多個(gè)構(gòu)造函數(shù)的類(lèi),在構(gòu)造函數(shù)上加上@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 注釋表示要使用哪個(gè)構(gòu)造函數(shù)

此類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù),因此調(diào)用不帶參數(shù)的實(shí)例化會(huì)拋出 InstantiationException。

靜態(tài)創(chuàng)建者方法

@Creator 注釋可以應(yīng)用于創(chuàng)建類(lèi)實(shí)例的靜態(tài)方法。

 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注解應(yīng)用于實(shí)例化類(lèi)的靜態(tài)方法

可以有多個(gè)注釋的“創(chuàng)建者”方法。如果有一個(gè)沒(méi)有參數(shù),它將是默認(rèn)的構(gòu)造方法。第一個(gè)帶參數(shù)的方法將用作主要構(gòu)造方法。

Enums

也可以內(nèi)省枚舉。給枚舉加上注解,可以通過(guò)標(biāo)準(zhǔn)的valueOf方法構(gòu)造。

在配置類(lèi)上使用@Introspected 注解

如果要內(nèi)省的類(lèi)已經(jīng)編譯并且不受您的控制,則另一種選擇是使用 @Introspected 注釋集的類(lèi)成員定義配置類(lèi)。

 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 類(lèi)為 Person 類(lèi)生成內(nèi)省。

您還可以使用 @Introspected 的 packages 成員,該包在編譯時(shí)掃描并為包中的所有類(lèi)生成內(nèi)省。但是請(qǐng)注意,此功能目前被視為實(shí)驗(yàn)性的。

編寫(xiě)一個(gè) AnnotationMapper 來(lái)檢查現(xiàn)有的注釋

如果您希望默認(rèn)內(nèi)省現(xiàn)有注釋,則可以編寫(xiě)一個(gè) AnnotationMapper。

這方面的一個(gè)例子是 EntityIntrospectedAnnotationMapper,它確保所有用 javax.persistence.Entity 注釋的 bean 在默認(rèn)情況下都是可自省的。

AnnotationMapper 必須位于注釋處理器類(lèi)路徑中。

The BeanWrapper API

BeanProperty 提供讀取和寫(xiě)入給定類(lèi)的屬性值的原始訪問(wèn),并且不提供任何自動(dòng)類(lèi)型轉(zhuǎn)換。

您傳遞給 set 和 get 方法的值應(yīng)該與底層屬性類(lèi)型相匹配,否則會(huì)發(fā)生異常。

為了提供額外的類(lèi)型轉(zhuǎn)換智能,BeanWrapper 接口允許包裝現(xiàn)有的 bean 實(shí)例并設(shè)置和獲取 bean 的屬性,并在必要時(shí)執(zhí)行類(lèi)型轉(zhuǎn)換。

 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. 使用靜態(tài) getWrapper 方法獲取 bean 實(shí)例的 BeanWrapper。

  2. 您可以設(shè)置屬性,BeanWrapper 將執(zhí)行類(lèi)型轉(zhuǎn)換,或者如果無(wú)法轉(zhuǎn)換則拋出 ConversionErrorException。

  3. 您可以使用 getRequiredProperty 檢索屬性并請(qǐng)求適當(dāng)?shù)念?lèi)型。如果該屬性不存在,則拋出 IntrospectionException,如果無(wú)法轉(zhuǎn)換,則拋出 ConversionErrorException。

Jackson and Bean Introspection

Jackson 配置為使用 BeanIntrospection API 來(lái)讀取和寫(xiě)入屬性值并構(gòu)造對(duì)象,從而實(shí)現(xiàn)無(wú)反射序列化/反序列化。這從性能的角度來(lái)看是有益的,并且需要更少的配置來(lái)正確運(yùn)行運(yùn)行時(shí),例如 GraalVM native。

默認(rèn)情況下啟用此功能;通過(guò)將 jackson.bean-introspection-module 配置設(shè)置為 false 來(lái)禁用它。

當(dāng)前僅支持 bean 屬性(具有公共 getter/setter 的私有字段),不支持使用公共字段。

此功能目前處于試驗(yàn)階段,將來(lái)可能會(huì)發(fā)生變化。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)