欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-26 22:45:46
...

最近看了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的反射技术来赋予这个注解特定的功能。几个重要的java Api类如:Method、Field、Paramter都实现了AnnotatedElement接口,这保证了我们可以使用反射技术,从这些类中获得到包含“注解”的东西,然后对这些东西进行操作。

AnnotatedElement接口继承关系图

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

AnnotatedElement里面的方法图

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

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

首先看看我的工程目录,作到心中有数,项目是使用springboot,工程中有最终实现完成的注解、有使用注解的实体类student、有实现访问的controller、有自定义的异常、有全局异常拦截器、有实现注解校验功能的工具类
java注解学习体会,并自己实现一个注解
以下是项目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中不包含“襄阳”的值,测试成功。
java注解学习体会,并自己实现一个注解
再发送address中包含“襄阳”的值,测试成功
java注解学习体会,并自己实现一个注解
如果改变注解中要求的值(mustContainStr),那么对postman传来值的要求也改变了。
结果:自定义注解并成功实现其功能!

相关标签: 心得总结 java