[譯]使用注解處理器生成代碼-3 生成源代碼

      網友投稿 793 2025-04-04

      本博文原文地址摸我

      本篇博文是關于使用注解處理器生成java代碼系列的第三篇也是最后一篇文章。在第一篇(在這里)中,我們介紹了注解和其一般用法。在第二篇(在這里)中,我們介紹了注解處理器,如何構造并且使用它。?在本篇博文中,我們將想你展示如何使用注解處理器來生成源代碼。

      簡介

      生成源代碼很簡單。生成正確的源代碼卻很難。優雅高效的去生成正確的代碼是很麻煩的任務。

      幸運的是,Model-Driver Engineering(1)為我們提供了基于已經證明有效的過程和工具的成熟的方法理論。

      MDE 的 Model 和 Meta-model

      在討論如何使用注解處理器生成源代碼之前,有幾個相關的概念我們要實現講明,那就是models 和 meta-model

      MDE的理論基礎之一為抽象的構造(construction of abstractions)。我們將軟件系統在不同的層次和細節上使用不同的方法進行建模。當軟件在一個抽象層次上被建模完成之后,我們就開始對下一個抽象層次進行建模,知道建立一個完備的,可部署的產品。

      在這種理論環境下,一個model 就是我們用來在某一抽象層級上表示軟件系統的抽象。

      meta-model就是我們用來寫model的規則,你可以理解為model的綱要或者語法。

      使用注解處理器生成源代碼

      由上述描述可見,注解是定義model和meta-model的好方法,注解類型(Annotation Type)充當meta-model的角色,標注在一段代碼上的注解來提供model。

      我們可以使用這個model來生成配置文件或者從現有代碼中生成新代碼。比如,通過注解bean來生成遠程代理或者數據訪問對象。

      這個方法的核心就是使用注解處理器。注解處理器可以讀取在源代碼中發現的注解,并且對注解做任何想做的事情-比如,打開文件,寫文件,等等。

      Filter

      我們在第二篇博文中曾經說過,每個處理器都可以通過處理環境(processing environment)對象獲得一些有用的工具,Filter就是其中之一。

      javax.annotation.processing.Filer接口定義了一些關于創建源文件,類文件和一般資源的方法。通過使用Filter我們可以使用正確的文件目錄,并且確保不會丟失文件系統中的生成的文件或者資源。

      下面這個例子可以顯示如何在注解處理器中生成代碼。生成的類名就是被注解的類名加上BeanInfo的后綴:

      if (e.getKind() == ElementKind.CLASS) { TypeElement classElement = (TypeElement) e; PackageElement packageElement = (PackageElement) classElement.getEnclosingElement(); JavaFileObject jfo = processingEnv.getFiler(). createSourceFile(classElement.getQualifiedName() + "BeanInfo"); BufferedWriter bw = new BufferedWriter(jfo.openWriter()); bw.append("package "); bw.append(packageElement.getQualifiedName()); bw.append(";"); bw.newLine(); bw.newLine(); // rest of generated class contents

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      不要這樣生成代碼

      上邊這個例子十分簡單,有趣但是很混亂。

      我們把從注解中讀取信息的邏輯和寫生成的源文件的邏輯混一起拉。

      按照上述那種方式很難寫出簡潔的代碼,如果當我們遇到一些更加復雜的邏輯時,就更難啦。

      我們需要一個更加優雅的實現方式:- 將不同邏輯分離- 使用模版來讓代碼生成更加簡單

      讓我們看看使用Apache的Velocity構造代碼生成器的例子吧。

      Velocity簡介

      Velocity 是通過混合模版和java類的數據來生成各類文本文件的模版引擎。?Velocity可以在MVC框架中渲染視圖或者在xml傳輸數據時替代XSLT

      Velocity有它自己的語言叫做Velocity Template Language(VTL)。在VTL中,我們可以定義變量,控制流,迭代和獲取java對象中的數據。

      下面就是Velocity模版的一個片段:

      **#foreach($field in $fields)** /** * Returns the ${field.simpleName} property descriptor. * * @return the property descriptor */ public PropertyDescriptor ${field.simpleName}PropertyDescriptor() { PropertyDescriptor theDescriptor = null; return theDescriptor; } #end #foreach($method in $methods) /** * Returns the * *${method.simpleName}**() method descriptor. * * @return the method descriptor */ public MethodDescriptor ${method.simpleName}MethodDescriptor() { MethodDescriptor descriptor = null;

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      正如你所看到的,VTL十分簡單并且易于理解。#foreach($field in $fields)代表對對象集合的迭代;${method.simpleName}則是打印數據信息。

      Velocity代碼生成器

      既然我們決定使用Veloctiy來增強我們的代碼生成器,那么我們就要重新進行設計:

      - 設計用來生成代碼的模版

      - 注解處理器會從round environment中讀取被注解元素,并且將其保存到對象中,比如保存成員變量,方法,或者類,包的表.

      - 注解處理器需要初始化Velocity相關上下文- 注解處理器需要加載Velocity模版- 注解處理器會創建源文件(使用Filer)并且傳遞一個Writer給Velocity模版

      - Veloctiy引擎生成源代碼

      使用這個方案,我們會發現處理器和生成器相關的代碼是清晰,良好組織并且易于理解和維護。

      讓我們一步一步的來實現這個方案吧。

      步驟一:實現一個模版

      為了簡單,我們并不會展示BeanInfo生成器的全部代碼,而是只展示我們注解處理器需要使用的一部分成員變量和方法。

      我們先創建一個名為beaninfovm的文件,并且放置在Maven的src/main/resource下。文件內容如下:

      package ${packageName}; import java.beans.MethodDescriptor; import java.beans.ParameterDescriptor; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; public class ${className}BeanInfo extends java.beans.SimpleBeanInfo { /** * Gets the bean class object. * * @return the bean class */ public static Class getBeanClass() { return ${packageName}.${className}.class; } /** * Gets the bean class name. * * @return the bean class name */ public static String getBeanClassName() { return "${packageName}.${className}"; } /** * Finds the right method by comparing name & number of parameters in the class * method list. * * @param classObject the class object * @param methodName the method name * @param parameterCount the number of parameters * * @return the method if found, null otherwise */ public static Method findMethod(Class classObject, String methodName, int parameterCount) { try { // since this method attempts to find a method by getting all // methods from the class, this method should only be called if // getMethod cannot find the method Method[] methods = classObject.getMethods(); for (Method method : methods) { if (method.getParameterTypes().length == parameterCount &&method.getName(). equals(methodName)) { return method; } } } catch (Throwable t) { return null; } return null; } #foreach($field in $fields) /** * Returns the ${field.simpleName} property descriptor. * * @return the property descriptor */ public PropertyDescriptor ${field.simpleName}PropertyDescriptor() { PropertyDescriptor theDescriptor = null; return theDescriptor; } #end#foreach($method in $methods) /** * Returns the ${method.simpleName}() method descriptor. * * @return the method descriptor */ public MethodDescriptor ${method.simpleName}MethodDescriptor() { MethodDescriptor descriptor = null; Method method = null; try { // finds the method using getMethod with parameter types // TODO parameterize parameter types Class[] parameterTypes = {java.beans.PropertyChangeListener.class}; method=getBeanClass(). getMethod("${method.simpleName}", parameterTypes); } catch (Throwable t) { // alternative: use findMethod // TODO parameterize number of parameters method = findMethod(getBeanClass(), "${method.simpleName}", 1); } try { // creates the method descriptor with parameter descriptors // TODO parameterize parameter descriptors ParameterDescriptor parameterDescriptor1 = new ParameterDescriptor(); parameterDescriptor1.setName("listener"); parameterDescriptor1.setDisplayName("listener"); ParameterDescriptor[] parameterDescriptors = {parameterDescriptor1}; descriptor = new MethodDescriptor(method, parameterDescriptors); } catch (Throwable t) { // alternative: create a plain method descriptor descriptor = new MethodDescriptor(method); } // TODO parameterize descriptor properties descriptor.setDisplayName("${method.simpleName} (java.beans.PropertyChangeListener)"); descriptor.setShortDescription("Adds a property change listener."); descriptor.setExpert(false); descriptor.setHidden(false); descriptor.setValue("preferred", false); return descriptor; } #end }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      [譯]使用注解處理器生成代碼-3 生成源代碼

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      71

      72

      73

      74

      75

      76

      77

      78

      79

      80

      81

      82

      83

      84

      85

      86

      87

      88

      89

      90

      91

      92

      93

      94

      95

      96

      97

      98

      99

      100

      101

      102

      103

      104

      105

      106

      107

      108

      109

      110

      為了使用上述的模版,我們需要向Velocity傳遞下邊這些信息:

      packageName:生成類的全限定包名

      className:生成類名

      field:生成類中的成員變量集合;每個成員變量我們需要以下信息:

      simpleName:成員變量名 - type:成員變量的類型(在本例中并未使用)

      description:自我解釋型信息(在本例中并未使用)

      method:生成類中函數的集合;每個函數我們需要一下信息:

      simpleName:函數名

      arguments:函數的參數(在本例中并未使用)

      returnType: 函數返回值類型(在本例中并未使用)

      description:自我解釋性信息(在本例中并未使用)

      所有的這些信息都會從源文件中的注解中獲得,并保存到JavaBean中,再傳遞給Velocity。

      步驟二:注解處理器讀取信息

      讓我們來實現一個注解處理器,并且注解它支持處理BeanInfo注解類型,相關原理請查看第二篇博文。

      @SupportedAnnotationTypes("example.annotations.beaninfo.BeanInfo") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class BeanInfoProcessor extends AbstractProcessor {

      1

      2

      3

      4

      注解處理器需要從注解和源文件中提取。你可以使用JavaBean來保存你需要的信息。但是在這個例子中,我們將使用javax.lang.model.element,因為我們不計劃傳遞給Velocity過多信息:

      String packageName = null; Map fields = new HashMap(); Map methods = new HashMap(); for (Element e : roundEnv. getElementsAnnotatedWith(BeanInfo.class)) { if (e.getKind() == ElementKind.CLASS) { TypeElement classElement = (TypeElement) e; PackageElement packageElement = (PackageElement) classElement.getEnclosingElement(); processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "annotated class: " + classElement.getQualifiedName(), e); fqClassName = classElement.getQualifiedName(). toString(); className = classElement.getSimpleName().toString(); packageName = packageElement.getQualifiedName(). toString(); } else if (e.getKind() == ElementKind.FIELD) { VariableElement varElement = (VariableElement) e; processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "annotated field: " + varElement.getSimpleName(), e); fields.put(varElement.getSimpleName().toString(), varElement); } else if (e.getKind() == ElementKind.METHOD) { ExecutableElement exeElement = (ExecutableElement) e; processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "annotated method: " + exeElement.getSimpleName(), e); methods.put(exeElement.getSimpleName().toString(), exeElement); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      步驟三:初始化Velocity并且加載模版

      下邊的代碼片段展示了如何初始化Velocity并且加載模版

      if (fqClassName != null) { Properties props = new Properties(); URL url = this.getClass().getClassLoader(). getResource("velocity.properties"); props.load(url.openStream()); VelocityEngine ve = new VelocityEngine(props); ve.init(); VelocityContext vc = new VelocityContext(); vc.put("classNameassName); vc.put("packageNameckageName); vc.put("fieldselds); vc.put("methodsthods); Template vt = ve.getTemplate("beaninfo.vm");

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      Velocity的配置文件,應該命名為Velocity.properties,并放置在src/main/resources文件夾下。配置文件的內容如下:

      runtime.log.logsystem.class = org.apache.velocity.runtime.log.SystemLogChute resource.loader = classpath classpath.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

      1

      2

      3

      這些屬性配置了Velocity的日志和尋找模版的類路徑。

      步驟四:創建新的文件并且生成代碼

      最后,我們建立新的代碼文件并以這個文件為目標運行模版。下邊的代碼片段展示了如何如何去做上述操作:

      JavaFileObject jfo = processingEnv.getFiler().createSourceFile ( fqClassName + "BeanInfo"); processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "creating source file: " + jfo.toUri()); Writer writer = jfo.openWriter(); processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "applying velocity template: " + vt.getName()); vt.merge(vc, writer); writer.close();

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      步驟五:打包并運行

      最終,注冊注解處理器(可以回想一下在第二篇博文中的服務配置相關內容),打包處理器并且在命令行,eclipse和Maven構建項目時使用它。

      假設下邊就是需要處理的類:

      package example.velocity.client; import example.annotations.beaninfo.BeanInfo; @BeanInfo public class Article { @BeanInfo private String id; @BeanInfo private int department; @BeanInfo private String status; public Article() { super(); } public String getId() { return id; } public void setId(String id) { this.id = id; } public int getDepartment() { return department; } public void setDepartment(int department) { this.department = department; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @BeanInfo public void activate() { setStatus("active"); } @BeanInfo public void deactivate() { setStatus("inactive"); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      當我們使用命令行執行編譯任務時,我們在終端上看到被注解標注的元素被找到并且BeanInfo類被生成。

      Article.java:6: Note: annotated class: example.annotations.velocity.client.Article public class Article { ^ Article.java:9: Note: annotated field: id private String id; ^ Article.java:12: Note: annotated field: department private int department; ^ Article.java:15: Note: annotated field: status private String status; ^ Article.java:53: Note: annotated method: activate public void activate() { ^ Article.java:59: Note: annotated method: deactivate public void deactivate() { ^ Note: creating source file: file:/c:/projects/example.annotations.velocity.client/src/main/java/example/annotations/velocity/client/ArticleBeanInfo.java Note: applying velocity template: beaninfo.vm Note: example\annotations\velocity\client\ArticleBeanInfo.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      檢查相應的文件夾我們會發現BeanInfo類文件被創建。任務完成!

      總結

      在這個系列文章中,我們學習了如何使用Java6中的注解處理器框架生成源代碼:

      - 我們學習了注解和注解類型的概念和他們的基本用法

      - 我們學習了注解處理器的概念,還有如何編寫,以及從不同工具運行它。

      - 我們大致討論了一下Model-Drive Engineer和代碼生成。

      - 我們展示了如何使用注解處理器生成代碼

      - 我們學習了如何使用Velocity來創建優雅的,強大的,可維護的基于注解處理器的代碼生成器。

      -

      (1) 如何你想詳細了解MDE,請查看這篇文件

      (2) Filter的API文檔可以在這里進行查看

      Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:wps表格保存過了怎么恢復原來的數據庫(wps表格已經保存了怎么還原)
      下一篇:怎樣才能到下一行(如何讓下一行到上一行去)
      相關文章
      亚洲国产成人精品久久久国产成人一区二区三区综| 亚洲国产精品综合福利专区| 亚洲欧洲精品国产区| 亚洲色欲一区二区三区在线观看| 亚洲另类少妇17p| 亚洲AV永久无码精品一区二区国产| MM1313亚洲精品无码久久| 亚洲狠狠婷婷综合久久| 亚洲人成自拍网站在线观看| 亚洲中文字幕久久精品无码VA | 亚洲国产人成中文幕一级二级| 国产成人精品久久亚洲高清不卡| 国产精品亚洲五月天高清| 国产精品亚洲五月天高清| 国产成人亚洲综合a∨| 老司机亚洲精品影院在线观看| 国产天堂亚洲国产碰碰| 亚洲AV无码不卡在线观看下载| 亚洲国产精品成人| 狠狠亚洲狠狠欧洲2019| 亚洲精品午夜无码电影网| 亚洲国产一二三精品无码| 亚洲va久久久噜噜噜久久狠狠 | 亚洲乱码中文字幕久久孕妇黑人| 亚洲精品V欧洲精品V日韩精品| 亚洲av午夜福利精品一区| 亚洲视频2020| 亚洲国产韩国一区二区| 亚洲综合色婷婷在线观看| 亚洲AV无码国产一区二区三区| 国产亚洲综合视频| 国产福利电影一区二区三区,亚洲国模精品一区 | 亚洲av永久中文无码精品综合| 精品国产日韩亚洲一区在线| 亚洲国产精品一区二区三区久久| 久久久久亚洲精品天堂久久久久久 | 亚洲AV成人影视在线观看| 亚洲av成人一区二区三区在线播放| 国产精品亚洲专一区二区三区| 亚洲午夜福利精品无码| 亚洲日韩精品无码一区二区三区 |