Java注解是一种用来给源码添加元数据的机制。所谓元数据就是用来描述源码中的包、类、接口、方法、字段的信息。这些注解的添加不会影响程序的执行。

我们用一个例子来说明如何自定义注解以及如何处理注解。我们这个例子是关于如何将一个对象转成Json的。

自定义注解

1. 首先,定义一个class级别的注解,用于标识某个类是否能序列化成Json字符串。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SerializableJSON {
}

定义一个注解时,要使用​​@interface​​​关键字。除了定义我们自己的注解之外,我们还可以用JDK提供的注解来为自定义注解添加些元数据,以此来说明自定义注解的用途。
因为我们这个注解是在运行程序的过程中需要使用的,那么我们这个注解的可见性就是在运行时,所以我们可以加上这个注解@Retention(RetentionPolicy.RUNTIME)​​
因为我们打算把这个注解应用在类上,那么我们可以加上这个注解​​​@Target(ElementType.TYPE)​​,这样之后,系统会检查这个自定义注解是否用在类上了。

2. 接着,再定义一个field级别的注解,用于指定该字段在Json中的键。

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

我们这个注解有一个特别的地方,就是定义 一个方法​​key()​​,它不能有参数,也不能抛异常,而且它返回的类型必须是原始类型、String、类、枚举、注解、或者是这些类型的数组,默认值不能为null。

3.最后,定义一个method级别的注解,用于标识某个方法要在序列化前被执行。

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

在一个类上应用以上注解

package com.example.customannotationjava;

@SerializableJSON
public class Student {
@JsonField
private String lastName;
@JsonField
private String firstName;
@JsonField(key = "fullName")
private String name;
@JsonField(key = "home")
private String address;
@JsonField
private int age;

private String school;

@BeforeSerializable
private void initFullName(){
name = lastName + firstName+"@";
}
// 省略gettters / setters
}
  • @SerializableJSON:通过此注解标识Student类可能被序列化为json
  • @JsonField:标了此注解的字段都会出现在json中,因为school字段没有被标记,所以它不会出现在json中,另外有些字段在json出现的键不是类中的字段名,如address,在json里的键是home。
  • @BeforeSerializable: 这个方法会在序列前执行。我将其的可见性标成了private,这意味着我们无法手动调用,并且在这里,我也没有在构造方法里调用它,那么它只能够在我处理注解时调用了。^_^

处理注解

上面提到的注解将会如何被处理,就是现在要讲的。也就是如果只定义注解而没有相应的注解处理实现,那么一切都是空谈。

1.检查将要序列化的对象的类是否标记了​​@SerializableJSON​

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

2.首先处理标记了​​@BeforeSerializable​​的方法,这是做序列化前要做的事

private void executeInitialMethod(Object object) throws Exception {
if(object == null){
throw new Exception("The object to serialize is null");
}
Class<?> clazz = object.getClass();
for(Method method:clazz.getDeclaredMethods()){
if(method.isAnnotationPresent(BeforeSerializable.class)){
method.setAccessible(true);
method.invoke(object);
}
}
}

​method.setAccessible(true)​​可以执行对象中的private方法。

3.将对象中的字段与对应的值,记录在map中,再拼成json

private String getJsonString(Object object) throws Exception {
if (object == null) {
throw new Exception("The object to serialize is null");
}
Class<?> clazz = object.getClass();
Map<String, Object> map = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(JsonField.class) && field.get(object) != null) {
String key = getKey(field);
if(!TextUtils.isEmpty(key)) {
map.put(key, field.get(object));
}
}
}
String json = map.entrySet().stream().map(stringStringEntry ->{

String a = "\"" + stringStringEntry.getKey() + "\":";
Object b = null;
if(stringStringEntry.getValue() != null){
if(stringStringEntry.getValue() instanceof String){
b = "\""+stringStringEntry.getValue()+"\"";
}else{
b = stringStringEntry.getValue();
}
}
return a + b;
})
.collect(Collectors.joining(","));
return "{" + json + "}";
}

private String getKey(Field field) {
JsonField jsonField = field.getAnnotation(JsonField.class);
if (jsonField != null) {
String key = jsonField.key();
if (key.length() == 0) {
key = field.getName();
}
return key;
}
return null;
}

来个单元测试

package com.example.customannotationjava;

import junit.framework.TestCase;

public class JsonParserTest extends TestCase {

public void testParseToJson() throws Exception {
Student student = new Student("Wong","ban","GZ",18,"110Mid");
JsonParser jsonParser = new JsonParser();
String actual = jsonParser.parseToJson(student);
String expected = "{\"lastName\":\"Wong\",\"firstName\":\"ban\",\"fullName\":\"Wong ban@\",\"age\":18,\"home\":\"GZ\"}";
assertEquals(expected,actual);
}
}

​Demo​