目录
SpringBoot中自定义注解
关于注解的解释
元注解
@Documented
@Target
@Retention
@Inherited
@Native
自定义注解
自定义注解与SpringBoot全局异常处理完成参数校验
约束验证器
自定义全局异常处理器
自定义注解完成数据脱敏
定义脱敏策略枚举
自定义注解
实行脱敏逻辑
自定义注解的与Aop切面
使用主定义注解
定义切面获取注解信息
自定义注解
、自定义注解配置SpringBoot全局异常处理完成参数校验
、自定义注解完成数据脱敏
、Aop与自定义注解的配合
注解
的解释Annotation
是注解的基本接口,所有的注解都继承此接口,如同Java中所有的类都继承Object
一样该元注解有一个ElementType数组类型的字段,主要用于标识自定义注解使用的位置
ElementType.TYPE:说明该注解只能被声明在一个类前。
ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
ElementType.PACKAGE:说明该注解只能声明在一个包名前
该元注解有一个RetentionPolicy类型的字段,主要用于标识自定义注解的生命周期
RetentionPolicy.SOURCE: 注解只保留在源文件中
RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
RetentionPolicy.RUNTIME: 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。
用来指示自定义注解是否可以被继承。当自定义注解被@Inherited
注解时,将自定义注解使用在某个类上,另一个类继承某个类的时候,此注解也会被继承。
可以能有些拗口,笔者举个例子。在SpringBoot中,@SpringBootApplication也是用了@Inherited
注解,把以下代码复制到SpringBoot项目中运行,也可以正常启动你的项目。
@SpringBootApplication
public class RunApp {
public static void main(String[] args) {
SpringApplication.run(RunApp.class, args);
}
}
/**
* 自定义注解完成参数校验
*
* @author unknown
* @see com.example.blog.base.common.annotation.MyText
* @since 2023/08/25 19:50
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = {MyText.class}) // 约束关联校验器 -> 注解定义了,那么在哪里校验这个注解的内容呢?
public @interface MyBlogText {
String message() default "不符合格式规范或值域范围";
int length() default 0;
// 正则校验
String regex() default "";
// 分组
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
ConstraintValidator
接口,重写isValid
方法,在方法中对MyBlogText
注解进行参数验证import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* 自定义注解关联验证器
*
* @author unknown
* @since 2023/08/25 19:59
*/
public class MyText implements ConstraintValidator {
// 注解校验逻辑
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
// 判断名字不能为null 并且长度不能唱过20
return value != null && value.length() <= 20 && !value.equals("张三");
}
}
/**
* 全局异常处理器
*
* @author unknown
* @since 2023/08/05 01:39
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@Autowired
private HttpServletRequest httpServletRequest;
/**
* Post请求的对象参数校验异常
* @param methodArgumentNotValidException
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public Result methodArgumentNotValidHandler(MethodArgumentNotValidException methodArgumentNotValidException) {
List allErrors = methodArgumentNotValidException.getBindingResult().getAllErrors();
String requestURI = httpServletRequest.getRequestURI();
String validExceptionMsg = getValidExceptionMsg(allErrors);
log.error("请求地址'{}',post方式请求参数异常'{}'", requestURI, validExceptionMsg);
// 还有一种写法 然后对这个hashMap进行JSON格式化
// List allErrors = methodArgumentNotValidException.getFieldErrors();
// LinkedHashMap
import java.util.function.Function;
/**
* @author unknown
* @since 2023/08/19 15:04
*/
public enum SensitiveStrategy {
/**
* Username sensitive strategy. $1 替换为正则的第一组 $2 替换为正则的第二组
*/
/**
* 地址
*/
ADDRESS(new Function() {
@Override
public String apply(String s) {
return s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****");
}
}),
/**
* 电子邮件
*/
EMAIL(new Function() {
@Override
public String apply(String s) {
return s.replaceAll("(?<=^.{3}).*(?=@)", "*****");
}
}),
/**
* 身份证号码
*/
ID_CARD(new Function() {
@Override
public String apply(String s) {
return s.replaceAll("(\\d{3})\\d{13}(\\w{2})", "$1****$2");
}
}),
/**
* 手机号
*/
PHONE(new Function() {
@Override
public String apply(String s) {
return s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
}),
/**
* 用户名称
*/
USERNAME(new Function() {
@Override
public String apply(String s) {
return s.replaceAll("(\\S)\\S(\\S*)", "$1*$2");
}
});
private final Function deSerializer;
/**
* 定义构造函数,传入一个函数
*/
SensitiveStrategy(Function deSerializer) {
this.deSerializer = deSerializer;
}
/**
* getter方法
*/
public Function deSerializer() {
return deSerializer;
}
}
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解完成数据脱敏
* @see com.example.blog.base.common.desensitization.SensitiveJsonSerializer
* @see com.example.blog.base.common.desensitization.SensitiveStrategy
* @author unknown
* @since 2023/08/19 15:07
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside //将多个注解组合到一起
@JsonSerialize(using = SensitiveJsonSerializer.class) //声明使用自定义的序列化器
public @interface Sensitive {
SensitiveStrategy strategy();
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;
/**
* @author unknown
* @since 2023/08/19 15:13
*/
public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer {
private SensitiveStrategy strategy;
//serialize方法执行脱敏序列化逻辑
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 返一个Function
// Function stringStringFunction = strategy.deSerializer();
// Function.apply(value) 执行枚举里面定义的脱敏方法
gen.writeString(strategy.deSerializer().apply(value));
}
//reateContextual方法用来获取实体类上的@Sensitive注解并根据条件初始化对应的JsonSerializer对象;
@Override
public JsonSerializer> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
/**
* 新增用户
* @see com.example.blog.base.common.annotation.MyBlogText
* @param userInsertRequestVO 新增用户请求体
* @return Result
*/
@MyBlogText(message = "李四")
@PostMapping(value = "insertUser")
public Result insertUser(@Validated @RequestBody UserInsertRequestVO userInsertRequestVO) throws JsonProcessingException {
log.info("UserController insertUser Request param: {}", userInsertRequestVO);
UserInsertRequestBO userInsertRequestBO = UserConvert.INSTANCE.toInsertUserRequestBO(userInsertRequestVO);
UserInsertResponseVO userInsertResponseVO = userService.insertUser(userInsertRequestBO);
log.info("UserController insertUser Response param: {}", objectMapper.writeValueAsString(userInsertResponseVO));
return ResultBuilder.successResult(userInsertResponseVO);
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
/**
* Aop切面注解进行校验
*
* @author unknown
* @since 2023/08/27 01:29
*/
@Slf4j
@Aspect
@Component
public class MyBlogTextAop {
@Around("@annotation(mySysLog)")
public Object around(ProceedingJoinPoint point, MyBlogText mySysLog) throws Throwable {
// 也可以直接用时 String message = mySysLog.message(); ,但是不能获取切面信息
MethodSignature signature = (MethodSignature) point.getSignature();
MyBlogText annotation = signature.getMethod().getAnnotation(MyBlogText.class);
String message = annotation.message();
System.out.println("获取注解上传入的参数: "+message);
String name = point.getTarget().getClass().getName();
System.out.println("被切面的类相对路径: "+name);
String name1 = signature.getName();
System.out.println("被切面的类名: "+name1);
return point.proceed();
}
}