java注解学习体会,并自己实现一个注解

最近看了b站up主codesheep的注解文章,自己对注解的理解很浅薄,就自己找资料研究了注解。

(codesheep关于注解使用的文章地址)https://www.bilibili.com/read/cv4802402

我先是体验了一下springboot自带的hibernate-validator的校验功能。

springboot依赖中自带了hibernate-validator,所以不用导入依赖

hibernate-validator的校验功能的总结如下

  • 在要检验的实体类的字段上加入hibernate-validator提供的注解,如@NotNull、@Max、@Min
  • 在controller的RequestMapping修饰的方式参数前加上@Valid注解
  • 如果嫌弃返回的参数绑定失败信息不好看,就自定义一个全局异常拦截器

以上步骤请参考我的这篇博客 https://blog.csdn.net/zm9898/article/details/104742097

体验了使用hibernate-validator注解后,我就想自己写一个注解,并且实现它的功能,我的需求是这样的:定义一个注解,当它加入到某个string类型的字段上后,这个字段在以后验证时就必须包含注解中定义的字符串。如下图,注解中填写了“襄阳”,那么address字段值在验证时就必须要包含“襄阳”。
在这里插入图片描述
在正式开始写代码前,先说说我学习注解后的一点心得体会:注解就仅仅是在程序代码中加了个一个标记而已,有了这个标记,我们就可以在代码中使用java的反射技术来赋予这个注解特定的功能。几个重要的java Api类如:Method、Field、Paramter都实现了AnnotatedElement接口,这保证了我们可以使用反射技术,从这些类中获得到包含“注解”的东西,然后对这些东西进行操作。

AnnotatedElement接口继承关系图

annotatedElement继承关系图

AnnotatedElement里面的方法图

AnnotatedElement里面的方法图
其中这些方法的含义是
T getAnnotation(Class annotationType):得到指定类型的注解引用。没有返回null。
Annotation[] getAnnotations():得到自己身上所有的注解,包含从父类继承下来的。
Annotation[] getDeclaredAnnotations():得到自己身上的注解。
boolean isAnnotationPresent(Class<? extends Annotation> annotationType):判断自己身上有没有指定的注解。

下面开始实现最开始的注解需求

首先看看我的工程目录,作到心中有数,项目是使用springboot,工程中有最终实现完成的注解、有使用注解的实体类student、有实现访问的controller、有自定义的异常、有全局异常拦截器、有实现注解校验功能的工具类
工程目录
以下是项目pom依赖,lombok是一种可以简化get、set方法的东西,并非项目中必需的

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

我定义的注解AddressAnnotation ,含义已经在上文和代码注释中说明。

/**
 * @Author 张满
 * @Description 自定义地址注解,加了本注解的字段就必须包含mustContainStr字符串
 * @Date 2020/3/8  21:12
 * @Param
 * @return
 **/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AddressAnnotation {
    //必须包含的字符串
    String mustContainStr();
}

使用AddressAnnotation注解的实体类student(重点看address属性!)

@Data
public class Student {

    @Min(value = 20,message = "最小值为20")
    @Max(value = 100,message = "最大值为100")
    @NotNull(message = "年龄不能为空")
    private Integer age;

    @NotBlank(message = "名字不能为空")
    private String name;

    @Length(max = 3,min = 3,message = "号码必须要3位")
    private String number;

    //加上地址注解,则必须包含相应的值
    @AddressAnnotation(mustContainStr = "襄阳")
    @NotBlank(message = "地址不能为空")
    private String address;
}

当前端请求controller中的方法时,如下:先使用@Valid通过hibernate-vlidator的校验,然后再使用MyAnnotationValidateUtil校验工具类校验我们自己的注解

@RestController
public class StudentController {

    @GetMapping("/addStudent")
    public Object addStudent(@Valid Student student) throws IllegalAccessException {
        MyAnnotationValidateUtil.validateAddressAnnotation(student);
        return "success";
    }

}

MyAnnotationValidateUtil代码如下,它也是我们注解开发的核心,由它来完成注解的功能。


/**
 * @Author 张满
 * @Description 自定义注解验证工具类
 * @Date 2020/3/8 23:21
 * @vsersion 1.0.0
 **/
public class MyAnnotationValidateUtil {
    
    /**
     * @Author 张满
     * @Description 验证AddressAnnotation注解
     * @Date 2020/3/8  22:41
     * @Param [student]
     * @return java.lang.String
     **/
    public static void validateAddressAnnotation(Object object) throws IllegalAccessException{

        Class<?> clazz = object.getClass();
        //获得对象的所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //设置让私有字段也可以访问
            field.setAccessible(true);
            //判断字段是否实现了AddressAnnotation注解
            boolean flag = field.isAnnotationPresent(AddressAnnotation.class);
            //如果实现了AddressAnnotation注解
            if(flag){
                //判断这个字段是不是String类型的,如果这个字段是
                if(field.getType().getName().equals("java.lang.String")){
                    //如果是String类型的,就检查字段值是否包含注解中的mustContainStr值(字段中是否包含襄阳?)
                    AddressAnnotation addressAnnotation = field.getAnnotation(AddressAnnotation.class);
                    //注解中要求包含的值
                    String mustContainStr = addressAnnotation.mustContainStr();
                    //传来的字段值
                    String fieldStr = (String) field.get(object);
                    //判断是否字段值是否包含注解要求的值
                    if(fieldStr.contains(mustContainStr)){
                        continue;
                    }else{
                        //不包含抛出异常
                        throw new NotContainsStrException(field.getName()+"中必须包含"+mustContainStr);
                    }
                }
            }
        }
    }
    

}

以上代码,我们先获得传入对象的所有字段,遍历出所有添加AddressAnnotation注解的字段,然后判断这个字段是不是String类型的,如果加注解的字段不是String类型的,则这个注解对这个字段不起任何作用。反之,如果这个字段是String类型的,我们就判断传来的字段值是否包含注解中要求的值,如果包含继续判断,如果不包含,抛出自定义异常类NotContainsStrException

自定义异常类NotContainsStrException的代码

/**
 * @Author 张满
 * @Description 不包含相应的字符串异常
 * @Date 2020/3/9 0:05
 * @vsersion 1.0.0
 **/
public class NotContainsStrException extends RuntimeException{

    public NotContainsStrException(String message) {
        super(message);
    }
}

当抛出自定义异常类时,我们的全局异常拦截器就会拦截,以下是全局异常拦截器类(主要看else if中的代码)

/**
 * @Author 张满
 * @Description 全局异常拦截器
 * @Date 2020/3/8 20:19
 * @vsersion 1.0.0
 **/
@ControllerAdvice
public class GlobalExceptionInterceptor {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Object exceptionHander(HttpServletRequest request,Exception e){
        List returnlist = new ArrayList();
        //返回hibernate-valitor的注解验证异常结果
        if(e instanceof BindException){
            Iterator<FieldError> iterator = ((BindException) e).getBindingResult().getFieldErrors().iterator();
            while (iterator.hasNext()){
                FieldError fieldError = iterator.next();
                String errorMsg = fieldError.getDefaultMessage();
                returnlist.add(errorMsg);
            }
            return returnlist;
        }
        else if(e instanceof NotContainsStrException){
            //如果出现 NotContainsStrException 异常,返回相应信息
            //返回自定义地址注解的验证异常结果
           return e.getMessage();
        }else {
            //出现无法预料的异常,就在控制台打印异常信息,并返回给请求者信息(生产环境下应关闭)
            e.printStackTrace();
           return e.getMessage();
        }
    }

}


使用postman进行测试

先发送address中不包含“襄阳”的值,测试成功。
在这里插入图片描述
再发送address中包含“襄阳”的值,测试成功
在这里插入图片描述
如果改变注解中要求的值(mustContainStr),那么对postman传来值的要求也改变了。
结果:自定义注解并成功实现其功能!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值