一、注解的定义
1.注解(Annotation),也叫元数据。它是jdk1.5后引入的一个新的特性。与类,接口,枚举是同一个层次。可以声明在类、字段、方法、局部变量、方法参数等的前面。注解也属于一种类型,有自己的语法
1 @Target(ElementType.METHOD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface TestAnnotation { 4 5 }
如上所示,声明一个注解很简单,使用@interface声明TestAnnotation注解,并且使用两个java内置元注解(标识其他注解的注解)进行修饰。
二、java中常见的注解
1.元注解
元注解专职负责注解其他注解,主要有下面四种:
- @Target,主要用来约束注解可以使用的地方(如方法、类或字段)
- @Retention,主要用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),主要区别:RetentionPolicy.SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)RetentionPolicy.CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等。RetentionPolicy.RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
- @Document,将此注解包含在Jacadoc中
- @Inherited,允许子类继承父类的注解
2.标准注解
- @Deprecated,Deprecated是弃用的意思,顾名思义这个注解用来标识过时的元素,例如过时的类,方法或者成员变量。
- @Override,这可能是我们最熟悉的注解之一了,表示被注解的方法是复写了当前类父类的方法。
1 public class Demo{ 2 @Deprecated 3 public void test(){ 4 System.out.println("我已经被弃用"); 5 } 6 @Override 7 public boolean equals(Object obj) { 8 return super.equals(obj); 9 } 10 }
- @SuppressWarnings,主要用来忽略各种警告用的。用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,使用如下:
public class Demo2 { @SuppressWarnings("deprecation") public void test2(){ Demo demo = new Demo(); demo.test();//当使用@SuppressWarnings("deprecation")后,警告消失 } }
上例只是SuppressWarnings的一种,以上三种注解的详细使用说明可以参考源码。例如SuppressWarnnings注解的源码如下:
1 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) 2 @Retention(RetentionPolicy.SOURCE) 3 public @interface SuppressWarnings { 4 String[] value(); 5 /* 6 deprecation:使用了不赞成使用的类或方法时的警告; 7 unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 8 fallthrough:当Switch 程序块直接通往下一种情况而没有 Break 时的警告; 9 path:在类路径、源文件路径等中有不存在的路径时的警告; 10 serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; 11 finally:任何 finally 子句不能正常完成时的警告; 12 all:关于以上所有情况的警告。 13 */ 14 }
3.注解元素
1).注解元素可用的类型如下所示:
所有的基本类型(int,float,boolean等等)
- String
- Class
- enum
- Annotation
- 以上类型的数组
2).注解元素的默认值限制
编译器对注解元素的默认值有些过分的挑剔,元素不能有不确定的值,即元素必须要有默认值,要么在使用注解的时候提供元素的值。其次对于非基本类型的元素,无论是源码中声明时,或是注解接口中定义默认值的时候,不能以null作为其默认值。在注解的声明中,所有的元素都存在,并且具有其值,可以赋予空字符串或者负数,如下:
1 @Target(ElementType.METHOD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface SimplDemo { 4 public int id() default -1; 5 public String description() default "";//元素的定义类似接口中方法的定义 6 7 }
注意:不能使用关键字extends来继承某个@interface.
二、自定义注解的使用
1.自定义注解的简单使用
自定义一个注解TestAnnotation,主要用在方法上,首先定义一个注解,如下所示:
1 @Target(ElementType.METHOD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface TestAnnotation { 4 String name() ; 5 String value(); 6 }
定义注解的使用如下:
1 public class Demo { 2 @TestAnnotation(name = "test", value = "zz") 3 public void test() { 4 System.out.println("被注解方法执行完毕"); 5 } 6 7 public void test2() { 8 9 } 10 11 public static void main(String[] args) { 12 Demo demo = new Demo(); 13 Class<?> clz = demo.getClass(); 14 Method[] methods = clz.getDeclaredMethods(); 15 for (Method method : methods) { 16 // 遍历Demo类定义的所有方法,如果方法被TestAnnotation注解,则执行方法,并获取注解的相关属性 17 if (method.isAnnotationPresent(TestAnnotation.class)) { 18 method.setAccessible(true); 19 try { 20 method.invoke(demo, null); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } 24 25 Annotation[] annotations = method.getAnnotations(); 26 27 for (Annotation annotation : annotations) { 28 System.out.println(annotation); 29 if (annotation instanceof TestAnnotation) { 30 TestAnnotation testAnnotation = (TestAnnotation) annotation; 31 System.out.println(testAnnotation.name() + "--" 32 + testAnnotation.value()); 33 } 34 } 35 36 } 37 } 38 39 } 40 41 }
结果输出:
被注解方法执行完毕 @com.demo.TestAnnotation(name=test, value=zz) test--zz
注意:当TestAnnotation注解的保留策略是RetentionPolicy.CLASS和RetentionPolicy.SOURCE时,通过反射是获取不到注解信息的。
通常对注解的使用,都是结合反射一起,如上通过反射和注解可以决定是都执行Demo对象的某个方法。上述例子也只是对注解的一个简单的使用,注解带来的好处和用法太多。
主要有下面三方面:
-提供信息给编译器:编译器可以利用注解来探测错误和警告信息
-编译阶段时的处理:软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
-运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
三、注解与反射
有句话叫"纸上得来终觉浅,绝知此事要躬行",可能对注解还是一知半解,很多东西都是从不懂到懂,需要一个过程,可能这个过程是痛苦的,不过只要坚持,迟早会懂。等到了那一天反而会觉得很简单。通过实际的代码去理解理论知识,是最好的途径,特别是当在工作中使用了后就更有体会。下面这个例子是java编程思想中的一个经典的例子,能够帮助很好的理解注解和反射的关系。
1 @Target(ElementType.TYPE)//用于注解类 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface DBTable { 4 public String name() default ""; 5 } 6 7 -------------------------------------------------------------- 8 @Target(ElementType.FIELD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface Constraints { 11 boolean primaryKey() default false; 12 boolean allowNull() default true; 13 boolean unique() default false; 14 } 15 16 --------------------------------------------------------------- 17 @Target(ElementType.FIELD) 18 @Retention(RetentionPolicy.RUNTIME) 19 public @interface SQLString { 20 int value() default 0; 21 String name() default ""; 22 Constraints constraints() default @Constraints; 23 24 } 25 26 ---------------------------------------------------------------- 27 @Target(ElementType.FIELD) 28 @Retention(RetentionPolicy.RUNTIME) 29 public @interface SQLInteger { 30 String name() default ""; 31 Constraints constraints() default @Constraints; 32 }
应用上述自定义注解的类
1 @DBTable(name="MEMBER") 2 public class Member { 3 @SQLString(30) 4 String firstName; 5 @SQLString(50) 6 String lastName; 7 @SQLInteger 8 Integer age; 9 @SQLString(value=30,constraints=@Constraints(primaryKey=true,allowNull=false,unique=true)) 10 String handle; 11 static int memberCount; 12 13 public String getHandle(){ 14 return handle; 15 } 16 public String getFirstName(){ 17 return firstName; 18 } 19 public String getLastName(){ 20 return lastName; 21 } 22 public String toString(){ 23 return handle; 24 } 25 public Integer getAge(){ 26 return age; 27 } 28 }
实现处理器,利用反射和注解实现自动生产创建数据库表的SQL语句
1 /** 2 * 注解处理器 3 * 4 */ 5 public class TableCreator { 6 public static void main(String[] args)throws Exception { 7 Class<?> clz = Class.forName("com.sun.lp.demo.Member"); 8 9 DBTable dbTable= clz.getAnnotation(DBTable.class);//获取DBTable注解 10 if (dbTable==null) { 11 System.out.println("no DBTable annotation in class Member"); 12 System.exit(0); 13 } 14 15 String tableName = dbTable.name(); 16 if(tableName.length()<1){ 17 tableName=clz.getName().toUpperCase(); 18 } 19 List<String> list = new ArrayList<String>(); 20 String tableCreate =null; 21 for(Field field:clz.getDeclaredFields()){ 22 String colName=null; 23 Annotation [] annotations = field.getAnnotations(); 24 if(annotations.length<1) 25 continue; 26 if(annotations[0] instanceof SQLInteger){ 27 SQLInteger sInt = (SQLInteger) annotations[0]; 28 if(sInt.name().length()<1) 29 colName = field.getName().toUpperCase(); 30 else 31 colName = sInt.name(); 32 list.add(colName+" INT"+getConstraints(sInt.constraints())); 33 } 34 if(annotations[0] instanceof SQLString){ 35 SQLString sString = (SQLString) annotations[0]; 36 if(sString.name().length()<1) 37 colName = field.getName().toUpperCase(); 38 else 39 colName = sString.name(); 40 list.add(colName+" VARCHAR("+sString.value()+")"+getConstraints(sString.constraints())); 41 } 42 43 } 44 StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName+"("); 45 for(String colDef : list) 46 createCommand.append(" "+colDef+","); 47 tableCreate = createCommand.substring(0,(createCommand.length()-1))+");"; 48 System.out.println(tableCreate); 49 } 50 51 private static String getConstraints(Constraints con){ 52 String constraints = ""; 53 if(!con.allowNull()) 54 constraints+=" NOT NULL"; 55 if(con.primaryKey()) 56 constraints+=" PRIMARY KEY"; 57 if(con.unique()) 58 constraints+=" UNIQUE"; 59 60 return constraints; 61 62 } 63 64 }
结果输出:
CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) NOT NULL PRIMARY KEY UNIQUE);