Mark up interfaces, annotations, and the past and present lives of annotation processors

Mark up interfaces, annotations, and the past and present lives of annotation processors

brief introduction

I believe that most developers have used annotations, especially for those who have used spring, annotations are an unavailable part of modern spring. Spring has had a very important impact on our programmers from the initial XML configuration to the subsequent annotation configuration, both in terms of programming habits and project construction.

In addition to using spring's own annotations, we can also customize annotations. Then, the annotations are intercepted through AOP to process the corresponding business logic.

In addition to spring, JDK itself comes with annotations. This article will deeply explore the origin and two different ways of using annotations.

More highlights:

Origin of annotations and marker interfaces

Let's take a look at the simplest annotation:

@CustUserAnnotation
public class CustUser {
}

Above, we marked custuser as a custom annotation @ custuserannotation.

Annotations are actually introduced in JDK 5. So before JDK 5, how were annotations represented? The answer is marker interfaces.

The Chinese translation of marker interfaces is called marker interface. Marker interface means that this interface is used for marking, and no method or field is provided internally.

There are many tag interfaces in Java. The most common are clonable, serializable, and Java EventListener and randomaccess in util package.

Take clonable as an example:

/*
 * @since   1.0
 */
public interface Cloneable {
}

This interface is from Java 1 0 started. Only classes that implement this interface can call the clone method in object.

How do we determine whether the class implements the clonable interface in the code?

public Object clone() throws CloneNotSupportedException {
        if (this instanceof Cloneable) {
            return super.clone();
        } else {
            throw new CloneNotSupportedException();
        }
    }

It is very simple. You can judge whether it is clonable by instanceof.

Marker interfaces are easy to use, but they have some disadvantages, such as no additional metadata information, too single function, and will be confused with normal interfaces. The implementation is also more complex than the general interface.

For these reasons, annotation is introduced in jdk5.

Definition of annotation

Annotations are defined by @ interface. To create an annotation, you need to specify its target and retention, and you can customize the parameters.

Let's take an example:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustUserAnnotation {
    int value();
    String name();
    String[] addresses();
}

Above is a comment I customized.

Retention

Retention indicates at what stage the annotation will be visible. It has three optional values:

Source means that it is only visible in the source code and will be discarded at compile time.

Class means that class is visible at compile time, but not at run time.

Runtime means visible at runtime. When is runtime visibility required? That's when reflection is used. We will describe this situation in the following examples.

Retention itself is also a comment:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

Target

Target indicates where this annotation will be used. It has 12 values.

Type is used for class, interface, enum or record.

Field indicates that it is used in the field of class.

Method indicates the method used.

Parameter indicates that it is used on the method.

Constructor is used on constructors.

LOCAL_ Variable is used on local variables.

ANNOTATION_ Type is used for annotation.

Package is used on package.

TYPE_ Parameter is used on type parameters.

TYPE_ Use is used for any type.

TYPE_ Parameter and type_ What's the difference between use?

TYPE_ Use is used for any type of use, such as declaration, generic type and conversion:

@Encrypted String data
List<@NonNull String> strings
MyGraph = (@Immutable Graph) tmpGraph;

And type_ Parameter is used on type parameters:

class MyClass<T> {...}

Module is used on module.

RECORD_ Component preview function, related to records.

Like retention, target is also an annotation.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

Custom parameters

The annotation can also set its own parameters. The parameters can be of the following types:

Our custom type above defines three parameters:

    int value();
    String name();
    String[] addresses();

Let's see how to use:

@CustUserAnnotation(value = 100,name="jack ma",addresses = {"人民路","江西路"})
public class CustUser {
}

In use, we need to pass in custom parameters. Of course, you can also use default to provide the default value in the annotation, so there is no need to pass in from the outside.

Use annotations at run time

At run time, we can use the reflected API to obtain annotations and get the user-defined variables in the annotations for corresponding business logic processing.

CustUser custUser= new CustUser();
        Annotation[] annotations= custUser.getClass().getAnnotations();
        Stream.of(annotations).filter(annotation -> annotation instanceof CustUserAnnotation)
                .forEach(annotation -> log.info(((CustUserAnnotation) annotation).name()));

As in the previous example, we get the annotation value through the getannotations method.

It's certainly a good idea to use annotations at runtime, but too much reflection will actually affect the performance of the program.

So can we advance runtime annotations to compile time? The answer is yes.

Use annotations at compile time

To use annotations at compile time, we need to introduce the last part of today's content, annotation processors.

Custom processors need to implement javax annotation. processing. Processor interface.

Next, we customize a processor:

@SupportedAnnotationTypes("com.flydean.*")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv) {
        System.out.println("process annotation!");
        annotations.forEach(annotation -> {
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
            elements.stream()
                    .filter(TypeElement.class::isinstance)
                    .map(TypeElement.class::cast)
                    .map(TypeElement::getQualifiedName)
                    .map(name -> "Class " + name + " is annotated with " + annotation.getQualifiedName())
                    .forEach(System.out::println);
        });
        return true;
    }
}

Supportedannotationtypes indicates the supported annotation types.

Supportedsourceversion indicates the supported source code version.

Finally, in the process method, we get some information about the annotation class.

With processor, how can we use it in Maven environment?

The simplest way is to add annotationprocessors to Maven's Maven compiler plugin, as shown below:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>14</source>
                    <target>14</target>
                    <annotationProcessors>
                       <annotationProcessor>com.flydean.MyProcessor</annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
        </plugins>
    </build>

If not, by default, the compiler will look for meta - inf / services / javax. XML from the classpath annotation. processing. Processor file, which lists the externally provided annotation processors. The compiler loads these annotation processors to process the annotations of the current project.

Lombok should have been used by everyone. It actually provides us with two annotation processors:

Unfortunately, because I used the log in Lombok in custuser, if the specified annotationprocessor is displayed as above, the default lookup path will be overwritten, and finally Lombok will become invalid.

What should be done to be compatible with Lombok and custom processors?

We can separate the custom processor into a module, or in the form of Lombok:

The module compilation parameter of this processor needs to be added with a parameter of proc none:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>14</source>
                    <target>14</target>
                    <proc>none</proc>
                </configuration>
            </plugin>
        </plugins>
    </build>

Proc is used to set whether the processor needs to be enabled in this project. For the processor project, it has not been compiled yet. If it is enabled, there will be an error that the class cannot be found. So here we need to set proc to none.

Finally, our annotation usage project can automatically read the custom processor from the classpath without the configuration of annotation processors.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>