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