<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
寫在前面
注:本文章使用的 SpringBoot 版本為 2.2.4.RELEASE,其 Spring 版本為 5.2.3.RELEASE
雖然Bean的建立可以採用BeanDefinition
API 也可以直接採用註解方式,但從學習角度出發這裡主要以API形式建立Bean。下面以一段Bean建立的範例來引出討論的議題。
public class XmlBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("載入的BeanDefinition個數為:" + beanDefinitions); //User就是普通的POJO類 這裡不再給出User定義 User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.wojiushiwo.dto.User"> <property name="name" value="wojiushiwo"/> <property name="age" value="18"/> </bean> </beans>
上面程式碼 是使用BeanDefinitionReader
去載入XML檔案中的Bean定義
先來看一下BeanDefinitionReader
的介面定義以及繼承關係
通過上面的類圖我們發現BeanDefinitionReader
有三個實現類,可以看出針對BeanDefiniiton
的不同載體 均提供瞭解析手段,有XML形式的、有Properties形式的等等。
public interface BeanDefinitionReader { //返回註冊了當前BeanDefinition的 BeanFactory BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); //BeanName 生成器,預設是DefaultBeanNameGenerator BeanNameGenerator getBeanNameGenerator(); //從指定資源中載入BeanDefinition,並返回載入到的BeanDefinition的個數 int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; //從指定資源路徑中中載入BeanDefinition,並返回載入到的BeanDefinition的個數 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
由於Bean元資訊的設定與解析是息息相關的,下面的一些例子也是將它們揉在一起討論的。
1、API方式
這種方式直接採用BeanDefinition
API 來構成Bean元資訊,並將其注入到IoC容器中,這裡主要使用到BeanDefinitionBuilder
或GenericBeanDefinition
兩種方式來建立Bean元資訊
關於BeanDefinition
這個議題的討論會放在其他篇章中,這裡不再贅述了。
public class ApiBeanMetaDataConfigDemo { public static void main(String[] args) { //建立註解相關的上下文 AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(); //建立GenericBeanDefinition GenericBeanDefinition beanDefinition=new GenericBeanDefinition(); //設定BeanClass beanDefinition.setBeanClass(User.class); //設定屬性 MutablePropertyValues propertyValues=new MutablePropertyValues(); propertyValues.addPropertyValue("name","我就是我"); propertyValues.addPropertyValue("age",18); beanDefinition.setPropertyValues(propertyValues); //註冊BeanDefinition context.registerBeanDefinition("user",beanDefinition); //重新整理IoC容器 context.refresh(); //獲取Bean User user = context.getBean("user", User.class); System.out.println(user); //關閉上下文 context.close(); } }
2、面向XML設定
從XML設定資源處 載入BeanDefinition
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.wojiushiwo.dto.User"> <property name="name" value="wojiushiwo"/> <property name="age" value="18"/> </bean> </beans>
public class XmlBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //載入指定檔案的BeanDefinition,並獲取載入地BeanDefinition個數 int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("載入的BeanDefinition個數為:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
3、面向Properties設定
這種設定方式不太常用,設定資源時需要遵守規則,設定規則可參考PropertiesBeanDefinitionReader
註釋檔案,其規則如下
Properties 屬性名 | 使用場景 |
---|---|
(class) Bean | 類全稱限定名 |
(abstract) | 是否為抽象的 BeanDefinition |
(parent) | 指定 parent BeanDefinition 名稱 |
(lazy-init) | 是否為延遲初始化 |
(ref) | 參照其他 Bean 的名稱 |
(scope) | 設定 Bean 的 scope 屬性 |
${n} | n 表示第 n+1 個構造器引數 |
## 指定BeanClass user.(class)=com.wojiushiwo.dto.User ## 屬性 user.name=我就是我 user.age=19
public class PropertiesBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory); ClassPathResource classPathResource=new ClassPathResource("META-INF/user.properties"); EncodedResource resource=new EncodedResource(classPathResource,"UTF-8"); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions(resource); System.out.println("載入的BeanDefinition個數為:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
上面 Properties檔案預設讀寫編碼為ISO-8859-1 因此這種直接載入方式會出現中文亂碼,可通過載入在載入資源時指定編碼方式來解決
4、面向註解
比如@Autowired
、@Bean
、@Component
、@Configuration
等,這些在當下都比較常用不再贅述
下面就XmlBeanDefinitionReader
呼叫鏈中比較重要的地方進行分析
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //解析Xml檔案生成Document,這裡不再展開 Document doc = doLoadDocument(inputSource, resource); // 解析Document 註冊BeanDefinition int count = registerBeanDefinitions(doc, resource); //省略紀錄檔列印 return count; } catch (BeanDefinitionStoreException ex) { //... 異常及紀錄檔 } }
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //獲取DefaultBeanDefinitionReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //獲取IoC容器中 已經存在的BeanDefinition的個數 int countBefore = getRegistry().getBeanDefinitionCount(); //這裡實際上執行解析Document檔案樹 註冊BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //返回此次載入的BeanDefinition個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // namespace=http://www.springframework.org/schema/beans 表示為預設名稱空間 則使用預設的解析方式去解析元素,否則將採用NamespaceHandler去解析 if (this.delegate.isDefaultNamespace(root)) { //獲取profile屬性,profile與Spring設定環境有關係 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //如果設定了profile if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); //如果當前Environment環境與profile不匹配 則流程結束 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { //省略紀錄檔 return; } } } //解析xml前置操作 preProcessXml(root); //解析xml parseBeanDefinitions(root, this.delegate); //解析xml後置操作 postProcessXml(root); this.delegate = parent; }
關於上面原始碼分析中profile以及NameSpace的理解 請看這裡的XML
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" profile="dev"> <!-- 一些設定--> </beans>
可以看出 xmlns所指代的就是namespace,而profile也可以設定在beans標籤上,其中
http://www.springframework.org/schema/beans
表示預設名稱空間。因為Spring允許自定義標籤,所以通過是否為預設名稱空間作為判斷依據來選擇使用不同的解析方式去解析標籤
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 { // 使用NamespaceHandler去解析標籤,比如ContextNamespaceHandler去解析<context:xx>的標籤等 delegate.parseCustomElement(ele); } } } } else { // 使用NamespaceHandler去解析標籤,比如ContextNamespaceHandler去解析<context:xx>的標籤等 delegate.parseCustomElement(root); } }
上面的程式碼邏輯,根據是否為預設名稱空間從而選擇不同的解析方式,自定義標籤或非預設名稱空間指令 需要繼承NamespaceHandler
去實現自己的標籤解析方式
非預設名稱空間指令舉例
<context:component-scan base-package="com.wojiushiwo"/><aop:xx></aop><context:component-scan base-package="com.wojiushiwo"/> <aop:xx></aop>
這裡直接看針對預設名稱空間的解析程式碼
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //如果是import指令 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //如果是alias指令 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //如果是bean指令 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
針對bean指令
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { //使用BeanRegistry對BeanDefinition進行註冊 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
我們再來梳理下主要流程:
1、解析Xml檔案 生成Document
2、針對Document名稱空間進行標籤解析
NamespaceHandler
去解析(Spring 內建了一些NamespaceHandler解析自定義標籤)3、使用BeanRegistry對BeanDefinition進行註冊
AnnotatedBeanDefinitionReader
從名稱上看它似乎與BeanDefinitionReader
有千絲萬縷的關係,實質上二者沒有關係。AnnotatedBeanDefinitionReader
主要是 對註解Bean進行解析的。
先舉例說明下
@Configuration public class BeanInitializationDemo { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 註冊 Configuration Class(設定類) applicationContext.register(BeanInitializationDemo.class); // 啟動 Spring 應用上下文 applicationContext.refresh(); applicationContext.close(); } }
藉助上面的例子我們看一下其呼叫流程
以上面的例子來看,AnnotationBeanDefinitionReader
的建立是在AnnotationConfigApplicationContext
建構函式中進行的。
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() { //建立AnnotatedBeanDefinitionReader this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } }
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 獲取BeanName String beanName = definitionHolder.getBeanName(); //註冊bd到IoC容器 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在別名,則將beanName與alias的關係也存起來 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
透過上面XmlBeanDefinitionReader
和AnnotationBeanDefinitionReader
對BeanDefinition的解析來看,最終BeanDefinition的註冊都指向了BeanDefinitionReaderUtils.registerBeanDefinition
。我們先來大概看一下程式碼實現
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 獲取BeanName String beanName = definitionHolder.getBeanName(); //註冊bd到IoC容器 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在別名,則將beanName與alias的關係也存起來 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45