中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring源碼中的配置文件分析

發布時間:2021-11-15 11:05:52 來源:億速云 閱讀:133 作者:iii 欄目:大數據

本篇內容介紹了“Spring源碼中的配置文件分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

鎖分析的jar包:spring-beans-3.2.5.RELEASE.jar

讀取配置文件的方式
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-test.xml"));
在spring 4.0中不推薦這種寫法,換成:
ClassPathXmlApplicationContext content = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
源碼分析都差不多,

源碼的核心:

1,DefaultListableBeanFactory 是注冊和加載bean的默認實現,

2,XML解析

 2.1 XmlBeanDefinitionReader 是用來讀取XML配置文件,在讀取XML配置文件,Spring實際是將不同的資源路徑抽象成URL,將配置文件封裝為Resource的過程,看構造方法

// 獲取配置文件路徑,最終是轉換成 ClassPathResource,而ClassPathResource這個類往上,繼承AbstractFileResolvingResource繼承AbstractResource,實現Resource接口
    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
    }

//創建XmlBeanFactory的過程
public class XmlBeanFactory extends DefaultListableBeanFactory {
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        //這個地方的super需要注意下,往上AbstractAutowireCapableBeanFactory里面構造方法調用了ignoreDependencyInterface()作用是忽略相應的依賴,
        super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
}

類總結:主要是初始化。看ignoreDependencyInterface()方法的作用:自動裝配忽略給定的依賴接口,舉例當A中有屬性B, 在獲取A的bean,B如果沒有被加載,那么spring 會自動初始化B,但某些情況下B不會被初始化。例如:B實現了BeanNameAware接口。自動裝配忽略,典型應用是通過其他方式解析Application上下文注冊依賴,類似于BeanFactory通過BeanNameAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入。

2.2 具體來看 this.reader.loadBeanDefinitions(resource); 這個方法的實現

//上層方法主要將Resource轉化為EncodedResource沒做其他事,主要看這個方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //前面這一段主要做一些空判斷,獲取需要傳遞的值,
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(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();
			}
		}
	}

//這個方法主要:1,判斷文件類型。2,根據不同類型創建Document對象。3,交給下層方法
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
            //這個主要判斷xml文件是DTD,還是XSD類型,
			int validationMode = getValidationModeForResource(resource);
            //根據獲取的文件類型,轉換成不用的Document對象
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            //注冊bean定義
			return registerBeanDefinitions(doc, resource);
		}
     //....省略catch方法 
}

//這個方法作用:1,創建BeanDefinitionDocumentReader。2,初始化XmlReaderContext。3,交給子類實現
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //使用 DefaultBeanDefinitionDocumentReader實例化 BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.setEnvironment(this.getEnvironment());
		int countBefore = getRegistry().getBeanDefinitionCount();
        //創建XmlReaderContext對象并初始化namespaceHandlerResolver為默認的DefaultNamespaceHandlerResolver,實現交給子類DefaultBeanDefinitionDocumentReader
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

解析XmlBeanDefinitionReader這個類主要做了

1,獲取XML文件驗證模式,

2,加載XML文件并獲取Document對象,

3,初始化BeanDefinitionDocumentReader,并初始化XmlReaderContext,

2.3 看BeanDefinitionDocumentReader實現類DefaultBeanDefinitionDocumentReader,具體做了那些事情

//賦值readerContext,獲取Document的element
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}

//還是在判斷各個參數
protected void doRegisterBeanDefinitions(Element root) {
       //處理profile屬性
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			if (!this.environment.acceptsProfiles(specifiedProfiles)) {
				return;
			}
		}
        //專門處理解析
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(this.readerContext, root, parent);

        //這里預留了空的方法,用戶可以根據需求,解析前的處理
		preProcessXml(root);
        //主要看這個方法
		parseBeanDefinitions(root, this.delegate);
        //解析會的處理
		postProcessXml(root);

		this.delegate = parent;
	}

    //這個方法是判斷是默認標簽,還是自定義標簽
    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 {
                        //默認標簽下面還有自定義屬性
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
            //否則按照自定義標簽解析
			delegate.parseCustomElement(root);
		}
	}

    //解析四種標簽,import,alias,bean,beans,
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            //解析import標簽
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            //解析alias標簽
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            //bean標簽的解析,
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse 解析beans
			doRegisterBeanDefinitions(ele);
		}
	}

看到這里,終于正式進入標簽的解析,其中profile屬性的作用可以同時在配置文件中部署兩套配置來適用于生產環境和開發環境

總結類上主要做的事情,

1,獲取所有標簽,判斷是默認標簽還是自定義標簽

2,解析屬性,有自定義屬性,再解析

3,bean標簽的解析

配置文件中經常用到bean,我們主要看這個解析過程,看這個方法做了哪些事情:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //委托BeanDefinitionHolder類的parseBeanDefinitionElement方法進行元素解析并返回BeanDefinitionHolder,經過這個方法的處理,BeanDefinitionHolder已經包含了配置中的各個屬性,例如:class,id,name,alias之類的屬性
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
        //當BeanDefinitionHolder返回結果不為空,并且發現默認標簽下面有自定義屬性,在解析
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
                //通過beanName注冊bean
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
            //發出響應事件,通知相關的監聽器,這個bean已經加載完了,
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

3.1 默認屬性的解析

看屬性解析parseBeanDefinitionElement()

//parseBeanDefinitionElement() 這個方法主要傳遞了一個null的BeanDefinition對象,主要調用還在這
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        //屬性id, name解析
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		List<String> aliases = new ArrayList<String>();
		if (StringUtils.hasLength(nameAttr)) {
            //將nameAttr以 ,;中的任一或者組合來分割字符,不包含空字符
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}
        //將解析到的id作為beanName
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            //如果id屬性為空,但有name屬性,將name賦值給beanName
			beanName = aliases.remove(0);
			if (logger.isDebugEnabled()) {
				logger.debug("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}

		if (containingBean == null) {
            //解析beanName 的值是否有重名
			checkNameUniqueness(beanName, aliases, ele);
		}

        //創建AbstractBeanDefinition對象并且,將解析到的屬性存在對象中
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
                    //如果beanName不存在,按照spring提供的命名規則生成beanName
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Neither XML 'id' nor 'name' specified - " +
								"using generated bean name [" + beanName + "]");
					}
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			String[] aliasesArray = StringUtils.toStringArray(aliases);
            //將獲取到的bean信息封裝到 BeanDefinitionHolder中
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}

以上就是對默認標簽解析的全過程,主要做了以下內容:

1,提供標簽的id, name屬性,

2,創建AbstractBeanDefinition 對象并保存屬性值,

3,檢測beanName是否存在。

4,將獲取到的信息封裝到BeanDefinitionHolder

具體來看下抽象類AbstractBeanDefinition 的創建,它繼承BeanDefinition接口,并有三種實現類,RootBeanDefinition, ChildBeanDefinition,GenericBeanDefinition,

RootBeanDefinition 是最常用的實現類,它對應一般性的bean 元素標簽,

GenericBeanDefinition是自2.5版本后加入的bean文件配置屬性定義類,

	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, BeanDefinition containingBean) {

		this.parseState.push(new BeanEntry(beanName));

		String className = null;
        //class屬性的解析
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}

		try {
			String parent = null;
            //parent屬性的解析
			if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
				parent = ele.getAttribute(PARENT_ATTRIBUTE);
			}
            //創建GenericBeanDefinition 并用AbstractBeanDefinition來接收
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            //scope,singleton,abstract各種屬性的解析
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            //子元素meta標簽的解析
			parseMetaElements(ele, bd);
            //子元素lookup的解析
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            //子元素replaceMethod解析
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            //子元素constructor-arg
			parseConstructorArgElements(ele, bd);
            //子元素property
			parsePropertyElements(ele, bd);
            //子元素quality
			parseQualifierElements(ele, bd);

			bd.setResource(this.readerContext.getResource());
			bd.setSource(extractSource(ele));

			return bd;
		} ... 省略catch

		return null;
	}

這個方法就是解析XM所有的標簽及子元素,的屬性,完成了從XML到GenericBeanDefinition對象的轉化,GenericBeanDefinition只是子類實現,大部分屬性都保存在AbstractBeanDefinition,

3.2 自定義屬性的解析

decorateBeanDefinitionIfRequired()方法,如在XML中的申明:

<beans:bean id="student" class="com.study.Student" >
    <myBean:user username="aaa" />
</beans:bean>
// decorateBeanDefinitionIfRequired方法主要傳遞 null的BeanDefinition對象
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {

		BeanDefinitionHolder finalDefinition = definitionHolder;

		// Decorate based on custom attributes first.
		// 遍歷所有屬性,是否有適用于修飾的屬性
		NamedNodeMap attributes = ele.getAttributes();
		for (int i = 0; i < attributes.getLength(); i++) {
			Node node = attributes.item(i);
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}

		// Decorate based on custom nested elements.
		// 遍歷子節點
		NodeList children = ele.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
			}
		}
		return finalDefinition;
	}
//看decorateIfRequired 的實現
private BeanDefinitionHolder decorateIfRequired(
			Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
        //獲取自定義標簽的命名空間,上面示例的 myBean
		String namespaceUri = getNamespaceURI(node);
        //處理非默認的標簽
		if (!isDefaultNamespace(namespaceUri)) {
            //根據命名空間找到處理器
			NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
			if (handler != null) {
                //進行修飾
				return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
			}
			else if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) {
				error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
			}
			else {
				// A custom namespace, not to be handled by Spring - maybe "xml:...".
				if (logger.isDebugEnabled()) {
					logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
				}
			}
		}
		return originalDef;
	}

首先獲取屬性或者標簽的命名空間,判斷如果是自定義標簽進一步解析,這里基本就是自定義標簽的解析,下一篇講解,

這里decorateBeanDefinitionIfRequired對默認標簽直接省略了,因為默認標簽已經解析完了,

到這里對于配置文件,已經解析完了,BeanDefinition可以滿足后續的要求,唯一剩下的就是注冊

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

和發出響應事件,通知監聽器

getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

4,Bean的注冊

注冊分為兩種:1,beanName的注冊。2,alias別名注冊

	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		// 通過beanName的注冊
		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 aliase : aliases) {
				registry.registerAlias(beanName, aliase);
			}
		}
	}

4.1 beanName的注冊

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
                //注冊前的檢測, 對于 AbstractBeanDefinition的methodOverrides校驗
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

        //這里beanDefinitionMap 是全局會有并發的情況
		synchronized (this.beanDefinitionMap) {
			Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
			if (oldBeanDefinition != null) {
                //如果已經注冊了,但不允許覆蓋,會拋異常
				if (!this.allowBeanDefinitionOverriding) {
					throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
							"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
							"': There is already [" + oldBeanDefinition + "] bound.");
				}
				else {
                    //可以覆蓋,日志記錄
					if (this.logger.isInfoEnabled()) {
						this.logger.info("Overriding bean definition for bean '" + beanName +
								"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
					}
				}
			}
			else {
                // 記錄beanName
				this.beanDefinitionNames.add(beanName);
				this.frozenBeanDefinitionNames = null;
			}
            //注冊BeanDefinition
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
        //重置beanName,清除緩存
		resetBeanDefinition(beanName);
	}

	protected void resetBeanDefinition(String beanName) {
		// Remove the merged bean definition for the given bean, if already created.
		clearMergedBeanDefinition(beanName);

		// Remove corresponding bean from singleton cache, if any. Shouldn't usually
		// be necessary, rather just meant for overriding a context's default beans
		// (e.g. the default StaticMessageSource in a StaticApplicationContext).
		destroySingleton(beanName);

		// Remove any assumptions about by-type mappings.
		clearByTypeCache();

		// Reset all bean definitions that have the given bean as parent (recursively).
		for (String bdName : this.beanDefinitionNames) {
			if (!beanName.equals(bdName)) {
				BeanDefinition bd = this.beanDefinitionMap.get(bdName);
				if (beanName.equals(bd.getParentName())) {
					resetBeanDefinition(bdName);
				}
			}
		}
	}

總結上面步驟:

1, AbstractBeanDefinition的methodOverrides校驗

2,對已經注冊的beanName的處理

3,加入map 緩存

4,刷新緩存

4.2 alias別名注冊

和上面beanName差不多,參照上面

5,通知監聽器

最后,這里通知監聽器,其實spring沒有做什么,這里實現只為拓展,方便開發人員需要對注冊的BeanDefinition事件進行監聽時,可以通過注冊監聽器的方式將處理邏輯寫入監聽器

“Spring源碼中的配置文件分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

克东县| 阿拉尔市| 勃利县| 梁河县| 和林格尔县| 光山县| 奉节县| 儋州市| 福州市| 体育| 道孚县| 濮阳市| 清苑县| 青铜峡市| 泸州市| 离岛区| 上饶市| 长武县| 梅河口市| 台中县| 长治市| 佳木斯市| 嵩明县| 比如县| 晋州市| 库伦旗| 滕州市| 全南县| 伊川县| 寿宁县| 灵璧县| 穆棱市| 寿阳县| 南皮县| 屯门区| 宜良县| 岑巩县| 慈溪市| 二手房| 贵阳市| 绥化市|