初识Java注解
所谓的元数据是指用来描述数据的数据,可能刚听到元数据的时候你会有点陌生,其实任何一个使用过struts或者hibernate的开发人员都在不知不觉中使用元数据,更通俗一点来说元数据是指描述代码间关系或者代码与其它资源(例如数据库表)之间内在联系的数据,对Struts来说就是struts-config.xml文件,对hibernate来说就是.hbm文件。 但是现有的以xml或其它方式存在的元数据文件都有一些不便之处(如:与被描述的文件分离,不利于一致性维护)。
基于元数据的广泛应用,JDK1.5引入了注解(Annotation)的概念来描述元数据,为我们提供了一种在代码中添加信息的方法,使我们可以在运行时或某个时刻方便地使用这些数据(通过解析注解来使用这些数据)
注解的作用
A、编写文档:通过代码里标识的元数据生成文档,常用的有@param、@return等;。
B、代码分析:通过代码里标识的元数据对代码进行分析。
(1)替换.properties和xml配置文件:注解可以作为软件部署的一部分配置信息,提供了一种简单和易于理解的方式。然而,当配置信息需要在运行时动态改变时,注解就不适合了。比较常见的是从spring 2.5开始的基于注解的配置,其作用就是减少配置文件;现在的框架基本都使用了这种配置来减少配置文件的数量。
(2)支持横切关注点:注解能够很好的处理依赖注入、服务发现管理对象、验证和许多其他类似的事情。如果需要用到面向方面编程,而你不想另外使用一种面向方面的语言(如AspectJ),这时注解是一个可行的选择。
C、编译检查:通过代码里标识的元数据让编译器实现基本的编译检查。
Java内置的注解集
注解可以用于类、方法、变量、参数和包等程序元素,Java定义了一个内置的注解集:
(1)用于Java代码的注解:
@Override:校验方法是重写方法,如果方法在父类中未找到会产生一个编译警告。
@Deprecated:标记方法已经废弃不用了,如果还在使用此方法会产生一个编译警告。
@SuppressWarnings:告知编译器抑制由注解参数指定的编译时期警告。
(2)用于其它的注解:以下说明的元注解有一个共同的特点就是它们都只能用在Annotation的声明上
@Retention:用来声明注解的保留策略,即生命范围,有CLASS、RUNTIME和SOURCE这三种,分别表示注解保存在类文件、JVM运行时和源代码中。只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息;如果注解声明中不存在Retention注解,则保留策略默认为RetentionPolicy.CLASS。
@Target:指示注解所适用的程序元素的种类,即注解作用范围(如方法、字段等),如果注解声明中不存在Target,则声明的注解可以用在任一程序元素上。
@Documented:指示某一类型的注解将通过javadoc和类似的默认工具进行文档化。
@Inherited:只能用于Class级别的Annotation,用来说明被标记的Annotation会被该类的所有子类自动继承
自定义注解
Java允许自定义注解,通过在类名前使用@interface来定义。包java.lang.annotation中包含所有定义自定义注解所需的元注解和接口,如接口java.lang.annotation.Annotation是所有注解继承的接口,且是自动继承,不需要定义时指定,类似于所有类都自动继承Object。
示例:
package com.test; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { public String name(); public int value() default 0; }
解说:上例中的每一个方法实际上是声明了一个配置参数,参数的名称就是方法的名称,参数的类型就是方法的返回值类型(返回值类型只能是基本类型、Class、String、enum),可以通过default来声明参数的默认值。
细说Java注解
初步了解Java注解后,此处细说一下一些参数
(1)@Target表示注解的作用范围,其可选的参数值在枚举类ElemenetType中,包括:
ElemenetType.CONSTRUCTOR---------------------------构造器声明
ElemenetType.FIELD --------------------------------------字段声明(包括 enum 实例)
ElemenetType.LOCAL_VARIABLE--------------------------局部变量声明
ElemenetType.METHOD ----------------------------------方法声明
ElemenetType.PACKAGE --------------------------------- 包声明
ElemenetType.PARAMETER ------------------------------参数声明
ElemenetType.TYPE--------------------------------------- 类、接口(包括注解类型)或enum声明
(2)@Retention表示在什么级别保存注解信息,其可选的参数值在枚举类型RetentionPolicy中,包括:
RetentionPolicy.SOURCE ---------------------------------注解将被编译器丢弃
RetentionPolicy.CLASS -----------------------------------注解在class文件中可用,但会被VM丢弃
RetentionPolicy.RUNTIME -------------------------------VM将在运行期保留注解,因此可以通过反射机制读取注解的信息。
注解处理器
在程序中添加的注解,可以在编译时或是运行时通过注解处理器来进行处理,注解处理器的本质是通过Java反射来处理。
应用:自定义一个注解,使用Java反射来解析注解
网上看到一个比较酷的案例,现展示如下:
注解的定义:
package com.annotation.test; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 联系方式校验 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented public @interface ContactValidator { public ContactType type(); }
联系方式枚举类:
package com.annotation.test; /** * 联系方式类型 */ public enum ContactType { EMAIL,PHONE,MOBILE,WEBSITE }
用户User:
package com.annotation.test; public class User { /** * 姓名 */ private String name; /** * 邮箱 */@ContactValidator(type = ContactType.EMAIL) private String email; /** * 电话 */@ContactValidator(type = ContactType.PHONE) private String phone; /** * 手机号 */@ContactValidator(type = ContactType.MOBILE) private String mobile; /** * 网址 */@ContactValidator(type = ContactType.WEBSITE) private String website; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getWebsite() { return website; } public void setWebsite(String website) { this.website = website; } }
校验工具类:
package com.annotation.test; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ValidatorUtil { /** * 校验邮箱 * * @param email * @return */ public static boolean isValidEmail(String email) { Pattern p =Pattern.compile("[email protected]+\\.[a-z]+"); Matcher m =p.matcher(email); return m.matches(); } /** * 校验电话 * * @param phone * @return */ public static boolean isValidPhone(String phone) { Pattern p =Pattern.compile("\\d\\d([,\\s])?\\d\\d\\d\\d([,\\s])?\\d\\d\\d\\d"); Matcher m =p.matcher(phone); return m.matches(); } /** * 校验手机号 * * @param mobile * @return */ public static boolean isValidMobile(String mobile) { Pattern p =Pattern.compile("^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$"); Matcher m =p.matcher(mobile); return m.matches(); } /** * 校验网址 * * @param website * @return */ public static boolean isValidWebsite(String website){ Pattern p =Pattern.compile("^(https?|ftp|file)://.+$"); Matcher m =p.matcher(website); return m.matches(); } }
解析器:
package com.annotation.test; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class FieldAnnotationParser { /** * 解析器 * @param user * @throws IllegalArgumentException * @throws IllegalAccessException */ public static void parser(User user)throws IllegalArgumentException, IllegalAccessException { //获取所有字段 Field[]fields = user.getClass().getDeclaredFields(); for(Field field : fields){ Annotation[] annotations = field.getAnnotations();//获取字段上的所有注解 for(Annotation annotation : annotations){ //如果是ContactValidator注解 if(annotation instanceof ContactValidator){ ContactValidator contactValidator = (ContactValidator) annotation; if(field.getModifiers() == Modifier.PRIVATE){//如果是私有字段,设置反射的对象在使用时取消Java语言访问检查 field.setAccessible(true); } boolean result =false;//标识变量 //获取字段值 String fieldValue = (String) field.get(user); switch (contactValidator.type()) { case EMAIL: result =ValidatorUtil.isValidEmail(fieldValue); break; case PHONE: result =ValidatorUtil.isValidPhone(fieldValue); break; case MOBILE: result =ValidatorUtil.isValidMobile(fieldValue); break; case WEBSITE: result =ValidatorUtil.isValidWebsite(fieldValue); break; } if(!result){ System.out.println("Invalid " + field.getName() + ": " +fieldValue); } } } } } }
测试类:
package com.annotation.test; public class AnnotationTest { /** *主函数 * @param args * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void main(String[] args)throws IllegalArgumentException,IllegalAccessException { User user =new User(); user.setName("TimSturt"); user.setPhone("0931234 3819"); //错误的电话格式 user.setMobile("13575309630"); //正确的手机格式 user.setEmail("[email protected]"); //正确邮箱格式 user.setWebsite("fttp://test.com"); //错误的网站url FieldAnnotationParser.parser(user); } }
输出如下:
Invalid phone: 0931234 3819 Invalid website: fttp://test.com
解说:校验工具类中的正则表达式不适用于所有情况,可自行调整
结语:
在Java中元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或在运行时知道被运行代码的描述信息。
针对前述内容进行小结:
(1)Annotation定义语法为:
modifiers @interface AnnotationName { element declaration1 element declaration2 . . . }
modifiers指:public,protected,private或者默认值(什么也没有)。
(2)一个元素的声明(element declaration)指:
type elementName();
或者
type elementName() default value;
(3)可以通过以下的方式来使用Annotation:
@AnnotationName(elementName1=value1, elementName2=value2, . . .)元素声明的顺序没有关系,有默认值的元素的声明可以不列在初始化表中,此时他们使用默认值。
例如,根据上述定义如下的三个Annotation的声明是等价的:
@BugReport(assignedTo="Harry", severity=0)
@BugReport(severity=0, assignedTo="Harry")
@BugReport(assignedTo="Harry")
第一、元数据以标签的形式存在于Java代码中。
第二、元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。
第三、元数据需要编译器之外的工具进行额外的处理用来生成其它的程序部件。
第四、元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。