Java注解实践
2017-10-30 08:35
127 查看
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为
SOURCE,
CLASS,
RUNTIME三值之一:
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
Field
Method
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()); } } }
模拟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; } //下同 }
重复注解
在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成员.
相关文章推荐
- attilax.java 注解的本质and 使用最佳实践(3)O7
- java 自定义注解实践
- attilax.java 注解的本质and 使用最佳实践(3)O7
- Java注解Annotation学习(例子实践篇)
- java注解及在butternife中的实践和原理
- java注解及在butternife中的实践和原理
- java注解及在butternife中的实践和原理
- java自定义注解实践
- Java注解实践--annotation学习三
- Android 利用java的注解方法快速keep住混淆实践操作
- Java 学习笔记06:Spring 基于注解(Annotation)的AOP
- Java对象的序列化和反序列化实践
- ArcSDE SDK Java 快速实践 6
- Java Web Start实践:动态生成JNLP
- [转]Java 理论与实践: 正确使用 Volatile 变量
- JAVA_WEB项目之三大框架中不使用HibernateTemplate而使用SessionFactory以及如何使用注解
- java注解demo
- Java互联网架构-远走高飞单例模式原理分析与设计实践
- Java 注解编程