注解(元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在之后的某一个时刻非常方便地使用这些数据。 ---《Java编程思想》
其实注解可以理解为一个工具类,只要使用了这个工具类后,主体类将会添加一些功能,就好像一个法师身边多了一个小精灵。注解在一定程度上是把元数据和源代码文件结合在一起,而不是保存在外部文档中这一个大的趋势所催生的,不用去配置xml文件,不用去修改一些变量。之所以这么说注解,有注解本身特性所决定的,只要在类、方法、变量等代码的上方或者前方添加某个注解,那么我们将有这个注解的某个功能。
不得不说,注解是Java SE5重要语言之一,可以用来提供完整地描述程序所需的信息,存储程序的额外有关的信息。注解可以用来生成描述符文件,甚至或是新的类定义,有助于减轻编写“样板”代码的负担。
一、常用的Java内置的注解
- @Override,表示当前的的方法定义将覆盖父类中的方法。所以我们有时候觉得自己在子类中定义了一个和父类中存在的方法一样的名字,就可以自己覆盖了父类的方法,却没有发现其实有可能没有覆盖成功,原因有可能是argument的个数和类型有偏差,这个时候用了@Override我们就可以知道是否覆盖了,因为覆盖成功,@Overrdie不会报错,否则将会提示错误。
- @Deprecated,将某个element给注释为过时危险的element,不鼓励使用element,但是还是可以使用这个element。这个注解很多都是在类改造过程或者版本升级过程中使用到,为了兼顾旧版本,被引用的方法还能陪旧的版本使用。
1 @Deprecated 2 public static void sayHello(){ 3 System.out.println("Hello!!!"); 4 }
- @SuppressWarnings,按照英文翻译,压制提醒,而且压制提醒的将会压制包含在压制element里面的element的,比如压制提醒了一个类,那么类里面的方法也可以被压制到。下列中的一个System.runFinalizersOnExit(true),是一个过期的方法,那么这个过期的方法被压制提醒后将不会再被提醒了。
1 @SuppressWarnings(value = { "deprecation" }) 2 public static void main(String[] args) { 3 System.runFinalizersOnExit(true); 4 5 }
二、定义注解
除了我们使用Java本身拥有的注解之外,我们更多要自己去定义注解,以满足各种个性化的需求。在很多框架,Spring,Mybatis中,注解是被使用非常频繁地一个Java工具。常见@Controller,@Component,@Service,@ResponseBody等等。下面就来看看这个定义中有方法和作用。
定义注解重要有两个元注解,@Target和@Retention,一个是注解作用的目标对象(FIELD,METHOD,CONSTRUCTOR),一个是注解作用的保持的一个生命周期(SOURCE,CLASS,RUNTIME),java源文件--》class文件--》内存中字节码。
例子一,用于密码的校验
- 定义注解,其中Target的目标是Method,用了ElementType.METHOD,而Retention则是RUNTIME,这个RUNTIME就是一个在编译和运行时都有效的一个标志。里面的内容有一个区分的id和描述的description。
1 package annotation; 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 @Target(ElementType.METHOD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface UseCase { 11 12 public int id(); 13 public String description() default "no description"; 14 }
- 应用注解,在PasswordUtils中应用了UseCase的注解。
1 package annotation; 2 3 import java.util.List; 4 5 public class PasswordUtils { 6 7 @UseCase(id =47 ,description = "passwords must contain at lease one numeric") 8 public boolean validatePassword(String password){ 9 return (password.matches("\\w*\\d\\w*")); 10 } 11 12 @UseCase(id =48) 13 public String encryptPassword(String password){ 14 return new StringBuilder(password).reverse().toString(); 15 } 16 17 @UseCase(id = 49,description = "New password can't equel previously used ones") 18 public boolean checkForNewPassword(List<String> prevPasswords, String password){ 19 return !prevPasswords.contains(password); 20 } 21 }
- 验证效果,我们会发现注解的很多部分都用到了反射机制中的方法,比如说是getDeclaredMethods得到PassUtils的中的方法,然后再通过getAnnotation去得到方法中的注解,再去根据注解中方法去拿到注解的值。
1 package annotation; 2 3 import java.lang.reflect.Method; 4 import java.util.ArrayList; 5 import java.util.Collections; 6 import java.util.List; 7 8 public class UseCaseTracker { 9 10 public static void trackUseCases(List<Integer> useCases, Class<?> cl){ 11 for(Method m : cl.getDeclaredMethods()){ 12 UseCase uc = m.getAnnotation(UseCase.class); 13 if(uc != null){ 14 System.out.println("Found Use Case:" + uc.id() + " " + uc.description()); 15 useCases.remove(new Integer(uc.id())); 16 } 17 } 18 for(int i :useCases){ 19 System.out.println("Warning : Missing use Case -" + i); 20 } 21 } 22 23 public static void main(String[] args) { 24 List<Integer> useCases = new ArrayList<Integer>(); 25 Collections.addAll(useCases, 47,48,49,50); 26 trackUseCases(useCases,PasswordUtils.class); 27 } 28 }
- 测试结果
Found Use Case:47 passwords must contain at lease one numeric Found Use Case:48 no description Found Use Case:49 New password can't equel previously used ones Warning : Missing use Case -50
下面我们将进行一个定义注解在《Java编程思想》的一个很好的例子。目的是根据注解去生成一个SQL语句,这也是十分有用的。
- 定义一系列有关于生成数据库的注解
1 package annotation; 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 @Target(ElementType.TYPE) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface DBTable { 11 12 public String name() default ""; 13 }
1 package annotation; 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 @Target(ElementType.FIELD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface Constraints { 11 12 boolean primaryKey() default false; 13 boolean allowNull() default true; 14 boolean unique() default false; 15 }
1 package annotation; 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 @Target(ElementType.FIELD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface SQLString { 11 12 int value() default 0; 13 String name() default ""; 14 Constraints constraints() default @Constraints; 15 }
1 package annotation; 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 @Target(ElementType.FIELD) 9 @Retention(RetentionPolicy.RUNTIME) 10 public @interface SQLInteger { 11 12 String name() default ""; 13 Constraints constraints() default @Constraints; 14 }
- 写出一个作用的类Member,然后在将我们上面所定义的注解应用到该注解中。
package annotation; @DBTable(name ="MEMBER") public class Member { @SQLString(30) String firstName; @SQLString(50) String lastName; @SQLInteger Integer age; @SQLString(value =30, constraints = @Constraints(primaryKey = true)) String handle; static int memberCount; public String getHandle(){return handle;} public String getFirstName(){return firstName;} public String getlastName(){return lastName;} public String toString(){return handle;} public Integer getAge(){return age;} }
- 接下来,进行一个TableCreator,表格Sql的生成。运用了getAnnotation,getDeclaredFields,getDeclaredAnnotations等有关于反射的一系列的方法,去得到被注解应用的类中的各项属性的中值。
1 package annotation; 2 3 import java.lang.annotation.Annotation; 4 import java.lang.reflect.Field; 5 import java.util.ArrayList; 6 import java.util.List; 7 8 public class TableCreator { 9 10 public static void main(String[] args) throws Exception { 11 getSQL(new String[]{"annotation.Member"}); 12 } 13 14 public static void getSQL(String[] args) throws ClassNotFoundException { 15 if(args.length <1){ 16 System.out.println("arguments : annotated classes"); 17 System.exit(0); 18 } 19 for(String className :args){ 20 Class<?> cl = Class.forName(className); 21 DBTable dbTable = cl.getAnnotation(DBTable.class); 22 if(dbTable == null){ 23 System.out.println("No DBTable annotation in class " + className); 24 continue; 25 } 26 String tableName = dbTable.name(); 27 //If the name is empty ,use the Class name; 28 if(tableName.length() <1){ 29 tableName = cl.getName().toUpperCase(); 30 } 31 List<String> columnDefs = new ArrayList<String>(); 32 for(Field field : cl.getDeclaredFields()){ 33 String columnName = null; 34 Annotation[] anns = field.getDeclaredAnnotations(); 35 if(anns.length <1){ 36 continue;//not a db table column 37 } 38 if(anns[0] instanceof SQLInteger){ 39 SQLInteger sInt = (SQLInteger) anns[0]; 40 //user field name if name not specified 41 if(sInt.name().length() < 1) 42 columnName = field.getName().toUpperCase(); 43 else 44 columnName= sInt.name(); 45 columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints())); 46 } 47 if(anns[0] instanceof SQLString){ 48 SQLString sString = (SQLString) anns[0]; 49 //use field name if name not specified 50 if(sString.name().length() <1){ 51 columnName = field.getName().toUpperCase(); 52 }else{ 53 columnName = sString.name(); 54 } 55 columnDefs.add(columnName + " VARCHAR(" +sString.value() + ")" 56 +getConstraints(sString.constraints())); 57 } 58 StringBuilder createCommand = new StringBuilder( 59 "CREATE TABLE " + tableName +"("); 60 for(String columnDef : columnDefs) 61 createCommand.append("\n " + columnDef + ","); 62 //Remove trailing comma 63 String tableCreate = createCommand.substring(0,createCommand.length() -1) +");"; 64 System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);; 65 } 66 67 } 68 } 69 70 private static String getConstraints(Constraints con) { 71 // TODO Auto-generated method stub 72 String constraints = ""; 73 if(!con.allowNull()) 74 constraints += "NOT NULL"; 75 if(con.primaryKey()) 76 constraints += "PRIMARY KEY"; 77 if(con.unique()) 78 constraints += "UNIQUE"; 79 return constraints; 80 } 81 }
- 运行的结果
Table Creation SQL for annotation.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30)); Table Creation SQL for annotation.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50)); Table Creation SQL for annotation.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT); Table Creation SQL for annotation.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30)PRIMARY KEY);
三、现阶段中很多在运用到注解的例子
- JUnit
- Spring
- SpringMVC
- MyBatis
- Hibernate