1. Introduction
1.绪论
Java annotations are a mechanism for adding metadata information to our source code. They’re a powerful part of Java that was added in JDK5. Annotations offer an alternative to the use of XML descriptors and marker interfaces.
Java注解是一种为我们的源代码添加元数据信息的机制。它们是Java的一个强大的部分,在JDK5中被加入。注释为使用XML描述符和标记接口提供了一种替代方法。
Although we can attach them to packages, classes, interfaces, methods, and fields, annotations by themselves have no effect on the execution of a program.
尽管我们可以把它们附加到包、类、接口、方法和字段上,但注解本身对程序的执行没有影响。
In this tutorial, we’re going to focus on how to create and process custom annotations. We can read more about annotations in our article on annotation basics.
在本教程中,我们将重点介绍如何创建和处理自定义注解。我们可以在我们关于注释基础知识的文章中阅读更多关于注释的信息。
2. Creating Custom Annotations
2.创建自定义注解
We’re going to create three custom annotations with the goal of serializing an object into a JSON string.
我们将创建三个自定义注解,目的是将一个对象序列化为一个JSON字符串。
We’ll use the first one on the class level, to indicate to the compiler that our object can be serialized. Then we’ll apply the second one to the fields that we want to include in the JSON string.
我们将在类的层面上使用第一个,以向编译器表明我们的对象可以被序列化。然后,我们将对我们想包含在JSON字符串中的字段应用第二个方法。
Finally, we’ll use the third annotation on the method level, to specify the method that we’ll use to initialize our object.
最后,我们将使用方法层的第三个注解,来指定我们将用来初始化对象的方法。
2.1. Class Level Annotation Example
2.1.类级注解示例
The first step toward creating a custom annotation is to declare it using the @interface keyword:
创建自定义注解的第一步是使用@interface关键字声明它:。
public @interface JsonSerializable {
}
The next step is to add meta-annotations to specify the scope and the target of our custom annotation:
下一步是添加元注释以指定我们的自定义注释的范围和目标。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface JsonSerializable {
}
As we can see, our first annotation has runtime visibility, and we can apply it to types (classes). Moreover, it has no methods, and thus serves as a simple marker to mark classes that can be serialized into JSON.
正如我们所看到的,我们的第一个注解具有运行时可见性,并且我们可以将其应用于类型(类)。此外,它没有方法,因此可以作为一个简单的标记来标记可以被序列化为JSON的类。
2.2. Field Level Annotation Example
2.2.字段级注解示例
In the same fashion, we create our second annotation to mark the fields that we are going to include in the generated JSON:
以同样的方式,我们创建第二个注解来标记我们要在生成的JSON中包含的字段。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElement {
public String key() default "";
}
The annotation declares one String parameter with the name “key” and an empty string as the default value.
注解声明了一个名为 “key “的String参数,默认值是一个空字符串。
When creating custom annotations with methods, we should be aware that these methods must have no parameters, and cannot throw an exception. Also, the return types are restricted to primitives, String, Class, enums, annotations, and arrays of these types, and the default value cannot be null.
在创建带有方法的自定义注解时,我们应该注意,这些方法必须没有参数,并且不能抛出异常。另外,返回类型仅限于基元、String、Class、枚举、注释和这些类型的数组, 默认值不能为空。
2.3. Method Level Annotation Example
2.3.方法层面的注释示例
Let’s imagine that before serializing an object to a JSON string, we want to execute some method to initialize an object. For that reason, we’re going to create an annotation to mark this method:
让我们想象一下,在将一个对象序列化为JSON字符串之前,我们想执行一些方法来初始化一个对象。出于这个原因,我们要创建一个注解来标记这个方法。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Init {
}
We declared a public annotation with runtime visibility that we can apply to our classes’ methods.
我们声明了一个具有运行时可见性的公共注解,我们可以应用于我们的类的方法。
2.4. Applying Annotations
2.4.应用注解
Now let’s see how we can use our custom annotations. For instance, let’s imagine that we have an object of type Person that we want to serialize into a JSON string. This type has a method that capitalizes the first letter of the first and last names. We’ll want to call this method before serializing the object:
现在让我们看看如何使用我们的自定义注解。例如,让我们想象一下,我们有一个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
}
By using our custom annotations, we’re indicating that we can serialize a Person object to a JSON string. In addition, the output should contain only the firstName, lastName, and age fields of that object. Moreover, we want the initNames() method to be called before serialization.
通过使用我们的自定义注解,我们表明我们可以将一个Person对象序列化为一个JSON字符串。此外,输出应该只包含该对象的firstName、lastName和age字段。此外,我们希望在序列化之前调用initNames()方法。
By setting the key parameter of the @JsonElement annotation to “personAge,” we are indicating that we’ll use this name as the identifier for the field in the JSON output.
通过将@JsonElement注解的key参数设置为 “personAge”,我们表明我们将使用这个名字作为JSON输出中字段的标识。
For the sake of demonstration, we made initNames() private, so we can’t initialize our object by calling it manually, and our constructors aren’t using it either.
为了便于演示,我们将 initNames()设为私有,所以我们不能通过手动调用它来初始化我们的对象,而且我们的构造函数也没有使用它。
3. Processing Annotations
3.处理注释
So far we’ve seen how to create custom annotations, and how to use them to decorate the Person class. Now we’re going to see how to take advantage of them by using Java’s Reflection API.
到目前为止,我们已经看到了如何创建自定义注解,以及如何使用它们来装饰Person类。现在我们将看到如何通过使用Java的Reflection API来利用它们。
The first step will be to check whether our object is null or not, as well as whether its type has the @JsonSerializable annotation or not:
第一步是检查我们的对象是否为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");
}
}
Then we look for any method with the @Init annotation, and we execute it to initialize our object’s fields:
然后我们寻找任何带有@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);
}
}
}
The call of method.setAccessible(true) allows us to execute the private initNames() method.
对method.setAccessible(true)的调用允许我们执行私有的initNames()method。
After the initialization, we iterate over our object’s fields, retrieve the key and value of JSON elements, and put them in a map. Then we create the JSON string from the map:
在初始化之后,我们遍历我们对象的字段,检索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 + "}";
}
Again, we used field.setAccessible(true) because the Person object’s fields are private.
同样,我们使用了field.setAccessible(true),因为Person对象的字段是私有的。
Our JSON serializer class combines all the above steps:
我们的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());
}
}
}
Finally, we run a unit test to validate that our object was serialized as defined by our custom annotations:
最后,我们运行一个单元测试来验证我们的对象是否按照我们的自定义注解的定义进行了序列化。
@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. Conclusion
4.总结
In this article, we learned how to create different types of custom annotations. We then discussed how to use them to decorate our objects. Finally, we looked at how to process them using Java’s Reflection API.
在这篇文章中,我们学习了如何创建不同类型的自定义注释。然后我们讨论了如何使用它们来装饰我们的对象。最后,我们研究了如何使用Java的反射API来处理它们。
As always, the complete code is available over on GitHub.
一如既往,完整的代码可以在GitHub上找到。