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

 // 未完待續(欠缺具體處理註解過程)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章