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-