Java注解实践

标签 : 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 注解的保留策略
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. RetentionPolicy value();
  6. }

value为SOURCE, CLASS, RUNTIME三值之一:

  1. public enum RetentionPolicy {
  2. /**
  3. * Annotations are to be discarded by the compiler.
  4. */
  5. SOURCE,
  6. /**
  7. * Annotations are to be recorded in the class file by the compiler
  8. * but need not be retained by the VM at run time. This is the default
  9. * behavior.
  10. */
  11. CLASS,
  12. /**
  13. * Annotations are to be recorded in the class file by the compiler and
  14. * retained by the VM at run time, so they may be read reflectively.
  15. *
  16. * @see java.lang.reflect.AnnotatedElement
  17. */
  18. RUNTIME
  19. }
  • @Target 指定Annotation可以放置的位置(被修饰的目标)
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Target {
  5. ElementType[] value();
  6. }
  1. public enum ElementType {
  2. /** Class, interface (including annotation type), or enum declaration */
  3. TYPE,
  4. /** Field declaration (includes enum constants) */
  5. FIELD,
  6. /** Method declaration */
  7. METHOD,
  8. /** Parameter declaration */
  9. PARAMETER,
  10. /** Constructor declaration */
  11. CONSTRUCTOR,
  12. /** Local variable declaration */
  13. LOCAL_VARIABLE,
  14. /** Annotation type declaration */
  15. ANNOTATION_TYPE,
  16. /** Package declaration */
  17. PACKAGE
  18. }
  • @Documented 指定被修饰的该Annotation可以被javadoc工具提取成文档.
  • @Inherited 指定被修饰的Annotation将具有继承性

    如果某个类使用@Xxx注解(该Annotation使用了@Inherited修饰)修饰, 则其子类自动被@Xxx注解修饰.

Annotation

  1. /**
  2. * Created by jifang on 15/12/22.
  3. */
  4. @Inherited
  5. @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
  6. @Retention(RetentionPolicy.RUNTIME)
  7. public @interface Testable {
  8. }

Client

  1. public class Client {
  2. @Test
  3. public void client(){
  4. new SubClass();
  5. }
  6. }
  7. @Testable
  8. class SupperClass{
  9. }
  10. class SubClass extends SupperClass{
  11. public SubClass() {
  12. for (Annotation annotation : SubClass.class.getAnnotations()){
  13. System.out.println(annotation);
  14. }
  15. }
  16. }

自定义注解

  • 根据Annotation是否包含成员变量,可以把Annotation分为两类:

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

提取Annotation信息

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

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

  1. /**
  2. * Created by jifang on 15/12/22.
  3. */
  4. public class Client {
  5. @Test
  6. public void client() throws NoSuchMethodException {
  7. Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
  8. for (Annotation annotation : annotations) {
  9. System.out.println(annotation.annotationType().getName());
  10. }
  11. }
  12. }

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

  1. @Tag(name = "client")
  2. public class Client {
  3. @Test
  4. public void client() throws NoSuchMethodException {
  5. Annotation[] annotations = this.getClass().getAnnotations();
  6. for (Annotation annotation : annotations) {
  7. if (annotation instanceof Tag) {
  8. Tag tag = (Tag) annotation;
  9. System.out.println("name: " + tag.name());
  10. System.out.println("description: " + tag.description());
  11. }
  12. }
  13. }
  14. }

模拟Junit框架

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

  1. /**
  2. * Created by jifang on 15/12/27.
  3. */
  4. @Inherited
  5. @Target(ElementType.METHOD)
  6. @Retention(RetentionPolicy.RUNTIME)
  7. public @interface Testable {
  8. }

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

  1. public class TestCase {
  2. @Testable
  3. public void test1() {
  4. System.out.println("test1");
  5. }
  6. public void test2() throws IOException {
  7. System.out.println("test2");
  8. throw new IOException("我test2出错啦...");
  9. }
  10. @Testable
  11. public void test3() {
  12. System.out.println("test3");
  13. throw new RuntimeException("我test3出错啦...");
  14. }
  15. public void test4() {
  16. System.out.println("test4");
  17. }
  18. @Testable
  19. public void test5() {
  20. System.out.println("test5");
  21. }
  22. @Testable
  23. public void test6() {
  24. System.out.println("test6");
  25. }
  26. }

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

  1. /**
  2. * Created by jifang on 15/12/27.
  3. */
  4. public class TestableProcessor {
  5. public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  6. int passed = 0;
  7. int failed = 0;
  8. Object obj = Class.forName(clazz).newInstance();
  9. for (Method method : Class.forName(clazz).getMethods()) {
  10. if (method.isAnnotationPresent(Testable.class)) {
  11. try {
  12. method.invoke(obj);
  13. ++passed;
  14. } catch (IllegalAccessException | InvocationTargetException e) {
  15. System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");
  16. e.printStackTrace(System.out);
  17. ++failed;
  18. }
  19. }
  20. }
  21. System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
  22. }
  23. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  24. TestableProcessor.process("com.feiqing.annotation.TestCase");
  25. }
  26. }

抛出特定异常

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

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

注解添加监听器

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

  1. /**
  2. * Created by jifang on 15/12/27.
  3. */
  4. public class SwingPro {
  5. private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
  6. private JButton ok = new JButton("确定");
  7. private JButton cancel = new JButton("取消");
  8. public void init() {
  9. JPanel jp = new JPanel();
  10. // 为两个按钮设置监听事件
  11. ok.addActionListener(new OkListener());
  12. cancel.addActionListener(new CancelListener());
  13. jp.add(ok);
  14. jp.add(cancel);
  15. mainWin.add(jp);
  16. mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  17. mainWin.pack();
  18. mainWin.setVisible(true);
  19. }
  20. public static void main(String[] args) {
  21. new SwingPro().init();
  22. }
  23. }
  24. class OkListener implements ActionListener {
  25. @Override
  26. public void actionPerformed(ActionEvent e) {
  27. JOptionPane.showMessageDialog(null, "你点击了确认按钮!");
  28. }
  29. }
  30. class CancelListener implements ActionListener {
  31. @Override
  32. public void actionPerformed(ActionEvent e) {
  33. JOptionPane.showMessageDialog(null, "你点击了取消按钮!");
  34. }
  35. }

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

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

重复注解

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

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

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

  • Client

    使用时需要用Table容器来盛装Table注解
  1. @Tables({
  2. @Table(name = "t_user", description = "用户表"),
  3. @Table(name = "t_feed", description = "动态表")
  4. })
  5. public class Client {
  6. @Test
  7. public void client() {
  8. Tables tableArray = this.getClass().getAnnotation(Tables.class);
  9. Table[] tables = tableArray.value();
  10. for (Table table : tables) {
  11. System.out.println(table.name() + " : " + table.description());
  12. }
  13. }
  14. }

在Java8中, 可以直接使用

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

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


参考:
Effective Java
疯狂Java讲义
Java核心技术

Java注解实践的更多相关文章

  1. Java注解实践--annotation学习三

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

  2. attilax.java 注解的本质and 使用最佳实践(3)O7

    attilax.java 注解的本质and 使用最佳实践(3)O7 1. 定义pojo 1 2. 建立注解By eclipse tps 1 3. 注解参数的可支持数据类型: 2 4. 注解处理器 2 ...

  3. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  4. JAVA 注解的几大作用及使用方法详解

    JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...

  5. JAVA 注解的几大作用及使用方法详解【转】

    java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...

  6. Java注解处理器(转)

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...

  7. Java注解处理器使用详解

    在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor).在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么:然后,我将一步一步实 ...

  8. Java注解处理器

    Java注解处理器 2015/03/03 | 分类: 基础技术 | 0 条评论 | 标签: 注解 分享到:1 译文出处: race604.com   原文出处:Hannes Dorfmann Java ...

  9. 通过项目了解JAVA注解

    java自定义注解实践 ² 背景 最近在为公司的技术改造做准备,我设计了一个提高Web开发效率的技术框架,为了增加框架的友好性和易用性,决定采用注解来代替配置文件,于是我查询了很多的资料,进行整理和学 ...

随机推荐

  1. 学习KnockOut第三篇之List

    学习KnockOut第三篇之List 欲看此篇---------------------------------------------可先看上篇.          第一步,先搭建一个大概的框架起来 ...

  2. javacript中的mvc设计模式

    以下内容为原创翻译,翻译不对的地方还请原谅,凑合着看吧. 原文网址是: 来源:http://www.alexatnet.com/articles/model-view-controller-mvc-j ...

  3. C# type - IsPrimitive

    Type t = typeof(string); if (t.IsPrimitive)//not { Console.WriteLine("string is a Primitive&quo ...

  4. c++ 时间格式化

    struct tm tm_time; strptime(time.c_str(), "%Y%m%d %H:%M:%S", &tm_time); time_t tt = mk ...

  5. VMware ESXi虚拟机克隆及迁移

    使用ESXi经常会遇到这样的问题,我需要建立多个虚拟机,都是linux操作系统,难道必须一个一个安装吗? VMware ESXi.VMware vCenter Server 和 vSphere Cli ...

  6. VIM Taglist安装配置和使用

    问题描述:            VIM  Taglist安装于配置 问题解决:             (1)安装Taglist包      (2)解压taglist压缩包         (3)将 ...

  7. Leetcode#143 Reorder List

    原题地址 先把链表分割成前后两半,然后交叉融合 实践证明,凡是链表相关的题目,都应该当成工程类题目做,局部变量.功能函数什么的随便整,代码长了没关系,关键是清楚,不容易出错. 代码: ListNode ...

  8. struts.properties配置详解(转)

    Struts 2框架有两个核心配置文件,其中struts.xml文件主要负责管理应用中的Action映射,以及该Action包含的Result定义等.除此之 外,Struts 2框架还包含     s ...

  9. QualityCenter10+Oracle10.2.1.0.1+Win2003SP2企业版安装步骤

    HpQualityCenter10+Oracle10.2.1.0.1+Win2003SP2企业版安装步骤: 1.点击setup.exe,等待,知道出现以下界面,然后按“下一步” 2.出现如下界面,接受 ...

  10. Iptables DDOS/CC 自动屏蔽脚本

    Iptables DDOS/CC 自动屏蔽脚本 May 20, 2013 最近不停地被 CC (DDOS的一种)频繁干扰,分享一个 iptables 屏蔽 DDOS 的脚本.让 crond 每分钟运行 ...