前言
本節將正式介紹Spring原始碼細節,將講解Bean生命週期。請注意,雖然我們不希望過於繁瑣地理解Spring原始碼,但也不要認為Spring原始碼很簡單。在本節中,我們將主要講解Spring 5.3.10版本的原始碼。如果您看到的程式碼與我講解的不同,也沒有關係,因為其中的原理和業務邏輯基本相同。為了更好地理解,我們將先講解Bean的生命週期,再講解Spring的啟動原理和流程,因為啟動是準備工作的一部分。
題外話
目前在該版本中,引入了一個名為jfr的JDK技術,類似於Java飛行日誌(JFL),也稱為飛行資料記錄器(Black Box)技術。具體作用不再詳細闡述,讀者可以參考此文:JFR介紹
如果您看到以下程式碼,請直接跳過,因為它並沒有太大的作用:
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
// 額外會建立StandardEnvironment
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
需要注意的是,其中的 StartupStep
關於預設實現並沒有什麼實際作用。但是,還有一種實現方式是 FlightRecorderStartupStep
,它是JDK的JFR技術。
Bean的生成過程
生成BeanDefinition
BeanDefinition的作用大家基本透過前面的文章也知道了大概,就是用來描述bean的。
那麼它是如何載入的呢?首先我們看一下 ClassPathBeanDefinitionScanner
類,它是用於掃描的。其中有一個屬性是 BeanDefinitionRegistry
,即Bean定義的註冊類。預設實現是 DefaultListableBeanFactory
,但是在 ClassPathBeanDefinitionScanner
類中並沒有直接使用該類作為屬性,而是使用了它的父介面 BeanDefinitionRegistry
。這是因為 ClassPathBeanDefinitionScanner
類實際上並沒有使用 BeanDefinitionRegistry
介面中的許多方法來註冊Bean定義。
接下來,我們來分析 ClassPathBeanDefinitionScanner
類的 scan
方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
// 解析@Lazy、@Primary、@DependsOn、@Role、@Description
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 檢查Spring容器中是否已經存在該beanName
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 註冊
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
大致邏輯如下:
- 獲取掃描包路徑
- findCandidateComponents:獲取符合條件的bean
- 遍歷candidate(候選bean),由於第二部使用了ASM技術,所以並沒有真正獲取beanclass而是使用了beanname替代所以,遍歷的做法就是將符合條件的bean定義進行註冊。
- scopeMetadata解析scope註解。
- beanNameGenerator構建當前bean的唯一名字。
- postProcessBeanDefinition這裡其實就是進行預設值賦值。
- processCommonDefinitionAnnotations進行解析@Lazy、@Primary、@DependsOn、@Role、@Description
- checkCandidate(beanName, candidate)再次檢查是否該beanName已經註冊過。
- registerBeanDefinition,註冊到我們的DefaultListableBeanFactory的BeanDefinitionMap中。
其實這裡基本就已經大概瞭解 的差不多了,然後再繼續講解下每一個流程裡面都走了那些邏輯:
findCandidateComponents
其主要邏輯會進入如下原始碼:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 獲取basePackage下所有的檔案資源
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// excludeFilters、includeFilters判斷
if (isCandidateComponent(metadataReader)) { // @Component-->includeFilters判斷
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
//此處省略部分程式碼
......
}
return candidates;
}
讓我們來深入瞭解一下Bean掃描的具體細節。以下是主要流程:
- 獲取basePackage下所有的檔案資源。以下分析註解資訊等都是用到了ASM技術,並沒有真正的去載入這個類。
- isCandidateComponent(metadataReader),進行判斷是否當前類具有@component註解。
- isCandidateComponent(sbd),進行判斷是否當前類屬於內部類、介面、抽象類
- 符合上述條件則會加入到bean定義候選集合中。
ASM技術這裡不做多解釋,主要看下Spring是如何進行判斷校驗當前bean是否符合條件的,第一個 isCandidateComponent(metadataReader)方法:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 符合includeFilters的會進行條件匹配,透過了才是Bean,也就是先看有沒有@Component,再看是否符合@Conditional
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
那麼includeFilters預設會在啟動AnnotationConfigApplicationContext時就會預設註冊一個解析Component註解的filter,程式碼如下:
protected void registerDefaultFilters() {
// 註冊@Component對應的AnnotationTypeFilter
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
如果符合過濾條件,那麼他就會開始生成最初的bean定義:
public ScannedGenericBeanDefinition(MetadataReader metadataReader) {
Assert.notNull(metadataReader, "MetadataReader must not be null");
this.metadata = metadataReader.getAnnotationMetadata();
// 這裡只是把className設定到BeanDefinition中
setBeanClassName(this.metadata.getClassName());
setResource(metadataReader.getResource());
}
那麼就剩下最後的校驗了:isCandidateComponent(sbd);再來看看他的作用子什麼:
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
看完一臉懵,那我們就好好解釋一下每個判斷都是什麼意思吧:
- metadata.isIndependent():是否當前類為內部類,眾說周知java語言編譯內部類的時候會產生兩個class檔案,比如下面這樣:
那麼這個Member內部類是不會被Spring作為單獨的類去掃描的。除非也加上@component註解,並且為static內部類 - metadata.isConcrete():這個類就是判斷下是否是介面還是抽象類
- metadata.hasAnnotatedMethods(Lookup.class.getName()):判斷是否有方法是帶有Lookup註解的,Lookup註解工作中用到的確實有些少,我在這裡簡要說明下:比如我們註冊給Spring的bean都是單例,但是如果我們有多例的bean被一個單例的bean所依賴的話,一次屬性只能注入一次,也打不到多例的效果,這時候就可以用Lookup註解實現了,比如這樣:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//UserService 為單例
UserService bean = applicationContext.getBean(UserService.class);
bean.test();
bean.test();
@Component
public class UserService {
@Autowired
private User user;
public void test(){
System.out.println(user);
}
}
@Component
@Scope("prototype")
public class User {
}
如果這樣執行的話,你永遠拿不到多例的User類,因為UserService 在屬性依賴注入的時候已經做完了賦值,每次呼叫拿到的都是同一個物件,那如果不這麼做可不可以,當然可以:比如這樣改下UserService 類:
@Component
public class UserService {
@Autowired
private User user;
public void test(){
System.out.println(get());
}
@Lookup
public User get(){
return null;
}
}
至於為什麼這裡返回null,後續在進行講解,只要知道他的註解的作用即可。到此我們終於解決完了findCandidateComponents方法找出了符合條件的bean定義。
generateBeanName
獲取當前bean的beanname,原始碼如下:
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// 獲取註解所指定的beanName
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
buildDefaultBeanName構建註解我們寫 beanname這個不難理解,如果沒有那麼就會走預設的構建,那麼這裡是jdk提供的,有個點需要注意下如果首字母和第二個字母都是大寫那麼名字直接return,如果你寫的是ABtest,那麼beanname就是ABtest:
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
postProcessBeanDefinition
這裡主要是進行設定BeanDefinition的預設值,直接看原始碼就能看懂:
public void applyDefaults(BeanDefinitionDefaults defaults) {
Boolean lazyInit = defaults.getLazyInit();
if (lazyInit != null) {
setLazyInit(lazyInit);
}
setAutowireMode(defaults.getAutowireMode());
setDependencyCheck(defaults.getDependencyCheck());
setInitMethodName(defaults.getInitMethodName());
setEnforceInitMethod(false);
setDestroyMethodName(defaults.getDestroyMethodName());
setEnforceDestroyMethod(false);
}
processCommonDefinitionAnnotations
這一步主要時進行解析類上的註解,看原始碼也可以基本看懂,沒有太多繞的邏輯,如下展示:
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
checkCandidate
這一步主要是檢查是否我們的bean定義map註冊中已經存在了,不過我們工作中基本上都會透過,如果存在多個那會拋異常:
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// 是否相容,如果相容返回false表示不會重新註冊到Spring容器中,如果不衝突則會拋異常。
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}
可是你像我這樣下面寫的話就不會出現異常,但是工作中肯定也不會這麼用,這裡只做展示,AppConfig和AppConfig1都是對同樣的配置,會對同一個包路徑掃描兩次:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.register(AppConfig1.class);
applicationContext.refresh();
UserService bean = applicationContext.getBean(UserService.class);
bean.test();
registerBeanDefinition
那麼最後當前的bean定義正式生成,並註冊到我們之前經常說的DefaultListableBeanFactory的Map<String, BeanDefinition> beanDefinitionMap屬性。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
合併bean定義
這個是非常重要的一個步驟,所以單獨拿出來說下,這個步驟也是獲取bean之前的最後一個對bean定義做修改的地方,getMergedLocalBeanDefinition(beanName);透過beanname來獲取合併後的bean定義,這是什麼意思呢?看下原始碼:
protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
RootBeanDefinition previous = null;
// Check with full lock now in order to enforce the same merged instance.
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
if (mbd == null || mbd.stale) {
previous = mbd;
if (bd.getParentName() == null) {
// Use copy of given root bean definition.
if (bd instanceof RootBeanDefinition) {
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
else {
mbd = new RootBeanDefinition(bd);
}
}
else {
// Child bean definition: needs to be merged with parent.
// pbd表示parentBeanDefinition
BeanDefinition pbd;
try {
String parentBeanName = transformedBeanName(bd.getParentName());
if (!beanName.equals(parentBeanName)) {
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
else {
......
}
}
}
catch (NoSuchBeanDefinitionException ex) {
......
}
// Deep copy with overridden values.
// 子BeanDefinition的屬性覆蓋父BeanDefinition的屬性,這就是合併
mbd = new RootBeanDefinition(pbd);
mbd.overrideFrom(bd);
}
// Set default singleton scope, if not configured before.
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(SCOPE_SINGLETON);
}
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}
if (previous != null) {
copyRelevantMergedBeanDefinitionCaches(previous, mbd);
}
return mbd;
}
}
為什麼需要進行合併bean定義,因為每個bean都會可能被其他宣告bean所引用,因為我們在工作中用到的都是註解形式,所以很少注意,我們看下Spring是xml時代時寫的宣告bean:
<bean id="user1" class="com.xiaoyu.service.User" scope="prototype"/>
<bean id="user2" class="com.xiaoyu.service.User" parent="user1"/>
然後我們在看下合併bean定義的原始碼邏輯:
- 判斷是否有parent,沒有的話就正常包裝原始bean定義為RootBeanDefinition。
- 如果有parent,那麼在判斷其父類是否還有父類,如果有則遞迴合併bean定義方法。
- 最關鍵的其實是這個程式碼:先以父類的bean定義生成RootBeanDefinition,然後如果子類定義了某個屬性的話那就覆蓋父類的bean定義。
mbd = new RootBeanDefinition(pbd); mbd.overrideFrom(bd);
public void overrideFrom(BeanDefinition other) {
if (StringUtils.hasLength(other.getBeanClassName())) {
setBeanClassName(other.getBeanClassName());
}
if (StringUtils.hasLength(other.getScope())) {
setScope(other.getScope());
}
setAbstract(other.isAbstract());
if (StringUtils.hasLength(other.getFactoryBeanName())) {
setFactoryBeanName(other.getFactoryBeanName());
}
if (StringUtils.hasLength(other.getFactoryMethodName())) {
setFactoryMethodName(other.getFactoryMethodName());
}
setRole(other.getRole());
setSource(other.getSource());
copyAttributesFrom(other);
//此處省略部分程式碼
......
}
- 最後將包裝的bean定義放入mergedBeanDefinitions合併定義的map中。
現在我們的的Spring中 了兩個bean定義Map,那麼在啟動時進行建立bean時用到的都是合併後的bean定義map。
結語
那麼現在Spring的Bean定義環節就基本講解完畢了。其實最主要的是Spring是如何判斷是否將Bean定義加入,並生成Bean定義,以及最後如何使用合併的Bean定義來包裝原始的Bean定義。下一節我們將開始講解Spring的Bean例項化。