Java注解与原理分析

news2025/7/8 6:30:25

使用的太多,被忽略的理所当然;

一、注解基础

注解即标注与解析,在Java的代码工程中,注解的使用几乎是无处不在,甚至多到被忽视;

无论是在JDK源码或者框架组件,都在使用注解能力完成各种识别和解析动作;在对系统功能封装时,也会依赖注解能力简化各种逻辑的重复实现;

基础接口

在Annotation的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关系,可以直接查看字节码文件;

-- 1、声明注解
public @interface SystemLog {}

-- 2、查看指令
javap -v SystemLog.class

-- 3、打印结果
Compiled from "SystemLog.java"
public interface com.base.test.SystemLog extends java.lang.annotation.Annotation

元注解

声明注解时使用,用来定义注解的作用目标,保留策略等;

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog { String model () default "" ; }
  • Documented:是否被javadoc或类似工具记录在文档中;
  • Inherited:标识注解是否可以被子类继承;
  • Target:作用目标,在ElementType枚举中可以看到取值包括类、方法、属性等;
  • Retention:保留策略,比如编译阶段是否丢弃,运行时保留;

此处声明一个SystemLog注解,作用范围是在方法上,并且在运行时保留,该注解通常用在服务运行时,结合AOP切面编程实现方法的日志采集;

二、注解原理

先来看一个简单的注解使用案例,再细致的分析其中原理,案例并不复杂,就是常见的标注与解析两个关键动作;

public class LogInfo {
    @SystemLog(model = "日志模块")
    public static void main(String[] args) {
        // 生成代理文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 反射机制
        Method[] methods = LogInfo.class.getMethods();
        for (Method method:methods){
            SystemLog systemLog = method.getAnnotation(SystemLog.class) ;
            if (systemLog != null){
                // 动态代理:com.sun.proxy.$Proxy2
                System.out.println(systemLog.getClass().getName());
                System.out.println(systemLog.model());
            }
        }
    }
}

这里涉及到两个核心概念:反射机制、动态代理;反射机制可以在程序运行时获取类的完整结构信息,代理模式给目标对象提供一个代理对象,由代理对象持有目标对象的引用;

案例中通过反射机制,在程序运行时进行注解的获取和解析,值得关注的是systemLog对象的类名,输出的是代理类信息;

案例执行完毕后,会在代码工程的目录下生成代理类,可以查看$Proxy2文件;

public final class $Proxy2 extends Proxy implements SystemLog {
    public final String model() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

在对SystemLog解析的过程中,实际上是在使用注解的代理类,$Proxy2继承了Proxy类并实现了SystemLog接口,并且重写了相关方法;有关反射和代理的逻辑,在之前的内容中有详说,此处不赘述;

值得一看是代理类中invoke方法调用,具体的处理逻辑在AnnotationInvocationHandler类的invoke方法中,会对注解原生方法和自定义方法做判断,并对原生方法提供实现;

三、常用注解

1、JDK注解

在JDK中有多个注解是经常使用的,例如Override、Deprecated、SuppressWarnings等;

  • Override:判断方法是否为重写方法;
  • Deprecated:标记过时的API,继续使用会警告;
  • FunctionalInterface:检验是否为函数式接口;
  • SuppressWarnings:代码的警告会静默处理;

这里注意FunctionalInterface注解,从1.8开始引入,检验是否为函数式接口,即接口只能有一个抽象方法,否则编译报错;

2、Lombok注解

在具体的看Lombok组件之前,需要先了解一个概念:代码编译;在open-jdk的描述文档中大致分为三个核心阶段;

第一步:读取命令行上指定的所有源文件,解析为语法树,进行符号表填充;

第二步:调用注解处理器,如果处理器生成任何新的源文件或类文件,编译会重新启动;

第三步:分析器创建的语法树被分析并转换为类文件;

更多细节说明可以参考openjdk文档中Compiler模块的内容,下面再回到Lombok组件上;

Lombok组件在代码工程中的使用非常频繁,通过注解的方式极大的简化Java中Bean对象的编写,提高了效率并且让源码显得简洁;

这里用一段简单的代码演示其效果,在IdKey的类中通过三个常用的Lombok注解,替代了类中很多基础方法的显式生成,查看编译后的文件实际是存在相关方法的;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IdKey {
    private Integer id ;
    private String key ;

    public static void main(String[] args) {
        IdKey idKey01 = new IdKey(1,"cicada") ;
        System.out.println(idKey01);
        idKey01.setId(2);
        idKey01.setKey("smile");
        System.out.println(idKey01);
    }
}

这里需要了解JDK中注解处理器的相关源码,AbstractProcessor作为超类,编译器在编译时会去检查该类的子类,子类中最核心的是process方法;

-- 1Lombok处理器
@SupportedAnnotationTypes("*")
public class LombokProcessor extends AbstractProcessor {
    private JavacTransformer transformer;
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
         transformer.transform(prio, javacProcessingEnv.getContext(), cusForThisRound, cleanup);
    }
}

-- 2AST抽象树
public class JavacTransformer {
    public void transform(long priority, Context context, List<JCTree.JCCompilationUnit> compilationUnits,
                          CleanupRegistry cleanup) {
        JavacAST ast = new JavacAST(messager, context, unit, cleanup);
        ast.traverse(new AnnotationVisitor(priority));
        handlers.callASTVisitors(ast, priority);
    }
}

-- 3、注解处理抽象类
public abstract class JavacAnnotationHandler<T extends Annotation> {
    public abstract void handle(AnnotationValues<T> annotation, JCAnnotation ast, JavacNode annotationNode);
}

-- 4Getter注解处理
public class HandleGetter extends JavacAnnotationHandler<Getter> {
    @Override 
    public void handle(AnnotationValues<Getter> annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) {
        JavacNode node = annotationNode.up();
        List<JCTree.JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode);
        switch (node.getKind()) {
            case FIELD:
                createGetterForFields(level, fields, annotationNode, true, lazy, onMethod);
                break;
        }
    }
}

IdKey类从简洁的源码编译为复杂的字节码文件,通过注解对结构处理时关联一个核心概念,叫AST抽象树,会涉及到很多语法、词法的解析逻辑;

四、自定义注解

在系统开发中通过自定义注解可以处理各种麻烦的重复逻辑,其最明显的好处就是可以大量的消除冗余的代码块;

1、同步控制

代码中可能存在很多方法是限制重复请求的,加锁处理是很常用的手段,此时完全可以通过注解结合AOP切面编程简化代码的复杂程度;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SyncLock {
    String lockKey();           // 锁的Key
    int time () default 3000 ;  // 有效时间
    int retryNum () default 3 ; // 重试次数
}

通过注解标记在方法上,可以极大简化同步锁的编码步骤,只是在读取KEY的时候需要设计好解析规则,结合反射原理进行获取即可;

基于相同的原理,也适应与日志采集、系统告警等功能,在之前的内容中都有详细的总结;

2、类型引擎

在数据处理的逻辑中,经常有这样一种场景,同一份数据要动态推送到多种数据源中存储,比如常见的MySQL表和ES索引双写模式,这就需要对实体对象做不同的解析逻辑;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BizType {
    EsIndexEnum esIndexEnum () ;        // ES索引解析适配
    MySqlTableEnum mySqlTableEnum () ;  // MySQL表解析适配
    ExcelEnum excelEnum () ;            // Excel解析适配
}

首先声明一个类型解析的注解,可以标记在实体对象的字段属性上,然后根据各种数据源的类型枚举,去适配不同解析工厂的执行逻辑,比如常用数据类型、格式、或者完全自定义。


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

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

相关文章

【Vue】url拼接参数获取解析

前言&#xff1a; 前端在开发的时候有很多情况下需要提供不带参数的链接或者带参数的链接给自己使用或者给第三方系统&#xff0c;如果提供给第三方系统使用的话一般是第三方需要通过iframe/window.open/a标签打开链接进入对应的页面。 情况一&#xff1a;获取当前浏览器地址栏…

【React二】ref与事件处理与生命周期钩子函数

关于react基础及组件化编程的可以参考本篇笔记 文章目录一、 React中的事件处理1-1 例子&#xff1a; 不要过度使用ref1-2 受控组件与非受控组件1-2-1 受控组件1-2-2 非受控组件1-3 高阶函数1-3-1 函数的柯里化1-3-2 不用柯里化的写法二、生命周期2-1 引出生命周期2-2 组件生命…

TiDB 数据库架构概述

TiDB 数据库架构概述 TiDB体系架构 水平扩容或者缩容金融级高可用实时HTAP云原生的分布式数据兼容MYSQL5.7 Region&#xff1a;存储单位&#xff0c;96~144MB之间,分布式存储在TiKV中 TiKV&#xff1a;默认三副本&#xff0c;将分布式数据存储&#xff0c;并进行存储副本。…

【ARM微型电脑/IoT设备/嵌入式】树莓派安装失败sysstat,成功后还是无法使用sar,并报错:-bash:sar:command not found

1. 安装sysstat报错 安装&#xff1a; sudo apt-get install sysstat错误如下&#xff1a; dpkg: error processing package sysstat (–configure): installed sysstat package post-installation script subprocess returned error exit status 10 Errors were encountered…

1737C - Ela and Crickets

原题链接&#xff1a; Problem - 1737C - Codeforces 题目描述&#xff1a; The problem, which involves a non-standard chess pieces type that is described below, reads: given 33 white crickets on a n⋅nn⋅n board, arranged in an "L" shape next to ea…

详细讲解磁盘及文件系统管理(图例解析)

磁盘及文件系统管理详解 目前市场上主流的磁盘是机械式硬盘 u盘&#xff0c;光盘&#xff0c;软盘&#xff0c;硬盘&#xff0c;磁带 机械式硬盘 【硬盘内部由一个个同心圆组成】如下图&#xff1a; 硬盘内部所有盘片都固定在一根“轴”上&#xff0c;所以&#xff1a;所有…

Vant的List组件列表 滑动后不触底也发送请求的Bug

&#x1f4c3;目录跳转&#x1f4da;简介&#xff1a;&#x1f389;页面效果&#xff1a;&#x1f4ad;使用Float:&#x1f9d0; 问题&#xff1a;&#x1f52d;div 转为行内块 &#xff08;解决&#xff09;&#x1f3c6;总结&#xff1a;&#x1f4da;简介&#xff1a; Vant的…

docke入门基础知识

一、Docker 架构 Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。 容…

html前端跨域问题的解决方案

前言&#xff1a; 在前端发出Ajax请求的时候&#xff0c;有时候会产生跨域问题&#xff0c;报错如下: Access to XMLHttpRequest at ‘http://127.0.0.1/api/post’ from origin ‘null’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is pre…

WINSOFT ComPort轻松连接到各种串行端口和连接设备

WINSOFT ComPort轻松连接到各种串行端口和连接设备 WINSOFT ComPort是一个Delphi库和CBuilder&#xff0c;通过USB端口连接到输入设备。该库提供CP210x、CP2130、CDC、FTDI、PL2303和CH34x设备。这些标准包括广泛的设备和电气从设备。例如&#xff0c;除了标准输入设备之外&…

Android App开发之利用Glide实现图片的三级缓存Cache讲解及实战(附源码 超详细必看 简单易懂)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、利用Glide实现图片的三级缓存 图片加载框架之所以高效&#xff0c;是因为它不但封装了访问网络的步骤&#xff0c;而且引入了三级缓存的机制。具体来说&#xff0c;是先到内存中查找图片&#xff0c;找到了就直接显示内存图…

国内访问Github超级慢?那是你没有用我这个脚本。直接起飞。

导语 之前很多朋友咨询过国内访问Github较慢的问题&#xff0c;然后我一般让他们自己去知乎上找攻略&#xff0c;但今天我才发现网上竟然没有一个一键配置的脚本&#xff0c;一般都需要我们跟着教程一步步地去做才行。这也太麻烦了&#xff0c;于是自己动手写了个脚本&#xf…

2-1 C++类的转换函数与禁止隐士转换(explicit)

1. 转换函数与explicit关键字 1.1 转换函数 下述代码的第5行operator double()即是一个转换函数&#xff0c;通过这个函数&#xff0c;编译器可以在需要的情况下&#xff0c;直接将Fraction类型的对象转换为double类型。这个函数有两个特点&#xff1a;首先因为转换函数的返回…

FFplay文档解读-50-多媒体过滤器四

32.21 showspectrumpic 将输入音频转换为单个视频帧&#xff0c;表示音频频谱。 过滤器接受以下选项&#xff1a; size&#xff0c;s指定输出的视频大小。 有关此选项的语法&#xff0c;请查阅&#xff08;ffmpeg-utils&#xff09;视频大小语法。 默认值为4096x2048。 mod…

Spring Boot入门必会(基本介绍+依赖管理+自动装配)

目录 一.基础入门 1.Spring Boot 是什么? 2.SpringBoot 快速入门 2.1完成步骤 2.2快速入门小结 3.Spring SpringMVC SpringBoot 的关系 3.1梳理关系 3.2如何理解 -约定优于配置 二.依赖管理和自动配置 1.依赖管理 1.1 什么是依赖管理 1.2 修改自动仲裁/默认版本号 …

论文阅读-Federated Social Recommendation with Graph NeuralNetwork

基于图神经网络的联邦社交推荐 1. 引言 因此&#xff0c;针对社交推荐任务&#xff0c;我们设计了一个联邦学习推荐系统&#xff0c;该系统具有异构性、个性化和隐私保护要求&#xff0c;具有一定的挑战性。为此&#xff0c;设计了一个基于图神经网络(FeSoG)的联邦社交推荐框…

【RHCE】ansible的简单配置

目录 使用静态清单文件指定受管主机 定义主机清单 定义方式 使用静态主机清单指定受管主机&#xff08;默认&#xff09; 验证清单 第一种方式 第二种方式&#xff08;图表形式显示&#xff09; 选择主机和组&#xff1a; 1>匹配所有主机 2>匹配指定的主机或者主…

二叉树与树、森林之间的转换

关于树的概念 树可以称为特殊的森林 &#xff0c; 其中二叉树是树中一些节点度数最大为2 &#xff0c;并且分左右孩子的树 ● 二叉树很重要 • 结构简单 • 存储效率高 • 运算算法相对简单 • 任何森林、树都可以转换成二叉树 ● 讨论 • 二叉树 度为2 的树 ? 答: 树的度就是…

官方盘点 .NET 7 新功能

.NET 7 为C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web API、WinForms、WPF 等应用程序带来了更高的性能和新功能。使用 .NET 7&#xff0c;您还可以轻松地将 .NET 7 项目容器化&#xff0c;在 GitHub 操作中设置 CI/CD 工作流&#xff0c;并实现云原生可观察性。欢迎下载…

java之Fork/Join框架

文章目录前言工作窃取算法Fork/Join框架的设计Fork/Join框架的异常处理Fork/Join框架的实现原理总结前言 Fork/Join框架是java7提供的一个用于执行并行任务的框架&#xff0c;是一个把大部分任务分割成若干个小任务&#xff0c;最终汇总每个小任务结果后&#xff0c;得到大任务…