java源碼級註解處理+位元組碼級註解處理__(3)core_java_advanced

來源:互聯網
上載者:User

【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):

t1) 編寫一個源檔案 BeanClassBeanInfo.java,繼承自SimpleBeanInfo, 覆蓋 getPropertyDescriptors 方法;

t2)對於每個已註解的方法,通過去除掉get 或 set 首碼,然後小寫化剩餘部分,就可以恢複屬性名稱;

t3)對於每個屬性,編寫一條用於構建 PropertyDescriptor 的語句;

t4)如果該屬性具有一個編輯器,那麼編寫一個方法去調用 setPropertyEditorClass;

t5)編寫代碼返回一個包含所有屬性描述符的數組; 看個荔枝(對於下面這個在 ChartBean 類中的註解,將被轉換為:)

@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 AbstractProcessor

8.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)

0)for complete source code about byte code engineering , please visit :  https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter10/10_7
1)處理註解的層級(levels): l1)運行期層級:具體執行個體,參見 http://blog.csdn.net/pacosonswjtu/article/details/50719233 l2)源碼層級: 具體執行個體,參見本章節【1】; l3)位元組碼層級: 具體執行個體,參見 本章節【2】; 2)對類檔案進行註解處理:類檔案格式歸檔過的, 這種格式相當複雜, 並且在沒有特殊類庫的情況下,處理類檔案具有很大的挑戰性。BCEL(Byte Code Engineering Library), 即位元組碼工程類庫, 就是這樣的特殊類庫之一;(乾貨——引入BCEL,位元組碼工程類庫) 3) 使用BCEL 向已註解方法中添加日誌資訊。 3.1)如果一個方法這樣註解過:  @LogEntry(logger=loggerName) 3.2)在方法的開始部分,我們添加: Logger.getLogger(loggerName).entering(className, methodName); 4)看個荔枝: 4.1)如果對 Item 類的 hashCode 方法做了如下註解:  @LogEntry(logger="global") public int hashCode(); 4.2)那麼,在任何時候調用該方法, 都會報告一條與下面列印出來的訊息相似的訊息: Aug 17, 2004,  Item hashCode FINER: ENTRY 5)為了實現這個任務, 我們需要遵循下面幾點(points): 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
Attention)插入這些位元組碼看起來很複雜,不過BCEL 卻使它變得簡單; 6)看個荔枝:  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;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.