注解原理剖析与实战

news2024/9/25 23:14:44

一、注解及其原理

在这里插入图片描述

1.注解的基本概念

注解,可以看作是对 一个类/方法的一个扩展的模版,每个类/方法按照注解类中的规则,来为类/方法注解不同的参数,在用到的地方可以得到不同的类/方法中注解的各种参数与值。
从JDK5开始,Java增加了对元数据(描述数据属性的信息)的支持。其实说白就是代码里的特殊标志,这些标志可以在编译、类加载和运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

2.标准注解与元注解

2.1.标准注解

2.1.1.@Override

定义在 java.lang.Override中,此注解只能作用于方法,表示一个方法声明打算重写超类中的另一个方法声明,简单来说就是子类在重写父类方法的时候可以加上这个注解。默认情况下,子类重写的方法会自动覆盖父类的方法,但经验告诉我们,必须显式地在子类重写父类的方法上添加@Override注解来检查并标记子类是否重写了父类的方法。

2.1.2.@Deprecated

定义在java.lang.Deprecated中,此注解可以作用于方法、属性 和类等等,表示不建议程序员使用被@Deprecated所作用的对象,通常是因为它很危险或者存在更好的选择,即此对象已经过时,不推荐使用。例如:

  • 作用在方法上
    在这里插入图片描述在这里插入图片描述
  • 作用在类上
    在这里插入图片描述
    在这里插入图片描述

2.1.3.@SuppressWarnings

定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息,与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的, 我们选择性的使用就好了,一般我们使用@SuppressWarnings(“all”)。

2.2.元注解

除了 Java 中为我们定义好的注解,我们还可以通过元注解来自定义注解, Java定义了4个标准的元注解(meta-annotation),用来定义和描述其他注解(自定义注解),所以也称为元数据注解。

2.2.1.@Target

用来描述注解的作用对象,即注解可以使用在什么地方,在定义注解的时候使用该注解可以清晰地知道它的使用范围,它的取值范围被定义在了一个枚举类中,常见的枚举值包括:

  • ElemenetType.CONSTRUCTOR 构造器声明 ;
  • ElemenetType.FIELD 域声明(包括 enum 实例);
  • ElemenetType.LOCAL_VARIABLE 局部变量声明;
  • ElemenetType.METHOD 方法声明;
  • ElemenetType.PACKAGE 包声明;
  • ElemenetType.PARAMETER 参数声明;
  • ElemenetType.TYPE 类,接口(包括注解类型)或enum声明;

2.2.2.@Retention

用来描述在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy中,包括:

  • RetentionPolicy.SOURCE 注解将被编译器丢弃;
  • RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃;
  • RetentionPolicy.RUNTIME VM将在运行期也保留注解,因此可以通过反射工具读取注解的信息。

2.2.3.@Documented

将此注解包含在javadoc中,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param等。

2.2.4.@Inherited

表示允许子类继承父类中的注解。

3.自定义注解

3.1.通过元注解定义注解

通过元注解,我们可以定义新的注解。长久以来,注解被视为一种轻量级的配置化技术方案,与XML相比,注解更加简单、便捷,但注解一般应用于比较简单的参数配置,而对于复杂的参数,就必须借助XML这个工具了。

3.1.1.定义注解及属性声明

  • 使用@interface关键字来定义新的注解。例如:
    在这里插入图片描述
    注解和类一样,也可以声明自己的属性,用来对注解自身进行描述。这样,原来写在配置文件中的信息,就可以通过注解的属性进行描述。
    1)定义注解的必录属性:String name(),在使用注解时,通过name=xxx来设置属性name的值,若没有设置属性name的值,系统会提示编译异常。
    2)定义注解属性的默认值:String name() default “test”,在使用注解时,若没有设置属性name的值,系统会自动将注解的name属性设置为默认值"test"。
    3)特殊属性value:如果注解@Query中只定义了一个名称为value的必录属性,那么使用注解时可以省略"value=",如@Query (“xxx”)。

3.1.2.定义不包含属性的注解

这类注解内部不存在任何属性,因此仅仅是起到一个标记的作用(与标记接口类似)。下面是一个不包含属性的自定义注解示例:
在这里插入图片描述

3.1.3.定义包含属性的注解

注解还可以定义自己的属性,这些属性可以用来存储一些关键、特征信息,以便程序通过当前注解的属性存储的信息来处理相应的业务逻辑。下面是一个包含属性的自定义注解示例:
在这里插入图片描述

3.1.3.1.注解属性支持的数据类型

注解的属性与类的属性除了声明方式存在差异外,还体现在所支持的数据类型。注解属性支持的数据类型包含以下几种:

  • 所有基本数据类型
  • 字符串类型String
  • 枚举类型enum
  • 注解类型Annotation
  • 以上数据类型的数组类型

例如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:如果你使用了其他数据类型,就会提示编译异常。

3.2.注解的使用

在上文中,我们定义了方法注解,因此可以作为方法级的轻量级配置使用,具体使用方式如下所示:
在这里插入图片描述

二、Spring注解原理剖析

Spring注解是自定义注解在实际开发中的最佳实践之一,一般而言,我接触到的Spring注解——@Controller、@Service和@Autowired等注解都是运行时注解,因此底层都是基于Java反射实现的。下面我们通过一个实际案例来深入理解Spring框架中注解的底层实现原理。

1.自定义自动注入注解思路点拨

思路点拨:由于Service类应用于Controller类中,因此,可以通过切面拦截Controller类,而切面无法拦截类上的注解,只能拦截方法上的注解,拦截到目标类的所有方法后,就遍历Controller类的所有属性,并判断属性上是否标记了@AutoInjected注解,再通过反射将实现类的实例注入到该属性中。

2.自定义@AutoInjected注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoInjected {
}

3.创建AutoInjectedAspect切面

import com.zh.test.annotation.AutoInjected;
import com.zh.test.system.utils.ApplicationUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Field;

@Aspect
@Component
public class AutoInjectedAcpect {

  @Pointcut("execution(* com.zh.test..*.controller..*.*(..))")
  public void beforeDoPointcut() {
  }

  @Before("beforeDoPointcut()")
  public void beforeDo(JoinPoint joinPoint) {
    Object target = joinPoint.getTarget();
    // 判断目标类是否标记了@RestController或@Controller注解
    RestController restController = AnnotationUtils.findAnnotation(target.getClass(), RestController.class);
    if(restController == null) {
      Controller controller = AnnotationUtils.findAnnotation(target.getClass(), Controller.class);
      if(controller == null) {
        return;
      }
    }
    Field[] fields = target.getClass().getDeclaredFields();
    for(Field field : fields) {
      // 获取属性上的注解
      AutoInjected autoInjected = field.getAnnotation(AutoInjected.class);
      // 如果为空,说明该属性上没有标记@AutoInjected注解,不予处理
      if(autoInjected == null) {
        continue;
      }
      Class<?> fieldType = field.getType();
      // 获取属性对应接口类型的实现类的实例
      Object bean = ApplicationUtil.getBean(fieldType);
      try {
        field.setAccessible(true);
        // 将属性对应接口类型的实现类的实例属注入到属性中
        field.set(target, bean);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e.getMessage());
      }
    }
  }
}

4.@AutoInjected注解的应用

import com.zh.test.annotation.AutoInjected;
import com.zh.test.system.entity.SysPageMeta;
import com.zh.test.system.service.IDemoService;
import com.zh.test.system.service.IMetaService;
import com.zh.test.system.service.impl.DemoServiceImpl;
import com.zh.test.system.utils.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Field;
import java.util.Map;

@RequestMapping("/demo")
@RestController
public class DemoController {

  @AutoInjected
  private IDemoService service;

  @AutoInjected
  private IMetaService metaService;

  @GetMapping("/queryByPage")
  public Map<String, Object>  queryByPage() {
    Map<String, Object> result = service.queryByPage();
    return result;
  }

  @PostMapping
  public Boolean addMetadata() {
    boolean result = metaService.addMetadata(new SysPageMeta());
    return result;
  }

}

三、注解的实际应用

在第一章中,我们自定义的注解都是运行时注解,因此可以通过反射工具获取注解信息。不同的作用对象,获取注解的方式也不相同,下面我们举例说明。

1.通过切面实现自定义(方法)注解

当注解定义在类或方法上时,可以通过切面拦截并获取注解实例。在SpringBoot项目中,如果要使用切面,就必须引入以下的jar包依赖:

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

可以通过注解@Aspect和@Component来定义一个切面类:
在这里插入图片描述
具体示例如下:

@Aspect
@Component
public class QueryAspect {

  @Before("@annotation(query)")
  public void beforeQuery(JoinPoint joinPoint, Query query) {
    // 数据库
    String dbName = query.dbName();
    // 表名
    String tableName = query.tableName();
    // 是否分页
    boolean paginate = query.paginate();
    // 字段信息
    Column[] columns = query.columns();
    System.out.println("dbName:" + dbName);
    System.out.println("tableName:" + tableName);
    System.out.println("paginate:" + paginate);
    System.out.println("columns:" + Arrays.toString(columns));
  }
}

注意:当切面的切点表达式为@annotation(xxx)时,目标注解必须作用于目标对象的方法上,否则切面将无法拦截到目标注解!

2.通过ConstraintValidator校验器自定义方法参数注解

当注解定义在方法参数上时,无法通过切面拦截并获取注解实例,此时就必须借助ConstraintValidator校验器来获取注解信息。示例如下:

  • 引入hibernate-validator.jar包依赖
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.0.Final</version>
</dependency>
  • 定义用于辅助校验的注解

例子如下:

// 注解作用对象为类和方法参数
@Target({ElementType.TYPE, ElementType.PARAMETER})
// 运行时类型的注解,可通过反射工具获取当前注解实例
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtCheck {
  String message() default "非法扩展字段";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

注意:辅助校验的注解必须至少包含message、groups和payload三个最基本的成员属性,否则将抛出运行时异常。

  • 实现ConstraintValidator接口
    例子如下:
    在这里插入图片描述
  • 在@ExtCheck注解上添加上述实现的ConstraintValidator校验器
    例子如下:
    在这里插入图片描述
  • 定义全局异常处理器
    例子如下:
import com.zh.test.exception.ExtException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(ExtException.class)
  public String handleAccessDeniedException(ExtException e, HttpServletRequest request) {
   return e.getMessage();
  }
}

public class ExtException extends RuntimeException {

  public ExtException() {
    super();
  }

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

}
  • 在Controller层的类或方法参数前添加@Validated(必须)和@ExtCheck注解
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【必学】最流行的云原生监控解决方案:Prometheus+Grafana

文章目录一、Prometheus和Grafana简介1.1、Prometheus是最受欢迎的云原生监控方案之一1.2、Grafana是最流行的开源可视化平台二、Prometheus的优势三、Prometheus架构原理四、Prometheus和Grafana安装部署一、Prometheus和Grafana简介 1.1、Prometheus是最受欢迎的云原生监控方…

如何进行单元测试

前言单元测试是指对软件中最小可测单元进行检查和验证&#xff1b;c语言中单元指一个函数&#xff0c;java中指一个类。图形化软件中可以指一个窗口或者一个菜单。总的来说&#xff0c;单元就是认为规定最小的被测试模块。1.1单元测试对我们开发程序有什么好处首先是一个前端单…

react: input 输入框 中文onChange事件异常问题 对input输入进行防抖处理

当我们使用Input时&#xff0c;我们可能会遇到一个问题&#xff0c;比如需要对用户输入的内容进行搜索时&#xff0c;当用户处于中文输入时&#xff0c;明明没有对内容进行确认&#xff0c;为什么会触发了onChange事件呢&#xff1f;比如以下场景&#xff0c;中文一边输入另外一…

机器学习知识总结 —— 20.使用朴素贝叶斯进行数据分类

文章目录准备基础数据计算先验概率计算条件概率预测分布验证结果作为一种监督学习分类方法&#xff0c;在上一章中我们已经介绍过它的数理原理。现在我们开始来实现一个简单的朴素贝叶斯分类的算法&#xff0c;这样我们能更好的理解它是怎么运作的。 准备基础数据 首先还是有…

加密流量专栏总览

文章目录加密流量专栏1. 原理篇2. 模型篇3. 文章分类总结3.1 研究方向3.2 特征提取3.3 机器学习模型改进3.4 深度学习模型改进3.5 其他模型改进3.7 实时检测3.8 概念漂移检索论文的方法加密流量专栏 1. 原理篇 原理&#xff1a; 会话、流、数据包之间的关系。 流&#xff1a;…

【离线数仓-4-数据仓库设计-分层规划构建流程】

离线数仓-4-数据仓库设计-分层规划&构建流程离线数仓-4-数据仓库设计-分层规划&构建流程1.数据仓库分层规划2.数据仓库构建流程1.数据调研1.业务调研2.需求分析3.总结2.明确数据域3.构建业务总线矩阵&维度模型设计4.明确统计指标1.指标体系相关概念1.原子指标2.派生…

【渝偲医药】DSPE-PEG-RGD;磷脂聚乙二醇多肽试剂级简介

DSPE-PEG-RGD、 二硬脂酰基磷脂酰乙醇胺-聚乙二醇-多肽、磷脂PEG多肽 英文名称: 1,2-Distearoyl-sn-Glycero-3-Phosphoethanolamine-PEG- RGD 溶剂:可溶解在水中和大多数有机溶剂中 外观:白色粉末 用途:用于链接带有链霉亲和素或其他的基团的分子 分子量(PEG ):2000、3400、…

那些开发过程中需要遵守的开发规范

入职公司三天&#xff0c;没干啥其他活&#xff0c;基本在配置本地环境和阅读相关文档。技术方面公司基本用的是主流的技术体系&#xff0c;入职后需要先阅读阿里的开发规范和其他的一些产研文档。今天整理一些平时需要关注的阿里规约和数据库开发规范&#xff0c;方便今后在开…

TatukGIS Developer Kernel for .NET

TatukGIS Developer Kernel for .NET 用于.NET的TatukGIS开发人员内核的强大功能&#xff1a; 打开、创建、编辑、保存和导出矢量、图片和网格的过程&#xff0c;包括类似于数据库的格式。 扩展属性、北箭头、比例和其他视觉控制也从TatukGIS编辑器/查看器商品中显示给用户开发…

Java基础系列(五): final关键字用法

一. 概述 final关键字代表最终,不可改变的. 常见有5种用法,我们来归纳总结一下: 1. 用来修饰一个类 2. 用来修饰一个方法 3. 用来修饰成员变量 4. 用来修饰局部变量 5. 用来修饰方法参数 二. final饰修类 如果声明一个类为final类, 那么这个类就是最终类,不能被继承 …

7 Python文件、文件夹、word及excel操作

0 建议学时和要求 4学时 掌握os和os.path模块对文件和文件夹操作的函数 掌握shutil模块对文件和文件夹操作的函数 掌握扩展库openpyxl对Excel文件的操作 1 文件的高级操作 1.1 文件的概念及分类 文本文件 文本文件可以使用记事本、gedit、ultraedit等字处理软件直接进行显…

ESP32设备驱动-DS1264数字温度传感器驱动

DS1264数字温度传感器驱动 1、DS1264介绍 DS1624 由两个独立的功能单元组成:一个 256 字节非易失性 E2 存储器和一个直接数字温度传感器。 非易失性存储器由 256 字节的 E2 存储器组成。 该存储器可用于存储用户希望的任何类型的信息。 这些内存位置通过 2 线串行总线访问。…

007永磁电机控制方式:别张嘴就FOC,其他常规控制方式也是伺服人的基本功

在读本篇文&#xff0c;我想做个小调查。到目前为止&#xff0c;你掌握的或者是你了解到的控制永磁同步电机的方式都有哪些&#xff1f;我想&#xff0c;你大概张口就说FOC控制吧。没错&#xff0c;FOC控制是我们日常生活中所见到的最普遍的永磁同步电机的控制方式。当然在本专…

微信电脑版字体模糊(或文字太小)怎么调整

文章目录第一步&#xff1a;设置屏幕缩放125%第二步&#xff1a;文本大小设置为125%第三步&#xff1a;微信设置--通用--勾选“适配系统缩放比例”第四步&#xff1a;微信高DPI缩放行为设置&#xff08;关键&#xff09;ClearType勾选&#xff08;可选&#xff09;笔者遇到这个…

【模板】线段树 2

题目描述 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面三种操作&#xff1a; 将某区间每一个数乘上 xxx 将某区间每一个数加上 xxx 求出某区间每一个数的和 输入格式 第一行包含三个整数 n,m,pn,m,pn,m,p&#xff0c;分别表示该数列数字的个数、操作的总个数…

计算机网络笔记(复试准备)第一章

计算机网络笔记&#xff08;复试准备&#xff09; 第一章 网络&#xff0c;互联网与因特网 网络由若干个结点和连接这些结点的链路组成 多个网络通过路由器连接起来这也就形成了一个更大的网络即是我们熟知的互联网也就是“网络的网络” 因特网是世界上最大的网络 问&#xf…

Open-Vocabulary Object Detection Using Captions论文讲解

文章目录一、论文前言二、提出原因三、论文的核心四、论文讲解4.1 论文流程4.2 OVD与之前相关的setting4.3 结果对比一、论文前言 目标检测是人工智能最突出的应用之一&#xff0c;也是深度学习最成功的任务之一。 然而&#xff0c;尽管深度对象检测取得了巨大进步&#xff0…

MongoDB在银行海量历史订单交易数据查询中的应用(Spring boot + Bee)

MongoDB在银行海量历史订单交易数据查询中的应用(Spring boot Bee) 近年来,随着各种便捷支付方式的普及,银行账户交易数据呈现爆炸式增长,同时数据模型也在不断变化,传统关系型数据库已难以满足这种海量的、模式灵活、高可用、高性能的数据存储和查询需求。通过对银行历史交易…

【编程入门】应用市场(php版)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目&#xff0c;使…

【MySQL】索引常见面试题

文章目录索引常见面试题什么是索引索引的分类什么时候需要 / 不需要创建索引&#xff1f;有什么优化索引的方法&#xff1f;从数据页的角度看B 树InnoDB是如何存储数据的&#xff1f;B 树是如何进行查询的&#xff1f;为什么MySQL采用B 树作为索引&#xff1f;怎样的索引的数…