小tips:本文主要以创建自定义的注解为线索。看懂本文需要你懂一些Java的基本知识,如果你还不会Java。可以去我的知乎文章中查看: java从入门到入土
Java注解 JDK的元注解 @Retention 该注解用于修饰注解定义,指定呗修饰的注解可以保留多长事件。value的值只能是以下三个
RententionPolicy.CLASS:编译器将注解记录在class文件中。当运行Java程序时,JVM不可获取注解信息。这是默认值。
RententionPolicy.RUNTIME:编译器将注解记录在class文件中。当运行Java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息。
RententionPolicy.SOURCE:注解只保留在源代码中,编译器直接丢弃这种注解
1 2 3 @Retention(value = RententionPolicy.RUNTIME) public @interface Testable()
使用该注解时包含一个value成员变量,如果()中只有value,那么可以省略value =
1 2 3 @Retention(RententionPolicy.SOURCE) public @interface Testable()
@Target @Target也只能修饰注解定义,它用于指定被修饰的注解能用于修饰哪些程序单元。该注解也包含一个名value的成员变量,该成员变量的值也只能是如下几个。
ElementType.ANNOTATION_TYPE:指定该策略的注解只能修饰注解
ElementType.CONSTRUCTOR:修饰构造器
ElementType.FIELD:修饰成员变量
ElementType.LOCAL_ VARIABLE:修饰局部变量
ElementType.METHOD:修饰方法定义
ElementType.PACKAGE:修饰包定义
ElementType. PARAMETER:修饰参数
ElementType. TYPE:指定该策略的注解可以修饰类、接口或枚举定义
1 2 @Target(ElementType.FIELD) public @interface ActionListenerFor()
上面的代码用于指定该注解只能修饰成员变量
@Documented 该注解用于指定该元注解修饰的注解类将被javadoc工具提取成文档,如果定义注解类使用了的话,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。
@Inherited 该元注解指定被它修饰的注解将具有继承性——如果某个类使用被这个注解修饰的自定义注解,则子类将自动被同样的注解修饰。
自定义注解 定义注解 定义注解需要使用@interface关键字
1 2 3 4 5 6 7 public @interface MyTest{ String name () ; int age () default 10 ; }
默认情况下,注解可用来修饰任何程序元素,而且你还可以在注解中自定义成员变量,可以给成员变量设置默认值。当你定义了成员变量的时候,所有不是默认的成员变量均需要显式地赋值。
1 2 3 4 5 6 7 8 public class Test { @MyTest(name = "xxx",age =11) public void info () { } }
如果为成员变量指定了值,则默认值不会起作用。
根据注解是否包含成员变量,可以把注解分为以下两类:
标记注解:没有定义成员变量的注解。例如@Override
元注解:包含成员变量的注解。
提取注解中的值 提取注解中的值需要一些Java反射的知识,如果你不清楚,可以看我的知乎:java反射
关于注解的反射方法:
方法名
描述
< A extends Annotation> A getAnnotation( Class< A> annotationClass)
返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null。
< A extends Annotation> A getDeclaredAnnotation( Class< A> annotationClass)
这是 Java 8 新增的方法,该方法尝试获取直接修饰该程序元素、 指定类型的注解。如果该类型的注解不存在,则返回 null。
Annotation[] getAnnotations()
返回 该 程序 元素 上 存在 的 所有 注解。
Annotation[] getDeclaredAnnotations()
返回直接修饰该程序元素的所有注解。
boolean isAnnotationPresent( Class< ? extends Annotation> annotationClass)
判断 该 程序 元素 上 是否 存在 指定 类型 的 注解, 如果 存在 则 返回 true, 否则 返回 false。
< A extends Annotation> A[] getAnnotationsByType( Class< A> annotationClass)
该方法的功能与前面介绍的 getAnnotation() 方法基本相似。但由于 Java 8 增加了重复注解功能,因此需要使用
< A extends Annotation> A[] getDeclaredAnnotationsByType( Class< A>annotationClass)
该方法的功能与前面介绍的 getDeclaredAnnotations() 方法基本相似。 但由于 Java 8 增加了重复注解功能, 因此需要使用该 方法获取直接修饰该程序元素、 指定类型的多个注解。
使用注解的实例,没有成员参数变量 自定义注解Testable,这个注解用于标记哪些方法是可测试的。
1 2 3 4 5 6 7 8 9 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.METHOD) public @interface Testable { }
如下MyTest测试用例定义了8个方法,这些方法没有太大的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MyTest { @Testable public static void m1 () { } public static void m2 () { } @Testable public static void m3 () { throw new IllegalArgumentException("参数出错了" ); } public static void m4 () { } @Testable public static void m5 () { } public static void m6 () { } @Testable public static void m7 () { throw new RuntimeException("程序业务出现异常" ); } public static void m8 () { } }
仅仅使用注解来标记程序元素对程序是不会有任何影响的,这也是Java注解的一条重要原则。
如下是处理注解的程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.lang.reflect.Method;public class ProcessorTest { public static void process (String clazz) throws ClassNotFoundException { int passed = 0 ; int failed = 0 ; for (Method m:Class.forName(clazz).getMethods()) { if (m.isAnnotationPresent(Testable.class)) { try { m.invoke(null ); passed++; }catch (Exception ex) { System.out.println("方法" + m + "运行失败,异常:" +ex.getCause()); failed++; } } } System.out.println("共运行了:" +(passed+failed) +"个方法,其中:\n" +"失败了:" +failed+"个,\n" +"成功了:" +passed+"个!" ); } }
如下是主启动类
1 2 3 4 5 6 7 public class RunTests { public static void main (String[] args) throws ClassNotFoundException { ProcessorTest.process("annotation_test.MyTest" ); } }
运行上面程序就可以看出来,程序中的@Testable起作用了,MyTest类里以@Testable注解修饰的方法都被测试了。
使用注解的实例,有成员参数变量 使用到了一些swing的包,看不懂也没关系,看个大概就行
定义了这个@ ActionListenerFor 之后,使用该注解时需要指定一个 listener 成员变量,该成员变量用于指定监听器的实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.awt.event.ActionListener;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor { Class<? extends ActionListener> listener(); }
如下是主要启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JOptionPane;import javax.swing.JPanel;public class AnnotationTest { private JFrame mainWin = new JFrame("使用注解绑定事件监听器" ); @ActionListenerFor(listener = OkListener.class) private JButton ok = new JButton("确定" ); @ActionListenerFor(listener = CancelListener.class) private JButton cancel = new JButton("取消" ); public void init () { JPanel jp = new JPanel(); jp.add(ok); jp.add(cancel); mainWin.add(jp); ActionListenerInstaller.processAnnotations(this ); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true ); } public static void main (String[] args) { new AnnotationTest().init(); } }class OkListener implements ActionListener { @Override public void actionPerformed (ActionEvent evt) { JOptionPane.showMessageDialog(null , "单击了确认按钮" ); } }class CancelListener implements ActionListener { @Override public void actionPerformed (ActionEvent e) { JOptionPane.showMessageDialog(null , "单击了取消按钮" ); } }
如下是注解处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import java.awt.event.ActionListener;import java.lang.reflect.Field;import javax.swing.AbstractButton;public class ActionListenerInstaller { public static void processAnnotations (Object obj) { try { Class cl = obj.getClass(); for (Field f : cl.getDeclaredFields()) { f.setAccessible(true ); ActionListenerFor a = f.getAnnotation(ActionListenerFor.class); Object fObj = f.get(obj); if (a != null && fObj != null && fObj instanceof AbstractButton) { Class<? extends ActionListener> listenerClazz = a.listener(); ActionListener al = listenerClazz.getDeclaredConstructor().newInstance(); AbstractButton ab = (AbstractButton)fObj; ab.addActionListener(al); } } }catch (Exception e) { e.printStackTrace(); } } }
运行AnnotationTest程序,会看到窗口。点击确定会弹出“单击确定按钮对话框”,点击取消也是如此。
重复注解 Java8之前,同一个程序元素最多只能使用同一个相同类型的注解;如果需要使用多个相同类型的注解,就必须使用注解容器,例如以下例子。
1 2 3 4 5 @Result({ @Result(name = "failure",location = "failed.jsp"), @Result(name = "success",location = "succ.jsp") }) public Action FooAction{...}
但是在Java8以后,可以通过使用@Repeatable修饰该注解就可以使用简化的写法。
首先需要定义一个自定义的注解,其中@Repeatable中的FkTags.class在后面定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.lang.annotation.ElementType;import java.lang.annotation.Repeatable;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(FkTags.class) public @interface FkTag { String name () default "疯狂软件" ; int age () ; }
定义上面注解的容器注解
1 2 3 4 5 6 7 8 9 10 11 12 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.TYPE) public @interface FkTags { FkTag[] value(); }
测试注解类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @FkTag(age = 5) @FkTag(name = "疯狂Java",age = 9) public class FkTagTest { public static void main (String[] args) { Class<FkTagTest> clazz = FkTagTest.class; FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class); for (FkTag tag:tags) { System.out.println(tag.name()+"-->" +tag.age()); } FkTags container = clazz.getDeclaredAnnotation(FkTags.class); System.out.println(container); } }
实际上重复注解只是一种简化写法,多个重复注解其实会作为容器注解的value成员对象的数组元素。而且容器注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错,这很容易理解。两者应该是同时存在的.
类型注解 Java8开始,类型注解可以修饰在任何出现的类型。比如,允许在如下位置使用类型注解.
创建对象(使用new关键字创建)
类型转换
使用implements实现接口
使用throws声明抛出异常
上面这些都会用到类型,因此都可以使用类型注解来修饰。因为Java8为ElementType枚举增加了TYPE_ PARAMETER、 TYPE_ USE 两个枚举值,所以可以使用元注解@Target来修饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.io.FileNotFoundException;import java.io.Serializable;import java.lang.annotation.ElementType;import java.lang.annotation.Target;import javax.swing.JFrame;@Target(ElementType.TYPE_USE) @interface NotNull{ }public class TypeAnnotationTest implements @NotNull Serializable { public static void main (@NotNull String[] args) throws @NotNull FileNotFoundException { Object obj = "fkjava.rog" ; String str = (@NotNull String)obj; Object win = new @NotNull JFrame("疯狂得一批" ); } }
上面的程序中,导出写满了类型注解,不过需要注意得是,上面程序虽然大量使用了@NotNull注解,但是这些注解并不会起任何做哟个,因为并没有对类型注解本身执行检查得框架,如果需要这些注解发挥作用,则需要开发者自己实现类型注解检查框架。
编译时处理注解 APT是一种注解处理工具,它对源代码文件进行检测,并找出源文件所包含得注解信息,然后针对注解信息进行额外的处理。
Java提供的javac.exe工具有-processor选项,该选项可指定一个注解处理器,如果在编译Java源文件时通过该选项指定了注解处理器,那么这个注解处理器将会在编译时提取并处理Java源文件中的注解。
每个处理器都需要实现javax. annotation. processing 包下的 Processor 接口。不过实现该接口必须实现它里面所有的方法,因此通常会采用 继承 AbstractProcessor 的方式来实现注解处理器。一个注解处理器可以处理一种或者多种注解类型。
下面程序实现了,当我们运行一个类时候,生成相应的xml文件。下面的代码中可能会需要使用到JavaIO的知识,假如你不清楚,可以先看看我之前的文章: JavaIO
1 2 3 4 5 6 7 8 9 10 11 12 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;@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) @Documented public @interface Persistent { String table () ; }
这自定义注解指定在源代码中保留,运行时不能通过反射来读取该注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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;@Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) @Documented public @interface Id { String column () ; String type () ; String generator () ; }
上面的注解只是多了两个成员变量而已,并且限定了是修饰普通成员属性
1 2 3 4 5 6 7 8 9 10 11 12 13 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;@Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) @Documented public @interface Property { String column () ; String type () ; }
和上面的注解相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Persistent(table = "person_inf") public class Person { @Id(column = "person_id",type = "integer",generator = "identity") private int id; @Property(column = "perseon",type = "string") private String name; @Property(column = "person_age",type = "integer") private int age; public Person () { } public Person (int id,String name,int age) { this .id=id; this .name=name; this .age=age; } public int getId () { return id; } public void setId (int id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } }
上面的Person是一个普通的Java类,但是因为有我们的注解,所以它变得不简单了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 import java.io.FileOutputStream;import java.io.PrintStream;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.RoundEnvironment;import javax.annotation.processing.SupportedAnnotationTypes;import javax.annotation.processing.SupportedSourceVersion;import javax.lang.model.SourceVersion;import javax.lang.model.element.Element;import javax.lang.model.element.ElementKind;import javax.lang.model.element.Name;import javax.lang.model.element.TypeElement;@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes({"Persistent","Id","Property"}) public class HibernateAnnotationProcessor extends AbstractProcessor { public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { PrintStream ps =null ; try { for (Element t:roundEnv.getElementsAnnotatedWith(Persistent.class)) { Name clazzName = t.getSimpleName(); Persistent per = t.getAnnotation(Persistent.class); ps = new PrintStream(new FileOutputStream(clazzName+".hbm.xml" )); ps.println("<?xml version=\"1.0\"?>" ); ps.println("<!DOCTYPE hibernate-mapping PUBLIC>" ); ps.println(" \"-//Hibernate/Hibernate" +"Mapping DTD 3.0//EN\"" ); ps.println(" \"http://www.hibernate.org/dtd/" +"hibernate-mapping-3.0.dtd\">" ); ps.println("<hibernate-mapping>" ); ps.println(" <class name=\"" +t); ps.println("\"table=\"" +per.table()+"\">" ); for (Element f:t.getEnclosedElements()) { if (f.getKind()==ElementKind.FIELD) { Id id = f.getAnnotation(Id.class); if (id !=null ) { ps.println(" <id name=\"" +f.getSimpleName() +"\"column=\"" +id.column() +"\"type=\"" +id.type() +"\">" ); ps.println(" <generator class=\"" +id.generator()+"\"/>" ); ps.println(" </id>" ); } Property p = f.getAnnotation(Property.class); if (p !=null ) { ps.println(" <property name=\"" +f.getSimpleName() +"\"column=\"" +p.column() +"\"type=\"" +p.type() +"\">" ); } } } ps.println("</class>" ); ps.println("</hibernate-mapping>" ); } } catch (Exception e) { e.printStackTrace(); }finally { if (ps!=null ) { try { ps.close(); } catch (Exception e2) { e2.printStackTrace(); } } } return true ; } }
上面的程序是本次功能的核心,这是我们的注解解析类,这个注解处理器使用 RoundEnvironment 来获取注解信息, RoundEnvironment 里包含了一个 getElementsAnnotatedWith() 方法, 可根据注解获取需要处理的程序单元, 这个程序单元由 Element 代表。Element里包含 一个getKind() 方法, 该方法返回 Element 所代表的程序单元,返回值可以是 ElementKind. CLASS( 类)、 ElementKind. FIELD( 成员 变量)……
除此之外, Element 还包含一个 getEnclosedElements() 方法,该方法可用 于获取该 Element 里定义的所有程序单元,包括成员变量、方法、构造器、 内部类等。接下来程序只处理成员变量前面的注解,因此程序先判断这个Element 必须是 ElementKind. FIELD。再接下来程序调用了 Element 提供 的 getAnnotation( Class clazz)方法来获取修饰该 Element 的注解,获取到成员变量上的@ Id、@ Property 注解之后,接下来就根据它们提供的信息执行输出。
接下来就可以使用javac来编译Person.java了
1 2 3 4 # 首先需要编译我们的注解解析类 javac -d . HibernateAnnotationProcessor.java # 然后再解析Person类 javac -processor HibernateAnnotationProcessor Person.java
这样当生成Person的class文件时,还会生成一个Person.hbm.xml文件。