Java注解之实战

2020-12-07  本文已影响0人  Good龙辉

一、什么是注解

二、JDK常用注解、元注解

常用注解如下

 ElementType.PARAMETER               声明在参数上
 ElementType.METHOD                  声明在方法上
 ElementType.FIELD                   声明在字段上
 ElementType.TYPE                    声明在类,接口,枚举
 ElementType.CONSTRUCTOR             声明在构造函数
 ElementType.LOCAL_VARIABLE          局部变量
 ElementType.ANNOTATION_TYPE         注释类型声明
 ElementType.PACKAGE                 包声明
RetentionPolicy.RUNTIME   程序运行时期起作用
RetentionPolicy.SOURCE    在原文件中有效,被编译器丢弃
RetentionPolicy.CLASS     程序编译时期起作用

三、如何使用注解

那么注解是怎么在某个阶段被方便的使用呢,比如我们自定义一个注解,程序是如何使用的呢?其实注解不能自己起作用,需要程序员自身去针对注解写专门的逻辑,比如:日志切面针对日志注解进行扫描处理;登录注解使用拦截器去判断是否包含再处理登录逻辑。

四、注解实战

接下来介绍下注解在实际中的使用,本次介绍的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、注解不会自己起作用,需要程序开发人员去开发对应的功能

上一篇 下一篇

猜你喜欢

热点阅读