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

Posted 小LUA

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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");
    }

    @Bean
    public User user(){
        return new User("KKK");
    }
}

运行

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定义,继续往下走

 进入工厂后置处理器【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

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    AnnotationMetadata original = sourceClass.getMetadata();
    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    // 标记@Bean的方法超过1个,有下面说的顺序问题,所以要用asm;否则直接返回
    if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
        // 尝试通过ASM读取.class文件以确定声明顺序
        // 不幸的是,JVM的标准反射以任意顺序返回方法,甚至在相同JVM上运行相同应用程序的不同实例之间也是如此。
        try {
            AnnotationMetadata asm =
                    this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
            if (asmMethods.size() >= beanMethods.size()) {
                Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
                for (MethodMetadata asmMethod : asmMethods) {
                    for (MethodMetadata beanMethod : beanMethods) {
                        if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                            selectedMethods.add(beanMethod);
                            break;
                        }
                    }
                }
                if (selectedMethods.size() == beanMethods.size()) {
                    // All reflection-detected methods found in ASM method set -> proceed
                    beanMethods = selectedMethods;
                }
            }
        }
        catch (IOException ex) {
            logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
            // No worries, let\'s continue with the reflection metadata we started with...
        }
    }
    return beanMethods;
}

获取到之后即添加到ConfigurationClass的beanMethods集合

 

看到这里,可以得知注解的作用:标记作用

以本例来看:无非就是标记了一个@Bean注解,之后扫描工具能够解析并得到MethodMetadata对象,再到后面由Spring进行统一实例化。

以上是关于Java注解及其原理以及分析spring注解解析源码的主要内容,如果未能解决你的问题,请参考以下文章

Spring注解Component原理源码解析

Spring实战Spring注解配置工作原理源码解析

spring注解怎么实现的

Spring实战Spring注解配置工作原理源码解析

java注解是怎么实现的

Java注解与原理分析