• Java 注解详解


    Java 注解详解

    上面几篇参考资料应该是网上最好的几篇资料了,下面都是我看这几篇文章做的笔记.

    主要是参考资料 2,后面涉及具体的 spring 框架源码解析,暂时有点难理解,先跳过,之后再回过头来学习.

    什么是注解?

    比较官方的说法是:注解是那些插入到源代码中,使用其他工具可以对其进行处理的标签。

    这就话可以解读出几个意思:

    1. 注解只是源码中的一个标记符号
    2. 注解需要其他工具进行处理

    举个例子,下面的@Test 就是注解,它标记了下面的 test 方法是我们要进行单元测试的方法.

    但是只有这个注解,程序本身并不会做任何事情,需要安装 JUnit 测试工具,它才会扫描调用标识为@Test 的方法.

    所以我们可以说,注解=注解定义+工具支持

    import org.junit.Test;
    
    public class SomeTest {
        @Test
        public void test(){
            // TODO
        }
    }
    

    下面我们通过一个 demo 来了解注解是如何定义和解析的.

    1. 我们将定义一个@BeanAnnotation,@ConfigurationAnnotation 两个注解.
    2. 然后分别用它们来修饰一个名为 Book 的 po,和我们的 BookConfig,两个类.
    3. 接着在 main 类中,通过定义 parseConfiguration 方法,解析 BookConfig 配置类,注册 bean.

    (注册 bean 具体过程:将配置类中用@BeanAnnotation 声明的构造函数建立实例并和 beanName 的映射添加到 beanMap).

    1. 最后在 main 类的 main 方法中,调用 parseConfiguration()方法,获取注册好 bean 的 config 实例,再通过它来 getBean(),测试 BookConfig 中注册的 Bean 是否正常.

    如何定义注解?

    Java 中有两类注解,一类是我们平常使用修饰类和方法的注解,另一类是修饰注解的注解,一般在定义注解的时候使用,我们叫它元注解.

    一共四个:

    • @Retention
      • 意如其名,retention 保留的意思,它用来标记这个注解可以保留到程序编译运行的哪个阶段
      • 它有这么几个参数
        • RetentionPolicy.SOURCE 保留在源文件里(编译时会被丢弃)
        • RetentionPolicy.CLASS 保留在 class 文件里(虚拟机不会载入它们)
        • RetentionPolicy.RUNTIME (保留在 class 文件里,并由虚拟机将它们载入,通过反射可以获取到它们)
          • 通常没有特殊需要,我们都会选择使用 RUNTIME,并通过反射来解析注解
    • @Target
      • 标记注解的使用目标
      • 它有以下参数
        • ElementType.TYPE 用于类和接口
        • ElementType.FIELD 用于成员域
        • ElementType.METHOD 用于方法
        • ElementType.PARAMETER 用于方法或者构造器里的参数
        • ElementType.CONSTRUCTOR 用于构造器
        • ElementType.LOCAL_VARIABLE 用于局部变量
        • ElementType.ANNOTATION_TYPE 用于注解类型声明
        • ElementType.PACKAGE 用于包
        • ElementType.TYPE_PARAMETER 类型参数
        • ElementType.TYPE_USE 类型用法
    • @Document
      • 为归档工具(如 Javadoc)提供提示
    • @Inherited
      • 只能应用于对类的注解。
      • 指明当这个注解应用于一个类 A 的时候,能够自动被类 A 的子类继承。

    了解了元注解,接着我们就能通过几个元注解的组合来定义我们这个 demo 需要的两个注解了.

    package tech.rpish.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface BeanAnnotation {
        String value()default"";
    }
    

    通过元注解我们可以知道@BeanAnnotation,是修饰方法,保留到运行时,有以供为 value 参数的注解.

    下面的@ConfigurationAnnotation 和它有点不一样,它的目标对象是类和接口.

    packagetech.rpish.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ConfigurationAnnotation {
        String value()default"";
    }
    

    使用注解

    我们先建以供 pojo,用于之后 bean 的注册.

    package tech.rpish.po;
    
    import lombok.Data;
    
    @Data
    public classBook {
    private String name;
    
    publicBook(String name) {
    this.name=name;
        }
    }
    

    接着在 BookConfig 类中,使用注解标记配置类和 bean

    package tech.rpish;
    
    importtech.rpish.annotation.BeanAnnotation;
    importtech.rpish.annotation.ConfigurationAnnotation;
    importtech.rpish.po.Book;
    
    importjava.util.LinkedHashMap;
    
    @ConfigurationAnnotation
    public classBookConfig {
    privateLinkedHashMap<String, Object>beans;
    
    public <T>T getBean(String name, Class<T>clazz) {
            Object o=beans.get(name);
    return(T) o;
        }
    
        @BeanAnnotation
    publicBook book() {
    return newBook("A Book");
        }
    
        @BeanAnnotation("anobook")
    publicBook book2() {
    return newBook("Another Book");
        }
    }
    

    解析注解

    这边基本每个重要的语句我都做了注释,大家可以看注释.

    package tech.rpish;
    
    import tech.rpish.annotation.BeanAnnotation;
    import tech.rpish.annotation.ConfigurationAnnotation;
    import tech.rpish.po.Book;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.LinkedHashMap;
    
    public classMain {
    
    public static voidmain(String[]args)throwsException{
    //        调用解析配置方法,获取解析后(将@BeanAnnotation标记的bean的 实例和beanName 添加到 beans字段的hashmap)的配置类实例
            BookConfig config=parseConfiguration(BookConfig.class);
    //        通过配置类的getBean方法,传入beanName获取实例
            Book book=config.getBean("book", Book.class);
            System.out.println(book);
    
            Book book1=config.getBean("anobook",Book.class);
            System.out.println(book1);
    
            Book book2=config.getBean("book2", Book.class);
            System.out.println(book2);
    
        }
    
    public static <T>T parseConfiguration(Class<T>clazz)throwsIllegalAccessError, InstantiationException, InvocationTargetException, NoSuchFieldException, IllegalAccessException {
    //        如果该类中没有 ConfigurationAnnotation(配置注解) 返回null
    if(!clazz.isAnnotationPresent(ConfigurationAnnotation.class)) {
    return null;
            }
    
    //        (如果存在配置类/有配置注解)实例化配置类
            T instance=clazz.newInstance();
    
    //        获取配置类中的字段(beanName和class的映射表) 和 声明的方法
            LinkedHashMap<String, Object>hashMap= newLinkedHashMap<>();
            Field beans=clazz.getDeclaredField("beans");
            Method[]methods=clazz.getDeclaredMethods();
    
    //        遍历寻找标记了 BeanAnnotation 的方法 (也就是我们在配置类中注册的bean)
    //        将bean的构造器,构造对象和beanName,添加到beanName和class的映射表
    for(Method m:methods) {
    if(m.isAnnotationPresent((BeanAnnotation.class))) {
                    Object o=m.invoke(instance);
                    BeanAnnotation t=m.getAnnotation(BeanAnnotation.class);
    
    //                beanName有在注解中指定value的话使用指定的,否则使用声明的构造方法名
                    String name;
    if(t.value()!= null && !t.value().equals("")) {
                        name=t.value();
                    }else{
                        name=m.getName();
                    }
    
                    hashMap.put(name, o);
                }
            }
    
    //        将beanName和class的映射表设为可访问,并添加新值(配置类中用@BeanAnnotation标记的bean)
            beans.setAccessible(true);
            beans.set(instance, hashMap);
    
    //        返回配置类实例
    returninstance;
        }
    }
    
    

    这个 demo,大致介绍了自定义@Bean,@Configuration 注解,然后在配置类中标记配置类和要注册的 bean 的构造方法.最后在 main 中调用 parseConfiguration()方法解析配置类,返回注册了 bean 的 config 对象,再通过它来 getBean().

    和 Spring 的使用体验是基本一直的,但是 spring 的实现过程会复杂得多,具体的推荐大家看上述的参考资料 2,我这篇也只是笔记罢了.

    很感谢写这篇文章的老哥,解决了困惑了我很久的问题.

  • 相关阅读:
    [转]SubVersion 和 CVSNT在Windows下的安装及初步管理
    [Java20071101]JDK配置
    [English20071023]疯狂英语永恒不变的18条黄金法则
    [文摘20071020]富人和穷人的经典差异
    [English20071024]疯狂突破高中句型300句
    [文摘20071017]回家真好 (工作是为了生活)
    [文摘20071020]老婆和老妈掉水里终于有答案啦
    [转]flash与后台数据交换方法整理
    Repeater使用:绑定时 结合 前台JS及后台共享方法
    [文摘20071019]九九重阳节的来历 重阳节传说故事 重阳节的活动习俗 重阳节诗篇
  • 原文地址:https://www.cnblogs.com/rpish/p/15449610.html
Copyright © 2020-2023  润新知