We encounter all sorts of annotations during business development, but the framework’s own annotations do not always meet complex business requirements, and we can customize annotations to meet our needs. \

Depending on where annotations are used, this article will introduce custom annotations by dividing them into field annotations, method annotations, and class annotations.

Field notes

The hibernate-validate dependency provides many validation annotations, such as @notnull, @range, etc., but these annotations are not suitable for all business scenarios.

For example, if we want the parameter passed to be in the specified String collection, the existing annotations will not suffice and we will need to implement them ourselves.

Custom annotations

Define an @check annotation and declare an annotation via @interface

@Target({ ElementType.FIELD}) // Can only be used on class fields
@Retention(RetentionPolicy.RUNTIME) // Annotations are kept for the duration of the program, at which point all annotations defined on a class can be retrieved by reflection
@Constraint(validatedBy = ParamConstraintValidated.class)
public @interface Check {
    /** * Valid parameter value ** /
    String[] paramValues();

    /**
     * 提示信息
     * */
    String message() default "Parameter not specified value"; Class<? >[] groups()default {};

    Class<? extends Payload>[] payload() default {};
}
Copy the code

@target defines where the annotation is used to indicate which elements the annotation can be declared before.

Elementtype. TYPE: Indicates that the annotation can only be declared before one class.

Elementtype. FIELD: Indicates that the annotation can only be declared before a FIELD of a class.

Elementtype. METHOD: Indicates that this annotation can only be declared before a METHOD of a class.

Elementtype. PARAMETER: Indicates that this annotation can only be declared before a method PARAMETER.

Elementtype. CONSTRUCTOR: Indicates that this annotation can only be declared before a class CONSTRUCTOR.

Elementtype. LOCAL_VARIABLE: Indicates that this annotation can only be declared before a local variable.

Elementtype. ANNOTATION_TYPE: Indicates that this annotation can only be declared before an annotation type.

Elementtype. PACKAGE: Indicates that this annotation can only be declared before a PACKAGE name

@constraint specifies the validator associated with the annotation by using validatedBy

@Retention is used to indicate the lifetime of the annotation class.

Retentionpolicy. SOURCE: Annotations are reserved only in the SOURCE file

Retentionpolicy. CLASS: Annotations remain in the CLASS file and are discarded when loaded into the JVM VIRTUAL machine

Retentionpolicy.runtime: Annotations are retained while the program is running, and all annotations defined on a class can be retrieved by reflection.

The validator class

The validator class needs to implement the ConstraintValidator generic interface

public class ParamConstraintValidated implements ConstraintValidator<Check, Object> {
    /** * Valid parameter value, get ** / from annotation
    private List<String> paramValues;

    @Override
    public void initialize(Check constraintAnnotation) {
        // Gets the value on the annotation when initialized
        paramValues = Arrays.asList(constraintAnnotation.paramValues());
    }

    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        if (paramValues.contains(o)) {
            return true;
        }

        // Not in the specified argument list
        return false; }}Copy the code

The first generic parameter type Check: annotation, and the second generic parameter Object: validation field type. The initialize and isValid methods need to be implemented. The isValid method is the verification logic, and the Initialize method does the initialization

use

Define an entity class

@Data
public class User {
    /** ** Name ** /
    private String name;

    /** ** ** */
    @Check(paramValues = {"man"."woman"})
    private String sex;
}
Copy the code

The value of the sex field must be either woman or man

test

@RestController("/api/test")
public class TestController {
    @PostMapping
    public Object test(@Validated @RequestBody User user) {
        return "hello world"; }}Copy the code

Note that you need to add an @Validated annotation to the User object. You can also use an @Valid annotation here

Method, class annotations

In the development process, I encountered such requirements that only authorized users can access the method or a specific method in the class, and the data is not searched from the database, first from guava cache, then from Redis, and finally from mysql (multi-level cache).

Custom annotations can be used to do this. The first scenario is to define a permission validation annotation, and the second scenario is to define an annotation like @cacheable in the spring-data-Redis package.

Permissions annotations

Custom annotations

@Target({ ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
    /** * resource key ** /
    String resourceKey();
}
Copy the code

The scope of this annotation is on a class or method

Interceptor class

public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
    /** * calls */ before the processor processes it
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        PermissionCheck permission = findPermissionCheck(handlerMethod);

        // Skip permission if no permission annotations are added
        if (permission == null) {
            return true;
        }

        // Get the value in the annotation
        String resourceKey = permission.resourceKey();

        //TODO permission verification generally requires obtaining user information and verifying permissions by querying the database
        //TODO is a simple demonstration. If resourceKey is testKey, the check succeeds; otherwise, the check fails
        if ("testKey".equals(resourceKey)) {
            return true;
        }

        return false;
    }

    /** * returns annotation information based on the handlerMethod ** @param handlerMethod method object * @return PermissionCheck annotation */
    private PermissionCheck findPermissionCheck(HandlerMethod handlerMethod) {
        // Look for annotations on methods
        PermissionCheck permission = handlerMethod.getMethodAnnotation(PermissionCheck.class);
        if (permission == null) {
            // Look for annotations on the class
            permission = handlerMethod.getBeanType().getAnnotation(PermissionCheck.class);
        }

        returnpermission; }}Copy the code

The logic of permission verification is that if you have permission, you can access it, and if you don’t, you can’t access it, which is essentially an interceptor. We need to get the annotation first, and then get the fields on the annotation for validation, returning true if validation passes, false otherwise

test

 @GetMapping("/api/test")
 @PermissionCheck(resourceKey = "test")
 public Object testPermissionCheck() {
     return "hello world";
 }
Copy the code

The PermissionCheck annotation is added because this method requires permission verification.

Cache annotations

Custom annotations

@Target({ ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomCache {
    /** * Cache key ** /
    String key();
}
Copy the code

Annotations can be used on methods or classes, but caching annotations are typically used on methods.

section

@Aspect
@Component
public class CustomCacheAspect {
    /** * The annotation is processed before the method executes ** @param PJD * @param customCache annotation * @return the value in the return ** /
    @Around("@annotation(com.cqupt.annotation.CustomCache) && @annotation(customCache)")
    public Object dealProcess(ProceedingJoinPoint pjd, CustomCache customCache) {
        Object result = null;

        if (customCache.key() == null) {
            //TODO throw error
        }

        //TODO business scenarios are much more complex than this, involving parsing of parameters such as key might be #{id}, data queries, etc
        //TODO returns Hello world if key is testKey
        if ("testKey".equals(customCache.key())) {
            return "hello word";
        }

        // Execute the target method
        try {
            result = pjd.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        returnresult; }}Copy the code

Because caching annotations requires a return value before the method executes, instead of processing the annotation through an interceptor, the annotation is processed before the method executes using sections.

If the annotation does not return a value, the value in the method will be returned

test

@GetMapping("/api/cache")
@CustomCache(key = "test")
public Object testCustomCache() {
    return "don't hit cache";
}
Copy the code

From: suo. Im / 60 gja2

                                -END-