1. 简介
Java 注解(Annotation)是一种向源代码中添加元数据的方式。它是从 JDK5 开始引入的强大特性,提供了一种替代 XML 配置文件和标记接口的手段。
虽然我们可以将注解应用于包、类、接口、方法或字段,但注解本身并不会影响程序的执行流程。
本文将重点介绍如何创建并处理自定义注解。如果你对注解的基本概念还不熟悉,可以先参考 Java 注解基础 这篇文章。
2. 创建自定义注解
我们的目标是实现一个简单的对象转 JSON 字符串的功能,为此我们将创建三个自定义注解:
- 一个用于类级别,表示该类可被序列化;
- 一个用于字段级别,标记哪些字段需要包含在 JSON 输出中;
- 一个用于方法级别,标记初始化方法。
2.1. 类级别的注解示例
创建自定义注解的第一步是使用 @interface
关键字声明:
public @interface JsonSerializable {
}
接下来,我们通过元注解指定其作用范围和目标:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JsonSerializable {
}
✅ 我们的第一个注解具有运行时可见性,并且只能应用在类上。它没有方法,因此只是一个简单的标记注解,用来标识可以被序列化的类。
2.2. 字段级别的注解示例
同样地,我们创建第二个注解,用于标记要包含在 JSON 中的字段:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElement {
public String key() default "";
}
⚠️ 该注解定义了一个名为 key
的 String 类型参数,默认值为空字符串。
当我们创建带有方法的注解时需要注意以下几点:
- 方法不能有参数;
- 方法不能抛出异常;
- 返回类型只能是基本类型、String、Class、枚举、其他注解及其数组;
- 默认值不能为 null。
2.3. 方法级别的注解示例
假设我们在序列化前需要调用某个方法来初始化对象状态,为此我们可以创建如下注解:
@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);
}
// 标准 getter 和 setter 省略
}
在这个例子中:
- 使用
@JsonSerializable
表示该类可被序列化; - 使用
@JsonElement
标记了三个字段,表示它们会出现在最终的 JSON 中; - 使用
@Init
标记了initNames()
方法,在序列化前会自动调用; - 通过设置
key = "personAge"
指定 JSON 中的字段名为personAge
而不是默认的age
。
⚠️ 注意:我们将 initNames()
设为私有方法,不能手动调用,也无法通过构造函数触发。
3. 处理注解
前面我们完成了注解的创建和使用,现在来看如何通过 Java 的反射机制处理这些注解。
首先检查对象是否为空,以及其类是否带有 @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);
}
}
}
✅ 使用 method.setAccessible(true)
可以调用私有方法,如 initNames()
。
初始化完成后,遍历所有字段,提取带有 @JsonElement
注解的字段名与值,存入 Map 并生成 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)
来访问。
完整的 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 动态处理注解信息,实现灵活的对象序列化功能。
这只是一个入门级的例子,实际项目中你可以结合 Spring、Jackson 等框架做更复杂的应用。注解虽小,威力不小,掌握好它是进阶 Java 工程师的必修课之一。