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
@Retention
@Inherited
@Native
- 指被@Native标注的字段是使用本地语言(如C、C++)实现的方法,该注解告诉Java虚拟机不要执行本地方法的标准Java调用,而是直接调用本地语言实现的方法。再具体一点就比较麻烦了,有好奇心自行搜索
自定义注解
- 在下面的自定义注解中,需要注意的有两点
- 注解的每个属性都有默认值,也可以不给默认值,但是在使用时就必须给没有默认值的字段属性赋值!
- @Constraint注解,此注解主要用于自定义注解关联约束验证器,至于怎么理解约束验证器,如定义了一个注解,约定了使用范围和每个属性的用处,那么怎么校验自定义注解被使用时是否符合你的要求呢
| |
| |
| |
| |
| |
| |
| |
| @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; |
| |
| |
| |
| |
| |
| |
| |
| public class MyText implements ConstraintValidator<MyBlogText, String> { |
| |
| |
| @Override |
| public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { |
| |
| |
| return value != null && value.length() <= 20 && !value.equals("张三"); |
| } |
| } |
自定义全局异常处理器
- @RestControllerAdvice :
- 此注解是对所有使用了@RestController注解的Controller层异常的处理,也就是所可以直接在Service层抛出异常,在Controller层也直接抛出异常,在GlobalExceptionHandler进行逐个类型的异常进行处理。如果Controller层用的是Controller注解,对应的可以使用@ControllerAdvice对异常同样的处理。
| |
| |
| |
| |
| |
| |
| @Slf4j |
| @RestControllerAdvice |
| public class GlobalExceptionHandler { |
| |
| @Autowired |
| private HttpServletRequest httpServletRequest; |
| |
| |
| |
| |
| |
| @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); |
| |
| |
| |
| |
| |
| |
| |
| return ResultBuilder.errorResult(ResultCode.INTERNAL_SERVER_ERROR, validExceptionMsg); |
| } |
| } |
自定义注解完成数据脱敏
定义脱敏策略枚举
| import java.util.function.Function; |
| |
| |
| |
| |
| |
| public enum SensitiveStrategy { |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| @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; |
| |
| |
| |
| |
| |
| public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer { |
| private SensitiveStrategy strategy; |
| |
| |
| |
| @Override |
| public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { |
| |
| |
| |
| gen.writeString(strategy.deSerializer().apply(value)); |
| } |
| |
| |
| @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(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; |
| |
| |
| |
| |
| |
| |
| |
| @Slf4j |
| @Aspect |
| @Component |
| public class MyBlogTextAop { |
| |
| @Around("@annotation(mySysLog)") |
| public Object around(ProceedingJoinPoint point, MyBlogText mySysLog) throws Throwable { |
| |
| |
| 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(); |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?