Thinking in Java ——第二十章-注解
来源:互联网 发布:魔域老网络连接堵塞 编辑:程序博客网 时间:2024/06/06 03:42
注解(也成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据
基本语法
在下面的代码中,使用@Test对testExecute()方法进行注解。该注解本身并不需要做任何事情,但是编译器要确保在其构造路径上必须有@Test注解的定义,程序员可以创建一个通过反射机制来运行testExecute()方法的工具。
import net.mindview.atunit.Test;public class Testable { public void execute() { System.out.println("Executing.."); } @Test void testExcute() { execute() }}
被注解的方法与其他的方法没有区别。在这个例子中,注解@Test可以与任何修饰符共同作用,例如public、static、void等。从语法的角度看,注解的使用方式几乎与修饰符的使用一模一样。
定义注解
下面就是前例中用到的注解@Test的定义。可以看到,注解的定义看起来很像接口的定义。事实上,与其他任何Java接口一样,注解也会被编译成class文件。
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Test {}
除了@符号以外,@Test的定义很像一个空的接口。定义注解时,会需要一些元注解[meta-annotation], 如@Test, @Retention
@Test被用来定义你的注解将用到什么地方[例如是一个方法或者是一个域]。@Retention用来定义该注解在哪一个级别可用,在源代码中[SURCE],类文件中[CLASS],或者运行时[RUNTIME]。
在注解中,一般都会包含一些方法以表示某些值。当分析处理注解时,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,唯一的区别是你可以为其指定默认值。。下面就是一个简单的例子。
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase { public int id(); public String description() default "no description";}
在下面的三个类中,有三个方法被注解为用例
public class PasswordUtils { @UseCase(id = 47, description = "Passwords must contain at least one numeric") public boolean validatePasswords(String password) { return (password.matches("\\w*\\d\\w*")); } @UseCase(id = 48) public String encryptPassword(String password) { return new StringBuilder(password).reverse().toString(); } @UseCase(id = 49, description = "New passwords can't equal previously used ones") public boolean checkForNewPassword(List<String> prevPasswords, String password) { return !prevPasswords.contains(password); }}
元注解
Java目前只内置了三种标准注解,即
- @Override,表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法中签名对不上被覆盖的方法,编译器就会发出错误提示
- @Deprected,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。
- @SuppressWarnings,关闭不当的编译器警告信息。
同时,还有四种负责元注解。元注解专门负责注解其他的注解,即
- @Target,表示该注解可以用于什么地方。可能的ElementType参数包括:
–CONSTRUCTOR,构造器的声明
–FIELD,域声明,包括[enum实例]
–LOCAL_VARIABLE, 局部变量声明
–METHOD,方法声明
–PACKAGE,包声明
–PARAMETER,参数声明
–TYPE:类,接口(包括注解类型)或enum声明 - @Retention,表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
–SOURCE,注解将被编译器丢弃
–CLASS,注解在class文件中可用 ,但会被VM丢弃[不读入内存]
–RUNTIME, VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息 - @Documented,将此注解包含在Javadoc中
- @Inherited,允许子类型继承父类中的注解
编写注解处理器
如果没有注解处理器,那注解跟注释[咸鱼]有什么区别。Java SE5拓展了反射机制的API,以帮助程序员构造这类工具。同时,它还提供了一个外部工具apt帮助程序员解析所有带有注解的Java源码。
下面是一个非常简单的注解处理器,我们来用它读取PasswordUtils类,并使用反射机制查找@UseCase标记。我们为其提供了一组id值,然后它会列出在PasswordUtils中找到的用例,以及缺失的用例。
public class UseCaseTracker { public static void trackUseCases(List<Integer> useCases, Class<?> cl) { for(Method m : cl.getDeclaredMethods()) { UseCase uc = m.getAnnotation(UseCase.class); if(uc != null) { System.out.println("Found Use Case: " + uc.id() + " " + uc.description()); useCases.remove(new Integer(uc.id())); } } for(int i : useCases) { System.out.println("Warning: Missing use case-" + i); } } public static void main(String[] args) { List<Integer> useCases = new ArrayList<Integer>(); Collections.addAll(useCases,47, 48, 49, 50); trackUseCases(useCases, PasswordUtils.class); }}/**OutputFound Use Case: 48 no descriptionFound Use Case: 47 Passwords must contain at least one numericFound Use Case: 49 New passwords can't equal previously used onesWarning: Missing use case-50*/
注解元素
注解@UseCase由UseCase.java,其中包含int元素id,以及一个String元素description。注解元素的可用的类型如下所示
- 所有的基本类型[int, float, boolean]
- String
- Class
- enum
- Annotation
- 以上类型的数组
如果你使用了其他类型,那编译器就会报错。注意,也不允许使用其他任何包装类型,不过由于自动打包的存在,这算不上是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套,稍后你会看到,这是一个很有用的技巧。
默认值限制
编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。
其次,对于非基本类型的元素,无论是在源代码中声明时,或者在注解接口中定义默认值时,都不能以null作为其值。这个约束使得处理器很难表现一个元素的存在或者缺失状态,因为每个注解的声明中,所有的元素都存在,并且有具体的值。为了绕开这个约束,我们只能自己定义一些特殊的值,例如空字符串或负数,以此表示某个元素不存在:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface SimulatingNull { public int id() default -1; public String description() default "";}
使用注解生成外部文件
我们在使用SSH框架的时候经常在JavaBean源文件中使用注解创建数据表,现在我们简单的实现曾经用到的注解及注解处理器[具体的注解名称可能会不一样]。
表名
//DBTable.java@Target(ElementType.TYPE) //Applies to classes only@Retention(RetentionPolicy.RUNTIME)public @interface DBTable { public String name() default "";}
约束[主键约束,非空约束,唯一约束]
//Constraints.java@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME)public @interface Constraints { boolean primarykey() default false; boolean allowNull() default true; boolean unique() default false;}
String类型,注意这里用到了嵌套注解
//SQLString.java@Target(ElementType.FIELD)public @interface SQLString { int value() default 0; String name() default ""; Constraints constraints() default @Constraints;}
Integer类型
//SQLInteger.java@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLInteger { String name() default ""; Constraints constraints() default @Constraints;}
注意在SQLString.java和SQLInteger.java中constraints()元素的默认值就是@Constraints注解设定的默认值。如果现在要令嵌入的@Constraints注解中的unique()元素为true, 并以此作为constraints元素的默认值,则需要如下定义该元素
//Uniqueness.java@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Uniqueness { Constraints constraints() default @Constraints(unique = true)}
下面是一个简单的Bean定义,我们在其中应用了以上这些注解:
//Member.java@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 getFirstName() { return firstName; } public String getLastName() { return lastName; } public Integer getAge() { return age; } public String getHandle() { return handle; } public static int getMemberCount() { return memberCount; }}
注解不支持继承
不能使用关键字extends来继承某个@interface
实现处理器
//TableCreator.java{Args:com.sdkd.database.Member}public class TableCreator { private static String getConstraints(Constraints con) { String constraints = ""; if(!con.allowNull()) constraints += "NOT NULL"; if(con.primarykey()) constraints += "PRIMARY KEY"; if(con.unique()) constraints += " UNIQUE"; return constraints; } public static void main(String[] args)throws Exception { if(args.length < 1) { System.out.println("arguments: annotated classes"); System.exit(0); } for(String className : args) { Class<?> cl = Class.forName(className); DBTable dbTable = cl.getAnnotation(DBTable.class); if(dbTable == null) { System.out.println("No DBTable annotations in class" + className); continue; } String tableName = dbTable.name(); //If the name is empty, use the Class name if(tableName.length() < 1) { tableName = cl.getName().toUpperCase(); } List<String> columnDefs = new ArrayList<String>(); for(Field field : cl.getDeclaredFields()) { String columnName = null; Annotation[] anns = field.getDeclaredAnnotations(); if (anns.length < 1) continue; if (anns[0] instanceof SQLInteger) { SQLInteger sInt = (SQLInteger) anns[0]; //Use field name if name not specified if (sInt.name().length() < 1) { columnName = field.getName().toUpperCase(); } else { columnName = sInt.name(); } columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints())); } if (anns[0] instanceof SQLString) { SQLString sString = (SQLString) anns[0]; if (sString.name().length() < 1) { columnName = field.getName().toUpperCase(); } else { columnName = sString.name(); } columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints())); } } StringBuilder createCommand = new StringBuilder( "CREATE TABLE " + tableName + "(" ); for(String columnDef : columnDefs) { createCommand.append("\n " + columnDef + ","); String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");"; System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate); } } }}
测试类
public class Test { public static void main(String[] args) throws Exception { String[] arg = {"com.sdkd.database.Member"}; new TableCreator().main(arg); }}/**OutputTable Creation SQL for com.sdkd.database.Member is :CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30));Table Creation SQL for com.sdkd.database.Member is :CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50));Table Creation SQL for com.sdkd.database.Member is :CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT);Table Creation SQL for com.sdkd.database.Member is :CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30)PRIMARY KEY); */
- Thinking in Java ——第二十章-注解
- Thinking in java - 注解(1)
- Thinking in Java 第20章 注解(元数据)
- 《Thinking in Java》学习笔记——第七章:多形性
- 第1章 对象入门——Thinking-in-Java
- 第2章 一切都是对象——Thinking-in-Java
- Thinking in Java——第二章-一切都是对象
- Thinking in Java——第三章-操作符
- Thinking in Java——第四章-控制执行流程
- Thinking in Java——第六章-访问控制权限
- Thinking in Java——第七章-复用类
- Thinking in Java——第八章-多态
- Thinking in Java——第十章-内部类
- Thinking in Java——第十一章-持有对象
- Thinking in Java——第十三章-字符串
- Thinking in Java——第十五章-泛型
- Thinking in Java——第十六章-数组
- Thinking in Java——第十九章-枚举类型
- ./a.sh: 行 1: !/bin/sh: 没有那个文件或目录
- windows cmd 定义和使用变量
- Java从数据库取出BLOB数据放入map中,从map中拿出转换成String类型数据
- Redis主从复制读写分离搭建
- TEW-654TR路由器漏洞分析和挖掘
- Thinking in Java ——第二十章-注解
- ASP.NET ZERO 学习 —— (3) 开发手册之介绍和MVC 应用前端
- 【maven】setting.xml配置国内镜像地址
- [题解]bzoj2152 聪聪可可
- 海思adc寄存器的配置
- LeetCode-520. Detect Capital
- 【LeetCode】492. Construct the Rectangle【E】【73】
- Linux常用命令大全
- idea 设置maven pom.xml的自动提示