简体   繁体   中英

How to implement annotations to limit number of member functions, function types, etc. like FunctionalInterface?

There are many internal Java annotations like SuppressWarning , FunctionalInterface , etc. which can limit the members of the class with the annotation, expand classes or even specify compiler options, but how can a normal programmer compose such annotations?

I searched on the annotation topics and all I could find is adding some meta values to the annotation like this , and how to use annotations, but nothing I can find that explains how to implement advanced annotations. Any directions would be helpful.

What you are looking for is compile-time annotation .

Basically, annotation processing can be done based on its RetentionPolicy . As per the Java docs , there are 3 type of RetentionPolicy -

CLASS Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time.

RUNTIME Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.

SOURCE Annotations are to be discarded by the compiler.

Compile-time annotation processing is related to RetentionPolicy.SOURCE because you want to process on source file at the time of compilation similar to other annotation like @Override .

Below is one example of a simple compile-time annotation processor -

  1. Create Maven Project - Annotation_Compile_Time -

    (A) Create a compile-time Annotation MyAnnotation in this project -

     package xxx; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Target(ElementType.TYPE) @Inherited @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { } 

    (B) Create a annotation Processor MyAnnotationProcessor -

     package xxx; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; @SupportedAnnotationTypes("xxx.MyAnnotation ") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyAnnotationProcessor extends AbstractProcessor { public MyAnnotationProcessor () { super(); } @Override public boolean process(Set<? extends TypeElement> typeElementSet, RoundEnvironment roundEnv) { for (Element e : roundEnv.getRootElements()) { String className = e.toString(); String message = "Annotated class - " + className; System.out.println(message); processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message); } return false; } } 

    (C) Create javax.annotation.processing.Processor file in directory - src/main/resources/META-INF/services with below content -

     xxx.MyAnnotationProcessor 

    (D) Update pom.xml with build configuration -

     <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <verbose>true</verbose> <fork>true</fork> <compilerArgument>-proc:none</compilerArgument> </configuration> </plugin> </plugins> </build> 

    (E) Compile and install this project using mvn clean install .

  2. Create Another Maven Project - Annotation_User - This project will use annotation defined in above project. Create 2 source files in this project annotated with this annotation

    (A) AnnotationUser1 -

     package xxx.consumer; import xxx.MyAnnotation; @MyAnnotation public class AnnotationUser1 { private String message; public AnnotationUser1(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 

    (B) AnnotationUser2 -

     package xxx.consumer; import xxx.MyAnnotation; @MyAnnotation public class AnnotationUser2 { private String message; public AnnotationUser1(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 

    (C) Update pom.xml with Annotation project dependency -

     <dependency> <groupId>xxx</groupId> <artifactId>Annotation_Compile_Time</artifactId> <version>1.0</version> </dependency> 

Now, whenever, you will compile this project using mvn clean compile , your annotation processor defined in project 1 will get called. Currently, processor is just printing the name of the classes annotated with this annotation.

You can also refer to this page for details.

Next step comes is to analyse the source file and calculate no. of methods. Since, it is compile time processing, so you can not use Reflection API for getting no. of methods. One solution is to use Eclipse AST for parsing source file and calculating no. of methods or you can write your own logic.

Project lombok is mainly based on Compile-time annotation processing. If you want to do something useful, it would be better to study Project lombok source code

I think you need annotation processing:

UPD: For some cases you can write javaagent instead which is much better at least because it is at least has some tools like bytebuddy (which is great IMHO)

I am sharing how I made a custom annotation to resolve an issue in my app.

Problem:

I was using Spring AOP to do the logging in my app. To people new to AOP, what it did in simple words is instead of writing logger.log() in every method and class, you can tell AOP to do something (in my case logging) after/before/after-and-before each method. Now the problem is since every method is going to get logged, how do I prevent a certain method(like authentication ) or parameter(like password ) from getting logged.

So to do this, I created an annotation SkipLogging

@Target(value = { ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SkipLogging {

}

and in my AOP class, I put in a condition that if any thing has this annotation, AOP should not do logging on that. Maybe following (partial) code will make more sense:

@Around("within(com.test..*) 
    public Object logAround(final ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // Log only if class/method is not annotated with SkipLogging
        if (!(signature.getDeclaringType().isAnnotationPresent(SkipLogging.class)
                || method.isAnnotationPresent(SkipLogging.class))) {
            try {
                // Log before method entry
                logger.info("");

                Object result = joinPoint.proceed();

                // Log after method exit
                logger.info("");

                return result;
            } catch (Exception e) {
                // Log after exception
                logger.error("");
            throw e;
            }
        } else {
            return joinPoint.proceed();
        }
    }

Without going into much detail, look at the condition:

    if (!(signature.getDeclaringType().isAnnotationPresent(SkipLogging.class)
                        || method.isAnnotationPresent(SkipLogging.class

)))

which prevents classes and methods annotated with SkipLogging from getting logged. Similarly, I had written some code for putting this annotation on parameters and skipping them.

In the next steps, I had created annotations like @DebugLogging, @ErrorLogging etc. and put in a check in my AOP, to write debug log or error log based on the annotation present.

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