Yuan notes
To customize the development of annotations, the first thing to know is meta-annotations. Meta-annotations can be simply understood as annotations that modify annotations, mainly including the following four:
@Documented
Documented is a marked annotation with no member. Annotations decorated with this annotation are logged by the Javadoc tool. By default, documentation generated using the Javadoc command does not include information about annotations.
@Target
The @target annotation is used to specify the scope of the annotation that it modifies. Its value range is defined in the ElementType enumeration, which can refer to the annotation in the enumerated class:
public enum ElementType {
TYPE, // Class, interface, enumeration class
FIELD, // Member variables (including enumeration constants)
METHOD, // Member methods
PARAMETER, // Method parameters
CONSTRUCTOR, // constructor
LOCAL_VARIABLE, // Local variables
ANNOTATION_TYPE, // Annotation class - somewhat similar to the scope of a meta-annotation
PACKAGE, // Can be used to modify: package
TYPE_PARAMETER, // Type parameter, new in JDK 1.8
TYPE_USE // Use type anywhere, new in JDK 1.8
}
Copy the code
@ Retention annotations
The @reteniton annotation defines the lifetime of the annotation class it modifies. There are three policies, defined in the RetentionPolicy enumeration.
public enum RetentionPolicy {
/** * Annotations are to be discarded by the compiler. Annotations remain in the source file only, and are discarded when a Java file is compiled into a class file. * /
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 are kept in the class file, but are discarded when the JVM loads the class file, which is the default lifecycle; * /
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.
*
* @seeJava. Lang. Reflect. AnnotatedElement annotation not only be saved to a class file, the JVM to load the class files, still existed; * /
RUNTIME
// These three life cycles correspond to: Java source files (.java files) -->.class files --> bytecode in memory.
}
Copy the code
@Inherited
annotations
The @Inherited annotations are used to make the annotations Inherited. If a class uses the annotations modified by @Inherited, its child classes inherit the annotations.
Custom @valid related annotation
Refer to the @valid annotation
The spring-boot-starter dependency will be included in the following version and will be introduced separately in the new version.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Copy the code
Code sample
// ==== create a User class =====
@Data
public class User {
private String username;
@notblank (message = "password cannot be blank ")
private String password;
private Integer id;
@past (message = "date of birth cannot be in the future ")
private Date birthday;
}
//====== verifies that the attribute of the user object ======= is received
@RestController
@Slf4j
public class UserController {
@PostMapping("/user")
public Boolean postUser(@Valid @RequestBody User user, BindingResult bindingResult){
// Validation failed to print an error message
if(bindingResult.hasErrors()){
// A typo
log.error("error={}",bindingResult.getFieldError().getDefaultMessage());
}
returnbindingResult.hasErrors(); }}// ==== The parameter passed in =======
{
"birthday": "The 2025-09-13 T01: but 148 z".// Greater than the current date
"id": 0."password": "".// The password is empty
"username": "string"
}
// ===== Logs printed ======
2020- 09 -10 09:24:49.517 ERROR 21968 --- [nio-7777-exec-9] C.A.C ontroller. CreateDataController: error = date of birth cannot be in the futureCopy the code
As you can see from the sample code, the @VALID annotation can be used in conjunction with the BindingResult object to validate the parameters of the passed object. Another annotation with similar functions is @Validated. The major difference between the two annotations is that the former does not support group verification and the latter does. If in doubt can refer to: https://www.cnblogs.com/xiaoqi/p/spring-valid.html.
Custom @valid related annotation
@Constraint
Qualify custom annotations to specify the processing logic for custom annotations.
Custom PhoneNumber annotations
package com.example.annotation;
import org.springframework.util.StringUtils;
import javax.validation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.regex.Pattern;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/** * Custom validate annotation *@author WT
* @date2022/01/02 13:26:42 * /
// Can be logged by javadoc
@Documented
// Specify a class for custom annotation processing logic
@Constraint(validatedBy = { PhoneNumber.PhoneNumberValidator.class })
// Life cycle to runtime
@Retention(RUNTIME)
// What can it be used for
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
public @interface PhoneNumber {
// Failure message returned when the isValid method of the class specified by the processing logic returns false
String message(a) default"Mobile phone number verification failed";
// constraint groupingClass<? >[] groups()default {};
Class<? extends Payload>[] payload() default {};
The first argument is the name of the annotation and the second argument is the argument to the annotation validation
class PhoneNumberValidator implements ConstraintValidator<PhoneNumber.CharSequence> {
/** * mobile phone */
private final static String PHONE_NUMBER_REGEX = "1[3456789]\d{9}";
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if(! StringUtils.hasText(value)) {return false;
}
returnPattern.compile(PHONE_NUMBER_REGEX).matcher(value).matches(); }}}Copy the code
Use custom annotations
// === entity class === =
package com.example.pojo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.annotation.PhoneNumber;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/** *@Validate
* @author WT
* @date2022/01/02 12:53:41 * /
@Data
@TableName("b_coin_account_alarm_data_month_yyyymm")
public class TestTable {
@TableId
@notnull (message = "ID cannot be empty ")
private Long id;
private String accountId;
private String strategyId;
@PhoneNumber
private String month;
private String mobile;
private String tag;
private String measurement;
private BigDecimal data;
}
// === used in controller
@PostMapping ("/test/valid")
public void testValid (@Valid @RequestBody TestTable testTable, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// A typo
log.error("== error: [{}]",bindingResult.getFieldError().getDefaultMessage()); }}Copy the code
Custom annotations
The previous section introduced custom validate-related annotations, but the usage scenarios are too limited. The above example is used as a brief introduction to custom annotations. Let’s customize a general annotation and show you how to use custom annotations in general.
Scene description
A developed system wants to add authentication information. When other services call the service, the verification operation needs to be performed.
The following requirements must be met:
- Do not change any logic of the original code
- You can specify which clients are accessible
- Check further for clients that have access permission
The solution
Use AOP in conjunction with custom annotations to fulfill this requirement.
Maven rely on
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Copy the code
Yaml configuration
auth:
Client 1 configuration
client1:
Example: md5crypt.apr1crypt ("client1-secret", "salt1")
appSecret: $apr1$salt1$EMKCT8e1YK6HkS0lR2Onv/
# salt used for MD5 encryption
salt: salt1
Client 2 configuration
client2:
Add salt to md5 for client2-secret
appSecret: $apr1$salt2$JnMmVb96AOcSz2kObzLc90
# salt used for MD5 encryption
salt: salt2
Copy the code
The configuration class
package com.example.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/** * Get information about the auth in the configuration file **@author WT
* @date2022/01/02 15:10:59 * /
@Data
@Configuration
@ConfigurationProperties("auth")
public class AuthConfig {
private ClientConfig client1;
private ClientConfig client2;
@Data
public static class ClientConfig {
private String appSecret;
privateString salt; }}Copy the code
Custom annotations
package com.example.annotation;
import java.lang.annotation.*;
/** * Custom annotation, when marked by this annotation, requires authentication to access the corresponding interface * authentication rule: Specifying appKeyArray in annotations indicates which clients have access to this interface, passing appKey and Secret in the header, using AOP to verify that secret in the request header is correct, and allowing access to * * if it is@author WT
* @date2022/01/02 13:45:29 * /
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface EnableAuth {
// Specify which clients have access to the interface being decorated
String[] appKeyArray() default "";
}
Copy the code
AOP handles logical classes
package com.example.aop;
import com.example.annotation.EnableAuth;
import com.example.aop.exception.AuthException;
import com.example.config.AuthConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.Md5Crypt;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/** * Specifies the section class **@author WT
* @date2022/01/02 14:34:25 * /
@Slf4j
@Aspect
@Component
public class AuthAspect {
private final AuthConfig authConfig;
public AuthAspect (AuthConfig authConfig) {
this.authConfig = authConfig;
}
/** * Specifies the pointcut that defines the place where Advice occurs as being@EnableAuthMethod of modification */
@Pointcut("@annotation(com.example.annotation.EnableAuth)")
public void auth(a) {}/** * Specify pre-advice enhancement *@paramThe enableAuth flag requires the annotation of the enhanced method */
@Before("@annotation(enableAuth)")
public void beforeAdvice(EnableAuth enableAuth) {
log.info("Go to the beforeAdvice method and start auth operation");
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new AuthException("Non-httpservletrequest request");
}
HttpServletRequest request = requestAttributes.getRequest();
String[] appKeyArray = enableAuth.appKeyArray();
if (appKeyArray == null || appKeyArray.length == 0) {
throw new AuthException(AppKeyArray cannot be empty);
}
String headerAppKey = request.getHeader("appKey");
String headerSecret = request.getHeader("secret");
if(! StringUtils.hasText(headerAppKey) || ! StringUtils.hasText(headerSecret)) {throw new AuthException("Header header headerAppKey or headerSecret is empty.");
}
boolean flag;
// Verify that the client requesting this interface has access permission
if(! Arrays.asList(appKeyArray).contains(headerAppKey)) { log.error("Clients with appKey: {} do not have permission to access this interface", headerAppKey);
throw new AuthException("AppKey is:" + headerAppKey + "The client does not have permission to access this interface");
}
/ / check appSecret
switch (headerAppKey) {
case "client1": {
AuthConfig.ClientConfig client1 = authConfig.getClient1();
flag = validateSecret(headerSecret, authConfig.getClient1().getSalt(), authConfig.getClient1().getAppSecret());
break;
}
case "client2": {
flag = validateSecret(headerSecret, authConfig.getClient1().getSalt(), authConfig.getClient2().getAppSecret());
break;
}
default: flag = false;
}
if(! flag) { log.error("Failed to verify headerSecret for {}", headerAppKey);
throw new AuthException("Check" + headerAppKey + "HeaderSecret failed");
}
log.info("== check {} headerSecret successfully", headerAppKey);
}
private static boolean validateSecret (String secret, String salt, String appSecret) {
returnappSecret.equals(Md5Crypt.apr1Crypt(secret, salt)); }}Copy the code
Use custom annotations for permission control
@EnableAuth(appKeyArray = {"client1", "client2"})
@GetMapping("/test/auth")
public void testAuth (a) {
log.info("== successful call to testAuth method");
}
Copy the code