java源码级注解处理+字节码级注解处理
来源:互联网 发布:微信群如何做淘宝客 编辑:程序博客网 时间:2024/06/05 06:40
【0】README
0.1)本文文字描述转自 core java volume 2, 旨在学习 java源码级注解处理+字节码级注解处理 的基础知识;
------------------------------------------------------------------------------------------------------------------------
【1】源码级注解处理
1)注解的用处之一: 就是自动生成包含程序额外信息的"附文件"。Java EE 5使用注解极大地简化了编程模型。
2)源码级注解是将注解处理器添加到Java编译器中。
3)看个荔枝: 我们编写了一个程序,可以自动产生bean信息类。该程序使用一个注解来标记bean的属性, 然后运行某个工具对这个源文件进行解析,分析其注解,最后输出 bean信息类的源文件;
4)提供@Property 注解: 为了避免编写 bean 信息类这项苦差事,我们提供了一个 @Property 注解,可以用来标记属性的获取器或设置器, 像下面这样:
@PropertyString getTitle() //获取器{ return title;}或者@Property(editor="TitlePositionEditor")public void setTitlePosition(int p) //设置器{ titlePosition = p;}
5)@Property 注解的定义
package com.corejava.chapter10_6;import java.lang.annotation.*;@Documented @Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Property{ String editor() default "";}
6)为了自动产生一个名字为 BeanClass 的bean 信息类,我们要实现下面这些任务(tasks):
看个荔枝(对于下面这个在 ChartBean 类中的注解,将被转换为:)t1) 编写一个源文件 BeanClassBeanInfo.java,继承自SimpleBeanInfo, 覆盖 getPropertyDescriptors 方法;
t2)对于每个已注解的方法,通过去除掉get 或 set 前缀,然后小写化剩余部分,就可以恢复属性名;
t3)对于每个属性,编写一条用于构建 PropertyDescriptor 的语句;
t4)如果该属性具有一个编辑器,那么编写一个方法去调用 setPropertyEditorClass;
t5)编写代码返回一个包含所有属性描述符的数组;
@Property(editor="TitlePositionEditor")public void setTitlePosition(int p){ titlePosition = p;}
将被转换为:
public class ChartBeanBeanInfo extends java.beans.SimpleBeanInfo{ public java.beans.PropertyDescriptor[] getProperties() { java.beans.PropertyDescriptor titlePositionDescriptor = new java.beans.PropertyDescriptor("titlePosition", ChartBean.class); titlePositionDescriptor.setPropertyEditorClass(TitlePositionEditor.class) ... return new java.beans.PropertyDescriptor[] { titlePositionDescriptor, ...... } }}
6.1)如果我们可以定位所有已经用 @Property 属性标记过的方法,那么所有这些都很容易实现。
7)从java SE6 开始,我们可以将注解处理器添加到 java 编译器中。
7.0)为了调用注解处理机制,需要运行 javac -processor ProcessorClassName1,ProcessorClassName2,... sourceFiles
7.1)编译器会定位源代码中的注解,然后选择恰当的注解处理器。 每个注解处理器会依次执行。如果某个注解处理器创建了一个新的源文件,那么将重复执行这个处理过程。如果某次处理循环没有再产生任何新的源文件,那么就编译所有的源文件。7.2)下图展示了 @Property 注解是如何处理的;
8)自定义注解处理器
8.1)注解处理器要扩展 AbstractProcessor,并重写 process 方法:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
8.1.1)process 方法有两个参数: 一个是在本轮中要进行处理的注解集,另一个是包含了有关当前处理轮次的信息的RoundEnv 引用;
8.1.2)在process 方法中,我们迭代所有注解过的方法,对于每个方法,我们剥离其名字中的get, set 和 is 前缀,并将随后紧挨着的字母改写为小写,从而得到属性名;
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement t : annotations) { Map<String, Property> props = new LinkedHashMap<>(); for (Element e : roundEnv.getElementsAnnotatedWith(t)) { props.put(property name, e.getAnnotation(Property.class)); } write bean info source file return ture;}
8.2)指定处理器支持的注解类型:
@SupportedAnnotationTypes("sourceAnnotations.Property")public class BeanInfoAnnotationProcessor extends AbstractProcessor8.3)编译注解处理器并运行
编译注解处理器: E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>javac com/corejava/chapter10_6/BeanInfoAnnotationProcessor.java运行: E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>javac -processor com.corejava.chapter10_6.BeanInfoAnnotationProcessor com/corejava/chapter10_6/ChartBean.javaresult: 之后就可以查看自动生成的 ChartBeanBeanInfo.java 文件;8.4)要查看注解处理器的行为,可以在 javac 命令中添加 XprintRounds。得到下面的输出:
Attention)for source code, please visit https://github.com/pacosonTang/core-java- volume/tree/master/coreJavaAdvanced/chapter10/10_6
package com.corejava.chapter10_6;import java.beans.*;import java.io.*;import java.util.*;import javax.annotation.processing.*;import javax.lang.model.*;import javax.lang.model.element.*;import javax.tools.*;import javax.tools.Diagnostic.*;/** * This class is the processor that analyzes Property annotations. * @version 1.11 2012-01-26 * @author Cay Horstmann */// 指定处理器支持的注解类型:@SupportedAnnotationTypes("com.corejava.chapter10_6.Property")@SupportedSourceVersion(SourceVersion.RELEASE_8)// 自定义注解处理器public class BeanInfoAnnotationProcessor extends AbstractProcessor{// process 方法有两个参数:// annotations 一个是在本轮中要进行处理的注解集,另一个是包含了有关当前处理轮次的信息的RoundEnv 引用; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement t : annotations) { Map<String, Property> props = new LinkedHashMap<>(); String beanClassName = null; for (Element e : roundEnv.getElementsAnnotatedWith(t)) { String mname = e.getSimpleName().toString(); String[] prefixes = { "get", "set", "is" }; boolean found = false; for (int i = 0; !found && i < prefixes.length; i++) if (mname.startsWith(prefixes[i])) { found = true; int start = prefixes[i].length(); String name = Introspector.decapitalize(mname.substring(start)); props.put(name, e.getAnnotation(Property.class)); } if (!found) processingEnv.getMessager().printMessage(Kind.ERROR, "@Property must be applied to getXxx, setXxx, or isXxx method", e); else if (beanClassName == null) beanClassName = ((TypeElement) e.getEnclosingElement()).getQualifiedName() .toString(); } try { if (beanClassName != null) writeBeanInfoFile(beanClassName, props); } catch (IOException e) { e.printStackTrace(); } } return true; } /** * Writes the source file for the BeanInfo class. * @param beanClassName the name of the bean class * @param props a map of property names and their annotations */ private void writeBeanInfoFile(String beanClassName, Map<String, Property> props) throws IOException { // 创建输出文件 JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile( beanClassName + "BeanInfo"); PrintWriter out = new PrintWriter(sourceFile.openWriter()); int i = beanClassName.lastIndexOf("."); // 编写源文件的代码简单明了,仅仅是一系列的 out.println 语句 if (i > 0) { out.print("package "); out.print(beanClassName.substring(0, i)); out.println(";"); } out.print("public class "); out.print(beanClassName.substring(i + 1)); out.println("BeanInfo extends java.beans.SimpleBeanInfo"); out.println("{"); out.println(" public java.beans.PropertyDescriptor[] getPropertyDescriptors()"); out.println(" {"); out.println(" try"); out.println(" {"); for (Map.Entry<String, Property> e : props.entrySet()) { out.print(" java.beans.PropertyDescriptor "); out.print(e.getKey()); out.println("Descriptor"); out.print(" = new java.beans.PropertyDescriptor(\""); out.print(e.getKey()); out.print("\", "); out.print(beanClassName); out.println(".class);"); String ed = e.getValue().editor().toString(); if (!ed.equals("")) { out.print(" "); out.print(e.getKey()); out.print("Descriptor.setPropertyEditorClass("); out.print(ed); out.println(".class);"); } } out.println(" return new java.beans.PropertyDescriptor[]"); out.print(" {"); boolean first = true; for (String p : props.keySet()) { if (first) first = false; else out.print(","); out.println(); out.print(" "); out.print(p); out.print("Descriptor"); } out.println(); out.println(" };"); out.println(" }"); out.println(" catch (java.beans.IntrospectionException e)"); out.println(" {"); out.println(" e.printStackTrace();"); out.println(" return null;"); out.println(" }"); out.println(" }"); out.println("}"); out.close(); }}
package com.corejava.chapter10_6;import java.lang.annotation.*;@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Property{ String editor() default ""; }
【2】字节码工程(english version, 参见 http://www.informit.com/articles/article.aspx?p=2027052&seqNum=7)
l1)运行期级别:具体实例,参见 http://blog.csdn.net/pacosonswjtu/article/details/50719233l2)源码级别: 具体实例,参见本章节【1】;l3)字节码级别: 具体实例,参见 本章节【2】;
3.1)如果一个方法这样注解过: @LogEntry(logger=loggerName)3.2)在方法的开始部分,我们添加: Logger.getLogger(loggerName).entering(className, methodName);
4.1)如果对 Item 类的 hashCode 方法做了如下注解: @LogEntry(logger="global") public int hashCode();4.2)那么,在任何时候调用该方法, 都会报告一条与下面打印出来的消息相似的消息:
Aug 17, 2004, Item hashCodeFINER: ENTRY
p1)加载类文件中的字节码;p2)定位所有的方法;p3)对于每个方法, 检查它是不是有一个 LogEntry 注解;p4)如果有, 在方法开始部分添加下面所列指令的字节码:ldc loggerNameinvokestatic java/util/logging/Logger.getLogger:(Ljava/lang/String;)Ljava/util/logging/Logger;ldc classNameldc methodNameinvokevirtual java/util/logging/Logger.entering:(Ljava/lang/String;Ljava/lang/String;)V
6.1) 如何向Item.java 文件添加记录日志指令
E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>javac -cp .;com/corejava/chapter10_7/bce l-6.0-SNAPSHOT.jar com/corejava/chapter10_7/EntryLogger.javaE:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java -cp .;com/corejava/chapter10_7/bcel -6.0-SNAPSHOT.jar com.corejava.chapter10_7.EntryLogger com.corejava.chapter10_7.Item Adding logging instructions to com.corejava.chapter10_7.Item.equalsAdding logging instructions to com.corejava.chapter10_7.Item.hashCodeDumping E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src\com\corejava\chapter10_7\Item.class
6.2)在对 Item类文件被修java改前和修改后分别运行下:
javap -c Item ,你就可以看到 在 hashCode, equals 以及 compareTo 方法开始部分插入的那些指令;
E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>javap -c com.corejava.chapter10_7.ItemCompiled from "Item.java"public class com.corejava.chapter10_7.Item { public com.corejava.chapter10_7.Item(java.lang.String, int); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 6: putfield #2 // Field description:Ljava/lang/String; 9: aload_0 10: iload_2 11: putfield #3 // Field partNumber:I 14: return public java.lang.String getDescription(); Code: 0: aload_0 1: getfield #2 // Field description:Ljava/lang/String; 4: areturn public java.lang.String toString(); Code: 0: new #4 // class java/lang/StringBuilder 3: dup 4: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 7: ldc #6 // String [description= 9: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: getfield #2 // Field description:Ljava/lang/String; 16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #8 // String , partNumber= 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: aload_0 25: getfield #3 // Field partNumber:I 28: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 31: ldc #10 // String ] 33: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 39: areturn public boolean equals(java.lang.Object); Code: 0: aload_0 1: aload_1 2: if_acmpne 7 5: iconst_1 6: ireturn 7: aload_1 8: ifnonnull 13 11: iconst_0 12: ireturn 13: aload_0 14: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 17: aload_1 18: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 21: if_acmpeq 26 24: iconst_0 25: ireturn 26: aload_1 27: checkcast #13 // class com/corejava/chapter10_7/Item 30: astore_2 31: aload_0 32: getfield #2 // Field description:Ljava/lang/String; 35: aload_2 36: getfield #2 // Field description:Ljava/lang/String; 39: invokestatic #14 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 42: ifeq 60 45: aload_0 46: getfield #3 // Field partNumber:I 49: aload_2 50: getfield #3 // Field partNumber:I 53: if_icmpne 60 56: iconst_1 57: goto 61 60: iconst_0 61: ireturn public int hashCode(); Code: 0: iconst_2 1: anewarray #15 // class java/lang/Object 4: dup 5: iconst_0 6: aload_0 7: getfield #2 // Field description:Ljava/lang/String; 10: aastore 11: dup 12: iconst_1 13: aload_0 14: getfield #3 // Field partNumber:I 17: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 20: aastore 21: invokestatic #17 // Method java/util/Objects.hash:([Ljava/lang/Object;)I 24: ireturn}
6.3)SetTest.java 程序会将Item 对象插入到一个散列集中。当你用修改过的类文件来运行该程序的时候,会看到下面的日志记录信息;official result)
Aug 18, 2004 10:57:59 AM Item hashCode
FINER: ENTRY
Aug 18, 2004 10:57:59 AM Item hashCode
FINER: ENTRY
Aug 18, 2004 10:57:59 AM Item hashCode
FINER: ENTRY
Aug 18, 2004 10:57:59 AM Item equals
FINER: ENTRY
[[description=Toaster, partNumber=1729], [description=Microwave, partNumber=4104]]
my result)
package com.corejava.chapter10_7;import java.io.*;import org.apache.bcel.*;import org.apache.bcel.classfile.*;import org.apache.bcel.generic.*;/** * Adds "entering" logs to all methods of a class that have the LogEntry annotation. * @version 1.10 2007-10-27 * @author Cay Horstmann */public class EntryLogger{ private ClassGen cg; private ConstantPoolGen cpg; /** * Adds entry logging code to the given class. * @param args the name of the class file to patch */ public static void main(String[] args) { try { if (args.length == 0) System.out.println("USAGE: java bytecodeAnnotations.EntryLogger classname"); else { JavaClass jc = Repository.lookupClass(args[0]); ClassGen cg = new ClassGen(jc); EntryLogger el = new EntryLogger(cg); el.convert(); String f = Repository.lookupClassFile(cg.getClassName()).getPath(); System.out.println("Dumping " + f); cg.getJavaClass().dump(f); } } catch (Exception e) { e.printStackTrace(); } } /** * Constructs an EntryLogger that inserts logging into annotated methods of a given class. * @param cg the class */ public EntryLogger(ClassGen cg) { this.cg = cg; cpg = cg.getConstantPool(); } /** * converts the class by inserting the logging calls. */ public void convert() throws IOException { for (Method m : cg.getMethods()) { AnnotationEntry[] annotations = m.getAnnotationEntries(); for (AnnotationEntry a : annotations) { if (a.getAnnotationType().equals("Lcom/corejava/chapter10_7/LogEntry;")) { for (ElementValuePair p : a.getElementValuePairs()) { if (p.getNameString().equals("logger")) { String loggerName = p.getValue().stringifyValue(); cg.replaceMethod(m, insertLogEntry(m, loggerName)); } } } } } } /** * Adds an "entering" call to the beginning of a method. * @param m the method * @param loggerName the name of the logger to call */ private Method insertLogEntry(Method m, String loggerName) { MethodGen mg = new MethodGen(m, cg.getClassName(), cpg); String className = cg.getClassName(); String methodName = mg.getMethod().getName(); System.out.printf("Adding logging instructions to %s.%s%n", className, methodName); int getLoggerIndex = cpg.addMethodref("java.util.logging.Logger", "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;"); int enteringIndex = cpg.addMethodref("java.util.logging.Logger", "entering", "(Ljava/lang/String;Ljava/lang/String;)V"); InstructionList il = mg.getInstructionList(); InstructionList patch = new InstructionList(); patch.append(new PUSH(cpg, loggerName)); patch.append(new INVOKESTATIC(getLoggerIndex)); patch.append(new PUSH(cpg, className)); patch.append(new PUSH(cpg, methodName)); patch.append(new INVOKEVIRTUAL(enteringIndex)); InstructionHandle[] ihs = il.getInstructionHandles(); il.insert(ihs[0], patch); mg.setMaxStack(); return mg.getMethod(); }}
package com.corejava.chapter10_7;import java.lang.annotation.*;/** * @version 1.00 2004-08-17 * @author Cay Horstmann */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface LogEntry{ String logger();}<strong></strong>
package com.corejava.chapter10_7;import java.util.*;/** * An item with a description and a part number. * @version 1.01 2012-01-26 * @author Cay Horstmann */public class Item{ private String description; private int partNumber; /** * Constructs an item. * @param aDescription the item's description * @param aPartNumber the item's part number */ public Item(String aDescription, int aPartNumber) { description = aDescription; partNumber = aPartNumber; } /** * Gets the description of this item. * @return the description */ public String getDescription() { return description; } public String toString() { return "[modified: description=" + description + ", partNumber=" + partNumber + "]"; } @LogEntry(logger = "global") public boolean equals(Object otherObject) { if (this == otherObject) return true; if (otherObject == null) return false; if (getClass() != otherObject.getClass()) return false; Item other = (Item) otherObject; return Objects.equals(description, other.description) && partNumber == other.partNumber; } @LogEntry(logger = "global") public int hashCode() { return Objects.hash(description, partNumber); }}
package com.corejava.chapter10_7;import java.util.*;import java.util.logging.*;/** * @version 1.02 2012-01-26 * @author Cay Horstmann */public class SetTest{ public static void main(String[] args) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).setLevel(Level.FINEST); Handler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).addHandler(handler); Set<Item> parts = new HashSet<>(); parts.add(new Item("Toaster", 1279)); parts.add(new Item("Microwave", 4104)); parts.add(new Item("Toaster", 1279)); System.out.println(parts); }}
【2.1】 在加载时修改字节码
1.1)problem: 把另一个工具添加到 程序的构建过程中, 会显得很笨重;1.2)solution: 更吸引人的做法: 是将字节码工程延迟到载入时,即类加载器加载类的时候;
2.1) 设备API 提供了一个安装字节码转换器的挂钩,不过,必须在main方法调用之前就安装这个 转换器。 (干货——引入字节码转换器)2.2)通过定义一个代理: 即被加载用来按照某种方式监视程序的一个类库, 就可以处理这个需求了。代理代码可以在 premain 方法中执行初始化;
s1)实现一个具有下面这个方法的类:public static void premain(String arg , Instrumentation instr) // 当代理加载的时候, 此方法就会被调用;s2)制作一个清单文件 EntryLoggingAgent.mf 来设置 Premain-Class 属性。 如:Premain-Class: bytecodeAnnotations.EntryLoggingAgents3)将代理代码打包, 并生成一个 JAR文件, 如:javac -classpath .:bcel-version.jar bytecodeAnnotations.EntryLoggingAgentjar cvfm EntryLoggingAgent.jar EntryLoggingAgent.mf bytecodeAnnotations/Entry*.class
s3.1)为了运行该代理的java程序, 使用如下命令行选项:java -javaagent: AgentJARFile=agentArgument ....s3.2)运行具有实体日志代理的SetTest 程序,需要调用:javac SetTest.javajava -javaagent: EntryLoggingAgent.jar=set.Item -classpath .:bcel-version.jar set.SetTest
C1)怎样向java程序中 添加注解;C2)怎样设计你自己的注解接口;(干货——diy 注解接口)C3)怎样实现可以利用注解 的工具;(干货——diy 注解工具)C4)你看到了三种处理代码的技术: 编写脚本, 编译java程序和处理注解。 前两种技术十分简单, 而另一方面, 构建注解工具可能会很复杂,但并不是大多数程序需要解决的问题;
- java源码级注解处理+字节码级注解处理
- java事务注解处理
- java annotation 注解 异常处理
- Java核心技术之注解处理
- Java使用反射处理注解
- java基础之注解及注解处理处理
- 源码级注解
- Java注解(3)-源码级框架
- Java注解(3)-源码级框架
- Java注解处理之反射API
- java笔记--springMessage处理自定义注解
- XStream 注解处理xml
- 注解-处理request
- 注解处理工具apt
- K:注解处理
- butterknife源码分析:如何处理注解—反射与注解处理器
- 使用Spring处理自定义注解
- 插入式注解处理API
- 基于javascript上手正则表达式
- 万兆环网
- iOS 判断版本是否升级,若是有新的版本,提醒升级
- 专访雷果国:从1.5K到18K 一个程序员的5年成长之路
- 图标消息提示效果
- java源码级注解处理+字节码级注解处理
- 程序员面试题精选100题(52)-C++面试题(1)
- 麦加《解密》简评
- 正态分布,Python实现
- OWASP ZAP2.4.3使用指南(中文版)
- [BZOJ2631] tree
- http://blog.csdn.net/limingchuan123456789/article/details/16849897
- js实现动态表格
- 在Source Insight中看Python代码