曹工說Spring Boot源碼系列開講了(1)-- Bean Definition到底是什么,附spring思維導圖分享


寫在前面的話&&About me

網上寫spring的文章多如牛毛,為什么還要寫呢,因為,很簡單,那是人家寫的;網上都鼓勵你不要造輪子,為什么你還要造呢,因為,那不是你造的。

我不是要造spring,我只是想把自己學習spring的一些感想,一些心得說出來,希望大家看到有不對的地方,請一定不吝賜教。

說說我自己,13年小本畢業,軟件工程專業,校招去了最近瘋傳的牢廠總部里待了2年,15年越獄出來,某落魄互聯網公司(PC時代風頭無兩)待了1年,慨嘆深圳買房之艱難,遂於16年底回蓉。趁着熱血未冷,去了一家創業公司,9個月后,欠薪3月,靠刷信用卡還貸,不得不含淚辭職;17年投奔國企,目前從事公共安全相關工作,趁着對技術還有一腔熱情,沒事寫寫文章,目前主要興趣是:分布式、微服務等后端技術;對k8s等新技術保持關注;沒事參加一些線下技術活動,歡迎大家和我交流。

本系列的源碼講解思路

本來,我是想分享一些 spring cloud 的東西,但后來發現,我自己在讀spring cloud的過程中,有些東西也不是理解得很透徹,比如各種@Enable注解其實是用到了spring boot的東西,然后我覺得應該倒回去先看看spring boot,然后呢,看spring boot的過程中,發現spring 和 spring boot其實是一個深度融合,“你中有我,我中有你”的關系,比如spring boot啟動時,不是會連帶啟動spring 容器嗎,等等。

我就想着,干脆把spring boot系統研究一把算了,我在github上找了spring boot的工程,克隆到了碼雲上(速度要快得多),然后自己回退到了spring boot的第一個版本,時間大概是2013年4月,其實這第一個版本,基本的代碼也已經成型了,我就拿這個版本的源碼在本地idea里面看,配套的spring 版本是4.0.0,也還行。

我自己加了不少注釋在工程里,工程地址在這:

https://gitee.com/ckl111/spring-boot-first-version-learn

工程結構如下:

這個工程我也會一直維護着,我覺得,spring 4.0.0的版本,暫時對我閱讀代碼來說,足夠了,如果大家大概了解spring 每個版本的新特性的話,可以發現,spring 4.0開始,各種注解已經很完善了,現在雖然已經出到5.2版本了,但核心的東西也還是沒有變化,所以,對我們研讀源碼,影響不大。如果真的把這個版本能讀得差不多了,那想必對spring /spring boot的核心也理解差不多了,到時候再讀新版本的源碼也不遲,是吧。

總體來說:

spring boot 版本,2013年4月,first version。

配套的spring版本,4.0.0.BOOTSTRAP-SNAPSHOT

那時候的spring boot長什么樣子,我這邊給個地址(我這已經克隆到碼雲了)

https://gitee.com/ckl111/spring-boot/tree/fb6b2244707dd5dfad12d62cb6a3c396555270d1/

目前已更新的部分

曹工說Spring Boot源碼(1)-- Bean Definition到底是什么,附spring思維導圖分享

曹工說Spring Boot源碼(2)-- Bean Definition到底是什么,咱們對着接口,逐個方法講解

曹工說Spring Boot源碼(3)-- 手動注冊Bean Definition不比游戲好玩嗎,我們來試一下

曹工說Spring Boot源碼(4)-- 我是怎么自定義ApplicationContext,從json文件讀取bean definition的?

曹工說Spring Boot源碼(5)-- 怎么從properties文件讀取bean

曹工說Spring Boot源碼(6)-- Spring怎么從xml文件里解析bean的

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中得到了什么(上)

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中得到了什么(util命名空間)

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中得到了什么(context命名空間上)

曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中得到了什么(context:annotation-config 解析)

曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中得到了什么(context:component-scan完整解析)

曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)

曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎么和Spring Instrumentation集成

曹工說Spring Boot源碼(15)-- Spring從xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工說Spring Boot源碼(16)-- Spring從xml文件里到底得到了什么(aop:config完整解析【上】)

曹工說Spring Boot源碼(17)-- Spring從xml文件里到底得到了什么(aop:config完整解析【中】)

曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)

曹工說Spring Boot源碼(19)-- Spring 帶給我們的工具利器,創建代理不用愁(ProxyFactory)

曹工說Spring Boot源碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操作日志

曹工說Spring Boot源碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了

曹工說Spring Boot源碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什么了

曹工說Spring Boot源碼(23)-- ASM又立功了,Spring原來是這么遞歸獲取注解的元注解的

曹工說Spring Boot源碼(24)-- Spring注解掃描的瑞士軍刀,asm技術實戰(上)

曹工說Spring Boot源碼(25)-- Spring注解掃描的瑞士軍刀,ASM + Java Instrumentation,順便提提Jar包破解

曹工說Spring Boot源碼(26)-- 學習字節碼也太難了,實在不能忍受了,寫了個小小的字節碼執行引擎

曹工說Spring Boot源碼(27)-- Spring的component-scan,光是include-filter屬性的各種配置方式,就夠玩半天了

spring思維導圖(bean definition部分)

因為我這個系列,大概會按照思維導圖的流程來走,然后思維導圖太大了,我這里先直接貼前面這部分:

思維導圖完整鏈接:https://www.processon.com/view/link/5deeefdee4b0e2c298aa5596

我大概的講解思路也會是上面那樣,從上到下,每個點細細地講。

正文

bean definition是什么

閑言少敘,進入正題。第一講,先說說bean definition吧,這個東西,實在太重要了,核心的存儲結構啊。

大家可以再想一想,spring 當初剛出來的時候,主打的是ioc容器,容器里裝了啥呢,bean啊!bean是什么呢?

恩。。。我也不知道是啥,反正spring里拿出來的就是bean。

行,那bean有什么特征嗎?

哦,bean是一個對象,有名字,有class類型,有scope(單例、prototype那些),有role(屬於應用的bean、還是spring框架的bean),有是否延遲初始化(lazy-init),有它依賴的其他bean,如果這個bean不好造(不能直接反射生成的話),可能還有個工廠方法和工廠bean呢,哎,好像還說漏了,反正挺多的。

那是不是每個bean都有這些屬性呢?

仔細想想,好像是的吧。

img

既然都有這些東西,那這個東西感覺像是個模板了,就像是最近寫了年終總結,hr小姐姐就給我們發了模板,上面姓名啊、部門啊、職位啊、述職的基本格式啊,都是固定的,我們只要拿來,填上自己的信息就行了,那我們是不是可以抽象一下,搞個class啊,比如下面這樣:

package com.learn;

import lombok.Data;

@Data
public class SpringDefinition {

    /**
     * bean class名
     */
    String beanClassName;

    /**
     * 工廠bean的名稱
     */
    String factoryBeanName;

    /**
     * 工廠方法的名稱
     */
    String factoryMethodName;

    /**
     * singleton/prototype等
     */
    String scope;

    /**
     * 是否延遲初始化
     */
    boolean isLazyInit;

    /**
     * 依賴的bean
     */
    String[] dependsOn;

    /**
     * bean的角色,比如:1:框架;2:應用
     */
    int role;

    /**
     * 是否為主候選bean
     */
    boolean primary;

    ...其他屬性
}

實話說,是可以的,一些簡單的,輕量級ioc容器就是這么玩的,但是spring作為優秀代碼的代表,肯定不能這么low,接口的抽象性要好得多,方便我們替換不同的實現,該用接口來抽象,肯定要抽象為接口。

下邊我們就看看該接口,先看接口的描述:

* A BeanDefinition describes a bean instance, which has property values,
* constructor argument values, and further information supplied by
* concrete implementations.
*
* <p>This is just a minimal interface: The main intention is to allow a
* {@link BeanFactoryPostProcessor} such as {@link PropertyPlaceholderConfigurer}
* to introspect and modify property values and other bean metadata.
* @author Juergen Hoeller
* @author Rob Harrop
* @since 19.03.2004

這里說的是,bean definition描述一個bean實例的各種屬性,尤其聲明了:這是一個最小化接口,主要目的是允許bean factory后置處理器,對bean property和其他元數據進行修改。而且,這是2004年的接口,可想而知,是多么核心的api了。

再看看具體定義的方法,更好理解:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

	/**
	 * Return the name of the parent definition of this bean definition, if any.
	 */
	String getParentName();

	/**
	 * Set the name of the parent definition of this bean definition, if any.
	 */
	void setParentName(String parentName);

	/**
	 * Return the current bean class name of this bean definition.
	 * <p>Note that this does not have to be the actual class name used at runtime, in
	 * case of a child definition overriding/inheriting the class name from its parent.
	 * Hence, do <i>not</i> consider this to be the definitive bean type at runtime but
	 * rather only use it for parsing purposes at the individual bean definition level.
	 */
	String getBeanClassName();

	/**
	 * Override the bean class name of this bean definition.
	 * <p>The class name can be modified during bean factory post-processing,
	 * typically replacing the original class name with a parsed variant of it.
	 */
	void setBeanClassName(String beanClassName);

	/**
	 * Return the factory bean name, if any.
	 */
	String getFactoryBeanName();

	/**
	 * Specify the factory bean to use, if any.
	 */
	void setFactoryBeanName(String factoryBeanName);

	/**
	 * Return a factory method, if any.
	 */
	String getFactoryMethodName();

	/**
	 * Specify a factory method, if any. This method will be invoked with
	 * constructor arguments, or with no arguments if none are specified.
	 * The method will be invoked on the specified factory bean, if any,
	 * or otherwise as a static method on the local bean class.
	 * @param factoryMethodName static factory method name,
	 * or {@code null} if normal constructor creation should be used
	 * @see #getBeanClassName()
	 */
	void setFactoryMethodName(String factoryMethodName);

	/**
	 * Return the name of the current target scope for this bean,
	 * or {@code null} if not known yet.
	 */
	String getScope();

	/**
	 * Override the target scope of this bean, specifying a new scope name.
	 * @see #SCOPE_SINGLETON
	 * @see #SCOPE_PROTOTYPE
	 */
	void setScope(String scope);

	/**
	 * Return whether this bean should be lazily initialized, i.e. not
	 * eagerly instantiated on startup. Only applicable to a singleton bean.
	 */
	boolean isLazyInit();

	/**
	 * Set whether this bean should be lazily initialized.
	 * <p>If {@code false}, the bean will get instantiated on startup by bean
	 * factories that perform eager initialization of singletons.
	 */
	void setLazyInit(boolean lazyInit);

	/**
	 * Return the bean names that this bean depends on.
	 */
	String[] getDependsOn();

	/**
	 * Set the names of the beans that this bean depends on being initialized.
	 * The bean factory will guarantee that these beans get initialized first.
	 */
	void setDependsOn(String[] dependsOn);

	/**
	 * Return whether this bean is a candidate for getting autowired into some other bean.
	 */
	boolean isAutowireCandidate();

	/**
	 * Set whether this bean is a candidate for getting autowired into some other bean.
	 */
	void setAutowireCandidate(boolean autowireCandidate);

	/**
	 * Return whether this bean is a primary autowire candidate.
	 * If this value is true for exactly one bean among multiple
	 * matching candidates, it will serve as a tie-breaker.
	 */
	boolean isPrimary();

	/**
	 * Set whether this bean is a primary autowire candidate.
	 * <p>If this value is true for exactly one bean among multiple
	 * matching candidates, it will serve as a tie-breaker.
	 */
	void setPrimary(boolean primary);


	/**
	 * Return the constructor argument values for this bean.
	 * <p>The returned instance can be modified during bean factory post-processing.
	 * @return the ConstructorArgumentValues object (never {@code null})
	 */
	ConstructorArgumentValues getConstructorArgumentValues();

	/**
	 * Return the property values to be applied to a new instance of the bean.
	 * <p>The returned instance can be modified during bean factory post-processing.
	 * @return the MutablePropertyValues object (never {@code null})
	 */
	MutablePropertyValues getPropertyValues();


	/**
	 * Return whether this a <b>Singleton</b>, with a single, shared instance
	 * returned on all calls.
	 * @see #SCOPE_SINGLETON
	 */
	boolean isSingleton();

	/**
	 * Return whether this a <b>Prototype</b>, with an independent instance
	 * returned for each call.
	 * @see #SCOPE_PROTOTYPE
	 */
	boolean isPrototype();

	/**
	 * Return whether this bean is "abstract", that is, not meant to be instantiated.
	 */
	boolean isAbstract();

	/**
	 * Get the role hint for this {@code BeanDefinition}. The role hint
	 * provides tools with an indication of the importance of a particular
	 * {@code BeanDefinition}.
	 * @see #ROLE_APPLICATION
	 * @see #ROLE_INFRASTRUCTURE
	 * @see #ROLE_SUPPORT
	 */
	int getRole();

	/**
	 * Return a human-readable description of this bean definition.
	 */
	String getDescription();

	/**
	 * Return a description of the resource that this bean definition
	 * came from (for the purpose of showing context in case of errors).
	 */
	String getResourceDescription();

	/**
	 * Return the originating BeanDefinition, or {@code null} if none.
	 * Allows for retrieving the decorated bean definition, if any.
	 * <p>Note that this method returns the immediate originator. Iterate through the
	 * originator chain to find the original BeanDefinition as defined by the user.
	 */
	BeanDefinition getOriginatingBeanDefinition();

}

大家仔細看看,是不是其實和我們定義的class差不多呢,主要都是一些get/set方法。里面的字段呢,下一講我們詳細講解一下,會結合一些融會貫通的地方。

bean definition接口的實現有哪些

然后我們看看這個接口有哪些實現吧?

可以看到,這里有兩個是我標紅了,因為他們特殊,特殊在他們不屬於spring-beans包,而是在spring-context包里。后邊遇到了我們再單說,這里存疑。

再來看看這個接口的繼承圖:

可以獲取注解信息的子接口AnnotatedBeanDefinition

我們看到,這個接口有一個子接口,是AnnotatedBeanDefinition。這個接口定義如下:

/**
 * Extended {@link org.springframework.beans.factory.config.BeanDefinition}
 * interface that exposes {@link org.springframework.core.type.AnnotationMetadata}
 * about its bean class - without requiring the class to be loaded yet.
 * 這個接口擴展了BeanDefinition,可以獲得bean definition中的bean class上的注解元數據。
 * 舉個例子,假設我們用@controller標注了某個類,那這里就能獲取到@controller這個注解里面的信息
 *
 * @author Juergen Hoeller
 * @since 2.5
 * @see AnnotatedGenericBeanDefinition
 * @see org.springframework.core.type.AnnotationMetadata
 */
public interface AnnotatedBeanDefinition extends BeanDefinition {

	/**
	 * Obtain the annotation metadata (as well as basic class metadata)
	 * for this bean definition's bean class.
	 * @return the annotation metadata object (never {@code null})
	 */
	AnnotationMetadata getMetadata();

}

可以想一想有什么用,這個接口能取到bean definition中對應bean class上標注的注解元數據。

比如下面的controller舉例:

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

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any
	 */
	String value() default "";

}

那這個AnnotatedBeanDefinition就能取到controller中的value字段的值。

我這里也寫了個簡單的例子,如下:

@Component("testService")
public class HelloWorldService {

}
	@Autowired
	private ApplicationContext applicationContext;

	@Override
	public void run(String... args) {
		DefaultListableBeanFactory beanFactory =
				(DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
         // 獲取bean definition,然后獲取其注解元數據 
		AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanFactory.getBeanDefinition("testService");
		AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
		Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes("org.springframework.stereotype.Component");
		log.info("annotationAttributes:{}",annotationAttributes);
	}

我這邊打印出來就是:

信息: annotationAttributes:{value=testService}

代碼在

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-bootstrap-bean-definition-demo

接口下的實現類

仔細看了兩個接口 AnnotatedBeanDefinitionBeanDefinition,其實實現類都是差不多那幾個。

基本上org.springframework.beans.factory.support.AbstractBeanDefinition充當了基本的實現,基本上,該實現的方法都實現了,除了一個:

	/**
	 * Clone this bean definition.
	 * To be implemented by concrete subclasses.
	 * @return the cloned bean definition object
	 */
	public abstract AbstractBeanDefinition cloneBeanDefinition();

趕緊這個方法,對我們分析也沒多大幫助,暫時跳過即可。

再看看org.springframework.beans.factory.support.GenericBeanDefinition,感覺很重要,我們看看:

public class GenericBeanDefinition extends AbstractBeanDefinition {

	private String parentName;


	/**
	 * 這里有點意思,類似於builder模式,先生成一個實例,再自己各種set方法設置相關屬性
	 * 
	 * Create a new GenericBeanDefinition, to be configured through its bean
	 * properties and configuration methods.
	 * @see #setBeanClass
	 * @see #setBeanClassName
	 * @see #setScope
	 * @see #setAutowireMode
	 * @see #setDependencyCheck
	 * @see #setConstructorArgumentValues
	 * @see #setPropertyValues
	 */
	public GenericBeanDefinition() {
		super();
	}
	...

	@Override
	public AbstractBeanDefinition cloneBeanDefinition() {
		return new GenericBeanDefinition(this);
	}

}

ok,都這么簡單的話,就再看兩個,spring beans包中的org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition:

public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {

	private final AnnotationMetadata metadata;


	/**
	 * Create a new AnnotatedGenericBeanDefinition for the given bean class.
	 * @param beanClass the loaded bean class
	 */
	public AnnotatedGenericBeanDefinition(Class<?> beanClass) {
		setBeanClass(beanClass);
		this.metadata = new StandardAnnotationMetadata(beanClass, true);
	}

	/**
	 * Create a new AnnotatedGenericBeanDefinition for the given annotation metadata,
	 * allowing for ASM-based processing and avoidance of early loading of the bean class.
	 * Note that this constructor is functionally equivalent to
	 * {@link org.springframework.context.annotation.ScannedGenericBeanDefinition
	 * ScannedGenericBeanDefinition}, however the semantics of the latter indicate that
	 * a bean was discovered specifically via component-scanning as opposed to other
	 * means.
	 * @param metadata the annotation metadata for the bean class in question
	 * @since 3.1.1
	 */
	public AnnotatedGenericBeanDefinition(AnnotationMetadata metadata) {
		Assert.notNull(metadata, "AnnotationMetadata must not be null");
		setBeanClassName(metadata.getClassName());
		this.metadata = metadata;
	}


	public final AnnotationMetadata getMetadata() {
		 return this.metadata;
	}

}

很簡單,就是多了獲取bean class的注解的功能。

再看這個呢,

/**
 * Extension of the {@link GenericBeanDefinition}
 * class, based on an ASM ClassReader, with support for annotation metadata exposed
 * through the {@link AnnotatedBeanDefinition} interface.
 *
 * <p>This class does <i>not</i> load the bean {@code Class} early.
 * It rather retrieves all relevant metadata from the ".class" file itself,
 * parsed with the ASM ClassReader. It is functionally equivalent to
 * {@link AnnotatedGenericBeanDefinition#AnnotatedGenericBeanDefinition(AnnotationMetadata)}
 * but distinguishes by type beans that have been <em>scanned</em> vs those that have
 * been otherwise registered or detected by other means.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 2.5
 * @see #getMetadata()
 * @see #getBeanClassName()
 * @see org.springframework.core.type.classreading.MetadataReaderFactory
 * @see AnnotatedGenericBeanDefinition
 */
@SuppressWarnings("serial")
public class ScannedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {

	private final AnnotationMetadata metadata;


	/**
	 * Create a new ScannedGenericBeanDefinition for the class that the
	 * given MetadataReader describes.
	 * @param metadataReader the MetadataReader for the scanned target class
	 */
	public ScannedGenericBeanDefinition(MetadataReader metadataReader) {
		Assert.notNull(metadataReader, "MetadataReader must not be null");
		this.metadata = metadataReader.getAnnotationMetadata();
		setBeanClassName(this.metadata.getClassName());
	}


	public final AnnotationMetadata getMetadata() {
		return this.metadata;
	}

}

我一開始,一眼看過去,感覺眼花了,差不多啊,但這個是在spring context包里,然后,可以看上面的注釋,說是使用asm去獲取注解信息。所以,這個和上面那個的差別是:

org.springframework.context.annotation.ScannedGenericBeanDefinition 位於spring-context,使用asm

org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition 位於spring-beans,使用反射

再看看org.springframework.beans.factory.support.RootBeanDefinition(位於spring-beans),這個類下面只有一個子類,位於spring-context的

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition

這兩個類,一看就比較特別,能看出來,和@configuration注解有莫大關系,這個我們放后面講。

總結

本篇就先到這里,留了一些問題,放到后面(有些我也要查一下,哈哈)。下一講繼續。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM