上一篇文章我們講解了 Spring 中
<bean>
標籤是怎麼被解析成 BeanDefinition 的。而如果不是<bean>
、<import>
、<beans>
、<alias>
這四類的其他標籤又是怎麼被解析成BeanDefinition
的呢?比如本篇要講的 context 相關的標籤是怎麼被解析成BeanDefinition
的。比如常用的<context:component-scan>
標籤,再比如<context:property-placeholder>
標籤是怎麼被處理的。本文以<context:component-scan>
標籤爲例講解,我提供了測試例子。感興趣的同學把<context:property-placeholder>
標籤的解析流程看看,基本流程差不多。接下來我們就來觀摩一下這些標籤被解析的全流程吧。
在文章開始前,還是老樣子,先提供一個測試的例子。方便同學們看完講解之後自己去 Debug 源碼。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 首先,我們先在applicationContext.xml文件裏面加上context標籤 -->
<!-- 同時我們在base-package指定的路徑下創建兩個類,分別是接口UserService,和其對應的實現類UserServiceImpl -->
<!-- 這裏給大家留個小思考,就是我們的文件頭裏面的內容,爲啥會有這麼一大堆東西?我在下篇自定義標籤中來揭曉答案 -->
<context:component-scan base-package="com.xzhao.service"/>
</beans>
提供好測試用例之後,我們就來看看其源碼是怎麼做的。直接來到上篇文章的開頭,即
DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()
方法。因爲<context:component-scan>
標籤不是DefaultNameSpace
的,所以會走delegate.parseCustomElement()
方法。那我們就來看看對於這種element
它是怎麼被解析的。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// '<context:component-scan>'會走這裏。
// 當然 '<context:property-placeholder>'標籤也是走這裏。
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
進去之後,解析
element
的實現邏輯如下。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// Step1:根據element獲取對應的namespaceuri,
// 根據這個我們才能獲得其相應的handle,即具體的解析處理器
// 其實在這裏返回的值長這個樣子:http://www.springframework.org/schema/context
// 相信如果仔細的同學應該不會這玩意兒陌生
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// Step2:這裏會首先拿到一個命名空間解析器,即NamespaceResolver對象
// 然後利用NamespaceResolve對象去解析NamespaceUri,從而就得到了一個非常重要的對象
// 命名空間處理器 NamespaceHandler 對象,其實正是這個對象去把element對象解析成BeanDefinition定義的。
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// Step3:利用的得到的NamespaceHandler去調用其parse方法,
// 即可獲得 '<context:component-scan>'標籤對應的BeanDefinition對象了
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
接下來大家應該會想,那麼多不同的標籤,我們應該找那個解析器來解析這個標籤呢?所以問題來了,怎麼獲得解析器?在獲得解析器之前首先要先拿到一個叫命名空間解析器(
namespaceHandlerResolve
),這個概念好像不太好理解,我截了個圖,大概就長這個樣子。如下:
有了命名空間解析器之後,我們來看看是怎麼獲得命名空間處理器的。
public NamespaceHandler resolve(String namespaceUri) {
// Step1:獲取所有的handlerMappings,即上圖紅框裏面的內容
// 其中key爲一個http鏈接,比如:http://www.springframework.org/context
// 這個key也就是namespaceUri了,而對應的value其實是一個handler,仔細看看其實就是一個class的全路徑名字
Map<String, Object> handlerMappings = getHandlerMappings();
// Step2:獲取我們指定的handler名字,如果取到不到則直接返回null
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
// Step3:由於'<context:component-scan>'標籤對應的namespaceUri是 http://www.springframework.org/context
// 所以獲取到的一定是className,即是一個字符串對象。
// 如果在一次解析的時候可能已經存在的是其對應的實例對象了,因爲每解析完一個標籤,則會將當前key的value替換成對象
// 所以就會在此處直接返回
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
// Step4:所以將className直接強轉爲String
String className = (String) handlerOrClassName;
try {
// Step4.1:利用反射機制獲取到className對應的Class對象
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
// Step4.2:檢查handlerClass是否是實現了NameSpaceHandler接口,從接口的繼承樹可以看出
// 所以的handler都是直接或間接的實現了NamespaceHandler接口,從而實現了 init() 、parse() 、decorate() 幾個非常重要的方法
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// Step4.3:根據Class實例化出對應的namespaceHandler對象,即獲取到了我們想要的命名空間解析器了
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// Step4.4:執行初始化操作,這一步非常非常重要,
// 點到 namespaceHandler 對應的Handler類裏面,我們會發現一些非常眼熟的東西
// 比如此處對應的handler類是ContextNamespaceHandler類。
// 一個小常識,一般標籤以什麼開頭,其對應的handler就是標籤開頭命名的
namespaceHandler.init();
// Step4.5:將初始化完成的handler對象放到handlerMapppings裏面
// 這樣做是爲了當在有context標籤需要解析的時候,則直接從該map裏面獲取即可,而不需要在重複創建了,相當於充當了一個本地緩存的角色,加速解析工作
handlerMappings.put(namespaceUri, namespaceHandler);
// Step4.6:將創建好的namespaceHandler對象返回
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
// 這裏簡單看下 ContextNamespaceHandler類的init方法幹了啥
// 其實非常簡單,就是將標籤以 <context:xxx> 開頭的各種標籤進行一個註冊操作
// 這裏面大家應該比較熟悉的標籤 'property-placeholder','component-scan'
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
// 註冊操作也比較簡單,就是將標籤對應的真正解析器放到其父類(NamespaceHandlerSupport類)的parse map中,方便後面開始具體的解析工作
// 從代碼也可以看出,'<context:component-scan>'標籤對應的真正解析器其實是 ComponentScanBeanDefinitionParser
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
到此,我們拿到了命名空間解析器了,接下來看看具體的解析工作是怎麼做的。
// 上面我們講到,將相關的解析器全部註冊到了NamespaceHandlerSupport類的parse map裏面
// 所以解析工作也肯定是先調用NamespaceHandlerSupport的parse方法獲取到對應的解析器
// 然後在調用具體解析器的parse方法執行解析工作獲得BeanDefinition定義
public BeanDefinition parse(Element element, ParserContext parserContext) {
// Step1:獲取解析器對象,具體怎麼找的,就不展開了,很簡單,
// 想想就是無非先拿到標籤的名字,然後從parse map裏面get得到對應的解析器
// 我們重點關注下具體的解析工作,因爲所有實現都是在具體的解析類的方法裏面實現的
BeanDefinitionParser parser = findParserForElement(element, parserContext);
// Step2:根據解析器對象調用其對應的parse方法,執行具體的解析工作,
// 從而得到BeanDefinition定義對象
return (parser != null ? parser.parse(element, parserContext) : null);
}
我們來看看
<context:component-scan>
標籤對應的解析器,即ComponentScanBeanDefinitionParse
類的parse
方法的實現。這裏有個小技巧,一般標籤名叫什麼,相應的 parse 類的名字也是與其對應的,大家閱讀別的標籤的源碼的時候可以注意下,方便找到對應的解析類。
public BeanDefinition parse(Element element, ParserContext parserContext) {
// Step1:獲取 'base-package' 屬性對應的值
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
// Step2:將獲取到的basePackage做了一些字符串的處理轉換工作,比如常見的那種佔位符
// 說白了就是字符串的一些替換操作,得到一個spring認爲的標準的字符串對象
// 感興趣的朋友,自行了解相應的內容,這裏不做深入探討
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
// Step3:字符串切割,這裏我做了個測試,按照規則寫了一個字符串,可以切割出多個子串
// 我猜想他的意思應該是base-package可以同時指定多個包路徑,
// 之前沒有做過同時指定多個包路徑的操作,大佬們見笑了
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
// Step4:註釋寫的也比較明朗,就是首先獲得一個scanner,即掃描器,
// 然後拿着掃描器去挨個掃描指定包下的類,從而得到多個BeanDefinition對象
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
// Step5:得到了多個BeanDefinition定義,又來到了比較熟悉的步驟,註冊這些BeanDefinition定義到BeanFactory裏面
// 到此,我們關於 '<context:component-scan>'標籤解析成對應的BeanDefinition對象也就講完了
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
接着來看看掃描器是怎麼被創建出來的,其實這個裏面就是對配置文件的相關屬性進行解析賦值操作。
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
// Step1:判斷當前scanner是否使用默認的過濾器
boolean useDefaultFilters = true;
if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
}
// Delegate bean definition registration to scanner class.
// Step2:將BeanDefinition的構建委託給掃描器去創建
// 創建一個scanner類,使用的是 ClassPathBeanDefinitionScanner 作爲掃描器
ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
// Step3:設置BeanDefinition的一些默認屬性
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
// Step4:配置w恩建如果指定了 'resource-pattern',則設置
if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
}
try {
// Step5:解析 'name-generator' 屬性
parseBeanNameGenerator(element, scanner);
}
catch (Exception ex) {
parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
}
try {
// Step6:解析 'scope-resolver' 屬性
parseScope(element, scanner);
}
catch (Exception ex) {
parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
}
// Step7:解析 'include-filter' 屬性
parseTypeFilters(element, scanner, parserContext);
// Step8:返回初始化好的scanner對象,其實這個方法裏面是對標籤相關屬性的解析和設置操作
return scanner;
}
得到
ClassPathBeanDefinitionScanner
掃描器之後,開始使用掃描器去執行真正的掃描工作了,我們來看下具體實現。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// Step1:用於存放掃描到的類對應的BeanDefinition定義
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// Step2:遍歷所有指定的掃描包
for (String basePackage : basePackages) {
// Step2.1:查詢當前掃描包下的所有候選BeanDefinition定義
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// Step2.2:遍歷每個候選的BeanDefinition
for (BeanDefinition candidate : candidates) {
// Step2.2.1:獲取scope的元數據信息,我們通常用的有 'signleton' 和 'property'。
// 而 'signleton'也是默認值。
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// Step2.2.2:獲取候選的BeanDefinition對應的beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// Step2.2.3:如果當前的候選BeanDefinition實現了AbstractBeanDefinition,
// 則執行BeanDefinition的後置處理操作
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// Step2.2.4:如果候選的BeanDefinition實現了AnnotatedBeanDefinitio,
// 則執行註解的通用邏輯,通常對BeanDefinition設置一些常用參數,
// 比如是否是懶加載,是否有 depends-on(依賴),role(角色),description(描述)這四個屬性
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// Step2.2.5:檢查候選的BeanDefinition
if (checkCandidate(beanName, candidate)) {
// Step2.2.5.1:將BeanDefinition封裝成BeanDefinitionHolder對象
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// Step2.2.5.2:獲取代理的BeanDefinitionHolder對象
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// Step2.2.5.3:將最終的BeanDefinition註冊到BeanFactory中,完成BeanDefinition定義的解析工作
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上面這個方法裏面幾個比較重要的方法我們來重點看下具體實現。
indCandidateComponents
: 獲取指定包下所有的候選BeanDefinition
對象postProcessBeanDefinition
:BeanDefinition
對象的後置操作checkCandidate
: 檢查候選的BeanDefinition
是否可以被註冊到BeanFactory
中
// 首先來看第一個方法的,如何獲取指定包路徑下的候選BeanDefinition定義
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// Step1:componentsIndex不爲null && 索引如果有includeFilters,
// 則根據type去獲取BeanDefinition定義,沒有遇到過這種使用方式。
// 所以這個地方請求大神賜教
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// Step2:我們通常的使用方式是這種
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// Step1:獲取要掃描的包下的那些類,比如根據我們的配置可以得到:
// classpath*:com/xzhao/service/**/*.class
// 指定的service下的任何子包和其下面的class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// Step2:根據指定的classpath,獲取對應下面的所有resource
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
// Step3:遍歷resource,根據resource創建BeanDefinition定義,即候選的BeanDefinition
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
// Step3.1:獲取resource對應的元數據讀取器
// matadataReader包含了三部分內容,分別是resource,簡單理解就是class的位置
// 第二部分是class的元數據,主要包含了classLoader,className(com.xzhao.service.UserService)等等信息
// 第三部分是註解的元數據,和class的元數據差不多
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// step3.2:檢查當前元數據是否可以創建BeanDefinition定義,
// 主要是過濾掉不包含(即exclude)的class,返回false
// 保留include的class,返回true
if (isCandidateComponent(metadataReader)) {
// Step3.2.1:創建BeanDefinition定義,ScannedGenericBeanDefinition 間接實現了BeanDefinition接口,
// 這裏創建是一個掃描的BeanDefinition定義
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// Step3.2.2:檢測候選bean是否合法,
// 即候選bean(是否是合法的 && 一個具體的bean(感覺可以歷程就是是否可以實例化出bean實例)) || (是一個抽象的 && 同時存在註解方法))
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
// Step3.2.2.1:檢測合法,就作爲一個候選的BeanDefinition定義。
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
// 在接着看第二方法,這個方法就比較簡單了,就這麼簡單,大家可以稍微鬆口氣
// 它的作用就是給設置一些通用的屬性
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
}
}
// 在看最後一個方法,該方法用於最後一步檢測當前的BeanDefinition是否合法可以被註冊到BeanFactory裏面
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
// Step1:當前BeanDefinition是否已經在BeanFactory裏面註冊過,如果沒有則可以正常註冊
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
// Step2:如果已經在BeanFactory裏面註冊了,
// 則獲取已經註冊了的BeanDefinition定義,然後嘗試獲取存在的BeanDefinition的父BeanDefinition
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// Step3:拿已經存在的BeanDefinition和將要註冊的BeanDefinition比較
// 已存在的實現了ScannedGenericBeanDefinition則可以覆蓋
// 或者已存在的BeanDefinition的source != 新的BeanDefinition的source,則也可以創建
// 或者已存在的BeanDefinition != 新的BeanDefinition,則也可以創建
// 以上三種情況下,可以將新創建的BeanDefinition註冊到BeanFactory裏面
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() + "]");
}
總結
至此,我們就完成了<context:component-scan>
標籤掃描類得到BeanDefinitions
定義,並將得到的BeanDefinitions
依次註冊到BeanFactory
裏面。我們只需要在被註冊的類上設置@Component @Service @Controller @Repository
這種註解就可以被<component-scan>
標籤掃描到。
總結一下就是先拿到指定的包路徑,然後讀取該路徑下的所有被聲明瞭註解的類,然後拿到這些類的元數據,再然後根據這些元數據創建出ScannedGenericBeanDefinition
對象,最後將符合條件的ScannedGenericBeanDefinition
註冊到BeanFactory
裏面。就完成了改標籤的工作。最後,本篇文章篇幅較長,需要慢慢理解其中的邏輯,不明白的地方就多看幾遍在理解。到此我們知道了 spring 提供的自定義的標籤是如何解析成BeanDefinition
定義的。
下一篇我們來介紹如何自己定義一個自定義的標籤,希望大家和我一起堅持下去。
歡迎關注我,共同學習