Java注解及其原理以及分析spring注解解析源码

注解的定义

注解是那些插入到源代码中,使用其他工具可以对其进行处理的标签

注解不会改变程序的编译方式:Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

在Java中,注解是被当做一个修饰符来使用的(修饰符:如public、private)

注解的常用用法:1. 附属文件的自动生成,例如bean信息类。 2. 测试、日志、事务等代码的自动生成。

单元测试例子:

import org.junit.Test;

public class SomeTest {

    @Test
    public void test(){
        // TODO
    }

}

以上是我们常见的代码。以前不了解的时候,都自然而然的认为是@Test让我们的代码拥有单元测试的能力,实际上:@Test注解自身并不会做任何事情,它需要工具支持才有用。例如,当测试一个类的时候,JUnit4测试工具会去调用所有标识为@Test的方法。

这也就解释了当我们要引入Test的Class时提示:

所以我们可以认为:注解=注解定义+工具支持

进入@Test,查看定义

package org.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
    Class<? extends Throwable> expected() default Test.None.class;

    long timeout() default 0L;

    public static class None extends Throwable {
        private static final long serialVersionUID = 1L;

        private None() {
        }
    }
}

可以看到@Test注解被两个注解@Retention和@Target给注解了,这两个注解叫做元注解(一共四个:@Retention、@Target、@Document、@Inherited)

1. @Retention(指明这个注解可以保留多久,一般都为RUNTIME

RetentionPolicy.SOURCE            保留在源文件里
RetentionPolicy.CLASS             保留在class文件里,但是虚拟机不需要将它们载入
RetentionPolicy.RUNTIME           保留在class文件里,并由虚拟机将它们载入,通过反射可以获取到它们。

2. @Target(指明这个注解的使用范围)

ElementType.TYPE                 用于类和接口
ElementType.FIELD                用于成员域
ElementType.METHOD               用于方法
ElementType.PARAMETER            用于方法或者构造器里的参数
ElementType.CONSTRUCTOR          用于构造器
ElementType.LOCAL_VARIABLE       用于局部变量
ElementType.ANNOTATION_TYPE      用于注解类型声明
ElementType.PACKAGE              用于包
ElementType.TYPE_PARAMETER       类型参数,1.8新增
ElementType.TYPE_USE             类型用法,1.8新增

3. @Document为例如Javadoc这样的归档工具提供了一些提示。

4. @Inherited只能应用于对类的注解。指明当这个注解应用于一个类A的时候,能够自动被类A的子类继承。

注解可以在运行时处理

也可以在源码级别上处理

也可以在字节码级别上进行处理

注解的使用

新建一个实体类Book

public class Book {

    private String name;

    public Book() {
    }

    public Book(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name=‘" + name + ‘\‘‘ +
                ‘}‘;
    }
}

建立一个在类上用的注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TypeAnnotation {

    // 如果注解里只有一个属性,就可以以这种固定的写法。使用的时候可以省略名称如:@TypeAnnotation("")
    String value() default "";
}

建立一个在方法上用的注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodAnnotation {

    String value() default "";
}

假装建立一个“配置类”

package com.demo.annotation;

import com.demo.tools.Book;

import java.util.LinkedHashMap;

@TypeAnnotation
public class BookConfig {

    private LinkedHashMap<String,Object> beans;

    public <T> T getBean(String name, Class<T> clazz){
        Object o = beans.get(name);
        return (T) o;
    }

    @MethodAnnotation
    public Book book(){
        return new Book("QQQ");
    }

    @MethodAnnotation("zzz")
    public Book book2(){
        return new Book("ZZZ");
    }

}

假装这是spring容器的初始化过程

package com.demo.annotation;

import com.demo.tools.Book;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;

public class Main {
    public static void main(String[] args) throws Exception {
        BookConfig config = parseAnnotation(BookConfig.class);
        Book book = config.getBean("book", Book.class);
        System.out.println(book);
        book = config.getBean("zzz", Book.class);
        System.out.println(book);
        book = config.getBean("book2", Book.class);
        System.out.println(book);
    }

    public static <T> T parseAnnotation(Class<T> clazz) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
        if (!clazz.isAnnotationPresent(TypeAnnotation.class)){
            return null;
        }
        T instance = clazz.newInstance();
        LinkedHashMap<String, Object> hashMap = new LinkedHashMap<>();
        Field beans = clazz.getDeclaredField("beans");

        Method[] methods = clazz.getDeclaredMethods();
        for (Method m : methods){
            if (m.isAnnotationPresent(MethodAnnotation.class)){
                Object o = m.invoke(instance);
                MethodAnnotation t = m.getAnnotation(MethodAnnotation.class);
                String name;
                // 注解有值用注解值作为name,否则用方法名字作为name
                if (t.value() != null && !t.value().equals("")){
                    name = t.value();
                }else{
                    name = m.getName();
                }
                hashMap.put(name, o);
            }
        }
        beans.setAccessible(true);
        beans.set(instance, hashMap);
        return instance;
    }
}

输出:

思路就是:用反射创建新类,利用【java.lang.reflect.Method#getAnnotation】方法获取注解类,然后获取注解里的值,然后进行操作即可。

Spring中的注解

引入Spring-Context的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

1. 在resource路径下新建一个beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="book" class="com.demo.tools.Book">
        <property name="name" value="AAA"></property>
    </bean>
</beans>

这个是原始的写法,先测试这个也是为了对比

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Book book = applicationContext.getBean(Book.class);
        System.out.println(book);
    }
}

运行输出

2. 建立一个配置类(配置类的作用等同于xml配置文件)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BookConfig {
    @Bean
    public Book book(){
        return new Book("BBB");
    }
}

运行

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BookConfig.class);
        Book book = context.getBean(Book.class);
        System.out.println(book);
    }
}

输出

这里不打算探究spring有多少种注解以及使用方法,只探究注解是怎样运行以取代xml配置的

先来看下容器的创建过程,这其中就包括了注解部分

进入AnnotationConfigApplicationContext构造方法

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

A:this()

public AnnotationConfigApplicationContext() {
    // 这是一个替代ClassPathBeanDefinitionScanner的注释解决方案,但只针对显式注册的类。
    this.reader = new AnnotatedBeanDefinitionReader(this);
    // 一个bean定义扫描器,它检测类路径上的bean候选,将相应的bean定义注册到给定的注册表(BeanFactory或ApplicationContext)
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

B:register(componentClasses);

public void register(Class<?>... componentClasses) {
    Assert.notEmpty(componentClasses, "At least one component class must be specified");
    this.reader.register(componentClasses);
}

public void register(Class<?>... componentClasses) {
    for (Class<?> componentClass : componentClasses) {
        registerBean(componentClass);
    }
}

public void registerBean(Class<?> beanClass) {
    doRegisterBean(beanClass, null, null, null, null);
}

// 根据给定的class注册bean,从类声明的注解派生其元数据
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
        @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
        @Nullable BeanDefinitionCustomizer[] customizers) {

    // new一个AnnotatedGenericBeanDefinition,其中包含bean的class和元数据
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }

    abd.setInstanceSupplier(supplier);
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class == qualifier) {
                abd.setPrimary(true);
            }
            else if (Lazy.class == qualifier) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    if (customizers != null) {
        for (BeanDefinitionCustomizer customizer : customizers) {
            customizer.customize(abd);
        }
    }
    // 包含名称和别名的bean定义的Holder
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 注册bean定义
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

C:refresh();

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 刷新前的准备
        prepareRefresh();

        // 获取bean工厂【ConfigurableListableBeanFactory】
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 对bean工厂的预设置,比如配置类加载器和后置处理器等等。(后置处理器能在bean初始化前后做一些工作)
        prepareBeanFactory(beanFactory);

        try {
            // 由子类实现的bean工厂的后置处理
            postProcessBeanFactory(beanFactory);

            // 执行工厂的后置处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册bean的后置处理器,用来拦截bean的创建过程
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // 初始化事件派发器
            initApplicationEventMulticaster();

            // 由子类实现,当容器刷新的时候,可以做一些额外的事情
            onRefresh();

            // 检查并注册容器中的监听器
            registerListeners();

            // 初始化剩下的所有单实例bean
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset ‘active‘ flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring‘s core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

DEBUG开始

打断点

当获取Bean工厂之后,出现了bookConfig的bean定义(当前出现的6个bean定义均是在B:register阶段完成的),但是还没有book的bean定义,继续往下走

当执行bean工厂的后置处理器之后,才出现了book的bean,所以进入工厂后置处理器【org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
    // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

进入第一行方法【org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)】

我发现过了invokeBeanDefinitionRegistryPostProcessors方法,bean工厂的beanDefinitionMap的内容发生了变化,那么进入这个方法【org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors

private static void invokeBeanDefinitionRegistryPostProcessors(
        Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

进入【org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);

    processConfigBeanDefinitions(registry);
}

进入最后一行代码【org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

此时可以看的已有的BeanDefinitionNames,下面进入循环

只有当循环到bookConfig时,才进入else if 并添加。继续走,通过下面的注释我们可以知道上面的逻辑是来寻找@Configuration标记的类,如果没有即返回。

然后开始解析所有配置类

进入parser.parse(candidates);方法【org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)】,有个

进入parse方法后最终进入【org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass

进入doProcessConfigurationClass方法【org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

这里就是我们要找的读取解析注解的方法了。

可以看到里面写了对各种注解的处理方式(比如:@ComponentScan、@Import等等),包括对配置类里面定义的bean的处理。

针对本案例,可以看到专门检索配置类里面被@Bean标记的Method的方法【org.springframework.context.annotation.ConfigurationClassParser#retrieveBeanMethodMetadata

// 未完待续(欠缺具体处理注解过程)

原文地址:https://www.cnblogs.com/LUA123/p/11700273.html

时间: 2024-10-09 22:44:56

Java注解及其原理以及分析spring注解解析源码的相关文章

Spring IOC 容器源码分析

前言: Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题. 阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的各种概念,少部分内容我还假设读者使用过 SpringMVC.本文要说的 IOC

Spring mvc之源码 handlerMapping和handlerAdapter分析

Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mvc的执行流程,以及如何根据URL查找处理器Controller的实现 (适合那些刚阅读源码不知道如何下手的人) http://www.guojinbao.com/borrow/borrowDetail/GETadLPjnf0[d].do 如何根据URL地址---->找到正确处理器Controller

Spring Core Container 源码分析三:Spring Beans 初始化流程分析

前言 本文是笔者所著的 Spring Core Container 源码分析系列之一: 本篇文章主要试图梳理出 Spring Beans 的初始化主流程和相关核心代码逻辑: 本文转载自本人的私人博客,伤神的博客: http://www.shangyang.me/2017/04/01/spring-core-container-sourcecode-analysis-beans-instantiating-process/ 本文为作者的原创作品,转载需注明出处: 源码分析环境搭建 参考 Sprin

Spring Core Container 源码分析七:注册 Bean Definitions

前言 原本以为,Spring 通过解析 bean 的配置,生成并注册 bean defintions 的过程不太复杂,比较简单,不用单独开辟一篇博文来讲述:但是当在分析前面两个章节有关 @Autowired.@Component.@Service 注解的注入机制的时候,发现,如果没有对有关 bean defintions 的解析和注册机制彻底弄明白,则很难弄清楚 annotation 在 Spring 容器中的底层运行机制:所以,本篇博文作者将试图去弄清楚 Spring 容器内部是如何去解析 b

Spring Developer Tools 源码分析:二、类路径监控

在 Spring Developer Tools 源码分析一中介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath 的监控. 二.类路径监控 首先看一些这一部分可能涉及到的类图: 在图中,红色斜线左上部分是第一部分中介绍的文件目录监控的类,其中 FileSystemWatcher 会通过独立线程监控指定的目录,当目录内容发生变化时,通过对比快照可以获得所有监控目录变化的文件ChangedFiles,然后将变化通知

Spring IOC 容器源码分析 - 创建单例 bean 的过程

1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去创建,而是从缓存中获取.如果某个 bean 还未实例化,这个时候就无法命中缓存.此时,就要根据 bean 的配置信息去创建这个 bean 了.相较于getBean(String)方法的实现逻辑,创建 bean 的方法createBean(String, RootBeanDefinition, Obj

Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反射创建一个原始的 bean 对象,然后再向这个原始的 bean 对象中填充属性.对于填充属性这个过程,简单点来说,JavaBean 的每个属性通常都有 getter/setter 方法,我们可以直接调用 setter 方法将属性值设置进去.当然,这样做还是太简单了,填充属性的过程中还有许多事情要做.

Spring IOC 容器源码分析 - 余下的初始化工作

1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bean 做最后的初始化工作.相较于之前几篇文章所分析的源码,initializeBean 的源码相对比较简单,大家可以愉快的阅读.好了,其他的不多说了,我们直入主题吧. 2. 源码分析 本章我们来分析一下 initializeBean 方法的源码.在完成分析后,还是像往常一样,把方法的执行流程列出来.好了,看源码

Hadoop之HDFS原理及文件上传下载源码分析(下)

上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文件上传.下载源解析. 文件上传 先上文件上传的方法调用过程时序图: 其主要执行过程: FileSystem初始化,Client拿到NameNodeRpcServer代理对象,建立与NameNode的RPC通信(楼主上篇已经介绍过了) 调用FileSystem的create()方法,由于实现类为Dis