Java custom annotations are generally used with  interceptors  or  AOP . Using the custom annotations to design your own framework can make the code look very elegant.

What is Annotations ?

Java annotation is an annotation mechanism, introduced by jdk5.0. Classes, methods, variables, parameters and packages in the Java language can be labeled. Unlike the Javadoc, Annotation can obtain annotation content through reflection.

When the compiler generates class files, annotations can be embedded in bytecode. The Java virtual machine can retain the annotation content and obtain the annotation content at run time. Of course, it also supports custom Java annotations.

Java defines a set of annotations. There are 7 in total, 3 are in Java Lang, the remaining 4 knowed as meta annotations are in Java.Lang.annotation.

The annotations that act on the code are:

  • @Override checks whether the method is an override method. If it is found that its parent class or the referenced interface does not have this method, a compilation error will be reported.
  • @Deprecated marks obsolete methods. If this method is used, a compilation warning will be reported.
  • @Suppresswarnings instructs the compiler to ignore warnings declared in annotations.

Meta annotations act on other annotations are:

  • @Retention identifies how the annotation is saved, whether it is only in the code, incorporated into the class file, or can be accessed through reflection at run time.
  • @Documented marks whether these annotations are included in the user document.
  • @Target marks which Java member this annotation should be.
  • @Inherited marks the annotation class to which the annotation is inherited (the default annotation does not inherit from any subclasses)

Common Meta Annotations

There are two kinds of meta-annotations that are more commonly used:

  • @Target Describes the range of objects modified by the annotation. The value is defined in java.lang.annotation.ElementType. Commonly used include:

    • METHOD: used to describe the method
    • PACKAGE: used to describe the package
    • PARAMETER: used to describe method variables
    • TYPE: used to describe a class, interface or enum type
  • @Retention Only it is defined as RetentionPolicy.RUNTIME can we get the annotation through reflection.

    • SOURCE: Valid in source files, ignored during compilation
    • CLASS: compiled with the source file in the class file, ignored at runtime
    • RUNTIME: Valid at runtime
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
    String description();
    int length();
}

Used Annotations Example

1. Reflection to get annotations

Continue to use the custom annotation @MyField above, Get annotations via reflection.

public class MyFieldTest {
    // user the custion annotataion
    @MyField(description = "username", length = 12)
    private String username;
    @Test
    public void testMyField(){
        // get the class Template
        Class c = MyFieldTest.class;
        // get all fields
        for(Field f : c.getDeclaredFields()){
            // Determine if this field has the MyField annotation
            if(f.isAnnotationPresent(MyField.class)){
                MyField annotation = f.getAnnotation(MyField.class);
                System.out.println("field:[" + f.getName() + "], description:[" + annotation.description() + "], length:[" + annotation.length() +"]");
            }
        }
    }
}

2. Annotation + interceptor = Login Verification

Next, we use the springboot interceptor to implement such a function. If @LoginRequired is added to the method, the user is prompted that the interface needs to be logged in to access, otherwise, no login is required.

First define a LoginRequired annotation.

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

Then write two simple interfaces to access sourceA, sourceB resources.

@RestController
public class IndexController {
    @GetMapping("/sourceA")
    public String sourceA(){
        return "you are visiting sourceA";
    }
	
	@LoginRequired
    @GetMapping("/sourceB")
    public String sourceB(){
        return "you are visiting sourceB";
    }
}

Implement spring’s HandlerInterceptor class to implement an interceptor for login authentication.

public class SourceAccessInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("entering the interceptor");
        // get the annotation through reflection
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        if(loginRequired == null){
            return true;
        }
        // Prompt user to log in
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().print("the source you are visiting needs logined");
        return false;
    }
	
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
    
	@Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

3. Annotation + AOP = Log Printing

First import the dependencies required by the aspect.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Then define an annotation @MyLog.

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

Define a logging aspect class.

// Indicates that this is an aspect class
@Aspect
@Component
public class MyLogAspect {
	// PointCut indicates that this is a pointcut, and @annotation indicates 
	// that this pointcut cuts to an annotation
    @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
    public void logPointCut(){};
    
	// around notify
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // access methodName
        String methodName = joinPoint.getSignature().getName();
        // access param
        Object[] param = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        System.out.println("enter [" + methodName + "] method, parameters are:" + sb.toString());
        // continue proceed method
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(methodName + "method done");
    }
}

The IndexController in the second example writes a sourceC for testing, plus our custom annotations:

    @MyLog
    @GetMapping("/sourceC/{source_name}")
    public String sourceC(@PathVariable("source_name") String sourceName){
        return "you are visiting sourceC"; 
    }