简体   繁体   中英

How to create a shortcut to existing annotation?

In my code I am using following annotation several times:

@JsonSerialize(using = classOf[CustomColorRGBASerializer])

To keep my code short and DRY , I would like to create a shortcut to this, something like:

class JsonSerializeARGB
  extends @JsonSerialize(using = classOf[CustomColorRGBASerializer])

which I could then use as a new @JsonSerializeARGB annotation

I can use annotation, but I do not know how to define them, therefore my attempt cetainly looks naive and obviously incorrect, but I hope it bears the meaning through.

I have read How do you define an @interface in Scala? and How to create annotations and get them in scala , but they did not help me much, as I do not want to create a brand new annotation, rather "subclass" existing annotation. Can this be done?

If there is no Scala solution, can something like this be done in Java? (The Jackson annotations I am working with are defined in Java anyway).

I'm afraid there is no way to subtype annotation with Java (and Scala) language mechanisms. I think that the only solution is to make a Scala macro with the annotation .

Macro annotations are available with Macro Paradise plugin for Scala compiler. Hopefully they 'll be included in Scala 2.13. To configure SBT for Macro Paradise you may want to follow this question . There is also a useful example of project making use of macro paradise.

I believe that this can be done better (especially DefDef matching), but macro similar to this one should solve your problem:

import scala.reflect.macros._
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros

class JsonSerializeARGB extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro JsonSerializeARGBMacroImpl.impl
}

object JsonSerializeARGBMacroImpl extends JsonSerializeARGBMacro

class JsonSerializeARGBMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
      import c.universe._

      def modifiedDef(d: DefDef) = {
        val (mods, name, tparams, paramss, tpt, body) = try {
          val q"$mods def $name[..$tparams](...$paramss): $tpt = $body" = d
          (mods, name, tparams, paramss, tpt, body)
        } catch {
          case _: MatchError => c.abort(c.enclosingPosition, "Failed to match...")
        }

        //TODO there is a problem with modifiers
        c.Expr(q"""
          @JsonSerialize(using = classOf[CustomColorRGBASerializer])
          def $name[..$tparams](...$paramss): $tpt = $body
          """)
      }

      annottees.map(_.tree) match {
        case (d: DefDef) :: Nil => modifiedDef(d)
        case _ => c.abort(c.enclosingPosition, "Invalid annottee.")
      }
    }
 }

Looking at Java, there is no reasonable way to do this. Annotations cannot be extended in current Java versions , so the easiest approach fails. An other possiblity would be to use reflection to replace all occurrences of a JsonSerializeARGB with JsonSerialize , though this would only work at runtime, not at compile time. Yet the Java Reflection API only supports reading annotations , not adding them.

So there are two theoretical approaches:

  • Messing with the compiled byte code, but nobody can honestly want to do that.
  • Modifying Jackson (or any other library that reads the annotations) to recognize your custom JsonSerializeARGB annotation.

I'm not familiar with Scala, so I do not know whether there are other options available there. But I doubt that Scala provides methods to add or extends annotation that Java doesn't.

Taking a different approach. Jackson supports programattically defining serializers. So you can define your own annotation and then use reflection to find all classes with your annotation and add the serializer mapping.

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("MyModule", new Version(1, 0, 0, null))
// use reflections to find all classes with Annotation the
for (classWithAnnotation <- classesWithAnnotation) {
  simpleModule.addSerializer(classWithAnnotation, new CustomColorRGBASerializer());
}
mapper.registerModule(simpleModule);

Here is the example I tried to get what you wanted to do with fasterXML library:

1. Create your own CustomSerializer

import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.core.JsonProcessingException;

public class CustomSerializer extends JsonSerializer<CustomDTO> {

@Override
public void serialize(CustomDTO value, JsonGenerator gen,
        com.fasterxml.jackson.databind.SerializerProvider serializers)
        throws IOException,
        JsonProcessingException {
    gen.writeStartObject();
    gen.writeStringField("AccentColor", value.getAccentColor());
    gen.writeStringField("ButtonColor", value.getButtonColor());
    gen.writeEndObject();
}

}

2. Create Annotation to use this CustomSerializer:

As of Scala 2.11 this needs to be done in Java, as in Scala it is currently not possible to define annotations with runtime retention.

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = CustomSerializer.class)
public @interface JsonSeriliazerCustom {}

3. Use this on CustomDTO or your class as follows:

@JsonSeriliazerCustom
public class CustomDTO {

    private String buttonColor;
    private String accentColor;
    private String frontColor;

    public String getButtonColor() {
        return buttonColor;
    }

    public void setButtonColor(String buttonColor) {
        this.buttonColor = buttonColor;
    }

    public String getAccentColor() {
        return accentColor;
    }

    public void setAccentColor(String accentColor) {
        this.accentColor = accentColor;
    }

    public String getFrontColor() {
        return frontColor;
    }

    public void setFrontColor(String frontColor) {
        this.frontColor = frontColor;
    }


}

4. Write your main method like this:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.opera.oss.core.dto.CustomDTO;

public class TestJson {

    public static void main(String[] args)
    {
        CustomDTO responseDTO = new CustomDTO();
        responseDTO.setAccentColor("red");
        responseDTO.setButtonColor("blue");
        responseDTO.setFrontColor("yellow");
        System.out.println("hey");
         ObjectMapper om = new ObjectMapper();
         VisibilityChecker<?> checker = om.getSerializationConfig().getDefaultVisibilityChecker();
            om.setVisibilityChecker(checker.withFieldVisibility(JsonAutoDetect.Visibility.ANY));

            try {
                System.out.println(om.writer().writeValueAsString(responseDTO));
            } catch (JsonProcessingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

    }
}

Libraries used: fasterXML - 2.5.0 version - jackson-core, jackson-data-bind and jackson-annotations

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM