摘要:下面跟蹤代碼到這個實(shí)現(xiàn)中看看是怎么做的在實(shí)例化的過程中,在構(gòu)造函數(shù)中調(diào)用了其超類的構(gòu)造函數(shù),而在超類中對其所處換環(huán)境進(jìn)行的判斷,所謂的環(huán)境呢,事實(shí)上指得就是是通過,還是通過加載的上下文,這也就意味著不同方式加載可能存在某些不同。
前言
本文基于《Spring源碼深度解析》學(xué)習(xí), 《Spring源碼深度解析》講解的Spring版本低于Spring3.1,當(dāng)前閱讀的版本為Spring5.x,所以在文章內(nèi)容上會有所不同。
這篇文章基于有一定Spring 基礎(chǔ)的人進(jìn)行講解,所以有些問題并不做詳細(xì)的實(shí)現(xiàn), 如有分析不合理或是錯誤的地方請指教指正,不勝感激。
在《Spring源碼深度解析》中有這樣一個實(shí)例:
public class BeanFactoryTest { @test public void testSimpleLoad() { BeanFactory bf = new XmBeanFactory(new ClassPathResource("beanFactoryTest.xml"); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr()); } }
當(dāng)然在這里會有一個Spring的配置文件 beanFactoryTest.xml, 當(dāng)使用xml文件的時候,會發(fā)現(xiàn)文件頭有一些
這樣的標(biāo)簽, 建議學(xué)習(xí)一下DOM,DOM2, DOM3結(jié)構(gòu), 以便更加清晰的了解xml文件頭中的內(nèi)容的真正意義。
這里的配置文件只寫一個相關(guān)的bean
這段代碼的作用就是以下幾點(diǎn):
讀取配置文件。
在Spring的配置中找到bean,并實(shí)例化。
使用斷言判斷實(shí)例的屬性。
@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory} and {@link XmlBeanDefinitionReader}
這是該類在當(dāng)前版本的部分注釋,在Spring3.1以后這個類被棄用了,Spring官方不建議使用這個類。建議使用以上這兩個類。XmlBeanDefinitionReader本就是以前定義在這個類中的一個final的實(shí)例,而DefaultListableBeanFactory則是該類的超類。加載配置文件可以這樣使用:
Resource resource = new ClassPathResource("beanFactoryTest.xml"); BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr());
這個過程和上面的過程實(shí)際上的實(shí)現(xiàn)只用一點(diǎn)不同、前者是在創(chuàng)建時就直接實(shí)例化了bean, 后者則是在加載的時候才實(shí)例化bean:
讀取配置文件。
創(chuàng)建BeanFactory。
創(chuàng)建BeanDefinitionReader。
加載resource資源。
獲取bean實(shí)例(實(shí)例化bean)。
使用斷言判斷實(shí)例的屬性。
事實(shí)上在實(shí)際的使用中,絕大多數(shù)時候都會通過以下這種ApplicationContext的方式來加載Spring的配置文件并進(jìn)行解析, 以后會寫到這里的實(shí)現(xiàn):
ApplicationContext sc = new ClassPathXmlApplicationContext("applicationContext.xml");二、加載并解析配置文件
Resource resource = new ClassPathResource("beanFactoryTest.xml");
通過ClassPathResource 加載配置文件,并構(gòu)建該實(shí)例的時候,是使用Resource接口進(jìn)行定義的, 這也就說明了創(chuàng)建的實(shí)際上是Resource的實(shí)例,通過查看Resource 的源碼不難發(fā)現(xiàn),Resource對Java中將要使用的資源進(jìn)行了抽象,Spring的設(shè)計中幾乎所有可以加載資源的
類需要直接或間接的實(shí)現(xiàn)Resource 這個接口。下面可以看一下這個接口:
boolean exists(); // 判斷是否資源是否存在 default boolean isReadable() { // 判斷資源是否可讀 return exists(); } default boolean isOpen() { // 判斷文件是否打開 return false; } default boolean isFile() { // 判斷文件是否是文件系統(tǒng)中的文件,Spring5.0后加入的 return false; } URL getURL() throws IOException; // 獲取文件的URL URI getURI() throws IOException; // 獲取文件的URI File getFile() throws IOException; // 獲取文件 default ReadableByteChannel readableChannel() throws IOException { // 返回一個Channel, 擁有最大效率的讀操作 return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; // 返回資源解析后的長度 long lastModified() throws IOException; // 最后一次休干時間 Resource createRelative(String relativePath) throws IOException; // 基于當(dāng)前資源創(chuàng)建一個相對資源 @Nullable String getFilename(); // 獲取文件名 for example, "myfile.txt" String getDescription(); // 獲取資源描述, 當(dāng)發(fā)生錯誤時將被打印
通過查看源碼,還有一點(diǎn)可以發(fā)現(xiàn), Resource接口繼承了InputStreamSource 接口,下面來看下這個接口:
public interface InputStreamSource { /** * Return an {@link InputStream} for the content of an underlying resource. *It is expected that each call creates a fresh stream. *
This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is required * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn"t exist * @throws IOException if the content stream could not be opened */ InputStream getInputStream() throws IOException; }
這個接口的作用非常簡單并且是頂層接口,它的作用就是返回一個InputStream, 最簡單的作用卻提供了最大的方便, 因?yàn)樗匈Y源加載類幾乎都直接或間接的實(shí)現(xiàn)了Resource, 這也就意味著, 幾乎所有的資源加載類都可以得到一個InputStream, 這將使得在資源加載之后能輕易地得到一個InputStream, 這非常重要。通過InputStream, Spring使所有的資源文件都能進(jìn)行統(tǒng)一的管理了, 優(yōu)點(diǎn)是不言而喻的。至于實(shí)現(xiàn)是非常簡單的, ClassPathResource 中的實(shí)現(xiàn)方式便是通過class或者classLoader提供的底層方法進(jìn)行調(diào)用的, 對于FileSystemResource的實(shí)現(xiàn)其實(shí)更加簡單, 就是直接使用FileInputStream對文件進(jìn)行實(shí)例化。
三、DefaultListableBeanFactory、XmlBeanDefinitionReader和BeanDefinitionRegistry配置文件加載完成后進(jìn)行的下一步操作是這樣的,這和3.1之前的版本不太一樣,至于為什么要棄用XmlBeanFactory(我猜是為了對其他部分進(jìn)行設(shè)計,從而讓這部分代碼更加充分的進(jìn)行解耦):
BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource);
配置文件加載完成后,創(chuàng)建了一個BeanFactory的實(shí)例。DefaultListableBeanFactory: 見名知意,這是一個默認(rèn)可列的bean工廠類,注釋用說道,典型的一個應(yīng)用就是在第一次定義時注冊所有bean。接下來的操作是利用多態(tài)將上一步創(chuàng)建的BeanFactory的實(shí)例轉(zhuǎn)成BeanDefinitionRegistry, 因?yàn)橄乱徊叫枰x取xml文件中定義的內(nèi)容,這也就是XmlBeanDefinitionReader的作用,而XmlBeanDefinitionReader在實(shí)例化的時候需要一個bean的定義注冊機(jī),所以就進(jìn)行了以上操作, 事實(shí)上:在創(chuàng)建BeanFactory實(shí)例時,同樣可以定義為BeanDefinitionRegistry類型。下面詳細(xì)說下一這三個類的作用:
DefaultListableBeanFactory:在定義時通過當(dāng)前類的類加載器(如果不存在就向上級加載器尋找直到系統(tǒng)加載器)下的所有的bean進(jìn)行注冊,注意這里只是進(jìn)行注冊而已。
BeanDefinitionRegistry: 為了注冊Spring持有的bean的一個接口,是在BeanFactory,AbstractBeanDefinition中間的一層接口。
XmlBeanDefinitionReader:注釋中這樣寫道
/** * Bean definition reader for XML bean definitions. * Delegates the actual XML document reading to an implementation * of the {@link BeanDefinitionDocumentReader} interface. * *Typically applied to a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * or a {@link org.springframework.context.support.GenericApplicationContext}. * *
This class loads a DOM document and applies the BeanDefinitionDocumentReader to it. * The document reader will register each bean definition with the given bean factory, * talking to the latter"s implementation of the * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface. */ 只說最重要的一個部分, 在這里它需要委托一個真正的xml文檔讀取器來讀取文檔內(nèi)容,也就是BeanDefinitionDocumentReader,而這個文檔讀取器將讀取所有的bean注冊內(nèi)容,而這些資源正是1、2中所得到的。
接下來就是重要的一步,beanDefinitionReader.loadBeanDefinitions(resource); 在解析了配置文件中的bean后,事實(shí)上配置文件中bean并沒有被真正的加載,并且上面的步驟也只是對所有的bean進(jìn)行了一次注冊, 所以,這個時候load了resoure中的內(nèi)容, 在編碼沒有問題以后,并且resource中bean可以在類加載器下找到這些類,這時就對這些bean進(jìn)行加載,實(shí)例化。下面跟蹤代碼到這個實(shí)現(xiàn)中看看Spring 是怎么做的:
/** * Create new XmlBeanDefinitionReader for the given bean factory. * @param registry the BeanFactory to load bean definitions into, * in the form of a BeanDefinitionRegistry */ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); } protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // Determine ResourceLoader to use. if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } // Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } }
在實(shí)例化XmlBeanDefinitionReader 的過程中,在構(gòu)造函數(shù)中調(diào)用了其超類的構(gòu)造函數(shù),而在超類中對其所處換環(huán)境進(jìn)行的判斷,所謂的環(huán)境呢,事實(shí)上指得就是是通過BeanFactory, 還是通過ApplicationContext加載的上下文,這也就意味著不同方式加載可能存在某些不同。寫這些的目的其實(shí)是為了引出這里的一個我們十分關(guān)注的東西, 就是自動裝配。在AbstractAutowireCapableBeanFactory這個抽象類的構(gòu)造方法中實(shí)現(xiàn)了相關(guān)的自動裝配,在BeanDefinitionRegistry 和DefaultListableBeanFactory中都繼承了這個抽象類, 并在其構(gòu)造函數(shù)內(nèi)直接調(diào)用了其超類的構(gòu)造函數(shù)也就是:
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
這里有必要提及一下ignoreDependencyInterface();這個方法。它的主要功能就是忽略接口中的自動裝配, 那么這樣做的目的是什么呢?會產(chǎn)生什么樣的效果呢?舉例來說, 當(dāng)A中有屬性B, 那么Spring在獲取A的時候就會去先去獲取B, 然而有些時候Spring不會這樣做,就是Spring通過BeanNameAware、BeanFactoryAware和BeanClassLoaderAware進(jìn)行注入的, 也就是根據(jù)環(huán)境的不同, Spring會選擇相應(yīng)的自從裝配的方式。在不是當(dāng)前環(huán)境中的注入,Spring并不會再當(dāng)前環(huán)境對Bean進(jìn)行自動裝配。類似于,BeanFactory通過BeanFactoryAwar進(jìn)行注入或者ApplicationContext通過ApplicationContextAware進(jìn)行注入。
經(jīng)過了這么長時間的鋪墊,終于應(yīng)該進(jìn)入正題了, 就是進(jìn)入通過loadBeanDefinitions(resource)方法加載這個文件。這個方法這樣實(shí)現(xiàn):
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } SetcurrentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
這段代碼很容易理解,不過真正實(shí)現(xiàn)的核心代碼是在return doLoadBeanDefinitions(inputSource, encodedResource.getResource());這里實(shí)現(xiàn)的。下面是這個方法的核心代碼:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } }
這段處理可以說非常容易了,對resource流進(jìn)行再封裝,封裝為Docment對象,然后解析并注冊這個doc中的bean對象,返回定義的bean的個數(shù)。在Spring3.1之前上面這個方法中還要驗(yàn)證加載Xml是否符合規(guī)范。而Spring5.x之后Spring將驗(yàn)證的工作放到了獲取Document中。
三、獲取Document看一下Document doc = doLoadDocument(inputSource, resource);這個方法的源碼:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
在這個方法中做了三件事:
getEntityResolver();這個方法將根據(jù)當(dāng)前的resource創(chuàng)建一個ResourceLoader實(shí)例,然后根據(jù)這個對ResourceLoader進(jìn)行封裝,封裝為EntityResolver實(shí)例, 這個EntityResolver的作用是進(jìn)行處理實(shí)體映射。
getValidationModeForResource(); 這個方法的作用是獲取資源的驗(yàn)證模式,通過自動或手動的方式對已經(jīng)加載到的資源進(jìn)行檢驗(yàn)。這里是真正對xml文件進(jìn)行驗(yàn)證的地方。
isNamespaceAware(); 這個方法用來判斷加載的xml文件是否支持明明空間。
實(shí)現(xiàn)上面方法的類繼承了這個接口:DocumentLoader,并且實(shí)現(xiàn)了這個接口中的唯一的抽象:
/** * Load a {@link Document document} from the supplied {@link InputSource source}. * @param inputSource the source of the document that is to be loaded * @param entityResolver the resolver that is to be used to resolve any entities * @param errorHandler used to report any errors during document loading * @param validationMode the type of validation * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD} * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD}) * @param namespaceAware {@code true} if support for XML namespaces is to be provided * @return the loaded {@link Document document} * @throws Exception if an error occurs */
Document loadDocument(
InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception;
那么詳細(xì)講一下上面提及的EntityResolver, 如果SAX(Simple API for XML:簡單的來講,它的作用就是不去構(gòu)建DOM,而是以應(yīng)用程序的方式以最有效率的方式實(shí)現(xiàn)XML與應(yīng)用實(shí)體之間的映射;當(dāng)然還有一種方式是解析DOM,具體兩種方式,我也沒有做過相應(yīng)深入探究)應(yīng)用驅(qū)動程序需要實(shí)現(xiàn)自定義的處理外部實(shí)體,在必須實(shí)現(xiàn)此接口并通過某種方式向SAX驅(qū)動器注冊一個實(shí)例。這需要根據(jù)XML頭部的DTD中的網(wǎng)絡(luò)地址下載聲明并認(rèn)證,而EntityResolver實(shí)際上就是在提供一個尋dtd找聲明的方法,這樣就可以在項目中直接定義好聲明,而通過本地尋找的方式避免了網(wǎng)絡(luò)尋找的過程,編譯器也避免了在網(wǎng)絡(luò)延遲高或沒有網(wǎng)絡(luò)的情況下報錯。
四、解析及注冊BeanDefinitions當(dāng)文件轉(zhuǎn)換為Document后,接下來提取及注冊Bean就是我們后頭的重頭戲。事實(shí)上,這一部分內(nèi)容并不會在我們的使用中出現(xiàn)了。
同樣在XmlBeanDefinitionReader這個類中,可以發(fā)現(xiàn)隨著Docment的獲取完成后,直接做的是下面的這個事情registerBeanDefinitions();:
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. *Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
作用在注釋中寫的很清楚,注冊DOM文檔(Spring的配置信息中. 也就是解析后的xml)中包含的bean。
首先創(chuàng)建一個bean定義文檔讀取器,這個對象是根據(jù)DefaultBeanDefinitionDocumentReader的class通過反射的方式來創(chuàng)建的,
DefaultBeanDefinitionDocumentReader實(shí)現(xiàn)了BeanDefinitionDocumentReader這個接口,這里唯一的抽象方法就是registerBeanDefinitions(Document doc, XmlReaderContext readerContext);,這也就意味著上面的第三行代碼是一個自然的應(yīng)用。
提示:這里的doc參數(shù)就是通過之前的doLoadDocument方法獲得的,而這很好的應(yīng)用了面向?qū)ο蟮膯我宦氊?zé)原則, 將轉(zhuǎn)換為Docment的復(fù)雜過程交給一個單一的類處理,而這個類就是BeanDefinitionDocumentReader, 事實(shí)上這是一個接口,而具體的實(shí)例化是在createBeanDefinitionDocumentReader這個方法中完成的。
getRegistry();的作用實(shí)際上是獲得一個BeanDefinitionRegistry對象。下面的圖片是程序最開始時,容器開始實(shí)現(xiàn)時候的代碼,這里可以看到BeanDefinitionRegistry這個接口。注意這里創(chuàng)建的BeanDefinitionRegistry是final的,也就是這里獲取的是Spring發(fā)現(xiàn)的所有的bean個數(shù),是不許改變的, 熟悉設(shè)計模式的同學(xué)肯定知道,這個BeanDefinitionRegistry是一個單例的。
而接下來做的就是就是,記錄所有對我們的資源文件進(jìn)行加載,這里是真正解析xml文件并加載的地方,而這個邏輯就是那么簡單了, 先統(tǒng)計當(dāng)前的bean defintions個數(shù)然后加載一些bean定義進(jìn)來,然后在統(tǒng)計bean 的個數(shù),然后用后來的減去開始的就是加載的。沒錯了,就是學(xué)前班加減法。
到這里我已經(jīng)不想探究xml文件是如何讀取的了,如果想看的話,可以去看下一篇《Spring源碼一(容器的基本實(shí)現(xiàn)2)》!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/72023.html
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續(xù)的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實(shí)例,比如下面的測試代碼測試結(jié)果如下本小節(jié),我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業(yè)級應(yīng)用開發(fā)框架,于 2004 年由 Rod Johnson 發(fā)布了 1.0 版本。經(jīng)過十幾年的迭代,現(xiàn)在的 Spring 框架已經(jīng)非常成熟了...
摘要:進(jìn)一步解析其他所有屬性并統(tǒng)一封裝至類型的實(shí)例中。是一個接口,在中存在三種實(shí)現(xiàn)以及。通過將配置文件中配置信息轉(zhuǎn)換為容器的內(nèi)部表示,并將這些注冊到中。容器的就像是配置信息的內(nèi)存數(shù)據(jù)庫,主要是以的形式保存。而代碼的作用就是實(shí)現(xiàn)此功能。 前言:繼續(xù)前一章。 一、porfile 屬性的使用 如果你使用過SpringBoot, 你一定會知道porfile配置所帶來的方便, 通過配置開發(fā)環(huán)境還是生產(chǎn)...
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進(jìn)階面試問題列表 -...
摘要:對于開發(fā)者來說,無疑是最常用也是最基礎(chǔ)的框架之一。概念上的東西還是要提一嘴的用容器來管理。和是容器的兩種表現(xiàn)形式。定義了簡單容器的基本功能。抽象出一個資源類來表示資源調(diào)用了忽略指定接口的自動裝配功能委托解析資源。 對于Java開發(fā)者來說,Spring無疑是最常用也是最基礎(chǔ)的框架之一。(此處省略1w字吹Spring)。相信很多同行跟我一樣,只是停留在會用的階段,比如用@Component...
閱讀 952·2021-10-25 09:44
閱讀 1343·2021-09-23 11:56
閱讀 1281·2021-09-10 10:50
閱讀 3180·2019-08-30 15:53
閱讀 2191·2019-08-30 13:17
閱讀 682·2019-08-29 18:43
閱讀 2577·2019-08-29 12:57
閱讀 934·2019-08-26 12:20