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

Love The Way You Lie 2023-07-12 14:21 70阅读 0赞

最近看了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方法的东西,并非项目中必需的

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-test</artifactId>
  8. <scope>test</scope>
  9. <exclusions>
  10. <exclusion>
  11. <groupId>org.junit.vintage</groupId>
  12. <artifactId>junit-vintage-engine</artifactId>
  13. </exclusion>
  14. </exclusions>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.projectlombok</groupId>
  18. <artifactId>lombok</artifactId>
  19. </dependency>

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

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

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

  1. @Data
  2. public class Student {
  3. @Min(value = 20,message = "最小值为20")
  4. @Max(value = 100,message = "最大值为100")
  5. @NotNull(message = "年龄不能为空")
  6. private Integer age;
  7. @NotBlank(message = "名字不能为空")
  8. private String name;
  9. @Length(max = 3,min = 3,message = "号码必须要3位")
  10. private String number;
  11. //加上地址注解,则必须包含相应的值
  12. @AddressAnnotation(mustContainStr = "襄阳")
  13. @NotBlank(message = "地址不能为空")
  14. private String address;
  15. }

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

  1. @RestController
  2. public class StudentController {
  3. @GetMapping("/addStudent")
  4. public Object addStudent(@Valid Student student) throws IllegalAccessException {
  5. MyAnnotationValidateUtil.validateAddressAnnotation(student);
  6. return "success";
  7. }
  8. }

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

  1. /** * @Author 张满 * @Description 自定义注解验证工具类 * @Date 2020/3/8 23:21 * @vsersion 1.0.0 **/
  2. public class MyAnnotationValidateUtil {
  3. /** * @Author 张满 * @Description 验证AddressAnnotation注解 * @Date 2020/3/8 22:41 * @Param [student] * @return java.lang.String **/
  4. public static void validateAddressAnnotation(Object object) throws IllegalAccessException{
  5. Class<?> clazz = object.getClass();
  6. //获得对象的所有字段
  7. Field[] fields = clazz.getDeclaredFields();
  8. for (Field field : fields) {
  9. //设置让私有字段也可以访问
  10. field.setAccessible(true);
  11. //判断字段是否实现了AddressAnnotation注解
  12. boolean flag = field.isAnnotationPresent(AddressAnnotation.class);
  13. //如果实现了AddressAnnotation注解
  14. if(flag){
  15. //判断这个字段是不是String类型的,如果这个字段是
  16. if(field.getType().getName().equals("java.lang.String")){
  17. //如果是String类型的,就检查字段值是否包含注解中的mustContainStr值(字段中是否包含襄阳?)
  18. AddressAnnotation addressAnnotation = field.getAnnotation(AddressAnnotation.class);
  19. //注解中要求包含的值
  20. String mustContainStr = addressAnnotation.mustContainStr();
  21. //传来的字段值
  22. String fieldStr = (String) field.get(object);
  23. //判断是否字段值是否包含注解要求的值
  24. if(fieldStr.contains(mustContainStr)){
  25. continue;
  26. }else{
  27. //不包含抛出异常
  28. throw new NotContainsStrException(field.getName()+"中必须包含"+mustContainStr);
  29. }
  30. }
  31. }
  32. }
  33. }
  34. }

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

自定义异常类NotContainsStrException的代码

  1. /** * @Author 张满 * @Description 不包含相应的字符串异常 * @Date 2020/3/9 0:05 * @vsersion 1.0.0 **/
  2. public class NotContainsStrException extends RuntimeException{
  3. public NotContainsStrException(String message) {
  4. super(message);
  5. }
  6. }

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

  1. /** * @Author 张满 * @Description 全局异常拦截器 * @Date 2020/3/8 20:19 * @vsersion 1.0.0 **/
  2. @ControllerAdvice
  3. public class GlobalExceptionInterceptor {
  4. @ExceptionHandler(value = Exception.class)
  5. @ResponseBody
  6. public Object exceptionHander(HttpServletRequest request,Exception e){
  7. List returnlist = new ArrayList();
  8. //返回hibernate-valitor的注解验证异常结果
  9. if(e instanceof BindException){
  10. Iterator<FieldError> iterator = ((BindException) e).getBindingResult().getFieldErrors().iterator();
  11. while (iterator.hasNext()){
  12. FieldError fieldError = iterator.next();
  13. String errorMsg = fieldError.getDefaultMessage();
  14. returnlist.add(errorMsg);
  15. }
  16. return returnlist;
  17. }
  18. else if(e instanceof NotContainsStrException){
  19. //如果出现 NotContainsStrException 异常,返回相应信息
  20. //返回自定义地址注解的验证异常结果
  21. return e.getMessage();
  22. }else {
  23. //出现无法预料的异常,就在控制台打印异常信息,并返回给请求者信息(生产环境下应关闭)
  24. e.printStackTrace();
  25. return e.getMessage();
  26. }
  27. }
  28. }

使用postman进行测试

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

发表评论

表情:
评论列表 (有 0 条评论,70人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java注解学习

    元注解 首先Java提供了4个元注解来定义其他注解 @Target用来定义注解将应用于什么地方(如一个方法或者一个域) @Retention用来定义注解在哪一个