在Java中创建自定义注解

2023/06/10

1. 概述

Java注解是一种将元数据信息添加到我们的源代码中的机制。它们是在JDK 5中添加的Java的强大部分。注解提供了使用XML描述符和标记接口的替代方法。

虽然我们可以将它们附加到包、类、接口、方法和字段,但注解本身对程序的执行没有影响。

在本教程中,我们将重点关注如何创建和处理自定义注解。我们可以在关于注解基础知识的文章中阅读更多关于注解的内容。

2. 创建自定义注解

我们将创建三个自定义注解,目的是将对象序列化为JSON字符串。

我们将在类级别使用第一个,向编译器表明我们的对象可以序列化。然后我们将第二个应用到我们想要包含在JSON字符串中的字段。

最后,我们将在方法级别使用第三个注解来指定我们将用于初始化对象的方法。

2.1 类级别注解示例

创建自定义注解的第一步是使用@interface关键字声明它:

public @interface JsonSerializable {
}

下一步是添加元注解来指定我们自定义注解的范围和目标:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface JsonSerializable {
}

正如我们所看到的,我们的第一个注解具有运行时可见性,我们可以将它应用于类型(类)。此外,它没有方法,因此可以作为一个简单的标记来标记可以序列化为JSON的类。

2.2 字段级注解示例

以同样的方式,我们创建第二个注解来标记我们将要包含在生成的JSON中的字段:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElement {
    String key() default "";
}

该注解声明了一个名为“key”的字符串参数和一个空字符串作为默认值。

在使用方法创建自定义注解时,我们应该注意这些方法必须没有参数,并且不能抛出异常。此外,返回类型仅限于基元、String、Class、枚举、注解和这些类型的数组,并且默认值不能为null。

2.3 方法级别注解示例

让我们想象一下,在将一个对象序列化为JSON字符串之前,我们想要执行一些方法来初始化一个对象。出于这个原因,我们将创建一个注解来标记此方法:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Init {
}

我们声明了一个具有运行时可见性的公共注解,我们可以将其应用于我们类的方法。

2.4 应用注解

现在让我们看看如何使用自定义注解。例如,假设我们有一个类型为Person的对象,我们希望将其序列化为JSON字符串。这种类型有一个方法,将名字和姓氏的首字母大写。我们希望在序列化对象之前调用此方法:

@JsonSerializable
public class Person {

    @JsonElement
    private String firstName;

    @JsonElement
    private String lastName;

    @JsonElement(key = "personAge")
    private String age;

    private String address;

    @Init
    private void initNames() {
        this.firstName = this.firstName.substring(0, 1).toUpperCase() + this.firstName.substring(1);
        this.lastName = this.lastName.substring(0, 1).toUpperCase() + this.lastName.substring(1);
    }

    // Standard getters and setters
}

通过使用我们的自定义注解,我们表明我们可以将Person对象序列化为JSON字符串。此外,输出应仅包含该对象的firstName、lastName和age字段。此外,我们希望在序列化之前调用initNames()方法。

通过将@JsonElement注解的关键参数设置为“personAge”,我们表示我们将使用此名称作为JSON输出中字段的标识符。

为了演示,我们将initNames()设为私有,因此我们无法通过手动调用来初始化我们的对象,而且我们的构造函数也不使用它。

3. 处理注解

到目前为止,我们已经了解了如何创建自定义注解,以及如何使用它们来装饰Person类。现在我们将了解如何通过使用Java的反射API来利用它们。

第一步是检查我们的对象是否为null,以及它的类型是否有@JsonSerializable注解:

private void checkIfSerializable(Object object) {
    if (Objects.isNull(object)) {
        throw new JsonSerializationException("The object to serialize is null");
    }
        
    Class<?> clazz = object.getClass();
    if (!clazz.isAnnotationPresent(JsonSerializable.class)) {
        throw new JsonSerializationException("The class " 
            + clazz.getSimpleName() 
            + " is not annotated with JsonSerializable");
    }
}

然后我们寻找任何带有@Init注解的方法,并执行它来初始化我们对象的字段:

private void initializeObject(Object object) throws Exception {
    Class<?> clazz = object.getClass();
    for (Method method : clazz.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Init.class)) {
            method.setAccessible(true);
            method.invoke(object);
        }
    }
 }

方法的调用.setAccessible(true)允许我们执行私有的initNames()方法。

初始化后,我们遍历对象的字段,检索JSON元素的键和值,并将它们放入映射中。然后我们从地图创建JSON字符串:

private String getJsonString(Object object) throws Exception {	
    Class<?> clazz = object.getClass();
    Map<String, String> jsonElementsMap = new HashMap<>();
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(JsonElement.class)) {
            jsonElementsMap.put(getKey(field), (String) field.get(object));
        }
    }		
     
    String jsonString = jsonElementsMap.entrySet()
        .stream()
        .map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"")
        .collect(Collectors.joining(","));
    return "{" + jsonString + "}";
}

同样,我们使用了field.setAccessible(true)因为Person对象的字段是私有的。

我们的JSON序列化器类结合了上述所有步骤:

public class ObjectToJsonConverter {
    public String convertToJson(Object object) throws JsonSerializationException {
        try {
            checkIfSerializable(object);
            initializeObject(object);
            return getJsonString(object);
        } catch (Exception e) {
            throw new JsonSerializationException(e.getMessage());
        }
    }
}

最后,我们运行一个单元测试来验证我们的对象是否按照我们自定义注解的定义进行了序列化:

@Test
public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException {
    Person person = new Person("soufiane", "cheouati", "34");
    ObjectToJsonConverter serializer = new ObjectToJsonConverter(); 
    String jsonString = serializer.convertToJson(person);
    assertEquals("{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}", jsonString);
}

4. 总结

在本文中,我们学习了如何创建不同类型的自定义注解。然后我们讨论了如何使用它们来装饰我们的对象。最后,我们研究了如何使用Java的反射API来处理它们。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章