转载

Java注解实践

注解对代码的语意没有直接影响, 他们只负责 提供信息给相关的程序使用注解永远不会改变被注解代码的含义 , 但可以通过工具对被注解的代码进行特殊处理 .

JDK 基本Annotation

注解

说明

@Override

重写

@Deprecated

已过时

@SuppressWarnings(value = "unchecked")

压制编辑器警告

@SafeVarargs

修饰”堆污染”警告

@FunctionalInterface

Java8特有的函数式接口

  • value 特权
    如果使用注解时只需要为 value 成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用 name=value 的形式. 如 @SuppressWarnings("unchecked") (SuppressWarnings的各种参数
    请参考 解析 @SuppressWarnings的各种参数 )
  • 请坚持使用 @Override 注解: 如果在每个方法中使用 Override 注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.

JDK 元Annotation

Annotation 用于修饰其他的Annotation定义.

元注解

释义

@Retention

注解保留策略

@Target

注解修饰目标

@Documented

注解文档提取

@Inherited

注解继承声明

  • @Retention  注解的保留策略
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention {     RetentionPolicy value(); }

value为 SOURCECLASSRUNTIME 三值之一:

public enum RetentionPolicy {     /**      * Annotations are to be discarded by the compiler.      */     SOURCE,       /**      * Annotations are to be recorded in the class file by the compiler      * but need not be retained by the VM at run time.  This is the default      * behavior.      */     CLASS,       /**      * Annotations are to be recorded in the class file by the compiler and      * retained by the VM at run time, so they may be read reflectively.      *      * @see java.lang.reflect.AnnotatedElement      */     RUNTIME }
  • @Target  指定Annotation可以放置的位置(被修饰的目标)
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target {     ElementType[] value(); }   public enum ElementType {     /** Class, interface (including annotation type), or enum declaration */     TYPE,       /** Field declaration (includes enum constants) */     FIELD,       /** Method declaration */     METHOD,       /** Parameter declaration */     PARAMETER,       /** Constructor declaration */     CONSTRUCTOR,       /** Local variable declaration */     LOCAL_VARIABLE,       /** Annotation type declaration */     ANNOTATION_TYPE,       /** Package declaration */     PACKAGE }
  • @Documented  指定被修饰的该Annotation可以被 javadoc 工具 提取成文档.
  • @Inherited  指定被修饰的Annotation将具有继承性
    如果某个类使用 @Xxx 注解(该 Annotation 使用了 @Inherited 修饰)修饰, 则其子类自动被 @Xxx 注解修饰.

Annotation

/**  * Created by jifang on 15/12/22.  */ @Inherited @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Testable { }    Client  public class Client {       @Test     public void client(){         new SubClass();     } }   @Testable class SupperClass{ }   class SubClass extends SupperClass{     public SubClass() {         for (Annotation annotation : SubClass.class.getAnnotations()){             System.out.println(annotation);         }     } }

自定义注解

  • 根据 Annotation 是否包含成员变量,可以把Annotation分为两类:
    • 标记 Annotation : 没有成员变量的Annotation; 这种Annotation仅利用 自身的存在与否来提供信息 ;
    • 元数据 Annotation : 包含成员变量的Annotation; 它们可以接受(和提供)更多的元数据;
  • 定义新注解使用 @interface 关键字, 其定义过程与定义接口非常类似(见上面的 @Testable ), 需要注意的是:Annotation的成员变量在Annotation定义中是以 无参的方法 形式来声明的, 其 方法名返回值类型 定义了该成员变量的 名字类型 , 而且我们还可以使用 default 关键字为这个成员变量设定默认值.
@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Tag {     String name() default "该叫啥才好呢?";       String description() default "这家伙很懒, 啥也没留下..."; }
  • 自定义的Annotation继承了 Annotation 这个接口, 因此自定义注解中包含了 Annotation 接口中所有的方法;
public interface Annotation {     /**      * @return true if the specified object represents an annotation      *     that is logically equivalent to this one, otherwise false      */     boolean equals(Object obj);       /**      * @return the hash code of this annotation      */     int hashCode();       /**      * @return a string representation of this annotation      */     String toString();       /**      * Returns the annotation type of this annotation.      */     Class<? extends Annotation> annotationType(); }

提取Annotation信息

  • 使用 Annotation 修饰了 / 方法 / 成员变量 等之后,这些Annotation不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理Annotation信息(当然,只有当定义Annotation时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析).
  • Java使用 Annotation 接口来代表程序元素前面的注解, 用 AnnotatedElement 接口代表程序中可以接受注解的程序元素.像 Class   Constructor   FieldMethod   Package 这些类都实现了 AnnotatedElement 接口.
public final     class Class<T> implements java.io.Serializable,                               java.lang.reflect.GenericDeclaration,                               java.lang.reflect.Type,                               java.lang.reflect.AnnotatedElement { ... }   public interface AnnotatedElement {     /**      * Returns true if an annotation for the specified type      * is present on this element, else false.  This method      * is designed primarily for convenient access to marker annotations.      */      boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);      /**      * Returns this element's annotation for the specified type if      * such an annotation is present, else null.      */     <T extends Annotation> T getAnnotation(Class<T> annotationClass);       /**      * Returns all annotations present on this element.       */     Annotation[] getAnnotations();       /**      * Returns all annotations that are directly present on this      * element.  Unlike the other methods in this interface, this method      * ignores inherited annotations.  (Returns an array of length zero if      * no annotations are directly present on this element.)  The caller of      * this method is free to modify the returned array; it will have no      * effect on the arrays returned to other callers.      */     Annotation[] getDeclaredAnnotations(); }

这样, 我们只需要获取到 Class Method Filed 等这些实现了 AnnotatedElement 接口的类实例, 就可以获取到我们想要的注解信息了.

/**  * Created by jifang on 15/12/22.  */ public class Client {       @Test     public void client() throws NoSuchMethodException {         Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();         for (Annotation annotation : annotations) {             System.out.println(annotation.annotationType().getName());         }     } }

如果需要获取某个注解中的元数据,则需要 强转 成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据:

@Tag(name = "client") public class Client {       @Test     public void client() throws NoSuchMethodException {         Annotation[] annotations = this.getClass().getAnnotations();         for (Annotation annotation : annotations) {             if (annotation instanceof Tag) {                 Tag tag = (Tag) annotation;                 System.out.println("name: " + tag.name());                 System.out.println("description: " + tag.description());             }         }     } }

模拟Junit框架

我们用 @Testable 标记哪些方法是可测试的, 只有被 @Testable 修饰的方法才可以被执行.

/**  * Created by jifang on 15/12/27.  */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Testable { }

如下定义 TestCase 测试用例定义了6个方法, 其中有4个被 @Testable 修饰了:

public class TestCase {       @Testable     public void test1() {         System.out.println("test1");     }       public void test2() throws IOException {         System.out.println("test2");         throw new IOException("我test2出错啦...");     }       @Testable     public void test3() {         System.out.println("test3");         throw new RuntimeException("我test3出错啦...");     }       public void test4() {         System.out.println("test4");     }       @Testable     public void test5() {         System.out.println("test5");     }       @Testable     public void test6() {         System.out.println("test6");     } }

为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.

/**  * Created by jifang on 15/12/27.  */ public class TestableProcessor {       public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {         int passed = 0;         int failed = 0;         Object obj = Class.forName(clazz).newInstance();         for (Method method : Class.forName(clazz).getMethods()) {             if (method.isAnnotationPresent(Testable.class)) {                 try {                     method.invoke(obj);                     ++passed;                 } catch (IllegalAccessException | InvocationTargetException e) {                     System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");                     e.printStackTrace(System.out);                     ++failed;                 }             }         }           System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");     }       public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {         TestableProcessor.process("com.feiqing.annotation.TestCase");     } }

抛出特定异常

前面介绍的只是一个 标记 Annotation ,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在 抛出特殊异常时才成功 添加支持,这样就用到了 具有成员变量的注解 了:

/**  * Created by jifang on 15/12/28.  */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestableException {     Class<? extends Throwable>[] value(); }
  • TestCase
/**  * Created by jifang on 15/12/27.  */ public class TestCase {       public void test1() {         System.out.println("test1");     }       @TestableException(ArithmeticException.class)     public void test2() throws IOException {         int i = 1 / 0;         System.out.println(i);     }       @TestableException(ArithmeticException.class)     public void test3() {         System.out.println("test3");         throw new RuntimeException("我test3出错啦...");     }       public void test4() {         System.out.println("test4");     }       @TestableException({ArithmeticException.class, IOException.class})     public void test5() throws FileNotFoundException {         FileInputStream stream = new FileInputStream("xxxx");     }       @Testable     public void test6() {         System.out.println("test6");     } }
  • 注解处理器
public class TestableExceptionProcessor {       public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {         int passed = 0;         int failed = 0;         Object obj = Class.forName(clazz).newInstance();         for (Method method : Class.forName(clazz).getMethods()) {             if (method.isAnnotationPresent(TestableException.class)) {                 try {                     method.invoke(obj, null);                     // 没有抛出异常(失败)                     ++failed;                 } catch (InvocationTargetException e) {                     // 获取异常的引发原因                     Throwable cause = e.getCause();                       int oldPassed = passed;                     for (Class excType : method.getAnnotation(TestableException.class).value()) {                         // 是我们期望的异常类型之一(成功)                         if (excType.isInstance(cause)) {                             ++passed;                             break;                         }                     }                     // 并不是我们期望的异常类型(失败)                     if (oldPassed == passed) {                         ++failed;                         System.out.printf("Test <%s> failed <%s> %n", method, e);                     }                 }             }         }         System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");     }       public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {         process("com.feiqing.annotation.TestCase");     } }

注解添加监听器

下面通过使用Annotation简化 事件编程 , 在传统的代码中总是需要通过 addActionListener 方法来为事件源绑定事件监听器:

/**  * Created by jifang on 15/12/27.  */ public class SwingPro {     private JFrame mainWin = new JFrame("使用注解绑定事件监听器");       private JButton ok = new JButton("确定");     private JButton cancel = new JButton("取消");       public void init() {         JPanel jp = new JPanel();           // 为两个按钮设置监听事件         ok.addActionListener(new OkListener());         cancel.addActionListener(new CancelListener());           jp.add(ok);         jp.add(cancel);         mainWin.add(jp);         mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         mainWin.pack();         mainWin.setVisible(true);     }       public static void main(String[] args) {         new SwingPro().init();     } }   class OkListener implements ActionListener {       @Override     public void actionPerformed(ActionEvent e) {         JOptionPane.showMessageDialog(null, "你点击了确认按钮!");     } }   class CancelListener implements ActionListener {       @Override     public void actionPerformed(ActionEvent e) {         JOptionPane.showMessageDialog(null, "你点击了取消按钮!");     } }

下面我们该用注解绑定监听器:

  • 首先, 我们需要自定义一个注解
/**  * Created by jifang on 15/12/27.  */ @Inherited @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor {     Class<? extends ActionListener> listener(); }
  • 然后还要一个注解处理器
/**  * Created by jifang on 15/12/27.  */ public class ActionListenerInstaller {       public static void install(Object targetObject) throws IllegalAccessException, InstantiationException {         for (Field field : targetObject.getClass().getDeclaredFields()) {             // 如果该成员变量被ActionListenerFor标记了             if (field.isAnnotationPresent(ActionListenerFor.class)) {                 // 设置访问权限                 field.setAccessible(true);                   // 获取到成员变量的值                 AbstractButton targetButton = (AbstractButton) field.get(targetObject);                   // 获取到注解中的Listener                 Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener();                   // 添加到成员变量中                 targetButton.addActionListener(listener.newInstance());             }         }     } }
  • 主程序(注意注释处)
public class SwingPro {       private JFrame mainWin = new JFrame("使用注解绑定事件监听器");       /**      * 使用注解设置Listener      */     @ActionListenerFor(listener = OkListener.class)     private JButton ok = new JButton("确定");       @ActionListenerFor(listener = CancelListener.class)     private JButton cancel = new JButton("取消");       public SwingPro init() {         JPanel jp = new JPanel();           // 使得注解生效         try {             ActionListenerInstaller.install(this);         } catch (IllegalAccessException | InstantiationException e) {             e.printStackTrace(System.out);         }           jp.add(ok);         jp.add(cancel);         mainWin.add(jp);         mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         mainWin.pack();         mainWin.setVisible(true);           return this;     }       //下同 }

Java注解实践

重复注解

在Java5到Java7这段时间里, 同一个程序元素前只能使用一个 相同类型的 Annotation ; 如果需要在同一个元素前使用多个相同的Annotation, 则必须使用 Annotation 容器 (在Java8中, 对这种情况做了改善, 但其实也只是一种写法上的简化, 其本质还是一样的).由于在实际开发中,Java8还未大面积的使用, 因此在此只介绍Java7中重复注解定义与使用.

  • Table Annotation定义(代表数据库表)
/**  * Created by jifang on 15/12/27.  */ @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Table {       String name() default "表名是啥?";       String description() default "这家伙很懒, 啥也没留下..."; }
  • Table 容器
@Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Tables {       Table[] value(); }

注意: 容器 注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃 容器 , 相应的注解也就丢失了.

  • Client

    使用时需要用Table容器来 盛装 Table 注解

@Tables({         @Table(name = "t_user", description = "用户表"),         @Table(name = "t_feed", description = "动态表") }) public class Client {       @Test     public void client() {         Tables tableArray = this.getClass().getAnnotation(Tables.class);         Table[] tables = tableArray.value();           for (Table table : tables) {             System.out.println(table.name() + " : " + table.description());         }     } }

在Java8中, 可以直接使用

@Table(name = "t_user", description = "用户表") @Table(name = "t_feed", description = "动态表")

的形式来注解 Client , 但 @Tables 还是需要开发者来写的, 由此可以看出, 重复注解只是一种简化写法, 这种写法只是一种假象: 多个重复注解其实会被作为 容器 注解的value成员.

参考 :

Effective Java

疯狂Java讲义

Java核心技术

原文  http://www.importnew.com/17524.html
正文到此结束
Loading...