Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 276|回复: 0

[注解学习]使用J2SE 5.0的注解来去除getter和setter

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-29 23:57:58 | 显示全部楼层 |阅读模式
    摘要:    getter/setter这种习惯用法一直是有问题的,它允许你的类更容易被访问,却使这些类失去了可维护性。
         

         
          J2SE 5.0的注释(或者说元数据)提供了另一种可能性。比起用自省寻找get/set方法,你可以用注释“标注”类,然后在编译或者运行时访问那个注释。这篇文章不仅描述了注释机制,还介绍了一个基于XML的持久化机制的输出端,这个机制使用注释来标注类和字段。
         

         
        我曾经在javaWorld对getter/setter这种习惯用法的缺点做了很详细的讨论。这种习惯用法一开始是在JavaBean规范中被介绍的,以作为一种“标注”对象属性的方法,这样,一个扩展的用户界面层工具(叫做BeanBox)可以为那个对象创建一系列的属性列表。你可以像下面那样提供方法来“标注”属性。
         
       
       
       
      
       
       
         
       

         
       
      


      String getFoo();

    void setFoo( String newValue );



           BeanBox使用Class类中的自省API获取方法列表,然后使用模式匹配来寻找getter/setter对。根据这些推断出属性是否存在,并确定属性的类型(在这个例子中,有一个String类型的Foo属性)。你是不会调用这些方法的,它们只会被BeanBox调用。



        有趣的是,JavaBean规范的作者完全清楚getter/setter标注机制的问题所在(主要缺点已经在以前的文章中讨论过了,getter/setter方法暴露了过多的对象实现信息,所以底层类很难维护)。因此,设计者提供了大量的面向对象解决方法,比如BeanInfo和Customizer接口。用户实现了这些接口以后,就可以在没有setter/getter的条件下建立图形用户界面。不幸的是,这些过度复杂的面向对象方法在规范里很少提到。Getter/setter方法是简单的,可是如果你不能理解面向对象关系的维护问题,getter/setter方法好象是很合理的。因此,BeanInfo/Customizer方法就没落了,而getter/setter策略则像兔子一样快速繁殖。然而,你所经常看到的习惯用法并不是最好的做法。



    JavaBean刚被提出时,许多人(包括我自己)赞成在Java中使用新的关键字来消除getter和setter。利用新引入的关键字的能力,我在早些时候像下面那样描述Foo属性:


    private @property String foo;[/code]      因为foo是私有的,所以用新的关键字把它暴露给BeanBox并没有违反封装的原则。可是这时,引入新关键字有些离经叛道,尽管这个关键字不可能跟已经存在的标识符混淆,毕竟它们包含一个非法字符@。



           当J2SE 5.0出现时,Sun已经领会到了它的微妙,并且对语言的主要语法做了一点调整。现在,你可以引入一个新的关键字(叫做注释)来表明一个属性会在编译时或者运行时被检查。你可以引入你选择的任何关键字。只需要做到这点,注释(关键字)必须有一个前导@符号,并且你必须像使用形容词一样使用注释(注释可以放在任何你可以声明static,final,或者public的地方)。最后,你可以抛开getter和setter了,一种更干净的语法能够做到相同的事情。



         Java内置了两个很棒的关于注释的例子。想想这样一种情况,你的类继承自AWT/Swing的Adapter,可是却不小心拼错了基类方法的名字。你认为你覆写了基类的方法,实际上却没有。这种意料之外的继承是非常难以发现,但是如下的代码中的错误却很容易被编译器检测出来。


    public class myListener implements MouseListener
    {   
        @Overrides
        void MousePressed(MouseEvent e)
        {   System.out.println("Mouse button clicked!");
        }
    }[/code] 编译器在这里会抱怨,因为基类的方法叫做mousePressed()(m是小写的),而不是MousePressed(),就像类定义的那样。



    另外,程序里的注释@Deprecated在语法上也比Javadoc中的要简洁(因为注释内容不会影响类的兼容性)。



         
    有两种途径可以处理注释。首先,Class类的自省API可以获取关联到类的注释,以及关联到类的字段和方法的注释。BeanBox可以使用这种机制来寻找被标注的属性,并建立起属性列表。



         如果你没有自己的BeanBox,那么还有另一种选择。JDK提供了apt (Annotation Processing Tool)处理器,它是javac的前端,能够理解注释,并允许你构建Java源代码。你需要给apt提供不同的注释处理器插件。在这个例子中,插件会建立一个包装类,像老式的BeanBox做的那样,使用getter/setter这种习惯用法来把被注释的属性暴露给外界。不过,(就算是按Sun的标准)apt的文档也是非常糟糕的。我会在以后的文章中在介绍如何使用。



          在这篇文章中,我会展示一个小的持久化框架的“输出”端来告诉你如何使用运行时注释。这个框架并没有解决全部持久关系问题的打算,但是它很容易的把一个对象的状态表示为一个XML字符串。你可以用这种原理来取代其他应用中的getter和setter,比如GUI或者帮助系统(通过注释一个类来说明详细的信息)。




    版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接

    作者:Allen Holub ;
    deafwolf(作者的blog:
    http://blog.matrix.org.cn/page/deafwolf)

    原文:
    http://www.javaworld.com/javaworld/jw-03-2005/jw-0321-toolbox.html

    Matrix:
    http://www.matrix.org.cn/resource/article/44/44458_annotation+persistence.html

    关键字:annotation;persistence




    使用XMLExporter类



    清单1示范了我的持久化框架是如何使用注释的,清单2则展示了相应的输出。



    清单1. Test.java:使用XMLExporter


        1  package com.holub.persist.test;
       2  
       3  import java.io.*;
       4  import java.util.*;
       5  import com.holub.persist.*;
       6  import com.holub.persist.Exportable;
       7  //----------------------------------------------------------------------
       8  @Exportable
       9  class Address
      10  {   private @Persistent             String  street;
      11      private @Persistent             String  city;
      12      private @Persistent             String  state;
      13      private @Persistent("zipcode")  int zip;
      14  
      15      public Address( String street, String city, String state, int zip )
      16      {   this.street = street;
      17          this.city   = city;
      18          this.state  = state;
      19          this.zip    = zip;
      20      }
      21  }
      22  //----------------------------------------------------------------------
      23  public class Test
      24  {   
      25      @Exportable( name="customer", description="A Customer" )
      26      public static class Customer
      27      {   
      28          @com.holub.persist.Persistent
      29          private String  name    = "Allen Holub";
      30         
      31          @Persistent
      32          private Address streetAddress =
      33                          new Address( "1234 MyStreet",
      34                                       "Berkeley", "CA", 99999 );
      35          @Persistent
      36          private StringBuffer notes = new StringBuffer( "Notes go here ");
      37         
      38          private int garbage; // Is not persistant
      39         
      40          @Persistent Collection<Invoice> invoices = new LinkedList<Invoice>();
      41          {   invoices.add( new Invoice(0) );
      42              invoices.add( new Invoice(1) );
      43          }
      44      }
      45      
      46      @Exportable
      47      public static class Invoice
      48      {   private @Persistent int number;
      49          public Invoice( int number ){ this.number = number; }
      50      }
      51  
      52      public static void main(String[] args ) throws IOException
      53      {   Customer x = new Customer();
      54          XmlExporter out =
      55              new XmlExporter(
      56                  new PrintWriter(System.out, true) );
      57          out.flush( x );
      58      }
      59  }[/code]



    Listing 2. Test output


        1  <!-- A Customer -->
       2  <customer className="com.holub.persistent.test.Test$Customer"  >
       3      <name>
       4          Allen Holub
       5      </name>
       6      <Address className="com.holub.persistent.test.Address"  name="streetAddress" >
       7          <street>
       8              1234 MyStreet
       9          </street>
      10          <city>
      11              Berkeley
      12          </city>
      13          <state>
      14              CA
      15          </state>
      16          <zipcode>
      17              99999
      18          </zipcode>
      19      </Address>
      20      <notes classname="java.lang.StringBuffer">
      21          Notes go here
      22      </notes>
      23      <invoices>
      24          <Invoice className="com.holub.persistent.test.Test$Invoice"  >
      25              <number>
      26                  0
      27              </number>
      28          </Invoice>
      29          <Invoice className="com.holub.persistent.test.Test$Invoice"  >
      30              <number>
      31                  1
      32              </number>
      33          </Invoice>
      34      </invoices>
      35  </customer>[/code]      我使用注释标识那些会以XML格式输出的类,以及这些类中会被存储的字段(示范而已,你不一定会存储全部的字段)。系统用简单的方法处理回路(一个Employee引用了一个Company,而Company则拥有它全部Employee的列表。):自引用的字段会被忽视。在这个例子中,我输出Company会输出Employee,但是这些Employee都不包含Company...Company元素。输出Employee则会输出Company(以及除了一开始那个的全部的Employee)。请注意,第二种情况很好的避免了把没有被标注的Employee的Company字段也给持久化了。如果一个需要被持久化的字段是容器或者Map,那么全部元素都会被保存。如果是Map,那么键和值都会被完好的保存下来。



    抱歉,我发现这一段跟上面的代码不是一回事,为什么出现这种情况我也不清楚,请酌情处理。



         让我们来看看系统是如何使用的:首先,请注意清单1第5行的引入声明。注释看起来像个中世纪的怪物:一部分是狮子,一部分是鹰,一部分是蛇。在现在这种情况下,它们的行为像是接口。而跟你刚看到的一样,注释的声明或多或少的像一个接口的声明。包含这种声明的文件可以正常编译,并且正常的引入你的代码之中。在清单1中,我同时使用了*格式和外部引入,只是为了说明这两种格式都会像预期的那样工作。



    如果需要的话,你也可以使用完整的限定名来消除歧义。忽略@的话,


    @com.holub.persist.Persistent private String name1;[/code] 和


    import com.holub.persist.*;
    //...
    @Persistent private String name2;[/code] 都会像预期的那样工作。



    回到清单1,拥有@Exportable前缀的类被标注为“可输出的”。 Address类(第9行)是一个例子,内部类Test.Customer也是如此(第26行)。



    Test.Customer(第26行)的注释表明:注释是可以有参数的。像13行的@Persistent("zipcode")那样,注释可以使用单一的无名参数。当然,这样你就不能匹配两个参数了。



    看一下测试程序所输出的清单2,你就可以知道这些参数都做了什么。一般来说,元素名是从类中的字段声明而来的。如果给出了这样的字段声明:
      private @Persistent String name;[/code] 那么这个字段输出的值会是这样:


    <name>
        Allen Holub
    </name>[/code]      这里有一个例外。如果一个类用@Exportable标注,然后这个类的对象会用类名代替字段名作为元素名来输出。例如,字段streetAddress(清单1第32行)是一个@Exportable类(Address类)的引用。所以,这个字段会输出为:


    <Address>
        ...
    </Address>[/code]      在注释中,你可以覆写这些缺省的元素名。清单1第25行的name="customer"参数说明框架会使用customer(用小写的c)来代替Customer(用大写的C&#8212;&#8212;类名)。我也在这个注释中指定了第二个参数:@Exportable的参数description="A Customer"会被转化为输出文件第一行的注释。



         显然,元素的内容来自字段,但是这里的情况有一点微妙。如果一个字段引用自一个没有@Exportable的对象,然后会返回toString()的值。清单1第36行的StringBuffer字段是这样的例子。那么字段会输出为:


    </notes>
        Notes go here
    </notes>[/code]      文本Notes go here是notes.toString()返回的值。这种值不能被框架的“输入”端重新装载到对象里,除非该字段的类拥有一个String构造函数可以解析这个内容。



         如果一个字段引用了一个@Exportable的对象,那么XML的子元素会被生成为应该表现的值,就如清单1第32行的streetAddress字段。



         可输出的容器(和maps)中的所有元素都会被输出,但是全部元素都会被包装在一个外部元素里,所以这个容器看起来是一个整体。清单2底部的<invoices>元素就是这样的例子。在原来的对象中,相关的字段是这样声明的:

    @Persistent Collection<Invoice> invoices = new LinkedList<Invoice>();



    map的处理也是相似的,但是元素都会以name=属性来标识键(键对象的toString()方法会被调用)。例如:


    <field_name_of_Map_field>

        <ValueType  name="value_returned_by_key_toString()_method" className="...">
            value is displayed here
        </ValueType">
    </field_name_of_Map_field>[/code]      最后,请注意,这个例子是一个持久化框架,所以全部表示成对象的元素,输出时都会在className=属性中指明完全的类名称。如果知道字段名的话,也会在name=属性中输出。清单2第6行就是如此。




    声明注释



         在我们开始考虑实现之前,我必须说,注释的声明语法其实不是很好。许多奇怪的事情正在发生,而且很多语法例子都被证明不适合手头的任务,但是我们不得不忍受这些。



         注释声明起来像是接口,但是有些奇怪得地方。清单3是一个简单的例子。接口名是注释名,并且你得在接口前加上@号。全部注释风格的接口都是从Annotation接口继承来的。



    清单3. Persistent.java: 声明@Persistent


        1  package com.holub.persist;
       2  
       3  import java.lang.annotation.*;
       4  
       5  @Retention(RetentionPolicy.RUNTIME)
       6  @Target( ElementType.FIELD )
       7  public @interface Persistent
       8  {   String  value()  default "";    // Won"t accept null;
       9  }
      10 [/code] 第5行和第六行的的“元注释”控制着注释会被如何使用。各种保留政策如下:



    RetentionPolicy.CLASS         注释在类文件中,但是不能在运行时被访问。对于工作在类文件上的后置信息处理器很有用。

    RetentionPolicy.RUNTIME        注释在类文件中,并且可以通过自省API来访问

    RetentionPolicy.SOURCE         注释会在编译时刻被apt处理器所使用





    元注释@Target表明哪种Java元素可以附在注释上。参数定义成java.lang.annotation.ElementType,多数类型是自说明的:



    ElementType.ANNOTATION_TYPE         应用于其他注释的元注释

    ElementType.CONSTRUCTOR         构造函数

    ElementType.FIELD         字段

    ElementType.LOCAL_VARIABLE         方法中的本地变量

    ElementType.METHOD         方法

    ElementType.PACKAGE         包

    ElementType.PARAMETER        方法的参数

    ElementType.TYPE        类,接口或者枚举声明





    你也可以联合使用它们,就像初始化数组一样:

    @Target( {ElementType.FIELD, ElementType.METHOD} );



    还存在两种元注释,不过我还没在这里用过:

    @Documented        注释会在缺省情况下被javadoc用于文档化

    @Inherited         注释用于基类,则派生类通过自省可得到这些注释的信息,哪怕派生类自己没标注



    回到清单3,方法名定义了注释参数。这里,名字value()被使用。如果一个方法使用了这个名字,那么你可以在代码中使用@annotation("value")格式(不使用arg= part)。方法的返回类型控制着参数的类型,这个类型必须是原始类型或者String。使用default子句来为参数指明一个初始值。如果没有default子句,那么参数就是必须的了。



    现在,我们来看看注释@Exportable,这个注释有多个参数。



    清单 4. Exportable.java: 声明@Exportable


        1  package com.holub.persist;
       2  
       3  import java.lang.annotation.ElementType;
       4  import java.lang.annotation.Retention;
       5  import java.lang.annotation.RetentionPolicy;
       6  import java.lang.annotation.Target;
       7  
       8  @Retention(RetentionPolicy.RUNTIME)
       9  @Target( ElementType.TYPE )
      10  public @interface Exportable
      11  {   String description()    default "";
      12      String name()           default "";
      13      String value()          default "";
      14  }
      15  [/code] 请注意,我为每一个参数都像value()一样指明了默认值,所以这个注释使用起来有几种形式:


    @Exportable
    @Exportable("element_name")
    @Exportable(name="element_name")
    @Exportable(description="Comment goes here")
    @Exportable(name="element_name" description="Comment goes here")[/code] 我实现了处理这个注释的代码,因此第2和第3两种格式处理起来是一样的,但是你从声明中是看不到实现细节的。




    在运行时实现注释



    继续实现,清单5中的XmlExporter类做了全部的工作:



    清单 5. XmlExporter.java: XML输出类


       1  package com.holub.persist;
       2  
       3  import java.lang.reflect.*;
       4  import java.io.*;
       5  import java.util.*;
       6  
       7  /** A very-simple persistent writer. Writes in XML format.
       8   *  Cyclic object graphs are handled by ignoring the
       9   *  cyclic field.  For example, consider a Company that has references
      10   *  to its Employees, which have references to the Company.
      11   *  Exporting the Company will export all the Employees, but
      12   *  none of these Employees will contain a <Company>...</Company>
      13   *  element. Exporting an Employee will cause the Company (and
      14   *  all of it"s employees except the original one) to be exported.
      15   *  Note that this second situation is best avoided by not
      16   *  marking the Employee"s "Company" field as @Persistent.
      17   */
      18  public class XmlExporter
      19  {
      20      private PrintWriter         out;

      21      private int                 indentLevel     = -1;
      22      private boolean             writeClassNames = false;
      23      private Collection<Object>  amVisiting      = new ArrayList<Object>();
      24  
      25      /** Create an XmlEporter.
      26       *
      27       * @param out send the XML output to this writer.
      28       * @param writeClassNames if true, then synthesized element names
      29       *      will have a "className=" attribute.
      30       */
      31      public XmlExporter( PrintWriter out, boolean writeClassNames )
      32      {   this.out = out;
      33          this.writeClassNames = writeClassNames;
      34      }
      35      
      36      /** Convenenience constructor, class names are written.
      37       * @see #XmlExporter(PrintWriter,boolean)
      38       */
      39      public XmlExporter( PrintWriter out )
      40      {   this( out, true );
      41      }
      42  
      43      public void flush( Object obj ) throws IOException
      44      {   flush(obj, null, null );
      45      }
      46      /**
      47       * Flush out a single object that"s represented as a class (not
      48       * a primitive). The fields of the object are flushed
      49       * recursively. The output takes the form:
      50       * <PRE>
      51       * <eleName className="..." name="..." >
      52       *      ...
      53       * </eleName>
      54       *
      55       * The element name typically defaults to the class name. However,
      56       * if the object is annotated and has a name= attribute (or
      57       * an unnamed value), then that attribute specifies the
      58       * element name. The className attribute is generated if
      59       * this XmlExporter was created with constructor"s
      60       * writeClassNames argument set to true.
      61       * It holds the fully-qualified class name.
      62       *
      63       * @param obj           The object to export. If the object is a Collection or
      64       *                      Map, then the elements are exported as if they had
      65       *                      been passed to flush(Object) one at a time.
      66       *
      67       * @param preferedElementName   if not null, and the object is not annotated, then
      68       *                      use this string instead of the class name for the
      69       *                      element name.
      70       * @param objName       if not null, a name= attribute is
      71       *                      included in the generated element, and this
      72       *                      parameter determines the value.
      73       * @throws IOException
      74       */
      75      public void flush( Object obj, String nameAttribute, String fallbackElementName ) throws IOException
      76      {   ++indentLevel;
      77      
      78          if( amVisiting.contains(obj) )  // Cyclic object graph.
      79              return;                     // Silently ignore. cycles.
      80         
      81          amVisiting.add(obj);
      82         
      83          if( obj instanceof Map )

      84          {   for( Iterator i = ((Map)obj).keySet().iterator(); i.hasNext(); )
      85              {   Object element = i.next();
      86                  flush( ((Map)obj).get(element), element.toString(), null );
      87              }
      88          }
      89          else if( obj instanceof Collection )
      90          {   for( Iterator i = ((Collection)obj).iterator(); i.hasNext(); )
      91                  flush( i.next() );
      92          }
      93          else
      94          {   Exportable annotation =
      95                      obj.getClass().getAnnotation( Exportable.class );
      96         
      97              if( fallbackElementName == null )
      98                  fallbackElementName = extractNameFromClass( obj );
      99              
    100              String elementName = fallbackElementName;
    101              
    102              if( annotation != null )
    103              {   
    104                  if( annotation.description().length() > 0 )
    105                      out.println("<!-- " + annotation.description() + " -->");
    106                  
    107                  elementName = annotation.value();
    108                  if( elementName.length() == 0 )     // If it"s not specified in the value= attribute,
    109                      elementName = annotation.name();    //      check the name= attribute.
    110                  if( elementName.length() == 0 )     // It"s not in the name= attribute either.
    111                      elementName = fallbackElementName;
    112              }
    113              
    114              out.println(
    115                      indent()
    116                      + "<"
    117                      + elementName
    118                      + ( !writeClassNames ? " " :

    119                               (" className="" + obj.getClass().getName()+"" ") )
    120                      + (nameAttribute == null ? " " :
    121                               (" name="" + nameAttribute +"" ") )
    122                      + ">" );
    123              
    124              if( annotation != null )    // If the object is exportable,
    125                  flushFields( obj );     // then process its fields.
    126              else   
    127                  out.println( indent() + "        " + obj.toString() );
    128              
    129              out.println( indent() + "</" + elementName + ">");
    130          }
    131         
    132          amVisiting.remove(obj);
    133          --indentLevel;
    134      }
    135      

    136      private void flushFields( Object obj ) throws IOException
    137      {   try
    138          {   Field[] fields = obj.getClass().getDeclaredFields();
    139              for( Field f : fields )
    140              {   
    141                  Persistent annotation = f.getAnnotation( Persistent.class );
    142                  if( annotation == null )
    143                      continue;
    144                  
    145                  f.setAccessible(true); // Make private fields accessible.
    146                  
    147                  String value = null;
    148                  Class  type = f.getType();
    149                  if(type == byte.class  ) value=Byte.toString    ( f.getByte(obj) );
    150                  if(type == short.class ) value=Short.toString   ( f.getShort(obj) );
    151                  if(type == char.class  ) value=Character.toString( f.getChar(obj) );
    152                  if(type == int.class   ) value=Integer.toString ( f.getInt(obj) );
    153                  if(type == long.class  ) value=Long.toString    ( f.getLong(obj) );
    154                  if(type == float.class ) value=Float.toString   ( f.getFloat(obj) );
    155                  if(type == double.class) value=Double.toString  ( f.getDouble(obj) );
    156                  if(type == String.class) value= (String)        ( f.get(obj) );
    157                  
    158                  // If an element name is specified in the annotation, use it.
    159                  // Otherwise, use the field name as the element name.
    160                  String name = annotation.value();
    161                  if( name.length() == 0 )
    162                      name = f.getName();
    163                  
    164                  if(value != null)   // Then it"s a primitive type or a String.
    165                  {   out.println (indent() + "        <" + name + ">");
    166                      out.println( indent() + "                " + value );
    167                      out.println( indent() + "        </" + name + ">");
    168                  }
    169                  else if(  f.get(obj) instanceof Collection
    170                          || f.get(obj) instanceof Map )
    171                  {   out.println (indent() + "        <" + name + ">");
    172                      flush( f.get(obj), f.getName(), null );
    173                      out.println( indent() + "        </" + name + ">");
    174                  }
    175                  else
    176                  {   
    177                      // The following if statement will kick out a type-safety warning
    178                      // from the compiler. Since we don"t actually know the type (so
    179                      // can"t cast it appropriately), there"s no way to eliminate
    180                      // this warning.
    181                  
    182                      if( type.getAnnotation(Exportable.class) != null )
    183                          flush( f.get(obj), f.getName(), null );
    184                      else
    185                          flush( f.get(obj), null, name );
    186                  }
    187              }
    188          }
    189          catch( IllegalAccessException e )   // Shouldn"t happen
    190          {   assert false : "Unexpected exception:
    " + e ;
    191          }
    192      }
    193      
    194      /** Get the class name from the prefix. If the fully-qualified name
    195       *  contains a $, assume it"s an inner class and the class name is
    196       *  everything to the right of the rightmost $. Otherwise, if the
    197       *  fully qualified name has a dot, then the class name is everything
    198       *  to the right of the rightmost dot. Otherwise, the name is the
    199       *  string returned from getClass().getName().
    200       *  
    201       * @param obj
    202       * @return
    203       */
    204      private String extractNameFromClass( Object obj )
    205      {   String name = obj.getClass().getName();
    206          int index;
    207          if( (index = name.lastIndexOf("$")) != -1 )
    208              return name.substring( index + 1 );
    209         
    210          if( (index = name.lastIndexOf(".")) != -1 )
    211              return name.substring( index + 1 );
    212         
    213          return name;

    214      }
    215      private static final String indents[] = new String[]
    216         {
    217              /* 00 */    "",
    218              /* 01 */    "        ",
    219              /* 02 */    "                ",
    220              /* 03 */    "                        ",
    221              /* 04 */    "                                ",
    222              /* 05 */    "                                        ",
    223              /* 06 */    "                                                ",
    224              /* 07 */    "                                                        ",
    225              /* 08 */    "                                                                ",
    226              /* 09 */    "                                                                        ",
    227              /* 10 */    "                                                                                ",
    228              /* 11 */    "                                                                                        ",
    229              /* 12 */    "                                                                                                ",
    230              /* 13 */    "                                                                                                        ",
    231              /* 14 */    "                                                                                                                ",
    232              /* 15 */    "                                                                                                                        ",
    233         };
    234                                                         
    235      private final String indent()
    236      {   return indents[ indentLevel %16 ];
    237      }
    238  }
    239  [/code]

          第一个有意思的方法是flush()(75行)。这个方法是顶级的方法,用来把整个对象转化为XML格式的输出流。flush()方法还是递归的,用来转化不是原始类型或者String的字段。方法的前几行设置了缩进的级别(所以XML看起来会漂亮些)。检测当前对象是否在amVisiting容器中,可以处理对象图中的回路。如果是,那么这个实例的flush()会被递归调用,并且这次处理中的一些调用就会被跳过。这种情况下,方法会返回。



         下一段代码(从83行开始)处理了当前对象为Map或者容器的情况。集合中的元素会被轮流处理,就像他们依次转到flush()一样。Map与容器在处理时只有一点不同,持有键的值的name=...属性也会被flush()递归调用。



    最后,我们开始解释处理注释的代码。你访问类对象来得到注释(94行):



         请注意,我用getAnnotation()来从类中寻找特定的注释,并且返回类型被神奇的泛型自动转化为当前接口的类型。如果你不使用泛型语法,那么在Javadoc中,这个方法会显得十分混乱:


    public <A extends Annotation> A getAnnotation(Class<A> annotationClass)[/code]       这个声明表明,类A(返回类型)必须继承自Annotation,并且参数也必须使用相同的泛型类型。实际上,你不需要转化返回值,但是,我讨厌这种语法。



    你可以使用从getAnnotation()返回的对象,就像使用接口的引用一样。例如,代码中给出了这样的标注:


    @Exportable(name="Fred")
    //...[/code] 我可以从相关的name=参数中得到字符串值:


    Exportable annotation = obj.getClass().getAnnotation( Exportable.class );
    //...
    annotation.name(); // Returns "Fred"[/code]      也请注意到这点,清单5从第107行开始,value()明确的为name=参数提供了一个缺省值。代码首先检测缺省值,如果值不存在,则使用名字代替。这种未命名参数的默认值必须在你的代码中手动处理。在注释的@interface声明中,是没有办法指定行为的。



          方法flushFields()(清单5第136行)工作时跟flush()方法很相似,至少在处理注释上是这样。我用Class的getDeclaredFields()获取字段。我调用setAccessible(true)来告诉系统忽略访问权限,这样我就可以处理私有的字段了。然后flushFields()方法就可以输出原始类型或者String类型的参数了。它调用flush()来递归的输出除原始类型和String以外任意类的对象,包括容器或者Map字段。




    结论

         这篇文章介绍了一个关于注释的简单例子,但是它很有效的示范了如何使用运行时注释来消除基于getter/setter标注的习惯用法。对于所有有自省关系的类(比如类,方法,字段),你都可以用getAnnotation()方法来访问注释。像我在这篇文章一开始所说的,这只是拼图的一部分,因为注释通常用在编译器上,而不是运行时。我会在以后的文章中介绍如何做到这一点。




    关于作者

         Allen Holub从1979年以来加入计算机产业,从操作系统到编译器,到网络应用。他是一名顾问,提供面向对象设计和Java开发的指导及训练,致力于技术和设计,并在适当的时候写程序。他在NetReliance是一名CTO,并且是Ascenium和Ontometrics的顾问。Holub写了九本书(包括Holub on Patterns: Learning Design Patterns by Looking at Code, Taming Java Threads, and Compiler Design in C),并发表了100多篇文章。他对于JavaWorld和SD Times来说是非常有贡献的作者。



      
      
       
       

         
       

         
       
      
    复制代码

    源码下载:http://file.javaxxz.com/2014/10/29/235757609.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 23:05 , Processed in 0.378634 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表