注释是java5中的新特性,谈到注释,先的谈谈Java元数据
(metadata)。元数据,就是“关于数据的数据”。Java元数据有3种基本类型,还有3个Java内置注释类型,另外还有4中元注释类型。你可能
用过Javadoc的注释自动生成文档。这就是元数据功能的一种。总的来说,元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的
配置文件(如Hibernate也提供了注释配置)。
注释有3中基本类型
a.标记注释 –没有变量,只有名称标识。例如 @annotation
b.单一值注释 –在标记注释的基础上提供一段数据。如 @annotation(“javachen”)
c.完整注释 –可以包括多个数据成员,每个数据成员由名称和值构成。@annotation(site=””,author=”javachen”)
Java中提供3个内置注释类型
a. Override ,只能用于方法(不能用于类,包声明或者其他构造)
作用:可以保证编译时候Override函数的声明正确性
用法:
@Override
public void fun(){..}
b.Deprecated 同样只能作用与方法
作用:对不应再使用的方法进行注解
用法:@Deprecated public void fun{…}
c.SupressWarnings 可以注释一段代码
作用:关闭特定的警告信息,例如你在使用泛型的时候未指定类型
用法: @SupressWarnings(value={“unchecked”})
Java中还提供了四种元注释,专门负责注释其他的注释
@Target 表示该注释可以用于什么地方。可用的ElementType参数包括:
CONSTRUCTOR : 构造器的声明
FIELD : 域声明(包括enum实例)
LOCAL_VARIABLE : 局部变量声明
METHOD : 方法声明
PACKAGE : 包声明
PARAMETER : 参数声明
TYPE : 类、接口 (包括注解类型) 或enum声明
@Retention 表示需要在什么级别保存该注释信息。可选的RetentionPoicy参数包括:
SOURCE : 注释将被编译器丢掉
CLASS : 注释在class文件中可用,但会被VM丢弃
RUNTIME : VM将在运行时也保留注释,因此可以通过反射机制读取注释的信息。
@Documented 将注释包含在JavaDoc中
@Inheried 允许子类继承父类中的注释。
在Java中定义自己的注释
Java语言支持一种新的类型——注释类型(annotation type),跟普通类差不多,在类中以符号( @ )的形式注释其他 Java 代码,用@interface 申明自定义注释类型。
package com.javachen.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用户自定义标签,带有成员变量的MyTag
*/
@Documented //将注释包含在JavaDoc中
@Inherited //允许子类继承父类中的注释。
@Target(value = {ElementType.METHOD,ElementType.CONSTRUCTOR})//标注这个注释使用的范围
@Retention(value = RetentionPolicy.RUNTIME)//要想使用反射得到注释信息,这个注释必须使用
public @interface MyTag {
String name() default "javachen";//给自定义注释类的成员加上默认值
int age() default 24;
}
注意:在自定义注释中只有一个成员时,方法名应该为value
使用标签最终是为了帮助开发人员提取注释信息,然后根据不同需求做进一步处理,下面我们来看看如何获取注释信息。
package com.javachen.annotation;
import java.lang.annotation.Annotation;
public class TagTest {
@MyTag(name = "MyTag", age = 1)
public void test() {
}
@Override //可以保证编译时候Override函数的声明正确性
public String toString() {
return super.toString();
}
@Deprecated //对不应再使用的方法进行注解
public String notToUse() {
return super.toString();
}
public static void main(String[] args) {
TagTest tt = new TagTest();
try {
Annotation[] annotation = tt.getClass().getMethod("test")
.getAnnotations();
for (Annotation tag : annotation) {
System.out.println("Tag is:" + tag);
System.out.println("tag.name()" + ((MyTag) tag).name());
System.out.println("tag.age()" + ((MyTag) (tag)).age());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
需要注意的一点是,在执行这段代码之前我们还有一点小工作要做,还需要给我们的自定义标签MyTag加上一个说明标签,@ Retention, 表明注释信息将可以在运行时刻通过反射机制得到。如果不加入这个标签,上面的代码将没有任何输出。
如何使用反射读取注释
在以前的JDK版本中,我们可以使用反射得到类的方法、方法的参数以及其它的类成员等信息。那么在J2SE5.0中同样也可以象方法一样得到注释的各种信息。
在使用反射之前必须使用import java.lang.reflect.* 来导入和反射相关的类。
如果要得到某一个类或接口的注释信息,可以使用如下代码:
Annotation annotation = TestAnnotation.class.getAnnotation(MyAnnotation.class);
如果要得到全部的注释信息可使用如下语句:
Annotation[] annotations = TestAnnotation.class.getAnnotations();
或
Annotation[] annotations = TestAnnotation.class.getDeclaredAnnotations();
getDeclaredAnnotations与getAnnotations类似,但它们不同的是getDeclaredAnnotations 得到的是当前成员所有的注释,不包括继承的。而getAnnotations得到的是包括继承的所有注释。
如果要得到其它成员的注释,可先得到这个成员,然后再得到相应的注释。如得到myMethod的注释。
Method method = TestAnnotation.class.getMethod(“myMethod”, null);
Annotation annotation = method.getAnnotation(MyAnnotation.class);
注:要想使用反射得到注释信息,这个注释必须使用
@Retention(value = RetentionPolicy.RUNTIME)进行注释。
注解(Annotation) 为我们在代码中天界信息提供了一种形式化的方法,是我们可以在稍后某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据)。
注解的语法比较简单,除了@符号的使用以外,它基本上与java的固有语法一致,java内置了三种注解,定义在java.lang包中。
@Override 表示当前方法是覆盖父类的方法。
@Deprecated 表示当前元素是不赞成使用的。
@SuppressWarnings 表示关闭一些不当的编译器警告信息。
下面是一个定义注解的实例
Java代码
package Test_annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
/*
* 元注解@Target,@Retention,@Documented,@Inherited
*
* @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
* ElemenetType.CONSTRUCTOR 构造器声明
* ElemenetType.FIELD 域声明(包括 enum 实例)
* ElemenetType.LOCAL_VARIABLE 局部变量声明
* ElemenetType.METHOD 方法声明
* ElemenetType.PACKAGE 包声明
* ElemenetType.PARAMETER 参数声明
* ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
*
* @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
* RetentionPolicy.SOURCE 注解将被编译器丢弃
* RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
* RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
*
* @Documented 将此注解包含在 javadoc 中
*
* @Inherited 允许子类继承父类中的注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/*
* 定义注解 Test
* 注解中含有两个元素 id 和 description
* description 元素 有默认值 "no description"
*/
public @interface Test {
public int id();
public String description() default "no description";
}
下面是一个使用注解 和 解析注解的实例
Java代码
package Test_annotation;
import java.lang.reflect.Method;
public class Test_1 {
/*
* 被注解的三个方法
*/
@Test(id = 1, description = "hello method_1")
public void method_1() {
}
@Test(id = 2)
public void method_2() {
}
@Test(id = 3, description = "last method")
public void method_3() {
}
/*
* 解析注解,将Test_1类 所有被注解方法 的信息打印出来
*/
public static void main(String[] args) {
Method[] methods = Test_1.class.getDeclaredMethods();
for (Method method : methods) {
/*
* 判断方法中是否有指定注解类型的注解
*/
boolean hasAnnotation = method.isAnnotationPresent(Test.class);
if (hasAnnotation) {
/*
* 根据注解类型返回方法的指定类型注解
*/
Test annotation = method.getAnnotation(Test.class);
System.out.println("Test( method = " + method.getName()
+ " , id = " + annotation.id() + " , description = "
+ annotation.description() + " )");
}
}
}
}
输出结果如下:
Test( method = method_1 , id = 1 , description = hello method_1 )
Test( method = method_2 , id = 2 , description = no description )
Test( method = method_3 , id = 3 , description = last method )
------------------------------------------------------------------------------------------
注解的入级入门
注解为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。它是对来自像C#之类的其他语言对Java造成的语言特性压力所做出的一种回应。
一、Java内置基本注解
在Java中内置几种了基本的注解,下面列出几种常用的注解:
1、@Deprecated:用该注解注释的程序元素,表明不鼓励程序员使用这样的元素,通常是它很危险或存在更好的选择。
2、@Override:表明当前的方法定义将覆盖超类中的方法。
3、@SuppressWarnings:表明关闭不当的编译器警告信息。
4、@Documented:表明某一类型的注释将通过javadoc和类似的默认工具进行文档化。
5、@Inherited:允许子类继承父类中的注解。
6、@Retention:表明需要在什么级别保存该注解信息。
7、@Target:表明该注解可以用于什么地方。
8、@Generated:该注解用于标记已生成的源代码,它可以用于区分单个文件中用户编写的代码和生成的代码。
9、@PostConstruct:该注解用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。
10、@PreDestroy:该注解作为回调通知用于各方法,以表示该实例正处于被容器移除的过程中。
11、@Resource:该注解用于标记应用程序所需要的资源。
二、定义注解
注解的定义看起来很像接口的定义,具体代码如下:
- import java.lang.annotation.*;
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUTIME)
- public @interface Test {}
除了@符号之外,@Test的定义很像一个空的接口,定义一个注解的时候,要用到一些元注解(Java内置的注解),如上面例子中的定 义,@Target表明新定义的注解将用于方法定义上,@Retention表明新定义的注解将在运行时起作用。在注解中,一般都会包含一些元素以表示某 些值,而没有元素的注解称为标记注解,如上面的@Test注解,下面定义一个新的注解,将包含一些元素,如:
- import java.lang.annotation.*;
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface UseCase
- {
- public int id();
- public String description() default "no description";
- }
该注解中定义了两个元素id和description,其中description元素通过default指定了默认值。
三、注解处理器
如果没有用来读取注解的工具,那注解也不会比注释更有用,使用注解的过程中,很重要的一部分就是创建与使用注解处理器,而注解处理器的编写需要更加实际应用来灵活应用。
四、注意地方
1、注解元素可以用的类型只有:所有基本类型(int,float,boolean)、String、Class、enum、Annotation、以上类型的数组。
2、在定义元素的默认值时有限制,首先,元素不能有不确定的值,也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值;其次,对于非基本类型的元素,不能以null作为其值。
3、注解不支持继承。
随着J2EE进入5.0时代后,Java EE5.0的很多特性也被广泛应用在J2EE程序中。而Java EE5.0的注释(Annotations)特性就是其中应用最广泛的特性之一。
如果稍微浏览一下最新的Java EE5.0(EJB3.0,JPA)的标准规范,就可以发现,这些规范的制定者或是支持者们宣称最多的莫过于,利用这些规范可使开发变得像开发POJOs一样的简单与简洁。但是,如果对那些源代码稍加浏览或查看,最引人注目的可能就是那些取代了XML描述作用的注释。那么,笔者们禁不住要问一句,注释的使用真的可以简化复杂的J2EE或组件的开发吗?
一、 引言
在以前的J2EE版本中,都是使用大量的配置文件来设置Web程序、EJB等。但这一切在Java EE5.0中得到了彻底的改善。Java EE5.0中的注释(Annotation)是专门针对Web和EJB程序而设计的。如@Resource、 @EJB和@WebServiceRef等,以及其它一些与安全相关的注释,如@RunAs和@DeclareRoles。
在Java EE5.0中,这种对元数据(Meta-data)的支持的数据就是注释。通过使用注释, 程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。举个例子,例如希望某个方法的参数或者返回值不为空,虽然可以在Java doc中进行说明,但是表达同样意思的作法有很多,比如“The return value should not be null”或者“null is not allowed here”。测试工具很难根据这些语言来分析出程序员所期望的前提条件(Pre-condition)和执行后的条件(Post-condition)。而使用注释(Annotation),这个问题就可以轻而易举的解决。
注释,简单的说,就是代码里的标记,这些标记可以在类加载、运行时或是编译的时候可以被解释。这给人的感觉极像C++的macros。
其实说起注解语法,对于任何一个Java开发人员来说都已经耳熟能详了,我们每天都在使用着 的@author、 @param等等,其实都是在编写注释,然后用Javadoc生成文档。Java的这种方便的文档生成方法受到了开发者的普遍赞誉。而从JDK1.5开始,注释语法提供了更为强大的功能。
二、 注释——真的能简化吗?
笔者问过一些做Java开发的朋友关于对注释的了解情况。很多都说,在实际的项目当中,依然在使用Java1.4及EJB2.0,因为还没有到不得不使用注释的时候。当然,在平时的学习当中,可能会有涉及到注释。
注释是代码文件中的伪代码,而代码之外的一些配置文件,如XML及*.properties,感觉更加容易从编译过的部署类里具体化。这两者可能各有优点,那我们普通的开发人员依据什么来决定,到底是把元数据写在源文件代码里,还是写在单独的配置文件中呢?
有一点可以肯定,其它能像Java这样提供注释功能的语言并不多。引入注释的目的无非就是想把一些需要单独或是额外解决的问题,引用于源文件中一并解决,但这并不一定能起到药到病除的效果。
让我们看看使用注释在类文件中写入配置信息的情况:这意味着需要重新编译才能反映配置信息的改变,配置不能在Java程序外面单独的加以操作与配置。同时,对于使用那些支持注释及非注释两种类型的框架,无疑会使项目的配置更加混乱,增加维护难度。
如果一个软件在试运行或是真正使用时,碰到用户需求变化或者原来功能不能正常运行,如果一些基本的配置信息都写进Java源文件中,一般的作法是:需求反馈到该程序设计程序员,程序员修改代码,再进行测试,可能测试不通过,影响其他功能了,再测试,折腾很长时间,最后编译打包,交付客户。
如果使用XML和Java分离方式:水平较低的维修人员赶到现场,修改一下配置XML,无须编译Java代码,测试,马上解决问题。很明显哪个更快呢?
所以,开发软件不能只顾自己开发时方便,还要考虑到运行维护时是否方便,软件不像冰箱,制作好交给用户,很坚固,很稳定,用户也不会提出什么修改意见,当然海尔的定制化冰箱有这个意思,但是这种水平不是一般厂商水平能够做出来的。
当 然,笔者并不反对或是拒绝任何可以提高软件开发效率及节约时间的方法或技术,但这有个前提,就是这种技术或方法的成本或是代价。有些人会说,将一些部署逻 辑或是信息嵌入在代码中,这样可以减少文件的间接访问,增加代码的集中度。但是,在一个满是注释的源文件中,去提取特定的配置注释,这无疑会增加代码的分 析时间。此外,笔者们又如何给那些没有源文件的类文件进行注释呢?最后,注释又为何要整一套自己的新语法?它本来就像Java语法,那有必要另起炉灶,再整一套自己的语法吗?
当然,笔者也承认,注释有它肯定的一面,并且可以肯定,其初衷是好的。但笔者认为,在EJB3.0规范中,其注释的规范有些太过极端了。因为很明显,这已经违背了软件的简易性原则,增加了开发的复杂性,使代码的可维护性降低了。
三、 注释——使J2EE开发变得容易一些
我们知道,注释其实也是一种修饰符,在可以使用其它修饰符(比如public,static,或 final)的任何地方都可以使用注释。按规定,注释优先于其它修饰符。它包括一个@符号,@后接注释类型和被括号括起来的元素值对。这些值必须是编译时常量。也就是说 Java本身就提高了注释信息的详细列表。注释并不直接影响程序的语义,但它影响工具和库处理程序的方式,从而对运行程序的语义产生影响。注释可从源文件、class文件中阅读,也会在运行时得到反映。将定义从执行中分离出来并提供一种可以内省约束的方式,这使得执行过程更加灵活。大多数Java开发者已经很熟悉注释了,比如所有的JavaDoc标签和瞬时标签都是注释的例子。
普遍的观点都认为,配置信息最好不要使用注释写在源文件中,因为这样需要每次编译修改过注释的类,最好是将其写成单独的XML等文件。
任何J2EE 开发者都知道开发Java 程序其实并不简单。但新的Java EE 5.0将使你的开发过程变得容易一些。Java EE 5.0具有Web 服务支持、注释和增强的CMP性能。
要开发一个简单的J2EE应用程序,程序员必须要写大量的样本文件代码(如JavaBeans企业版)和设定无数个配置文件(XML中的描述文件)。所以要成为一个J2EE开发者,程序员必须熟悉EJB和XML。对于初学者来说,这些将令他们望而生畏。
目前的J2EE说明书(1.4)非常长,是用较老的JDK 1.2版本写的,这使J2EE更加复杂难懂。新的JDK版本提供了丰富的、简单好用的性能,比如Java EE 5.0的一般性能和注释支持。
而最新的Java EE 5.0,其中一个主要目的就是,既保持J2EE强大的功能,又可以使一般的开发任务变得容易一些。为了达到这个目的,Java EE 5.0将提供更好的默认性态和设置,允许大多数容器无需使用部署描述符就可以得到需要的东西。为此,Java EE 5.0做了很多注释。开发者不需要知道执行的细节(由容器完成执行任务)。这些新性能都使企业的Java应用程序更小、更快。
四、 注释——有所为,有所不为
注释作为元数据的保存或是持有是很实用的。例如,在构建一个实体类或是控制器时变得很方便。同时,在两个不同类之间进行关联时也很有用。
但是,如果把注释作为配置目的来使用,则有违它最初的设计初衷。因为配置文件或信息是经常需要修改的,例如数据库的映射文件等,在这方面使用注释,显然有滥用或过分使用的成分。因此,在这方面,Java EE5.0及Hibernae走得有点偏了,这常常使得注释在元数据与配置文件之间混用。
但是,这并不能说注释是有问题的,只能说是使用者误用问题造成的。注释比较适合用于元数据的定义或保存。但并不适用于应用运行的配置信息。
此外,注释另外一个很成功的应用可能就是在JUnit4 API里的应用。如:
增加一个@Test注释,而不需要像以前那样写成testXxx();
增加一个@Test注释,就可以替代以前的try/catch出错处理了;
增加一个@Before@After注释,可以替换以前的setUp()等方法;
及一个方法@Parameters public static Collection,被测试类的构造参数都保存在Object[]里。这样就进行参数化的测试了,而无须像从前那样public static Test suite()。
据笔者所知,JUnit框架中注释的使用,的确使它变成更加的灵活,代码更加的紧凑。
其 实,注释只是我们普通开发人员手里的一个工具,它也许是一个新玩意,带有几分的新鲜,但也有几分的讨厌,其实也是一把双刃剑。笔者记得当初学习设计模式的 时候,就想把所有的这些模式赶快应用到开发当中,不管适用不适用。只有当这种新鲜感过后,才能正常与正确的使用这些新特性。
五、 小结
那注释到底用还是不用呢?笔者认为,把注释应用到有意义的地方,而不是一味的滥用即可。例如,在那些没有注释就不能运行的地方肯定需要就注释。再如,改变注释将可能导致运行时类的行为,那可以考虑使用注释。
Java EE5.0使用注释这样新语法,替代XML,当然,修改元注释还是需要重新编译的,这和修改源码没有两样,但是如果没有单元测试这一人工工程管理跟上,程序上线正式运行,因为粗心等各种琐碎问题全部爆发,更是可怕。
另外,笔者认为,ROR中的约定优于配置(Convention Over Configuration ),这种做法Java的框架可以借鉴一下,在前期把问题都解决,这总比到了维护实施后期左找右改XML要强吧,牺牲了灵活性得到的却是可靠和稳定。使用ROR这样的解释语言,对于注释的修改是不需要编译,当然约定优于配置不是ROR首先提出来,默认简单,容易上手,需要细节还是可以使用XML配置。总之,约定优于配置是基于XML基础上的改进。当然,笔者不反对探索简化,但是目前探索结果还是发现,XML比较稳定,符合分离OO思想,能够健壮地对付扩展和维护。