Do you understand the charm of custom annotations

preface

Do you know the charm of custom annotations?

Do you know how to use custom annotations?

These two questions at the beginning of this article need you to think carefully, and then read the following contents in combination with these two questions; If you have a clear understanding of these two questions after reading the article, please use your little hand to make a fortune and leave a praise message!

The main line of this paper:

  • What are annotations;
  • Implement a custom annotation;
  • Actual application scenarios of user-defined annotations;

Note: when introducing the actual application scenario of user-defined annotation, it needs to be used in combination with interceptor and AOP, so this article will also briefly talk about the relevant knowledge points of AOP. If you are not clear about the relevant contents of AOP, you can refer to this Detailed explanation of Spring -- AOP Understand the article.

annotation

What is the annotation?

① . content quoted from Wikipedia:

Java annotation, also known as Java annotation, is jdk5 Version 0 supports adding special syntax metadata of source code.

Classes, methods, variables, parameters and packages in the Java language can be labeled. Unlike Javadoc, Java annotations 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.

② . contents quoted from the network:

Java annotation is a new feature introduced in JDK5. Annotation (also known as metadata) provides a formal method for us to add information to the code, so that we can easily use these data at a later time.

What is meta annotation?

The function of meta annotation is to annotate other annotations. Java5.0 defines four standard meta annotation types, which are used to describe other annotation types.

Standard meta annotation:
  • @Target
  • @Retention
  • @Documented
  • @Inherited

Before describing the meaning of these four metadata in detail, let's take a look at the @ Autowired annotation that is often used in work. Enter this annotation and have a look: the three meta annotations @ Target, @ Retention and @ Documented are used in this annotation.

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}
@Target meta annotation:

@The Target annotation is specifically used to define which Java elements a user-defined annotation can be applied to, indicating the scope of action; The value is in Java lang.annotation. ElementType is defined.

public enum ElementType {
    /** Class, interface (including annotation type) or enumeration declaration */
    TYPE,

    /** Declaration of attributes */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Method formal parameter declaration */
    PARAMETER,

    /** Declaration of construction method */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE
}

Here you can know the scope of @ Autowired annotation:

// It can act on construction methods, methods, method parameters, attributes and annotation types
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention meta note:

@Retention annotation, translated as persistence and retention. That is, the life cycle used to decorate custom annotations.

The lifecycle of annotations has three phases:

  • Java source file stage;
  • Compile to class file stage;
  • Operation phase;

The RetentionPolicy enumeration type is also used to define these three stages:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     * (Annotations will be ignored by the compiler)
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * (Annotations will be recorded in the class file by the compiler, but will not be retained by the virtual machine at run time, which is a default behavior)
     */
    CLASS,

    /**
     * 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.
     * (Annotations will be recorded in the class file by the compiler and will be retained by the virtual machine at run time, so they can be read through reflection)
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

Describe these three stages in detail:

① If it is defined as retentionpolicy Source, then it will be limited to the Java source file, then this annotation will not participate in the compilation and will not play any role in the run-time. This annotation has the same effect as an annotation and can only be seen by those reading the java file;

② If it is defined as retentionpolicy Class, it will be compiled into the class file. Then the compiler can do some processing actions according to the annotation during compilation, but the runtime JVM (Java virtual machine) will ignore it and cannot read it during runtime;

③ If it is defined as retentionpolicy Runtime, then this annotation can be loaded into the Class object in the loading phase of the runtime. Then in the program running stage, you can get this annotation through reflection, and judge whether there is this annotation or the value of the attribute in this annotation, so as to execute different program code segments.

Note: almost all custom annotations in actual development use retentionpolicy RUNTIME .

@Documented meta annotation:

@Documented annotations are used to specify whether custom annotations can be generated into JavaDoc documents along with the defined java files.

@Inherited meta annotation:

@Inherited annotation is to specify a user-defined annotation. If it is written in the declaration part of the parent class, the declaration part of the child class can also have the annotation automatically.

@Inherited annotation is defined as ElementType only for those @ Target Custom annotations for type work.

Custom annotation implementation:

After understanding the above content, let's try to implement a custom annotation:

According to the meta annotation used in the above custom annotation:

① . the scope of this annotation can be used on classes (interfaces, enumerations) and methods;

② . the life cycle of this annotation is saved in the class file by the compiler, and will be reserved by the JVM at run time, which can be read through reflection;

Simple use of custom annotations:

A custom annotation has been created above, so how to use it? The following is a brief description of its usage. Later, it will be used to combine interceptor and AOP aspect programming for practical application;

Application scenario implementation

After understanding the knowledge of the above annotation, we will take advantage of the victory to see what its actual application scenario is, so as to deepen our understanding;

The Demo project implemented is implemented in SpringBoot. The engineering structure of the project is as follows:

Scenario 1: user defined annotation + interceptor = packaging of interface response

Using user-defined annotations combined with interceptors to gracefully implement the packaging of API interface response.

Before introducing the user-defined implementation methods, first briefly introduce the common implementation methods. Through the comparison between the two, we can more clearly find out who is the most elegant.

Common interface response packaging method:

At present, most projects adopt the front-end and back-end separation method, so the front-end and back-end need to interact through the interface; At present, the most commonly used data format in interface interaction is json, and the most common response format returned from the back end to the front end is as follows:

{
    #Return status code
    code:integer,       
    #Return information description
    message:string,
    #Return data value
    data:object
}

Enumeration classes are often used in projects to define status codes and messages. The codes are as follows:

/**
 * @author [ Muzilei] official account
 * @Title: ResponseCode
 * @Description: Use the enumeration class to encapsulate the response status code and the corresponding response message
 * @date: 2019 At 7:12:50 pm on August 23
 */
public enum ResponseCode {

    SUCCESS(1200, "Request succeeded"),

    ERROR(1400, "request was aborted");


    private Integer code;

    private String message;

    private ResponseCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer code() {
        return this.code;
    }

    public String message() {
        return this.message;
    }

}

At the same time, a return response wrapper class will be designed in the project, and the code is as follows:

import com.alibaba.fastjson.JSONObject;
import java.io.Serializable;

/**
 * @author [ Muzilei] official account
 * @Title: Response
 * @Description: Encapsulated unified response return class
 * @date: 2019 August 23, 2007 7:07:13 PM
 */
@SuppressWarnings("serial")
public class Response<T> implements Serializable {

    /**
     * Response data
     */
    private T date;

    /**
     * Response status code
     */
    private Integer code;

    /**
     * Response description information
     */
    private String message;

    public Response(T date, Integer code, String message) {
        super();
        this.date = date;
        this.code = code;
        this.message = message;
    }


    public T getDate() {
        return date;
    }

    public void setDate(T date) {
        this.date = date;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }


    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

Finally, the response wrapper class and status code enumeration class are used to realize the wrapper of the returned response:

@GetMapping("/user/findAllUser")
public Response<List<User>> findAllUser() {
    logger.info("Start querying all data...");

    List<User> findAllUser = new ArrayList<>();
    findAllUser.add(new User("Muzilei", 26));
    findAllUser.add(new User("official account", 28));

    // Return response for packaging
    Response response = new Response(findAllUser, ResponseCode.SUCCESS.code(), ResponseCode.SUCCESS.message());

    logger.info("response: {} \n", response.toString());
    return response;
}

Enter the web address in the browser: http://127.0.0.1 : 8080/v1/api/user/findAllUser, then click enter to get the following data:

{
    "code": 1200,
    "date": [
        {
            "age": 26,
            "name": "Muzilei"
        },
        {
            "age": 28,
            "name": "official account"
        }
    ],
    "message": "Request succeeded"
}

Can we find any problems by looking at the way to implement response packaging?

A: the code is very redundant and needs to be packaged in each interface method; The interface method contains a lot of non business logic code;

Is there any version optimized? EN thinking..... Ah, custom annotation + interceptor can be realized!

User defined annotation implements interface response packaging:

① First, create a custom annotation for response packaging:

/**
 * @author [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.annotation
 * @ClassName: ResponseResult
 * @Description: The return value of the tag method needs to be wrapped with a custom annotation
 * @Date: 2020-11-10 10:38
 **/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {

}

② Create an interceptor to intercept the request and see if the user-defined annotation is used on the requested method or class:

/**
 * @author [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.interceptor
 * @ClassName: ResponseResultInterceptor
 * @Description: Interceptor: intercepts the request and determines whether the custom @ ResponseResult annotation is used on the requested method or class,
 *               And set whether the flag bit attribute of user-defined annotation is used in the request;
 * @Date: 2020-11-10 10:50
 **/
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {

    /**
     * Tag bit, the custom annotation is used on the controller class or method of the tag request, and the returned data needs to be wrapped
     */
    public static final String RESPONSE_ANNOTATION = "RESPONSE_ANNOTATION";

    /**
     * Request preprocessing to determine whether custom annotations are used
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // Requested interface method
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            // Determine whether annotations are added to class objects
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                // Set the attribute flag that requires response packaging in the request and handle it in the following ResponseBodyAdvice enhancement
                request.setAttribute(RESPONSE_ANNOTATION, clazz.getAnnotation(ResponseResult.class));
            } else if (method.isAnnotationPresent(ResponseResult.class)) {
                // Set the attribute flag that requires response packaging in the request and handle it in the following ResponseBodyAdvice enhancement
                request.setAttribute(RESPONSE_ANNOTATION, method.getAnnotation(ResponseResult.class));
            }
        }
        return true;
    }
}

③ Create an enhanced Controller to realize the enhanced processing of packaging the returned response:

/**
 * @author [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.interceptor
 * @ClassName: ResponseResultHandler
 * @Description: Enhanced processing of packaging the return response
 * @Date: 2020-11-10 13:49
 **/
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * Tag bit, the custom annotation is used on the controller class or method of the tag request, and the returned data needs to be wrapped
     */
    public static final String RESPONSE_ANNOTATION = "RESPONSE_ANNOTATION";

    /**
     * Whether the request contains the mark that the response needs to be wrapped. If not, it will be returned directly without rewriting the return body
     *
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest sr = (HttpServletRequest) ra.getRequest();
        // Flag to query whether response packaging is required
        ResponseResult responseResult = (ResponseResult) sr.getAttribute(RESPONSE_ANNOTATION);
        return responseResult == null ? false : true;
    }


    /**
     * Package the responders; In addition, the response body can be uniformly encrypted and signed
     *
     * @param responseBody  Get the return value (return response) after the requested interface method is executed
     */
    @Override
    public Object beforeBodyWrite(Object responseBody, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        logger.info("Return response packaging in progress...");
        Response response;
        // Judge whether the operations of adding, updating and deleting some databases are successful when using boolean type
        if (responseBody instanceof Boolean) {
            if ((Boolean) responseBody) {
                response = new Response(responseBody, ResponseCode.SUCCESS.code(), ResponseCode.SUCCESS.message());
            } else {
                response = new Response(responseBody, ResponseCode.ERROR.code(), ResponseCode.ERROR.message());
            }
        } else {
            // Judge whether to query some returned data. If the data cannot be queried, null will be returned;
            if (null != responseBody) {
                response = new Response(responseBody, ResponseCode.SUCCESS.code(), ResponseCode.SUCCESS.message());
            } else {
                response = new Response(responseBody, ResponseCode.ERROR.code(), ResponseCode.ERROR.message());
            }
        }
        return response;
    }
}

④ Finally, use our custom annotation in the Controller; Use @ ResponseResult custom annotation on the Controller class or method; Enter the web address in the browser: http://127.0.0.1 : 8080/v1/api/user/findAllUserByAnnotation to view:

// Custom annotations are used on methods
@ResponseResult
@GetMapping("/user/findAllUserByAnnotation")
public List<User> findAllUserByAnnotation() {
    logger.info("Start querying all data...");

    List<User> findAllUser = new ArrayList<>();
    findAllUser.add(new User("Muzilei", 26));
    findAllUser.add(new User("official account", 28));

    logger.info("use @ResponseResult Custom annotation for response packaging, so that controller More introduction to the code");
    return findAllUser;
}

So far, our interface return response packaging custom annotation implementation design is completed. Let's see if the code is concise and elegant.

Conclusion: This paper only makes a simple implementation for this scheme. If you are interested, you can optimize it better.

Scenario 2: user defined annotation + AOP = elegant use of distributed locks

The most common use process of distributed lock:

Let's first look at the implementation of the most common use of distributed locks, and then talk about how user-defined annotations can gracefully realize the use of distributed locks.

Common distributed lock usage:

From the above code, we can get a message: if there are many methods that need to use distributed locks, each method must have code to obtain and release distributed locks, which will lead to code redundancy;

Is there any good solution? Custom annotations make the code more concise and elegant;

User defined annotations use distributed locks gracefully:

① First, implement a user-defined annotation for marking the use of distributed locks:

/**
 * @author [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.annotation
 * @ClassName: GetDistributedLock
 * @Description: Get redis distributed lock annotation
 * @Date: 2020-11-10 16:24
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetDistributedLock {

    // Distributed lock key
    String lockKey();

    // Distributed lock value. The default value is lockValue
    String lockValue() default "lockValue";

    // Expiration time: 300 seconds by default
    int expireTime() default 300;

}

② Define a section in which you can make a surround enhancement notification for the method using @ GetDistributedLock custom annotation:

/**
 * @author: [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.aop
 * @ClassName: DistributedLockAspect
 * @Description: User defined annotation combined with AOP facet programming elegant use of distributed locks
 * @Date: 2020-11-10 16:52
 **/
@Component
@Aspect
public class DistributedLockAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    RedisService redisService;


    /**
     * Around Surround enhanced notification
     *
     * @param joinPoint Connection point, all methods belong to connection point; However, when @ GetDistributedLock custom annotation is used on some methods,
     *                  Then it changes the connection point into the tangent point; Then, additional reinforcement treatment is woven into the tangent point; The pointcut and its corresponding enhancement constitute the facet Aspect.
     */
    @Around(value = "@annotation(com.lyl.annotation.GetDistributedLock)")
    public Boolean handlerDistributedLock(ProceedingJoinPoint joinPoint) {
        // Get custom annotation objects through reflection
        GetDistributedLock getDistributedLock = ((MethodSignature) joinPoint.getSignature())
                .getMethod().getAnnotation(GetDistributedLock.class);

        // Gets the attribute value in the custom annotation object
        String lockKey = getDistributedLock.lockKey();
        String LockValue = getDistributedLock.lockValue();
        int expireTime = getDistributedLock.expireTime();

        if (redisService.tryGetDistributedLock(lockKey, LockValue, expireTime)) {
            // After obtaining the distributed lock successfully, continue to execute the business logic
            try {
                return (boolean) joinPoint.proceed();
            } catch (Throwable throwable) {
                logger.error("Business logic execution failed.", throwable);
            } finally {
                // Finally, the release of distributed lock is guaranteed
                redisService.releaseDistributedLock(lockKey, LockValue);
            }
        }
        return false;
    }

}

③ Finally, use @ GetDistributedLock custom annotation on the method in the Controller; When a user-defined annotation is used on a method, the method is equivalent to a tangent point, and the method will be enhanced (before and after method execution);

Enter the web address in the browser: http://127.0.0.1 : 8080/v1/api/user/getDistributedLock trigger method execution after carriage return:

// Use of custom annotations
@GetDistributedLock(lockKey = "userLock")
@GetMapping("/user/getDistributedLock")
public boolean getUserDistributedLock() {
    logger.info("Acquire distributed locks...");
    // Write specific business logic

    return true;
}

By customizing annotations, you can see that the code becomes more concise and elegant.

Scenario 3: user defined annotation + AOP = print log

First look at the most common way of log printing, and then talk about how user-defined annotations can gracefully realize log printing.

Printing method of ordinary log:

By looking at the above code, we can know that if each method needs to print the log, there will be a lot of redundant code;

User defined annotation enables log printing:

① First, create a custom annotation for marking log printing:

/**
 * @Author: [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.annotation
 * @ClassName: PrintLog
 * @Description: User defined annotation for log printing
 * @Date: 2020-11-10 18:05
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrintLog {

}

② Define a section, in which the method using @ PrintLog custom annotation is notified:

/**
 * @author: [ Muzilei] official account
 * @PACKAGE_NAME: com.lyl.aop
 * @ClassName: PrintLogAspect
 * @Description: User defined annotation combined with AOP facet programming can realize log printing gracefully
 * @Date: 2020-11-10 18:11
 **/
@Component
@Aspect
public class PrintLogAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     *  Around Surround enhanced notification
     *
     * @param joinPoint Connection point, all methods belong to connection point; However, when @ PrintLog custom annotation is used on some methods,
     *                  Then it changes the connection point into the tangent point; Then, additional reinforcement treatment is woven into the tangent point; The pointcut and its corresponding enhancement constitute the facet Aspect.
     */
    @Around(value = "@annotation(com.lyl.annotation.PrintLog)")
    public Object handlerPrintLog(ProceedingJoinPoint joinPoint) {
        // Gets the name of the method
        String methodName = joinPoint.getSignature().getName();
        // Get method input parameters
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for (Object o : param) {
            sb.append(o + "; ");
        }
        logger.info("Enter<{}>method, Parameter is: {}", methodName, sb.toString());

        Object object = null;
        // Continue execution method
        try {
            object = joinPoint.proceed();

        } catch (Throwable throwable) {
            logger.error("Print log processing error. . ", throwable);
        }
        logger.info("{} Method execution ended..", methodName);
        return object;
    }

}

③ Finally, use @ PrintLog custom annotation on the method in the Controller; When a user-defined annotation is used on a method, the method is equivalent to a tangent point, and the method will be enhanced (before and after method execution);

@PrintLog
@GetMapping(value = "/user/findUserNameById/{id}", produces = "application/json;charset=utf-8")
public String findUserNameById(@PathVariable("id") int id) {
    // Simulate querying user name according to id
    String userName = "Muzilai official account";
    return userName;
}

④ . enter the web address in the browser: http://127.0.0.1 : 8080/v1/api/user/findUserNameById/66 press enter to trigger the method execution. It is found that the console prints a log:

Enter< findUserNameById>method, Parameter is: 66; 
findUserNameById Method execution ended..

How elegant it is to use user-defined annotations. The code looks clean, and the more you look at it, the more you like it; Use it in your project, hehe...

end . . . This is the end of this article. We look forward to our next meeting.

Finally, I would like to ask the two questions at the beginning of the article. Do you already have answers in your heart! hey..

❤ Follow + like + collect + comment

If this article is helpful to you, please wave your little hand and praise it. Your support is the driving force of my continuous creation; thank you!

If you want the Demo source code, please search [muzilai] official account in VX and reply to "comments" for it; Thank you again for reading this article!

reference material

①,User defined annotation details

②,Java custom annotations and usage scenarios

③,Want to write your own framework? Not if you can't write Java annotations

④,Look at the back-end API interface written by others. It's called elegant!

Tags: Java Spring Boot Annotation

Posted by google_man2000 on Sat, 07 May 2022 13:15:52 +0300