Java注解之实战
一、什么是注解
- Java 注解是在 JDK5 时引入的新特性,注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个阶段方便的使用这些数据。
二、JDK常用注解、元注解
常用注解如下
- @Override: 表示注解修饰的方法必须满足重写的规则
- @Deprecated: 表示成员过时,编译器可以在程序运行的时候获取到该注解
- @SupressWarnings: 表示忽略编译器的警告
常见的元注解: - @Target 可声明在哪些目标元素之前
ElementType.PARAMETER 声明在参数上
ElementType.METHOD 声明在方法上
ElementType.FIELD 声明在字段上
ElementType.TYPE 声明在类,接口,枚举
ElementType.CONSTRUCTOR 声明在构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注释类型声明
ElementType.PACKAGE 包声明
- @Retention 注解类的生命周期,及作用在哪个阶段
RetentionPolicy.RUNTIME 程序运行时期起作用
RetentionPolicy.SOURCE 在原文件中有效,被编译器丢弃
RetentionPolicy.CLASS 程序编译时期起作用
- @Documented 生成文档的时候,会被写入到文档中
三、如何使用注解
那么注解是怎么在某个阶段被方便的使用呢,比如我们自定义一个注解,程序是如何使用的呢?其实注解不能自己起作用,需要程序员自身去针对注解写专门的逻辑,比如:日志切面针对日志注解进行扫描处理;登录注解使用拦截器去判断是否包含再处理登录逻辑。
四、注解实战
接下来介绍下注解在实际中的使用,本次介绍的demo是一个利用注解来实现动态参数校验功能。
1、首先新建一个验证注解,用于切入点进行参数校验。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidParam {
}
2、其次编写实现此注解的扫描,对含有此注解的方法参数进行校验。这里我们用到了SpringMVC中的参数解析器,RequestBody注解的操作类继承了AbstractMessageConverterMethodArgumentResolver,因此我们也使用此类实现RequestBody相同参数解析功能。
@Slf4j
public class ValidMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
private static Validator validator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public ValidMethodArgumentResolver(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice){
super(converters, requestResponseBodyAdvice);
}
public ValidMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(ValidParam.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
final Object object = readWithMessageConverters(nativeWebRequest, methodParameter, methodParameter.getParameterType());
validateParam(object);
return object;
}
/**
* 参数校验
* @param object
*/
private void validateParam(Object object) {
Set<ConstraintViolation<Object>> constraintViolations;
if(object instanceof List){
List list = (List)object;
list.forEach(p -> validateParam(p));
}else {
constraintViolations = validator.validate(object);
if (!constraintViolations.isEmpty()) {
ConstraintViolation<Object> constraint = (ConstraintViolation<Object>)constraintViolations.iterator().next();
throw new ParamException(GloabEnums.PARAM_ERROR.getCode(), constraint.getMessage());
}
}
}
}
使用之后我们需要将此类配置到参数解析器中,及实现WebMvcConfigurer类中,具体代码如下:
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
list.add(new ValidMethodArgumentResolver(converters));
}
首先是实现HandlerMethodArgumentResolver方法的supportsParameter方法,用于判断是否包含自定义验证注解,其次包含则调用resolveArgument方法对参数进行处理验证;这里我们使用了validator验证工具包进行校验。
3、接下来我们对入参实体进行校验验证,这里我们使用了NotB
@Data
public class UserDto {
@NotBlank(message = "名字不能为空")
private String name;
@NotBlank(message = "电话号码为空")
@Pattern(regexp = "^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\\d|9\\d)\\d{8}$", message = "电话号码有误")
private String phone;
private int age;
}
@Slf4j
@RestController
@RequestMapping("valid")
public class ValidController {
@PostMapping("user")
public R insertAddress(@ValidParam UserDto dto){
log.info("参数验证成功,执行正常逻辑:{}", dto);
return R.ok();
}
}
调用该接口:
image.png
有人会问,当我们使用增、删、改需要使用同一个类,如用如上参数注解校验,则会引起新增的方法判断了修改方法的校验,我们不同的方法是需要对此类不同的字段进行校验,这不是要写多个类才能进行校验吗?
既然有不同的需求,我们就有不同的方案来进行处理,这里我们引入group组这个概念,将字段划分组进行区分校验。
public interface QueryGroup {}
public @interface ValidParam {
Class value() default QueryGroup.class;
}
@NotBlank(message = "名字不能为空", groups = QueryGroup.class)
private String name;
@PostMapping("user")
public R insertAddress(@ValidParam(value = QueryGroup.class) UserDto dto){
log.info("参数验证成功,执行正常逻辑:{}", dto);
return R.ok();
}
/**
* 参数校验
* @param object
*/
private void validateParam(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations;
if(object instanceof List){
List list = (List)object;
list.forEach(p -> validateParam(p, groups));
}else {
constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
ConstraintViolation<Object> constraint = (ConstraintViolation<Object>)constraintViolations.iterator().next();
throw new ParamException(GloabEnums.PARAM_ERROR.getCode(), constraint.getMessage());
}
}
}
四、总结
1、注解就是对代码的解释,可以把它类同于标签
2、注解的定义只是interface前加了个@
3、注解不会自己起作用,需要程序开发人员去开发对应的功能