文档章节

Java Annotation 再度历险

typeorigin
 typeorigin
发布于 2013/11/03 19:38
字数 2379
阅读 135
收藏 0

本文是对 Java Annotation 基本知识的总结和梳理

Java annotation 官方说明

Annotations, a form of metadata ( "meta data" 我的理解是编程时就能确切知道的能够提供给运行时的信息), provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

从上面定义的话大概体会是这么个意思:

Annotaion 是对目标代码的标签性注释,或者叫说明,用来给Annotation所标记的对象打上标签,就好比人的名字,某种意义来说就是标签。比如某场活动中要找出所有叫 “张三” 的人,这个时候 “张三” 这个标签就起到标识的作用了。

比如Spring 提供的 Annotation, @Autowired, @Transaction 等表明了注解标注的对象在某种场景下会具有标识性。

Annotation 的用法

首先来看 Annotation 可以提供的一些比较重要的作用:

  • Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings. (为编译器提供有用的信息来发现错误或者忽略警告,例如 @SuppressWarnings)

  • Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth. (在编译和部署期间生成代码、XML文件等)

  • Runtime processing — Some annotations are available to be examined at runtime. (提供运行时的信息)

下面就来定义个简单的 Annotation 试试

:::Java
/**
 * 定义一个用来生成代码的作者信息的 Annotation。 定义方法和 
 * Interface 类似,只是在 Interface 之前添加了一个 @ 符号
 */
public @interface Author {

    String name();
    String date();
}

/**
 * 然后用定义好的类来修饰一个Class
 */
@Author(name="Falcon", date="2013-11-02")
public class HelloWorld {
    
    
    @Author(name="Matt", date="2013-11-01")
    public static String var = "";


    @Author(name="Newton", date="2013-11-03")
    public static void main(String[] args) {

        System.out.println("Hello World!");

    }
}

这样就可以了,但是如果只标记 Class 而什么事都不做的话岂不是太无聊了。所以呢继续替 Autor 注解添加一个 jdk 预定义的 Annotation @Documented, 这样就可以使用Javadoc 在生成的代码文档中添加 Author 的注解信息了。(没错,注解可以修饰注解,这叫原注解 - meta annotation)

@Documented
public @interface Author {

    String name();
    String date();
}

接下来用 Javadoc 生成文档看看。

javadoc org.cgfalcon.annotation

命令将生成 org.cgfalcon.annotation 包下所有源文件的文档。

先补个不加 @Documented 注解时生成的图

annotation_nodoc

加了@Documented 注解的样子

annotation_doc

Annotation 定义

Annotation - 注解

注解类似被修饰对象的标签,可以在类(包括interface、enum)方法字段声明时使用。 你可以使用jdk 在 java.langjava.lang.annotation 中定义的 Annotation,也可以像前面一样自己定义 Annotation。 定义的语法如下:

@interface <NameOfAnnotation> {
   <TYPE> <FIELD_NAME>() [default <DEFAULT_VALUE>];
}

<TYPE> 可以是String, int, String[], Class<?> 等
<DEFAULT_VALUE> 给出字段的默认值

另外在 Java SE 8 中将扩展 Annotation 的使用范围,不但可以在声明时使用注解,而且可以在程序体中使用注解,这类注解叫 Type Annotation,例如

  • 创建实例时使用注解

      new @Interned MyObject();
    
  • 类型转换

      myString = (@NonNull String) str;
    
  • implements 子句

      class UnmodifiableList<T> implements @Readonly List<@Readonly T> {...}
    
  • 抛出异常时的声明

      void monitorTemperature() throws @Critical TemperatureException {...}
    

Meta Annotation - 元注解

Annotations that apply to other annotations are called meta-annotations.

也就是修饰注解的注解称为 元注解

Java SE 内置的 Annotation

JDK内置了三个预定义的Annotaion,分别是 @SuppressWarnings, @Override, @Deprecated。除此之外还有一类 元注解, 就是之前说的 修饰注解的注解,就比如之前的 @Documented 一样

三个预定义注解

  • @Deprecated, indicates that the marked element is deprecated and should no longer be used. (表示被标记的方法、类、字段不应当再使用了,可能会在未来的版本中删除)
  • @Override, informs the compiler that the element is meant to override an element declared in a superclass. (这个注解告诉编译器,被标记的对象将会覆盖父类中声明的相同的对象)
  • @SuppressWarnings, tells the compiler to suppress specific warnings that it would otherwise generate (这个注解告诉编译器,忽略被标记对象中可能存在的警告信息)

四个元注解(meta annotation)

  • @Target, marks another annotation to restrict what kind of Java elements the annotation can be applied to. (表示注解可以在那些类型上使用) 可取的值有:

    • ElementType.ANNOTATION_TYPE
    • ElementType.CONSTRUCTOR
    • ElementType.FIELD
    • ElementType.LOCAL_VARIABLE
    • ElementType.METHOD
    • ElementType.PACKAGE
    • ElementType.PARAMETER
    • ElementType.TYPE
  • @Retention, specifies how the marked annotation is stored. (表示注解可以存在于哪些阶段)可取的值有:

    • RetentionPolicy.SOURCE, 源码级的注解,将会被编译器忽略
    • RetentionPolicy.CLASS, 编译级注解,对编译器有用,但是会被JVM忽略
    • RetentionPolicy.RUNTIME,运行时级注解,只有这类注解才能在运行时通过反射获取到
  • @Documented, indicates that whenever the specified annotation is used those elements should be documented using the Javadoc tool. (告诉javadoc 是否需要在文档中生成)

  • @Inherited, indicates that the annotation type can be inherited from the super class. (This is not true by default.) (这个注解允许,当用户对当前 class 查询的注解不存在时,能够向该 class 的父类class查询该注解。另外,这个注解只能修饰 类)

  • @Repeatable, Java SE 8新增Annotatoin, 表明相同名称的注解可以出现超过1次

Annotation 行为的控制

Annotation 如果只是定义出来,放在 方法字段上来起个简单的标签作用,那其实功能就大大减弱了。因此在自定义注解时,通常会定义和自定义Annotation 相匹配的一组行为来进行对代码增强。

下面的代码演示通过定义 Bean,@Inject Annotation 来完成实例注入。例子中定义 EmailValidator 是对Email地址的检测工具, EmailService 是对外提供的Email名称检测服务。

/**
 * Bean Annotation.  被 @Bean 修饰的对象表示需要被容器加载
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {

}

/**
 * @Inject 用来修饰字段,说明该字段需要由容器注入。
 * @Inject 提供了属性 target 用来指定要注入的 Class
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
    Class<?> target();
}

接下来是 Email服务 的定义

/**
 * Email 实例,只提供 address字段
 */
public class Email {

    private String address;

    public Email(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Email{" +
                "address='" + address + '\'' +
                '}';
    }
}

/**
 * EmailValidator 提供对邮箱地址的检测功能
 */
@Bean
public class EmailValidator {

    /**
     * 使用正则表达式来检测邮箱地址是否合法
     */
    private Pattern emailPattern = Pattern.compile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?");

    public boolean validate(Email email) {
        if (email == null || email.getAddress() == null) {
            return false;
        }
        Matcher matcher = emailPattern.matcher(email.getAddress());
        if (matcher.find()) {
            return true;
        } else {
            return false;
        }
    }
}


/**
 * EmailService Bean,同样EmailService会由容器初始化并维护
 */
@Bean
public class EmailService {

    /**
     * 此处使用 @Inject Annotation,表明字段 emailValidator需要由容器注入
     */
    @Inject(target = EmailValidator.class)
    private EmailValidator emailValidator;

    public void setEmailValidator(EmailValidator emailValidator) {
        this.emailValidator = emailValidator;
    }

    public void validateEmail(Email email) {
        if(emailValidator.validate(email)) {
            System.out.println("Eamil valid");
        } else {
            System.out.println("Illegal Email Address Format");
        }
    }
}

这样 @Bean, @Inject 就定义完了,使用也很简单,只需要在需要的字段或方法上加上注解即可。但是只标记不做解析的话就只能当花瓶了,根本毫无作用。因此我们还得自己定义对注解的解析逻辑。

下面的代码 BeanContext 实现一个简单的容器功能,主要做两个工作:

  1. 负责搜索指定 Package 下被 @Bean 标记的类并将之实例化
  2. 对于已经实例化的类,遍历其字段,查找出含有 @Inject 的字段,并使用合适的Class 实例给字段注入值。

代码如下:

/**
 * BeanContext 充当一个简单Bean容器,负责查找 LOAD_PACKAGE 包下的类,加载
 * 被 @Bean 修饰的类,然后对含有 @Inject 的字段进行注入
 */
public class BeanContext {

    public static Map<Class<?>, Object> beanMap = new HashMap<Class<?>, Object>();
    /**
     * 加载类的起始包
     */
    public static final String LOAD_PACKAGE = "org.cgfalcon.annotation";

    static {

        List<Class<?>> clazzList = new ArrayList<Class<?>>();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(LOAD_PACKAGE.replace(".", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String cPath = url.getPath();
                        loadBeans(clazzList, cPath, LOAD_PACKAGE);
                    }
                }
            }

            /**
             * 将查找完的被 @Bean 修饰的类实例化放入 beanMap中
             */
            for (Class<?> clazz : clazzList) {
                Object instance = clazz.newInstance();
                beanMap.put(clazz, instance);
            }

            /*
             * Dependencies inject
             *
             */
            iocInject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 查找实例化好的实例,对其中需要 Inject 的字段注入合适的字段
     */
    private static void iocInject() {
        for (Map.Entry<Class<?>, Object> entry : beanMap.entrySet()) {
            Class<?> clazz = entry.getKey();
            Object instance = entry.getValue();
            Field[] fields = clazz.getDeclaredFields();
            if (fields != null && fields.length > 0) {
                for (Field eachField : fields) {
                    if (eachField.isAnnotationPresent(Inject.class)) {
                        /* 在我的定义中 @Inject 包含一个字段 target, 用来制定要注入的实力类型,
                         * 此处通过该类型来从 beanMap中查找相应的实例
                          * */
                        Class<?> injectClass = eachField.getAnnotation(Inject.class).target();
                        Object injectInstance = beanMap.get(injectClass);
                        if (injectInstance != null) {
                            try {
                                /*
                                 * 查找出来的实例注入instance 的指定字段中
                                 */
                                eachField.setAccessible(true);
                                eachField.set(instance, injectInstance);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }

                    }
                }
            }
        }
    }

    public static void loadBeans(List<Class<?>> clazzList, String packagePath, String loadPackage) {
        File packageDir = new File(packagePath);
        if (packageDir.isDirectory()) {
            getSubpackageClasses(clazzList, packageDir, loadPackage);
        }
    }

    /**
     * 查找 packageDir 下的class文件,将含有 @Bean 注解的类取出来
     *
     * @param clazzList
     * @param packageDir
     * @param loadPackage
     */
    private static void getSubpackageClasses(List<Class<?>> clazzList, File packageDir, String loadPackage) {
        File[] subfiles = packageDir.listFiles();
        for (File subFile : subfiles) {
            if (subFile.isFile() && subFile.getName().endsWith(".class")) {
                String fileName = subFile.getName();
                String className = loadPackage + "." + fileName.substring(0, fileName.indexOf("."));

                try {
                    Class<?> clazz = Class.forName(className);
                    if (clazz.isAnnotationPresent(Bean.class)) {

                        clazzList.add(clazz);
                    }
                } catch (ClassNotFoundException e) {
                    System.out.printf("Failed to load class: <%s>\n", className);
                }
            } else if (subFile.isDirectory()) {
                getSubpackageClasses(clazzList, subFile, loadPackage + "." + subFile.getName());
            }
        }
    }

    public static <T> T getBean(Class<T> clazz) {
        if (beanMap == null) {
            return null;
        }
        return (T) beanMap.get(clazz);
    }
}

然后来实际使用看看

public class Main {

    public static void main(String[] args) {
        EmailService emailService = BeanContext.getBean(EmailService.class);

        emailService.validateEmail(new Email("ddd"));
        emailService.validateEmail(new Email("hotmail@gmail.com"));
    }
}

输出结果为:

Email{address='ddd'} - illegal
Email{address='hotmail@gmail.com'} - valid

由上观之,自定义Annotation的使用通常需要经历: 自定义Annotation, 解析Annotation 两个步骤, 通常比较麻烦的地方就是在解析的步骤上。因此但凡说 Annotation,必定要涉及到使用的特定环境,假设脱离的语境,那么也就像花瓶一样,只能做摆设了。

博客链接: http://typeorigin.com

参考资料

  1. Oracle | The Java Tutorials | Lesson: Annotations
  2. InfoQ | Java深度历险(六)——Java注解

© 著作权归作者所有

共有 人打赏支持
typeorigin

typeorigin

粉丝 21
博文 19
码字总数 13325
作品 0
杭州
程序员
Cannot make a static reference to the non-stati...

今天碰到这样一些错误,Eclipse提示Cannot make a static reference to the non-static type T。代码如下: public class DAOFactory<D extends TemplateDAO<B>, B> {private static Map<Str......

开源中国驻成都办事处
2013/05/30
0
0
Java深度历险:Java注解

在开发Java程序,尤其是Java EE应用的时候,总是免不了与各种配置文件打交道。以Java EE中典型的S(pring)S(truts)H(ibernate)架构来说,Spring、Struts和Hibernate这三个框架都有自己的 XML格...

李长春
2012/02/06
0
0
Java注解(Annotation)详解

Java注解(Annotation)详解 1.Annotation的概念 An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may......

幻海流心
05/23
0
0
深度探讨Java字节代码的操纵方法

本文为IBM工程师成富编写的《Java深度历险》的第一部分Java字节代码的操纵,像这样Java语言的深度理解和运用还没有很多文章,我们把他奉献给读者,希望读者们喜欢。 51CTO编者按:我们曾给大...

mj4738
2011/11/02
0
0
一起学Java7新功能扩展——深入历险分享(一)

特此声明:因网友疑问,这里声明一个重要的安全,就是大家所知的java惊现0day漏洞!8月30日,Oralce紧急发布了新版本的JDK和JRE,原因是发现了一个严重的0day漏洞CVE-2012-4681,远程攻击者可...

Beyond-Bit
2012/09/03
0
26

没有更多内容

加载失败,请刷新页面

加载更多

Mybatis中jdbcType和javaType的对应关系 

Mybatis中jdbcType和javaType的对应关系 1 JDBC Type Java Type 2 CHAR String 3 VARCHAR String 4 LONGVARCHAR String 5 NUMERIC java.math.BigDecimal 6 DECIMAL java.math.BigDecimal 7 ......

DemonsI
29分钟前
3
0
Python中字符串和datetime

遇到的问题: 今天在写一个爬虫时,需要将今天的数据和昨天、一周前的数据做比较。所以就需要一个方法可以方便的计算出指定日期的前几天的日期。比如10月3号,则一周前的日期是9月26号。 问题...

akane_oimo
32分钟前
1
0
企业级 SpringBoot 教程 (四)SpringBoot 整合JPA

JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 JPA 的目标之一是制定一个可以由很多供应商实现的API,并且开发...

itcloud
33分钟前
2
0
白话SpringCloud | 第六章:Hystrix监控面板及数据聚合(Turbine)

前言 前面一章,我们讲解了如何整合Hystrix。而在实际情况下,使用了Hystrix的同时,还会对其进行实时的数据监控,反馈各类指标数据。今天我们就将讲解下Hystrix Dashboard和Turbine.其中Hys...

oKong
43分钟前
2
0
Java JDK 11:现在可以使用所有新功能

删除了CORBA,Java EE和JavaFX支持,但添加了十几个主要新功能 目录 哪里可以下载JDK 11 Java 11 JDK中的新功能 从Java JDK 11中删除了什么 Java Development Kit(JDK)11现已普遍可用,可供...

GuoMengyue
45分钟前
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部