Я не совсем уверен, почему вам нужно сериализовать экземпляры аннотаций, которые являются постоянными по конструкции, но вы, скорее всего, используете JVM Oracle, который создает аннотации с использованием java.lang.proxy.Proxy
. Прокси обрабатываются как объекты данных с отражающим доступом в Gson (и в стандартном пакете Gson нет упоминаний о прокси), и Gson просто не удается сериализовать java.lang.Class
, что не имеет большого смысла в вашем сценарии.
Вам необходимо создать новую фабрику адаптеров типов, которая может генерировать адаптеры с поддержкой аннотаций, которые будут интенсивно использовать отражение над аннотациями. Скажем,
final class AnnotationTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new AnnotationTypeAdapterFactory();
private AnnotationTypeAdapterFactory() {
}
static TypeAdapterFactory getInstance() {
return instance;
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
@Nullable
final Class<? extends Annotation> annotationClass = Annotations.lookupAnnotationClass(typeToken.getRawType());
if ( annotationClass == null ) {
return null;
}
final List<Method> methods = Annotations.lookupMethods(annotationClass);
final int count = methods.size();
final String[] names = new String[count];
@SuppressWarnings("unchecked")
final TypeAdapter<Object>[] typeAdapters = new TypeAdapter[count];
final Map<String, TypeAdapter<Object>> namedTypeAdapters = new HashMap<>();
for ( int i = 0; i < count; i++ ) {
final Method method = methods.get(i);
names[i] = method.getName();
@SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<Object> typeAdapter = (TypeAdapter) gson.getAdapter(method.getReturnType());
typeAdapters[i] = typeAdapter;
namedTypeAdapters.put(names[i], typeAdapter);
}
final TypeAdapter<T> typeAdapter = new TypeAdapter<T>() {
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter out, final T annotation)
throws IOException {
try {
out.beginObject();
for ( int i = 0; i < count; i++ ) {
out.name(names[i]);
typeAdapters[i].write(out, methods.get(i).invoke(annotation));
}
out.endObject();
} catch ( final IllegalAccessException | InvocationTargetException ex ) {
throw new RuntimeException(ex);
}
}
@Override
public T read(final JsonReader in)
throws IOException {
try {
in.beginObject();
final Map<String, Object> properties = new HashMap<>();
while ( in.hasNext() ) {
final String name = in.nextName();
@Nullable
final TypeAdapter<Object> objectTypeAdapter = namedTypeAdapters.get(name);
if ( objectTypeAdapter == null ) {
in.skipValue();
} else {
properties.put(name, objectTypeAdapter.read(in));
}
}
in.endObject();
@SuppressWarnings("unchecked")
final T annotation = (T) Annotations.create(annotationClass, properties);
return annotation;
} catch ( final NoSuchMethodException ex ) {
throw new RuntimeException(ex);
}
}
};
return typeAdapter.nullSafe();
}
}
где класс Annotations
выглядит следующим образом:
final class Annotations {
private static final boolean SUN_PACKAGE = false;
private Annotations() {
}
static <T extends Annotation> T create(final Class<T> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
return create(annotationClass.getClassLoader(), annotationClass, properties);
}
static <T extends Annotation> T create(final ClassLoader classLoader, final Class<T> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
if ( SUN_PACKAGE ) {
@SuppressWarnings("unchecked")
final T annotation = (T) AnnotationParser.annotationForMap(annotationClass, properties);
return annotation;
}
@SuppressWarnings("unchecked")
final T annotation = (T) Proxy.newProxyInstance(
classLoader,
new Class<?>[]{ annotationClass },
DynamicAnnotation.fromMap(annotationClass, lookupProperties(annotationClass, properties))
);
return annotation;
}
@Nullable
static Class<? extends Annotation> lookupAnnotationClass(final Class<?> clazz) {
if ( clazz.isAnnotation() ) {
@SuppressWarnings("unchecked")
final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz;
return annotationClass;
}
final Class<?>[] interfaces = clazz.getInterfaces();
if ( interfaces.length != 1 ) {
return null;
}
final Class<?> iface = interfaces[0];
if ( !Annotation.class.isAssignableFrom(iface) ) {
return null;
}
@SuppressWarnings("unchecked")
final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) iface;
return annotationClass;
}
static List<Method> lookupMethods(final Class<? extends Annotation> annotationClass) {
final List<Method> methods = new ArrayList<>();
for ( final Method method : annotationClass.getMethods() ) {
if ( method.getDeclaringClass() == annotationClass ) {
methods.add(method);
}
}
return Collections.unmodifiableList(methods);
}
static Map<String, Object> lookupProperties(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
final Map<String, Object> namedProperties = new HashMap<>();
namedProperties.putAll(lookupDefaultProperties(annotationClass));
namedProperties.putAll(properties);
return Collections.unmodifiableMap(namedProperties);
}
static Map<String, Object> lookupDefaultProperties(final Class<? extends Annotation> annotationClass) {
final Map<String, Object> defaultProperties = new HashMap<>();
for ( final Method method : lookupMethods(annotationClass) ) {
@Nullable
final Object defaultValue = method.getDefaultValue();
if ( defaultValue != null ) {
defaultProperties.put(method.getName(), defaultValue);
}
}
return Collections.unmodifiableMap(defaultProperties);
}
static String toString(@SuppressWarnings("TypeMayBeWeakened") final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
final StringBuilder builder = new StringBuilder("@")
.append(annotationClass.getTypeName())
.append('(');
boolean atTail = false;
for ( final Map.Entry<String, Object> e : properties.entrySet() ) {
if ( atTail ) {
builder.append(", ");
}
builder.append(e.getKey())
.append('=')
.append(e.getValue());
atTail = true;
}
return builder.append(')')
.toString();
}
}
и пользовательская реализация аннотаций:
abstract class DynamicAnnotation
implements Annotation, InvocationHandler {
private static final Method java_lang_Object_equals;
private static final Method java_lang_Object_hashCode;
private static final Method java_lang_Object_toString;
private static final Method java_lang_annotation_Annotation_annotationType;
static {
try {
java_lang_Object_equals = Object.class.getDeclaredMethod("equals", Object.class);
java_lang_Object_hashCode = Object.class.getDeclaredMethod("hashCode");
java_lang_Object_toString = Object.class.getDeclaredMethod("toString");
java_lang_annotation_Annotation_annotationType = Annotation.class.getDeclaredMethod("annotationType");
} catch ( final NoSuchMethodException ex ) {
throw new Error(ex);
}
}
private final String toString;
private final Class<? extends Annotation> annotationClass;
private DynamicAnnotation(final String toString, final Class<? extends Annotation> annotationClass) {
this.toString = toString;
this.annotationClass = annotationClass;
}
static DynamicAnnotation fromMap(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
return FromMap.create(annotationClass, properties);
}
@Nullable
protected abstract Object invoke(final Method method)
throws Throwable;
@Override
public final Class<? extends Annotation> annotationType() {
return annotationClass;
}
// must conform the https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/Annotation.html#hashCode() contract
@Override
public final int hashCode() {
//return hashCode;
throw new UnsupportedOperationException();
}
// must conform the https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/Annotation.html#equals(java.lang.Object) contract
@Override
public final boolean equals(@Nullable final Object obj) {
throw new UnsupportedOperationException();
}
@Override
public final String toString() {
return toString;
}
@Override
@Nonnull
public final Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ( method.equals(java_lang_annotation_Annotation_annotationType) ) {
return annotationType();
}
if ( method.equals(java_lang_Object_equals) ) {
return equals(args[0]);
}
if ( method.equals(java_lang_Object_hashCode) ) {
return hashCode();
}
if ( method.equals(java_lang_Object_toString) ) {
return toString();
}
@Nullable
final Object returnValue = invoke(method);
if ( returnValue == null ) {
throw new NoSuchMethodException("The instance of " + annotationClass + " has no value associated with " + method.getName());
}
return returnValue;
}
private static final class FromMap
extends DynamicAnnotation {
private final Map<String, Object> properties;
private FromMap(final String toString, final Class<? extends Annotation> annotationClass, final Map<String, Object> properties) {
super(/*hashCode, */toString, annotationClass);
this.properties = properties;
}
private static DynamicAnnotation create(final Class<? extends Annotation> annotationClass, final Map<String, Object> properties)
throws NoSuchMethodException {
final Map<String, Object> toStringProperties = new LinkedHashMap<>();
for ( final Method method : Annotations.lookupMethods(annotationClass) ) {
final String name = method.getName();
if ( !properties.containsKey(name) ) {
throw new NoSuchMethodException("Cannot find " + name + " in " + properties + " while constructing an instance of " + annotationClass);
}
final Object value = properties.get(name);
toStringProperties.put(name, value);
}
final String toString = Annotations.toString(annotationClass, Collections.unmodifiableMap(toStringProperties));
return new FromMap(toString, annotationClass, properties);
}
@Override
protected Object invoke(final Method method) {
return properties.get(method.getName());
}
}
}
Вы также можете использовать AnnotationUtils
(если это работает для вас) или AnnotationParser
из «пакета Sun» (если это также опция) для выполнения контрактов интерфейса аннотации.
Вот пример использования для раунда поездка:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(AnnotationTypeAdapterFactory.getInstance())
.create();
public static void main(final String... args)
throws NoSuchFieldException {
final SendEmail before = Wrapper.class.getDeclaredField("constant").getAnnotation(SendEmail.class);
System.out.println(before);
final String json = gson.toJson(before);
System.out.println(json);
final SendEmail after = gson.fromJson(json, SendEmail.class);
System.out.println(after);
System.out.println(after.annotationType());
System.out.println(gson.toJson(after));
}
private static final class Wrapper {
@SendEmail(id = "email@mail.com")
private static final Object constant = new Object();
}