当前位置: 首页 > 后端技术 > Java

Java注解与原理解析

时间:2023-04-01 22:16:30 Java

用得太多,习以为常;1、基础注解就是注解和解析。在Java代码工程中,注解的使用几乎无处不在,甚至多到被忽略;无论是在JDK源码还是框架组件中,都在使用注解能力来完成各种识别和分析动作;在封装系统功能时,也依靠注解能力来简化各种逻辑的重复实现;基础接口在Annotation的源码注解中有解释:所有注解类型都需要继承公共接口,注解本质上是一个接口,但是代码中没有明确声明继承关系,可以直接查看字节码文件;--1.声明注解public@interfaceSystemLog{}--2.查看命令javap-vSystemLog.class--3.打印结果Compiledfrom"SystemLog.java"publicinterfacecom.base.test.SystemLogextendsjava.lang.annotation.Annotation元注解在声明注解时使用,定义注解的目标。保留政策等;@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceSystemLog{Stringmodel()default"";}Documented:是否被javadoc或类似工具记录在文档中;Inherited:标识注解是否可以被子类继承;Target:函数目标,在ElementType枚举中可以看到,其值包括类、方法、属性等;Retention:保留策略,比如编译阶段是否丢弃,运行时保留;这里声明一个SystemLog注解,作用范围在方法上,运行时保留。该注解通常在服务运行时使用,结合AOP切面编程实现方法的日志收集;2.注解原理先来看一个简单的注解用例。然后详细分析原理,案例并不复杂,就是常见的注解和分析的两个关键动作;publicclassLogInfo{@SystemLog(model="logmodule")publicstaticvoidmain(String[]args){//生成代理文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//反射机制Method[]methods=LogInfo.class.getMethods();for(Methodmethod:methods){SystemLogsystemLog=method.getAnnotation(SystemLog.class);if(systemLog!=null){//动态代理:com.sun.proxy.$Proxy2System.out.println(systemLog.getClass().getName());System.out.println(systemLog.model());}}}}这里涉及到两个核心概念:反射机制和动态代理;反射机制可以在程序运行时获取类的完整结构信息,代理模式为目标对象提供一个代理对象,代理对象持有目标对象的引用;在这种情况下,反射机制用于在程序运行时获取和解析注解。值得关注的是systemLog对象的类名,输出的是代理类Information;案例执行后,会在代码工程目录下生成一个代理类,可以查看$Proxy2文件;publicfinalclass$Proxy2extendsProxyimplementsSystemLog{publicfinalStringmodel()throws{try{return(String)super.h.invoke(this,m3,(Object[])null);}catch(RuntimeException|Errorvar2){throwvar2;}catch(Throwablevar3){thrownewUndeclaredThrowableException(var3);}}}在解析SystemLog的过程中,其实是在使用注解的代理类。$Proxy2继承了Proxy类并实现了SystemLog接口,重新编写了相关方法;反射和代理的逻辑在前面的内容中已经详细介绍过,这里不再赘述;代理类中的invoke方法调用值得一看,具体处理逻辑在AnnotationInvocationHandler类的invoke方法中。对注解的原生方法和自定义方法进行判断,并为原生方法提供实现;三、常用注解1、JDK注解JDK中有多个注解是经常用到的,比如Override、Deprecated、SuppressWarnings等;override:判断方法是否为覆盖方法;Deprecated:标记一个过时的API,并继续使用它来警告;FunctionalInterface:检查是否为函数式接口;SuppressWarnings:代码警告将被静默处理;注意1.8引入的FunctionalInterface注解,检查是否为functionType接口,即接口只能有一个抽象方法,否则编译会报错;2、Lombok注解在详细看Lombok组件之前,需要了解一个概念:代码编译;在open-jdk描述文档中大致分为三个核心阶段;第一步:读取命令行指定的所有源文件,解析成语法树,填充符号表;第二步:调用注解处理器,如果处理器产生任何新的源文件或类文件,编译将重新开始;第三步:对分析器创建的语法树进行解析,转换为class文件;更多细节请参考openjdk文档中Compiler模块的内容,再回到Lombok组件;Lombok组件在代码项目中使用频率很高,通过注解大大简化了Java中Bean对象的编写,提高了效率,使源码看起来简洁;这里有一个简单的代码来演示一下它的效果,在IdKey类中使用了三个常用的Lombok注解替换了类中很多基本方法的显式生成,编译文件中其实也有相关的方法;@Data@AllArgsConstructor@NoArgsConstructor公共类Id密钥{私有整数id;私人字符串密钥;publicstaticvoidmain(String[]args){IdKeyidKey01=newIdKey(1,"cicada");System.out.println(idKey01);idKey01.setId(2);idKey01.setKey("微笑");System.out.println(idKey01);}}这里需要了解一下JDK中注解处理器的相关源码。AbstractProcessor是超类,编译器在编译时会检查这个类的子类,子类的核心是process方法;--1.Lombok处理器@SupportedAnnotationTypes("*")publicclassLombokProcessorextendsAbstractProcessor{privateJavacTransformertransformer;@Overridepublicbooleanprocess(Setannotations,RoundEnvironmentroundEnv){transformer.transform(prio,javacProcessingEnv.getContext(),cusForThisRound,清理);}}--2.AST抽象树publicclassJavacTransformer{publicvoidtransform(longpriority,Contextcontext,ListcompilationUnits,CleanupRegistrycleanup){JavacASTast=newJavacAST(messager,context,unit,cleanup);ast.traverse(newAnnotationVisitor(priority));handlers.callASTVisitors(ast,priority);}}--3、注解处理抽象类publicabstractclassJavacAnnotationHandler{publicabstractvoidhandle(AnnotationValuesannotation,JCAnnotationast,JavacNodeannotationNode);}--4、Getter注解处理publicclassHandleGetterextendsJavacAnnotationHandler{@Overridepublicvoidhandle(AnnotationValuesannotation,JCTree.JCAnnotationast,JavacNodeannotationNode){JavacNodenode=annotationNode.up();列表onMethod=unboxAndRemoveAnnotationParameter(ast,"onMethod","@Getter(onMethod",annotationNode);switch(node.getKind()){caseFIELD:createGetterForFields(level,fields,annotationNode,true,lazy,onMethod);中断;}}}IdKey类由简洁的源代码编译成复杂的字节码文件。通过注解处理结构体时,会关联一个核心概念,称为AST抽象树,涉及到大量语法和词法分析逻辑;4.自定义注解在系统开发中,可以使用自定义注解来处理各种麻烦的重复逻辑。最明显的好处是可以消除大量的冗余代码块;1、同步控制代码中可能有很多方法来限制重复请求,加上Lock处理是一种很常见的方法。这时候可以通过注解结合AOP切面编程来简化代码的复杂度;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceSyncLock{StringlockKey();//锁的键inttime()default3000;//有效时间intretryNum()default3;//重试次数}通过注解标注在方法上,可以大大简化同步锁的编码步骤,只需要读取KEY就可以结合反射原理设计解析规则获取;基于同样的原理,它也适用于日志收集、系统告警等功能,在前面的内容中有详细总结;2.数据处理中使用了类型引擎在逻辑上,经常会出现需要将同一份数据动态推送到多个数据源存储的场景,比如常见的MySQL表和ES索引双写模式,这对实体对象需要不同的解析逻辑;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public@interfaceBizType{EsIndexEnumesIndexEnum();//ES索引解析适配MySqlTableEnummySqlTableEnum();//MySQL表解析适配ExcelEnumexcelEnum();//Excel解析适配}先声明一个类型解析注解,可以标注在实体对象的field属性上,然后根据各种数据源的类型进行枚举,适配不同解析工厂的执行逻辑,比如common数据类型、格式或完全可定制。5.参考源码编程文档:https://gitee.com/cicadasmile/butte-java-note应用程序存储库:https://gitee.com/cicadasmile/butte-flyer-parent