Java注解
来源:互联网 发布:好乐宝软件下载 编辑:程序博客网 时间:2024/05/19 21:43
Java注解
定义
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后某个时刻非常方便的使用这些数据。
产生背景
JavaSE5之前是没有注解的,注解是为了在一定程度上把元数据与源代码结合在一起,而不是保存在外部文档中这一趋势下所产生的。
定义
定义一个注解很简单:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Test {}
可以看出,主要由两部分组成:
- 元注解
- 类似于定义接口的结构
先给出一个定义标记注解(什么都不做)的示例,在对示例进行说明以阐述注解定义过程,下面代码定义了一个Test注解。
package com.rainmonth.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/27. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Test {}
再给出一个一般注解的定义示例:
package com.rainmonth.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/27. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase { int id(); String descriptions() default "no description";}
可以看到,但从代码表现形式上,其定义很像一个空接口的定义(多了一个@),注意,注解也将编译成class文件。定义注解时,会用到一些元注解(meta-annotation),即上面的@Target和@Retention。以下是对元注解的部分说明:
元注解
- @Target,表示该注解将应用于什么地方,可选参数(参考java.lang.annotation.ElementType中的定义)包括:
- TYPE,类、接口(包括注解类型)或enum等的声明;
- FIELD,域声明(包括enum实例);
- METHOD,方法声明;
- PARAMETER,参数声明;
- CONSTRUCTOR,构造函数声明;
- LOCAL_VARIABLE,局部变量声明;
- ANNOTATION_TYPE,注解类型声明;
- PACKAGE,包声明;
- TYPE_PARAMETER,类型参数声明(Java 1.8新增);
- TYPE_USE,类型使用(Java 1.8新增);
- @Retention,表示需要在什么级别保存该注解信息,可选参数(参考java.lang.annotation.RetentionPolicy中的定义)包括:
- SOURCE,注解将被编译器丢弃;
- CLASS,注解将在编译的class文件中保存,但会被VM丢弃;
- RUNTIME,注解将在编译的class文件中保存,在VM运行期也会被保留,因而可以通过反射来读取此类注解信息;
- @Documented,将此注解包含在Javadoc中;
- @Inherited,表示允许子类继承父类的注解;
- @Repeatable;
- @Native;
标准注解
- @Deprecated,将被他标记的元素标记为过时的,当程序使用该元素的时候,编译器会发出警告;
- @FunctionalInterface
- @Override,表示当前方法定义将覆盖父类中方法的定义;
- @SafeVarargs
- @SuppressWarnings,关闭不当的编译器警告信息;
注解元素
注解元素可用的类型如下
- 所有的基本类型(int,float, boolean等)
- String
- Class
- enum
- Annotation(说明注解可以嵌套)
- 以上类型的数组
默认值限制
- 元素不能有不确定的值,要么具有默认值,要么在使用注解时提供元素值;
- 对于非基本类型元素,无论是在源代码中声明或是在注解接口中定义默认值,其值都不能为null(妥协的方法就是利用一些特殊值来表示某些元素的却是状态);
使用
下面的代码演示如何使用上面定义的Test注解,@Test注解并不作什么操作(标记注解),但编译器必须能在对应路径找到它的定义。
下面演示标记注解的使用:
package com.rainmonth.annotation;/** * Created by RandyZhang on 2017/9/27. */public class UseTestAnnotation { public void execute() { } @TestAnnotation.Test void testExecute() { execute(); }}
接下来演示一般注解的使用:
package com.rainmonth.annotation;import java.util.List;/** * Created by RandyZhang on 2017/9/27. */public class PasswordUtils { @UseCase(id = 47, descriptions = "Password must contain at least on numeric") public boolean validatePassword(String password) { return (password.matches("\\w*\\d\\w*")); } @UseCase(id = 48) public String encryptPassword(String password) { return new StringBuffer(password).reverse().toString(); } @UseCase(id = 49, descriptions = "new passwords can't equal previously used ones") public boolean checkForNewPassword(List<String> prePasswords, String password) { return !prePasswords.contains(password); }}
编写注解处理器
注解处理器,就是用来读取注解的工具,可以通过JAVA SE5中扩展的反射机制API和其提供的外部工具APT来编写注解处理器。
使用反射
下面利用Java的反射来编写一个简单的注解处理器,我们会读取PasswordUtils,找到其中的@UseCase标记,然后判断提供的一组id值中,哪些址已经找到了对应的用例,哪些值对应的用例缺失:
package com.rainmonth.annotation;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Collections;import java.util.List;/** * Created by RandyZhang on 2017/9/28. */public class UserCaseTracker { 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.descriptions()); useCases.remove(new Integer(uc.id())); } } for(int i : useCases) { System.out.println("Warning: Missing use case, case id=" + i); } } public static void main(String[] args) { List<Integer> useCases = new ArrayList<>(); Collections.addAll(useCases, 47, 48, 49, 50); trackUseCases(useCases, PasswordUtils.class); }}
该示例先使用getDeclaredMethods()来遍历PasswordUtils中的方法,然后利用getAnnotation()方法来获取使用了UseCase注解的方法,再做相应输出处理,很简单。
利用注解生成外部文件
这里以通过为JavaBean对象添加注解来自动生成创建存储该JavaBean对象的数据表脚本(sql语句)为例,来描述如何利用注解生成外部文件的。
首先是注解的定义:
DBTable.java
package com.rainmonth.annotation.database;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/28. */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface DBTable { String name() default "";}
JavaBean域相关注解
SQInteger.java
package com.rainmonth.annotation.database;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/28. */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLInteger { String name() default ""; // 注解嵌套 Constraints constraints() default @Constraints;}
SQLString.java
package com.rainmonth.annotation.database;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/28. */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLString { // String的长度 int value() default 0; String name() default ""; // 注解嵌套 Constraints constraints() default @Constraints;}
Constraints.java
package com.rainmonth.annotation.database;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by RandyZhang on 2017/9/28. */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Constraints { boolean primaryKey() default false; boolean allowNull() default true; boolean unique() default false;}
定义一个JavaBean,并使用上面定义的注解对它的域进行注解
package com.rainmonth.annotation.database;/** * Created by RandyZhang on 2017/9/28. */@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 String toString() { return handle; }}
注意,注解中的元素一般是采用name-value的形式来赋值的,但由于SQLString的元素中,有一个value元素,所以就可以只写值而不写name(必须定义value这个元素才能这样写。
实现注解处理器,即利用上面定义好的注解和JavaBean,来生成创建数据表的sql命令。代码如下:
TableCreator.java
package com.rainmonth.annotation.database;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.List;/** * 创建表的命令 * Created by RandyZhang on 2017/9/28. */public class TableCreator { public static void main(String[] args) { if (args.length < 1) { System.out.println("arguments: annotated classes"); System.exit(0); } for (String className : args) { try { Class<?> cl = Class.forName(className); DBTable dbTable = cl.getAnnotation(DBTable.class); if (dbTable == null) { System.out.println("No DBTable annotation in class " + className); continue; } String tableName = dbTable.name(); if (tableName.length() < 0) { tableName = cl.getName().toUpperCase(); } List<String> columnDefList = new ArrayList<>(); for (Field field : cl.getDeclaredFields()) { String columnName; Annotation[] ann = field.getDeclaredAnnotations(); if (ann.length < 1) { continue; } if (ann[0] instanceof SQLInteger) { SQLInteger sInt = (SQLInteger) ann[0]; if (sInt.name().length() < 1) { columnName = field.getName().toUpperCase(); } else { columnName = sInt.name(); } columnDefList.add(columnName + " INT" + getConstraints(sInt.constraints())); } if (ann[0] instanceof SQLString) { SQLString sString = (SQLString) ann[0]; if (sString.name().length() < 1) { columnName = field.getName().toUpperCase(); } else { columnName = sString.name(); } columnDefList.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints())); } } StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "("); for (String columnDef : columnDefList) { createCommand.append("\n ").append(columnDef).append(","); } String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");"; System.out.println("Table Creation SQL for " + className + " is:\n" + tableCreate); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } private static String getConstraints(Constraints con) { String constrains = ""; if (!con.allowNull()) { constrains += " NOT NULL"; } if (con.primaryKey()) { constrains += " PRIMARY KEY"; } if (con.unique()) { constrains += " UNIQUE"; } return constrains; }}
上述main函数在运行时需要传递一个参数,即Member的具体路径(在Idea这个IDE具体操作为找到要运行的Main函数,在Configuration选项卡下面的Program arguments中添加如下参数:
com.rainmonth.annotation.database.Member
然后运行即可。会看到如下输出:
Table Creation SQL for com.rainmonth.annotation.database.Member is:CREATE TABLE MEMBER( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) PRIMARY KEY);Process finished with exit code 0
使用apt处理注解
由于Java 8 已经把apt即相关的Api移除了,这里只做大致的描述。使用apt的一般步骤:
定义要被处理的注解;
定义一个Processor(继承自AnnotationProcessor),处理注解的核心工作都在这;
指明一个工厂类(继承自AnnotationProcessorFactory),该工厂类用来为apt工具指定一个正确的处理器(即2中定义的Processor的实例)
执行apt命令开始处理注解
apt -factory [工厂类][含有要处理注解的java文件]
总结
首先注解的引入将我们的源代码和文档紧密的连接起来,可在编译期对代码进行检查,并使代码变的简洁干净易读,这就是为什么在开源库中注解使用越来越广泛的原因了。
- Java注解-自定义注解
- Java注解----自定义注解
- Java注解自定义注解
- Java注解--四种元注解
- Java注解-三种內建注解
- 【Java】【注解】自定义注解
- java注解
- java注解
- java注解
- java 注解
- Java注解
- java 注解
- Java 注解
- Java注解
- java 注解
- JAVA注解
- Java注解
- Java注解
- 5. Longest Palindromic Substring
- BZOJ 2210: Pku1379 Run Away 模拟退火
- 杭电1001 Sum Problem
- 算法笔记:使用栈实现汉诺塔(Hanoi)经典算法
- 垃圾收集与分配策略——(三)HotSpot的算法实现
- Java注解
- QWT的配置和使用(1)
- java递归浅析合并排序
- 杭电1002 A + B Problem II
- 机器学习之旅:数据预处理的对象-数据
- C++常成员函数和常对象、对象指针和对象引用
- C++11基础-----std::function & std::bind
- 内存中的堆和栈
- java语言基础(98)——定时器和定时任务