SpringBoot中自定义注解

SpringBoot中自定义注解

  • 本文主要记录在SpringBoot中对注解的操作,如自定义注解自定义注解配置SpringBoot全局异常处理完成参数校验自定义注解完成数据脱敏Aop与自定义注解的配合

关于注解的解释

  • 注解的本质是继承了Annotation的特殊接口,其具体实现类是Java运行生成的动态代理类。当通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,最会调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中取出应的值。而memberValues的来源则是Java常量池。在java中,所有的元注解都定义java.lang.annotation包下。其中Annotation是注解的基本接口,所有的注解都继承此接口,如同Java中所有的类都继承Object一样

元注解
  • 在java.lang.annotation包下提供了六种元注解,其作用于帮你定义注解时使用。
@Documented
  • 主要用于指示编译器将被注解元素的注释信息包含在生成的API文档中
@Target
  • 该元注解有一个ElementType数组类型的字段,主要用于标识自定义注解使用的位置

    ElementType.TYPE:说明该注解只能被声明在一个类前。
    ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
    ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
    ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
    ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
    ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
    ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
    ElementType.PACKAGE:说明该注解只能声明在一个包名前


@Retention
  • 该元注解有一个RetentionPolicy类型的字段,主要用于标识自定义注解的生命周期

    RetentionPolicy.SOURCE: 注解只保留在源文件中
    RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
    RetentionPolicy.RUNTIME: 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。


@Inherited
  • 用来指示自定义注解是否可以被继承。当自定义注解被@Inherited注解时,将自定义注解使用在某个类上,另一个类继承某个类的时候,此注解也会被继承。

    • 可以能有些拗口,笔者举个例子。在SpringBoot中,@SpringBootApplication也是用了@Inherited注解,把以下代码复制到SpringBoot项目中运行,也可以正常启动你的项目。

      @SpringBootApplication
      public class RunApp {
      
          public static void main(String[] args) {
              SpringApplication.run(RunApp.class, args);
          }
      }
      

@Native
  • 指被@Native标注的字段是使用本地语言(如C、C++)实现的方法,该注解告诉Java虚拟机不要执行本地方法的标准Java调用,而是直接调用本地语言实现的方法。再具体一点就比较麻烦了,有好奇心自行搜索

自定义注解

  • 在下面的自定义注解中,需要注意的有两点
    • 注解的每个属性都有默认值,也可以不给默认值,但是在使用时就必须给没有默认值的字段属性赋值!
    • @Constraint注解,此注解主要用于自定义注解关联约束验证器,至于怎么理解约束验证器,如定义了一个注解,约定了使用范围和每个属性的用处,那么怎么校验自定义注解被使用时是否符合你的要求呢
/**
 * <P>自定义注解完成参数校验</P>
 *
 * @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 {};

}

自定义注解与SpringBoot全局异常处理完成参数校验

约束验证器
  • 在一下代码中,实现 ConstraintValidator接口,重写isValid方法,在方法中对MyBlogText注解进行参数验证
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * <P>自定义注解关联验证器</P>
 *
 * @author unknown
 * @since 2023/08/25 19:59
 */
public class MyText implements ConstraintValidator<MyBlogText, String> {

    // 注解校验逻辑
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {

        // 判断名字不能为null 并且长度不能唱过20
        return value != null && value.length() <= 20 && !value.equals("张三");
    }
}

自定义全局异常处理器
  • @RestControllerAdvice :
    • 此注解是对所有使用了@RestController注解的Controller层异常的处理,也就是所可以直接在Service层抛出异常,在Controller层也直接抛出异常,在GlobalExceptionHandler进行逐个类型的异常进行处理。如果Controller层用的是Controller注解,对应的可以使用@ControllerAdvice对异常同样的处理。
/**
 * <P>全局异常处理器</P>
 *
 * @author unknown
 * @since 2023/08/05 01:39
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private HttpServletRequest httpServletRequest;
    /**
     * <P>Post请求的对象参数校验异常</P>
     * @param methodArgumentNotValidException
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result<String> methodArgumentNotValidHandler(MethodArgumentNotValidException methodArgumentNotValidException) {

        List<ObjectError> allErrors = methodArgumentNotValidException.getBindingResult().getAllErrors();
        String requestURI = httpServletRequest.getRequestURI();
        String validExceptionMsg = getValidExceptionMsg(allErrors);
        log.error("请求地址'{}',post方式请求参数异常'{}'", requestURI, validExceptionMsg);

        // 还有一种写法 然后对这个hashMap进行JSON格式化
//        List<FieldError> allErrors = methodArgumentNotValidException.getFieldErrors();
//        LinkedHashMap<Object, Object> fieldErrors = new LinkedHashMap<>();
//        for (FieldError allError : allErrors) {
//            fieldErrors.put(allError.getField(), allError.getDefaultMessage());
//        }
        return ResultBuilder.errorResult(ResultCode.INTERNAL_SERVER_ERROR, validExceptionMsg);
    }
}    

自定义注解完成数据脱敏

定义脱敏策略枚举
import java.util.function.Function;

/**
 * @author unknown
 * @since 2023/08/19 15:04
 */
public enum SensitiveStrategy {

    /**
     * Username sensitive strategy.  $1 替换为正则的第一组  $2 替换为正则的第二组
     */
    /**
     * 地址
     */
    ADDRESS(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****");
        }
    }),
    /**
     * 电子邮件
     */
    EMAIL(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.replaceAll("(?<=^.{3}).*(?=@)", "*****");
        }
    }),
    /**
     * 身份证号码
     */
    ID_CARD(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.replaceAll("(\\d{3})\\d{13}(\\w{2})", "$1****$2");
        }
    }),
    /**
     * 手机号
     */
    PHONE(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
        }
    }),
    /**
     * 用户名称
     */
    USERNAME(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.replaceAll("(\\S)\\S(\\S*)", "$1*$2");
        }
    });


    private final Function<String, String> deSerializer;

    /**
     * 定义构造函数,传入一个函数
     */
    SensitiveStrategy(Function<String, String> deSerializer) {
        this.deSerializer = deSerializer;
    }

    /**
     * getter方法
     */
    public Function<String, String> 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;

/**
 * <P>自定义注解完成数据脱敏</P>
 * @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<String> implements ContextualSerializer {
    private SensitiveStrategy strategy;


    //serialize方法执行脱敏序列化逻辑
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 返一个Function
//        Function<String, String> 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);
    }
}

自定义注解的与Aop切面

使用主定义注解
  • @MyBlogText
 /**
     * <P>新增用户</P>
     * @see com.example.blog.base.common.annotation.MyBlogText
     * @param userInsertRequestVO 新增用户请求体
     * @return Result<T>
     */
    @MyBlogText(message = "李四")
    @PostMapping(value = "insertUser")
    public Result<UserInsertResponseVO> 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;

/**
 * <P>Aop切面注解进行校验</P>
 *
 * @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();
    }
}
posted @ 2023-08-27 23:32  unknown-n2  阅读(603)  评论(0编辑  收藏  举报