`
Mysun
  • 浏览: 270488 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Sun的apt工具及Mirrors包学习

阅读更多
  最近看来一些有关Java Annotation的东西,主要是翻了一下Thinking in Java这本书。算是对Java的Annotation有了一个大致的了解。在看Thinking in Java的时候,书里面有一段代码,专门介绍如何利用Java 1.5之后自带的apt(Annotation Processing Tool)工具来处理Annotation的。代码如下:
//: annotations/database/TableCreationProcessorFactory.java 
// The database example using Visitor. 
// {Exec: apt -factory 
// annotations.database.TableCreationProcessorFactory 
// database/Member.java -s database} 
package annotations.database; 
import com.sun.mirror.apt.*; 
import com.sun.mirror.declaration.*; 
import com.sun.mirror.util.*; 
import java.util.*; 
import static com.sun.mirror.util.DeclarationVisitors.*; 
 
public class TableCreationProcessorFactory 
  implements AnnotationProcessorFactory { 
  public AnnotationProcessor getProcessorFor( 
    Set<AnnotationTypeDeclaration> atds, 
    AnnotationProcessorEnvironment env) { 
    return new TableCreationProcessor(env); 
  } 
  public Collection<String> supportedAnnotationTypes() { 
    return Arrays.asList( 
      "annotations.database.DBTable", 
      "annotations.database.Constraints", 
      "annotations.database.SQLString", 
      "annotations.database.SQLInteger"); 
  } 
  public Collection<String> supportedOptions() { 
    return Collections.emptySet(); 
  } 
  private static class TableCreationProcessor 
    implements AnnotationProcessor { 
    private final AnnotationProcessorEnvironment env; 
    private String sql = ""; 
    public TableCreationProcessor( 
      AnnotationProcessorEnvironment env) { 
      this.env = env; 
    } 
    public void process() { 
      for(TypeDeclaration typeDecl : 
        env.getSpecifiedTypeDeclarations()) { 
        typeDecl.accept(getDeclarationScanner( 
          new TableCreationVisitor(), NO_OP)); 
        sql = sql.substring(0, sql.length() - 1) + ");"; 
        System.out.println("creation SQL is :\n" + sql); 
        sql = ""; 
      } 
    } 
    private class TableCreationVisitor 
      extends SimpleDeclarationVisitor { 
      public void visitClassDeclaration( 
        ClassDeclaration d) { 
        DBTable dbTable = d.getAnnotation(DBTable.class);
        if(dbTable != null) { 
          sql += "CREATE TABLE "; 
          sql += (dbTable.name().length() < 1) 
            ? d.getSimpleName().toUpperCase() 
            : dbTable.name(); 
          sql += " ("; 
        } 
      } 
      public void visitFieldDeclaration( 
        FieldDeclaration d) { 
        String columnName = ""; 
        if(d.getAnnotation(SQLInteger.class) != null) { 
          SQLInteger sInt = d.getAnnotation( 
              SQLInteger.class); 
          // Use field name if name not specified 
          if(sInt.name().length() < 1) 
            columnName = d.getSimpleName().toUpperCase(); 
          else 
            columnName = sInt.name(); 
          sql += "\n    " + columnName + " INT" + 
            getConstraints(sInt.constraints()) + ","; 
        } 
        if(d.getAnnotation(SQLString.class) != null) { 
          SQLString sString = d.getAnnotation( 
              SQLString.class); 
          // Use field name if name not specified. 
          if(sString.name().length() < 1) 
            columnName = d.getSimpleName().toUpperCase(); 
          else 
            columnName = sString.name(); 
          sql += "\n    " + columnName + " VARCHAR(" + 
            sString.value() + ")" + 
            getConstraints(sString.constraints()) + ","; 
        } 
      } 
      private String getConstraints(Constraints con) { 
        String constraints = ""; 
        if(!con.allowNull()) 
          constraints += " NOT NULL"; 
        if(con.primaryKey()) 
          constraints += " PRIMARY KEY"; 
        if(con.unique()) 
          constraints += " UNIQUE"; 
        return constraints; 
      } 
    } 
  } 
} ///:~ 

  这段代码做的事情是利用Java mirrors包提供的Visitor模式来处理Java源代码中的Annotation,然后生成一个创建数据库表的SQL语句。至于那些个Annotation怎么定义的大家参看Thinking in Java第四版的相关章节,这里不再累述了。
  在这里我要将的主要有两个方面,一个是关于apt的,一个是关于上面代码中的Visitor模式的。
   一,关于apt
   apt是Sun从JDK 1.5(包括1.5)之后开始提供的处理源代码级别Annotation的工具,主要目标是根据源代码生成新的代码或者其他的一些文件,例如对象关系映射文件等。apt在处理源代码中的Annotation的时候,会调用特定的AnnotationProcessor,这些AnnotationProcessor是需要开发人员去实现的,一个Processor可以对应一个自定义的Annotation或者多个自定义的Annotation。
   但是apt不是直接调用AnnotationProcessor的,而是要通过AnnotationProcessorFactory来获得处理具体Annotation的。而AnnotationProcessorFactory是一个接口,所以为了使用apt,我们除了要实现AnnotationProcessor之外,还要实现AnnotationProcessorFactory。具体的方法见Thinking in Java第四版。
   apt的实现是在com.sun.tools.apt及其子包中,这个包实现了com.sun.mirror及其相关子包中的接口,用来处理源代码级别的Annotation。mirror包是Sun提供的专门用来获取源代码中的类型信息的工具包,而reflect包则是用来获取运行时类型信息的工具包。Sun建议在处理Java代码文件时,使用mirror包。
   为了降低处理源码中Annotation的代码复杂度,mirror中的关于类型的所有接口(xxxDeclaration)实现了Visitor模式,每个Declaration接口的子类都有一个accept方法,用来接收一个DeclarationVisitor接口的实现类,并调用该接口中的相应方法。具体可以参看Sun的相关API文档。
   我通过上面的代码看了一下mirror包里面相关类的源代码,从源代码以及上面代码的写法来看,apt是在扫描了全部的Java源文件之后,才去调用Processor的。也就是说AnnotationProcessorFactory接口的getProcessorFor方法是在apt分析了所有的源代码之后才被调用的,之后apt就会调用getProcessorFor方法返回的AnnotationProcessor接口实现类的process()方法,来处理Annotation。apt在扫描源代码的时候会将相关的Annotation信息保存在AnnotationProcessorEnvironment对象中,这样在AnnotationProcessor中就可以拿到所有Annotation信息了。至于那些Annotation的信息会被保存下来,这是在AnnotationProcessorFactory接口的supportedAnnotationTypes()方法中规定的。
   关于apt大概就是这些了。
   二,关于Visitor模式
   在Sun的com.sun.mirror.declaration包里面定义了很多的Declaration接口,用来对应源代码中的不同层次的元素,包括类型,构造函数,成员变量,方法参数等等。但是在mirror包里面没有提供实现类。具体的实现类是在com.sun.tools.apt.mirror.declaration包里面定义的。因为apt工具需要使用这些信息。
   在每个Declaration中都有个accept方法,该方法接收一个DeclarationVisitor接口的实现类,并调用DeclarationVisitor接口的实现类相应方法。在DeclarationVisitor定义了正对不同Declaration接口的方法,具体可以参看Sun的相关API文档。
   在最开始的代码里面,TableCreationProcessor的process方法是这样写的:
  
   public void process() { 
      for(TypeDeclaration typeDecl : 
        env.getSpecifiedTypeDeclarations()) { 
        typeDecl.accept(getDeclarationScanner( 
          new TableCreationVisitor(), NO_OP)); 
        sql = sql.substring(0, sql.length() - 1) + ");"; 
        System.out.println("creation SQL is :\n" + sql); 
        sql = ""; 
      }
   }
   

   其中的for循环遍历所有的TypeDeclaration,并调用每个TypeDeclaration的accept方法。在调用accept方法的时候并不是直接初始化一个DeclarationVisitor接口的实现类,而是调用的com.sun.mirror.util.DeclarationVisitors静态类的getDeclarationScanner静态方法。这个方法的作用看一下API的说明就知道了,是用来包装DeclarationVisitor的。官方的说明如下:
  
引用

   Return a DeclarationVisitor that will scan the declaration structure, visiting declarations contained in another declaration. For example, when visiting a class, the fields, methods, constructors, etc. of the class are also visited. The order in which the contained declarations are scanned is not specified.

The pre and post DeclarationVisitor parameters specify, respectively, the processing the scanner will do before or after visiting the contained declarations. If only one of pre and post processing is needed, use DeclarationVisitors.NO_OP for the other parameter.
  

   大概意思就是这个方法包装出来的DeclarationScanner类实现了DeclarationVisitor接口,同时在Visit一个Declaration的时候,会同时Visit这个Declaration下的所有子Declaration,比如在Visit一个ClassDeclaration的时候,就会同时Visit这个Class中的TypeParameterDeclaration,FieldDeclaration,MethodDeclaration,TypeDeclaration(因为Type可能会有嵌套),ConstructorDeclaration等。
   这样的话就节省了开发人员的代码量了,要不然我们自己还是要写同样的代码去处理这些个东西,而且还容易出错。

   要写的主要问题大概就这么一些,其他的感觉都是比较好理解的,看下书应该就知道了。
分享到:
评论
1 楼 xjw1987524 2011-03-16  
想问下你怎么调用呢,我也看到这里,不过很是郁闷啊,不知道怎么运行它,命令行编译也没有main函数啊。。。

相关推荐

Global site tag (gtag.js) - Google Analytics