1.注解的一些基本概念
Java从1.5开始引入注解。注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。Java注解已经在很多框架中得到了广泛的使用,用来简化程序中的配置。在使用注解时候的配置参数的值必须是编译时刻的常量(java基本类型和String类型,Class类型,Enum类型,Anotation类型,数组类型)从某种角度来说,可以把注解看成是一个XML 元素,该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中使用注解,就相当于把一部分元数据从XML 文件移到了代码本身之中,在一个地方管理和维护。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 @Retention(RetentionPolicy.SOURCE) 2 @Target({ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE}) 3 public @interface MyAnotation { 4 public int intTest() default 1; 5 public long longTest() default 1l; 6 public String stringTest() default "name"; 7 public MyEnum enumTest(); 8 public String[] arrayTest(); 9 public MyAnotation2 anotationTest(); 10 } 11 12 13 enum MyEnum{ 14 TestAnotation1,TestAnotation2; 15 } 16 17 18 @interface MyAnotation2{ 19 public String value()default "myAnotation2"; 20 }
@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值@Retention用来声明注解的保留策略,有SOURCE(仅存在于源码中,在class字节码文件中不包含)、CLASS(会在class字节码文件中存在,但运行时无法获得)、RUNTIME(在class字节码文件中存在,在运行时可以通过反射获取到)这三种。只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息。
@Target用来声明注解可以被添加在哪些类型的元素上,其元素包括(TYPE(指一般的类,接口,枚举,注解), FIELD(属性,枚举常量), METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE)。
一般我们最常用的一些注解有
@Override(声明重写),@Deprecated(表示方法或类等是不被建议使用的)
@SupressWarnings(用于抑制警告常用值有“unchecked”,"unused"),@Documented(声明注解将被javaDoc中)
使用刚才定义的注解:
//当自定义注解的方法有默认值时可以不提供,也可以覆盖它 //当自定义注解只一个方法value或values(返回一个数组)时,在使用时可以不指 //定属性值,直接写如@SupressWarnings注解 @MyAnnotation(anotationTest = @MyAnotation2,intTest=1, arrayTest = { "1","2" }, enumTest = MyEnum.TestAnotation1) public class TestMyAnotation { }
2.实现一个自定义在Source或Class的注解
定义为Source或Class的注解,影响的就是程序的编译时刻,只有在程序的编译时候才能对它们作处理,只是定义在Source策略的在编译之后不会进入到Class文件中,而定义为Class策略的编译后会保留在class文件中。
在编译时刻处理的时候,是分成多趟来进行的。如果在某趟处理中产生了新的Java源文件,那么就需要另外一趟处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编译。
在JDK 6 中,通过JSR269 把自定义注解处理器这一功能进行了规范化, 有了新的javax.annotation.processing这个新的API。对Mirror API(之前jdk1.5中)也进行了更新,形成了新的javax.lang.model包。注解处理器的使用也进行了简化,不需要再单独运行apt(在jdk下tools.jar中)这样的命令行工具,Java编译器本身就可以完成对注解的处理。
于JDK 6中通过元注解@SupportedAnnotationTypes来声明所支持的注解类型。另外描述程序静态结构的javax.lang.model包使用了不同的类型名称。使用的时候也更加简单,只需要通过javac -processor Processor Demo.java这样的方式即可。
假设我们想在源代码编译的时候为一些加了@Property注解的属性生成setter 和getter方法
定义注解类和测试类放到一个文件中如下:
1 public class PropertyAnnotationTest { 2 @Property 3 private String username; 4 @Property(PropertyType.Set) 5 private String password; 6 7 } 8 9 @Retention(RetentionPolicy.SOURCE) 10 @Target(ElementType.FIELD) 11 @interface Property { 12 public PropertyType value()default PropertyType.GetAndSet; 13 } 14 15 enum PropertyType{ 16 Get,Set,GetAndSet; 17 }
编写自己的注解处理器类如下:
@SupportedAnnotationTypes(value = { "Property" })//用元注解声明些注解处理器要处理的注解类型 @SupportedSourceVersion(SourceVersion.RELEASE_7)//元注解声明源代码的版本 public class MyPropertyAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set,RoundEnvironment roundenvironment) { if (!roundenvironment.processingOver()) { for (Element e : roundenvironment.getRootElements()) { TypeElement te = findEnclosingTypeElement(e);//找到最底层的元素 System.out.printf("\n Scanning Type %s\n\n ",te.getQualifiedName()); for (ExecutableElement ee : ElementFilter.methodsIn(te.getEnclosedElements())) { Property property = ee.getAnnotation(Property.class);//从元素上获得注解 System.out.printf("%s property value = %s\n", ee.getSimpleName(), //获得注解所在的方法,类或属性等的简单不带修饰符的名字 property == null ? null: property.value());//获得此注解在此元素上的值 } } } return false; } public static TypeElement findEnclosingTypeElement(Element e) { while (e != null && !(e instanceof TypeElement)) { e = e.getEnclosingElement(); } return TypeElement.class.cast(e); } @Override public synchronized void init(ProcessingEnvironment processingenvironment) { super.init(processingenvironment); } } // 参考<http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/>
把cmd目录指向MyPropertyAnnotationProcessor(处理器)的bin目录下,执行javac -verbose -g -processor annotationTest.MyPropertyAnnotationProcessor D:\myworkspace\test\src\annotationTest\PropertyAnnotationTest.java
但是并没有看到输出相关的信息,但处理器的确调用了,至于如何在字节码中构造setter 和getter方法是处理字节码的事了,现在已经获得了属性的名称和注解的值,应该没有问题了
2.实现一个runtime策略的注解
运行时刻处理注解,一般通过java反射api来处理。反射API提供了在运行时刻读取注解信息的支持。不过前提是注解的保留策略声明的是运行时(runtime)
举一个例子,简单模仿hibernate的自动建表操作,首先定义一些注解,这些注解可以指定表名,属性名有ID
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Table { public String tableName(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface Id{ public String IdName(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface Column{ public String ColumnName(); }
再建一个测试类应用自己建的注解
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 @Table(tableName="testTabel") 2 public class PoJoClass { 3 4 @Id(IdName="ID") 5 private int Id; 6 7 @Column(ColumnName="username") 8 private String username; 9 10 @Column(ColumnName="password") 11 private String password; 12 13 public int getId() { 14 return Id; 15 } 16 public void setId(int id) { 17 Id = id; 18 } 19 public String getUsername() { 20 return username; 21 } 22 public void setUsername(String username) { 23 this.username = username; 24 } 25 public String getPassword() { 26 return password; 27 } 28 public void setPassword(String password) { 29 this.password = password; 30 } 31 32 33 }
然后再建一个处理我们注解的类,此处为简单起见就直接传Class文件进去到处理类中,在实际中应该传配置进去或是用动态代理方式处理拿到类来处理。
1 public class ProcessorTest { 2 3 4 public static void main(String[] args) { 5 6 filter(PoJoClass.class); 7 8 } 9 10 public static String filter(Class clzz){ 11 StringBuffer Sql = new StringBuffer(); 12 Annotation[] annotations = clzz.getAnnotations();//获得类上的所有注解 13 for(Annotation anno:annotations){ 14 if(anno instanceof Table){ 15 String tableName = ((Table) anno).tableName();//获取注解的值 16 Sql.append("creat Table ").append(tableName+"("); 17 } 18 19 } 20 21 Field[] fields = clzz.getDeclaredFields(); 22 23 for(Field field:fields){ 24 Annotation[] annotations2 = field.getDeclaredAnnotations();//获得属性上的所有注解 25 26 for(Annotation anno:annotations2){ 27 if(anno instanceof Id) 28 { 29 String idName = ((Id) anno).IdName();//获得注解的值 30 Sql.append(idName +" primary key, "); 31 } 32 else if(anno instanceof Column) 33 { 34 String columnName = ((Column) anno).ColumnName();//获得注解的值 35 Sql.append(columnName +","); 36 } 37 } 38 } 39 40 String sql = Sql.substring(0, Sql.length()-1)+")"; 41 System.out.println(sql); 42 return sql; 43 44 } 45 46 }
为测试方便直接在处理注解类中调用处理注解的方法,返回结果为:
creat Table testTabel(ID primary key, username,password)
注解一般在框架中用的比较多,所以熟悉注解的知识对学习框架作用是比较大的。