Java中注解与原理分析详解

目录
  • 一、注解基础
  • 二、注解原理
  • 三、常用注解
    • 1、JDK注解
    • 2、Lombok注解
  • 四、自定义注解
    • 1、同步控制
    • 2、类型引擎

一、注解基础

注解即标注与解析,在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方法;

//1、Lombok处理器
@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);
    }
}

//2、AST抽象树
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);
}

//4、Getter注解处理
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解析适配
}

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

到此这篇关于Java中注解与原理分析详解的文章就介绍到这了,更多相关Java注解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java @GlobalLock注解详细分析讲解

    目录 GlobalLock的作用 全局锁 为什么要使用GlobalLock 工作原理 GlobalLock的作用 对于某条数据进行更新操作,如果全局事务正在进行,当某个本地事务需要更新该数据时,需要使用@GlobalLock确保其不会对全局事务正在操作的数据进行修改.防止的本地事务对全局事务的数据脏写.如果和select for update组合使用,还可以起到防止脏读的效果. 全局锁 首先我们知道,seata的AT模式是二段提交的,而且AT模式能够做到事务ACID四种特性中的A原子性和D持久性

  • Java @Autowired注解底层原理详细分析

    目录 1.概念 2.注入数据的注解 3.@Autowired注解是如何实现的 1.概念 @Autowired 是 Spring 提供的注解,默认的注入方式为 byType (按类型自动注入):@Autowired 注释,它可以对类成员变量.方法及构造函数进行标注,完成自动装配的工作:通过 @Autowired的使用来消除 set ,get方法. 2.注入数据的注解 @Value 含义:通过set方式注入基本类型与String,set方法可以省略 语法:@Value("数据")/@Val

  • Java中注解与原理分析详解

    目录 一.注解基础 二.注解原理 三.常用注解 1.JDK注解 2.Lombok注解 四.自定义注解 1.同步控制 2.类型引擎 一.注解基础 注解即标注与解析,在Java的代码工程中,注解的使用几乎是无处不在,甚至多到被忽视: 无论是在JDK源码或者框架组件,都在使用注解能力完成各种识别和解析动作:在对系统功能封装时,也会依赖注解能力简化各种逻辑的重复实现: 基础接口 在Annotation的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关

  • java方法及this关键字原理分析详解

    目录 步骤1 .给顾客增加一个吃饭的方法 步骤 2 . 没有加static的属性和方法,一定需要先new对象 步骤 3 . 用new出来的对象去执行eat方法 步骤 4 . 怎么理解c.eat() 步骤 5 . 消息接受器 步骤 6 . 如果有两个顾客? 步骤 7 . 答案 步骤 8 .其实有个this 步骤 9 . 在eat方法里面直接使用this 步骤 10 . 构造方法 步骤 11 . 总结:this的意义是什么? 步骤 12 . 道理我都懂,那static又是什么? 步骤 13 . 本节

  • Java CompletableFuture实现原理分析详解

    目录 简介 CompletableFuture类结构 CompletableFuture回调原理 CompletableFuture异步原理 总结 简介 前面的一篇文章你知道Java8并发新特性CompletableFuture吗?介绍了CompletableFuture的特性以及一些使用方法,今天我们主要来聊一聊CompletableFuture的回调功能以及异步工作原理是如何实现的. CompletableFuture类结构 1.CompletableFuture类结构主要有两个属性 pub

  • java 中enum的使用方法详解

    java 中enum的使用方法详解 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 下面是我在使用 enum 过程中的一些经验和总结. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu

  • Java Spring之@Async原理案例详解

    目录 前言 一.如何使用@Async 二.源码解读 总结 前言 用过Spring的人多多少少也都用过@Async注解,至于作用嘛,看注解名,大概能猜出来,就是在方法执行的时候进行异步执行. 一.如何使用@Async 使用@Async注解主要分两步: 1.在配置类上添加@EnableAsync注解 @ComponentScan(value = "com.wang") @Configuration @EnableAsync public class AppConfig { } 2.在想要异

  • java中Spring Security的实例详解

    java中Spring Security的实例详解 spring security是一个多方面的安全认证框架,提供了基于JavaEE规范的完整的安全认证解决方案.并且可以很好与目前主流的认证框架(如CAS,中央授权系统)集成.使用spring security的初衷是解决不同用户登录不同应用程序的权限问题,说到权限包括两部分:认证和授权.认证是告诉系统你是谁,授权是指知道你是谁后是否有权限访问系统(授权后一般会在服务端创建一个token,之后用这个token进行后续行为的交互). spring

  • 多用多学之Java中的Set,List,Map详解

    很长时间以来一直代码中用的比较多的数据列表主要是List,而且都是ArrayList,感觉有这个玩意就够了.ArrayList是用于实现动态数组的包装工具类,这样写代码的时候就可以拉进拉出,迭代遍历,蛮方便的. 也不知道从什么时候开始慢慢的代码中就经常会出现HashMap和HashSet之类的工具类.应该说HashMap比较多一些,而且还是面试经典题,平时也会多看看.开始用的时候简单理解就是个键值对应表,使用键来找数据比较方便.随后深入了解后发现 这玩意还有点小奥秘,特别是新版本的JDK对Has

  • Java中CountDownLatch进行多线程同步详解及实例代码

    Java中CountDownLatch进行多线程同步详解 CountDownLatch介绍 在前面的Java学习笔记中,总结了Java中进行多线程同步的几个方法: 1.synchronized关键字进行同步. 2.Lock锁接口及其实现类ReentrantLock.ReadWriteLock锁实现同步. 3.信号量Semaphore实现同步. 其中,synchronized关键字和Lock锁解决的是多个线程对同一资源的并发访问问题.信号量Semaphore解决的是多副本资源的共享访问问题. 今天

  • Mybatis中注解@MapKey的使用详解

    mybatis的原身是ibatis,现在已经脱离了apache基金会,新官网是http://www.mybatis.org/. 在研究Mybatis源码之前并不知道这个注解的妙用的,但是当我看到参数解析的时候 有这个一个注解,所以我了解了一下,当我们返回像Map<String, Map<String, Object>>这种类型的时候,我们往往很难做到,因为这里面可能是多个表的数据,所以我们不可能再建一个模型. 这时候我们就可以使用这个注解了 @Retention(Retention

  • 关于java中构造函数的一些知识详解

    java的构造函数是一个非常重要的作用,首先java里的构造函数是可以重载的,而且因为也是可以继承在父类的构造函数,所以在子类里,首先必然是调用父类的构造函数.可以看下面的两个例子来对比: public class Test { public static void main(String args[]) { B b = new B(100); } } class A { public A() { System.out.println("A without any parameter"

随机推荐