寫在前面的話&&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都有這些屬性呢?
仔細想想,好像是的吧。
既然都有這些東西,那這個東西感覺像是個模板了,就像是最近寫了年終總結,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}
代碼在
接口下的實現類
仔細看了兩個接口 AnnotatedBeanDefinition
和BeanDefinition
,其實實現類都是差不多那幾個。
基本上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注解有莫大關系,這個我們放后面講。
總結
本篇就先到這里,留了一些問題,放到后面(有些我也要查一下,哈哈)。下一講繼續。