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,我这篇也只是笔记罢了.

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

posted @ 2021-10-23 21:39  rpish  阅读(117)  评论(0编辑  收藏  举报