文档章节

Java Annotation 再度历险

typeorigin
 typeorigin
发布于 2013/11/03 19:38
字数 2379
阅读 135
收藏 0
点赞 0
评论 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字节代码的操纵方法

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

mj4738
2011/11/02
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
一起学Java7新功能扩展——深入历险分享(一)

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

Beyond-Bit
2012/09/03
0
26
在linux上使用yum安装JDK

1.查找java相关得列表[root@localhost ~]# yum -y list java*Loaded plugins: fastestmirror, refresh-packagekit, securityLoading mirror speeds from cached hostfile base: mirrors.zju.......

罗荣熙
2015/05/04
0
0
Flask URL Mapping的注册方式。

由于先用的Java,然后现在学学python。 下面都是我根据我现在的知识来理解的。 Java中有annotation,Python中有decorator。用着感觉一样,原理还是不一样。 Java中的annotation类似于代码的注...

SwordHua
2013/10/04
0
0
Groovy 2.5.0 发布,JVM 动态脚本语言

Apache Groovy 2.5.0 已发布。此版本包含大量修复和依赖更新,并包含以下新特性: [GROOVY-6744] - Have the elvis operator (?:) support the Optional type in Java 8 [GROOVY-7089] - Bas......

淡漠悠然
05/31
0
1
第二章 第一节 spring-beans之BeanNameGenerator深入详解

前言 BeanNameGenerator是beans体系非常重要的一个组件,主要功能是从一定的条件中计算出bean的name.如果出现问题,是可以规避的。同样可以重写解决。 从上面的数据中可以看出,bean的管理基...

鸟菜啊
04/26
0
0
Spring 3.0.0 is Now Available (2009-12-16)

Spring 3.0.0 is Now Available News and Announcements It's here just in time for the holidays! Arjen Poutsma has just announced that Spring 3.0.0 is now final and Juergen Hoeller......

晨曦之光
2012/03/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Python -re模块及正则表达式解析

传送门: https://blog.csdn.net/pipisorry/article/details/25909899 ps:上面文章中"命名分组"的语法格式不能执行。正确的如下: (?P<name>正则表达式) #name是一个合法的标识符 除了使用别名...

一口今心
10分钟前
0
0
mybatis中session.getMapper方法源码分析

0开始代码AuthorMapper mapper = session.getMapper(AuthorMapper.class); 1 DefaultSqlSession类 @Override public <T> T getMapper(Class<T> type) { //最后会去调用MapperRegistry.getMap......

writeademo
18分钟前
0
0
spring cloud zuul网关的作用

zuul一般有两大作用,1是类似于Nginx的网址重定向,但zuul的重定向的一般是整个spring cloud里在Eureka注册中心的模块. zuul: ignored-services: '*' sensitiveHeaders: routes: ...

算法之名
18分钟前
8
0
java按比例之原图生成缩略图

package com.wxp.test; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import javax.imageio.ImageIO; import sun.......

恋码之子
28分钟前
1
0
SpringCloud 微服务 (十五) 服务容错 Hystrix

壹 工作中的微服务架构,某个服务通常会被多个服务调用或者多层调用完成需求,如果某个服务不可用,导致一个系统功能不可用或者服务直接没用了的情况,这种情况称为雪崩效应 有A服务调用B服务,B服...

___大侠
30分钟前
0
0
Spring框架中的设计模式(五)

Spring框架中的设计模式(五) 通过以前的4篇文章,我们看到Spring采用了大量的关于创建和结构方面的设计模式。本文将描述属于行为方面的两种设计模式:命令和访问者。 前传: Spring框架中的...

瑞查德-Jack
33分钟前
0
0
解决phpstorm运行很卡问题!

phpStorm一旦达到这个临界值,所有智能提示、自动补全都失效了 这TM就很尴尬了,顿时感觉自己就是个废人了,纯手写代码跟便秘一样 众所周知phpStorm基于JAVA,那么这个内存限制肯定跟JAVA的虚...

sjcehui2010
35分钟前
0
0
javascript前端AES加密解密

参考了一下网上的代码加上自已的一些想法,修改,key也可以是中文, 要引入一个aes.js的js文件。 html代码 <html> <head> <title>AES加解密</title> <meta http-equiv="Content-Type"......

oisan_
39分钟前
0
0
MacOS和Linux内核的区别

有些人可能认为MacOS和Linux内核有相似之处,因为它们可以处理类似的命令和类似的软件。甚至有人认为苹果的MacOS是基于linux的。事实上,这两个内核的历史和特性是非常不同的。今天,我们来看...

六库科技
43分钟前
0
0
Vue.js-自定义事件例子

自定义组件的 v-model 2.2.0+ 新增 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。m...

tianyawhl
47分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部