JAVA_自定义注解_参数校验_日志拦截

注解的功能有很多,不一一赘述,我一般通过自定义注解实现比较通用的功能,比如哪些接口需要特殊校验,哪些接口访问记录需要入库等。根据你的业务需要进行处理。

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestDemo {
    String name() default "";

    String value() default "";
}

注解类上方声明注解相关信息,包含以下四种信息

@Documented – 注解是否将包含在JavaDoc中

@Retention – 什么时候使用该注解

@Target – 注解用于什么地方

@Inherited – 是否允许子类继承该注解

@Retention 定义该注解的生命周期

   RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,

            所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。

   RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

   RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括

● ElementType.CONSTRUCTOR: 用于描述构造器

● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)

● ElementType.LOCAL_VARIABLE: 用于描述局部变量

● ElementType.METHOD: 用于描述方法

● ElementType.PACKAGE: 用于描述包

● ElementType.PARAMETER: 用于描述参数

● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中

@Inherited – 定义该注释和子类的关系

@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。

  如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

既 如果A类被@Inherited 修饰的annotation 注解了,B继承了A类,B类不需要加annotation注解,B类也会有了annotation注解的属性,

这种简单的注解,可以通过aop进行拦截处理即可:

import com.springcloud.demo.MyInterface.TestDemo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Component
@Aspect
public class MyAop {
    @Pointcut("@annotation(com.springcloud.demo.MyInterface.TestDemo)")
    public void pointCut(){
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        MethodSignature signature = (MethodSignature)point.getSignature();
        TestDemo annotation = signature.getMethod().getAnnotation(TestDemo.class);
        //获取注解对应的值
        String value = annotation.value();
        String name = annotation.name();
        //根据业务需要 进行你所需要的业务处理
        //.........业务处理中
        
        
        Object object = point.proceed();

        return object;
    }

}

简单的例子:

    @TestDemo(name = "保存",value = "保存到mongo中")
    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public CommonResult save(@RequestBody ProductParam param){
        productDao.saveTest(param);
        return CommonResult.getSuccess(null);
    }

上面是一种简单的拦截,下面介绍自定义注解对字段的校验处理

引入依赖:

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

eg:考虑有一个API,接收一个Student对象,并希望对象里的age域的值是偶数,这时候就可以创建以下注解:

 @Target(ElementType.FIELD) //作用在字段 枚举上
  @Retention(RetentionPolicy.RUNTIME)
  @Constraint(validatedBy = AgeValidator.class) //注解逻辑验证器
  public @interface Odd {
  String message() default "Age Must Be Odd";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
 }

  • @Constraint是最关键的,它表示这个注解是一个验证注解,并且指定了一个实现验证逻辑的验证器

  • message()指明了验证失败后返回的消息,此方法为@Constraint要求

  • groups()和payload()也为@Constraint要求,可默认为空,详细用途可以查看@Constraint文档

有了注解之后,就需要一个验证器来实现验证逻辑:

public class AgeValidator implements ConstraintValidator<Odd,Integer> {
@Override
public void initialize(Odd constraintAnnotation) {
}
@Override
public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) {
return age % 2 = 0;
}
}
  • 验证器有两个类型参数,第一个是所属的注解,第二个是注解作用地方的类型,这里因为作用在age上,因此这里用了Integer

  • initialize()可以在验证开始前调用注解里的方法,从而获取到一些注解里的参数,这里用不到

  • isValid()就是判断是否合法的地方

注解和验证器创建好之后,就可以使用注解了

@data
 public class Student {
 @Odd
 private int age;
 private String name;
}

测试一下:

 @RestController
 public class StudentResource {
 @PostMapping("/student")
 public String addStudent(@Valid @RequestBody Student student) {
 return "Student Created";
 }
 }

在需要启用验证的地方加上@Valid注解,这时候如果请求里的Student年龄不是奇数,就会得到一个400响应:

{
    "timestamp": 1628823085792,
    "status": 400,
    "error": "Bad Request",
    "path": "/users/student"
}

但是无法显示具体出了什么问题,有两种处理方式,第一种,在需要校验的接口上增加BindingResult参数单独处理:

    @PostMapping(value = "/student")
    public CommonResult getstudent(@RequestBody @Valid Student param,BindingResult result){
        FieldError fieldError = result.getFieldError();
        if(Objects.nonNull(fieldError)){
            String defaultMessage = fieldError.getDefaultMessage();
            CommonResult.getFail(defaultMessage);
        }
        return CommonResult.getSuccess(null);
    }
第二种:不需要在接口层处理,做一个统一拦截的处理即可(个人比较倾向第二种)
@Order(-1)
@ControllerAdvice
public class ValidationException {

    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult validationException(MethodArgumentNotValidException exception) {
        BindingResult bindingResult = exception.getBindingResult();
        if (bindingResult.hasErrors()) {
            String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
            return CommonResult.getFail(defaultMessage);
        } else {
            return CommonResult.getFail("系统异常");
        }
    }
}

返回的结果就比较清晰了:

{
    "code": -1,
    "msg": "Age Must Be Odd",
    "data": null
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值