(11)容器的拓展點(diǎn)

容器的擴(kuò)展

通常來(lái)說(shuō)商佛,開(kāi)發(fā)者不需要通過(guò)繼承ApplicationContext來(lái)實(shí)現(xiàn)自己的子類擴(kuò)展功能。但是Spring IoC容器確實(shí)可以通過(guò)實(shí)現(xiàn)接口來(lái)增加一些功能狰腌。下面將描述一下這些接口击纬。
接下來(lái)主要分是哪個(gè)部分來(lái)講解:

1.通過(guò) BeanPostProcessor后置處理器來(lái)對(duì)一個(gè)bean(可以是監(jiān)控bean或者增加bean)
2.通過(guò)BeanFactoryPostProcessor工廠后置bean處理器來(lái)定義配置元數(shù)據(jù)(容器實(shí)例初始話Bean之前就改變配置元數(shù)據(jù))
3.自定義FactoryBean的初始化邏輯

1.通過(guò) BeanPostProcessor后置處理器來(lái)對(duì)一個(gè)bean

BeanPostProcessor接口定義了一些回調(diào)方法球恤,開(kāi)發(fā)者可以通過(guò)實(shí)現(xiàn)來(lái)自己的實(shí)例化邏輯哀九,依賴解析邏輯等等剿配。如果開(kāi)發(fā)者只是想在Spring容器完成了實(shí)例化搅幅,配置以及初始化Bean之后來(lái)做一些操作的話阅束,可以通過(guò)使用BeanPostProcessor來(lái)做到

開(kāi)發(fā)者可以配置多個(gè)BeanPostProcessor實(shí)例茄唐,開(kāi)發(fā)者可以通過(guò)配置order屬性來(lái)指定配置的BeanPostProcessor的執(zhí)行順序息裸。當(dāng)然,想要配置順序必須同時(shí)要實(shí)現(xiàn)Ordered接口沪编。如果開(kāi)發(fā)者寫(xiě)了自己的BeanPostProcessor呼盆,開(kāi)發(fā)者最好同時(shí)考慮實(shí)現(xiàn)Ordered接口

BeanPostProcessors是操作Bean實(shí)例的,換言之蚁廓,Spring IoC容器必須先初始化好Bean访圃,然后BeanPostProcessors才開(kāi)始工作
BeanPostProcessors作用范圍是基于容器的相嵌。當(dāng)然腿时,只有當(dāng)開(kāi)發(fā)者使用容器的層級(jí)的時(shí)候才是需要考慮的。如果開(kāi)發(fā)者在容器中定義了一個(gè)BeanPostProcessor饭宾,這個(gè)實(shí)例只會(huì)在它所在的容器來(lái)處理Bean初始化以后的操作批糟。換言之,一個(gè)容器中的Bean不會(huì)被另一個(gè)容器中定義BeanPostProcessor的在初始化以后進(jìn)行后續(xù)處理看铆,甚至就算兩個(gè)容器同屬同一個(gè)容器的部分徽鼎。

org.springframework.beans.factory.config.BeanPostProcessor接口包含了2個(gè)回調(diào)方法。當(dāng)這個(gè)接口的實(shí)例注冊(cè)到容器當(dāng)中時(shí)弹惦,對(duì)于每一個(gè)由容器創(chuàng)建的實(shí)例否淤,這個(gè)后置處理器都會(huì)在容器開(kāi)始進(jìn)行初始化之前獲得回調(diào)的調(diào)用。后置處理器可以針對(duì)Bean實(shí)例采取任何的操作棠隐,包括完全無(wú)視回調(diào)函數(shù)叹括。Bean的后置處理器通常檢查回調(diào)接口或者將Bean用代理包裝一下。一些諸如Spring AOP代理的基礎(chǔ)類都是 通過(guò)Bean的后續(xù)處理器來(lái)實(shí)現(xiàn)的宵荒。

ApplicationContext會(huì)自動(dòng)檢查配置的Bean是否有實(shí)現(xiàn)BeanPostProcessor接口汁雷,ApplicationContext會(huì)將這些Bean注冊(cè)為后續(xù)處理器這樣這些后續(xù)處理器就會(huì)在Bean創(chuàng)建之后調(diào)用报咳。Bean的后續(xù)處理器就像其他的Bean一樣侠讯,由容器管理的

盡管Spring團(tuán)隊(duì)推薦的注冊(cè)Bean的后續(xù)處理的方式是通過(guò)ApplicationContext的自動(dòng)檢查暑刃,但是Spring也支持通過(guò)編程的方式厢漩,通過(guò)addBeanPostProcessor方法。這種方式有的時(shí)候也很有用岩臣,當(dāng)需要在注冊(cè)前執(zhí)行一些條件判斷的時(shí)候溜嗜,或者在結(jié)構(gòu)化的上下文中復(fù)制Bean后續(xù)處理器的時(shí)候尤其有效宵膨。需要注意的是,通過(guò)編程實(shí)現(xiàn)的BeanPostProcessors是會(huì)忽略掉Ordered接口的:由編程注冊(cè)的BeanPostProcessors總是在自動(dòng)檢查到的BeanPostProcessors之前來(lái)執(zhí)行的炸宵,而回忽略掉明確的順序定義辟躏。

容器會(huì)特殊對(duì)待那些實(shí)現(xiàn)了BeanPostProcessor接口的類。所有的BeanPostProcessors和他們所引用的Bean都會(huì)在啟動(dòng)時(shí)直接初始化土全,作為ApplicationContext啟動(dòng)的一個(gè)特殊階段捎琐。然后,所有的BeanPostProcessors會(huì)按順序注冊(cè)并應(yīng)用到容器中的Bean

下面通過(guò)一個(gè)例子來(lái)演示一下

/**
 * @Project: spring
 * @description:  模擬一個(gè)普通的bean
 * @author: sunkang
 * @create: 2018-09-16 15:06
 * @ModificationHistory who      when       What
 **/
public class BeanDemo{
    public BeanDemo() {
        System.out.println("beanDemo已經(jīng)初始化了");
    }
}
/**
 * 模擬 BeanPostProcessor來(lái)監(jiān)控其他bean,進(jìn)行相應(yīng)的處理
 */
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor,Ordered {

    //在bean初始化之后調(diào)用
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("beforeBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("afterBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

在spring-extensionPoint.xml的配置中如下:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="beanPostProcessor" class="com.spring.extensionPoint.InstantiationTracingBeanPostProcessor" />

    <bean class="com.spring.extensionPoint.BeanDemo"/>
</beans>

測(cè)試的演示:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");

測(cè)試結(jié)果如下:可以發(fā)現(xiàn)BeanPostProcessor的兩個(gè)回調(diào)方法在容器構(gòu)造方法之后進(jìn)行執(zhí)行的

beanDemo已經(jīng)初始化了
beforeBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
afterBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe

spring的例子:RequiredAnnotationBeanPostProcessor
使用回調(diào)接口或者注解和BeanPostProcessor實(shí)現(xiàn)是常見(jiàn)的來(lái)擴(kuò)展Spring IoC容器的方式裹匙。Spring中的一個(gè)例子就是RequiredAnnotationBeanPostProcessor就是用來(lái)確保JavaBean的標(biāo)記有注解的屬性確實(shí)注入了依賴瑞凑。

2.通過(guò)BeanFactoryPostProcessor定義配置元數(shù)據(jù)

org.springframework.beans.factory.config.BeanFactoryPostProcessor。語(yǔ)義上來(lái)說(shuō)概页,這個(gè)接口有些類似BeanPostProcessor籽御,只是有一個(gè)很大的區(qū)別:BeanFactoryPostProcessor操作Bean配置元數(shù)據(jù),也就是說(shuō)惰匙,Spring允許BeanFactoryPostProcessor來(lái)讀取配置源數(shù)據(jù)技掏,并且可能會(huì)在容器實(shí)例初始話Bean之前就改變配置元數(shù)據(jù)

如前文所述徽曲,開(kāi)發(fā)者可以配置多個(gè)BeanFactoryPostProcessors零截,而且開(kāi)發(fā)者可以控制其具體執(zhí)行的順序。當(dāng)然秃臣,配置順序是必須實(shí)現(xiàn)Ordered接口的涧衙。如果實(shí)現(xiàn)了自己的BeanFactoryPostProcessor,開(kāi)發(fā)者也應(yīng)該考慮實(shí)現(xiàn)Ordered接口

在ApplicationContext中聲明了后續(xù)處理器奥此,Bean的后續(xù)處理器就會(huì)自動(dòng)的執(zhí)行弧哎,來(lái)實(shí)現(xiàn)在配置中定義的行為。Spring包括一些預(yù)定義好的后續(xù)處理器都可以使用稚虎,比如PropertyOverrideConfigurerPropertyPlaceholderCOnfigurer撤嫩,也能夠使用自定義的BeanFactoryPostProcessor,比如蠢终,來(lái)注冊(cè)自定義屬性編輯器序攘。

例子,類名替換BeanFactoryPostProcessor
下面就開(kāi)始演示一下

/**
 * @Project: spring
 * @description:   模擬PropertyPlaceholderConfigurer來(lái)進(jìn)行測(cè)試
 * @author: sunkang
 * @create: 2018-09-16 16:15
 * @ModificationHistory who      when       What
 **/
public class DataSourceConfig {
    private String driverClassName;

    private  String url;

    private String username;

    private String password;

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "DataSourceConfig{" +
                "driverClassName='" + driverClassName + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

在extensionPoint目錄下放置datasouce.properties文件寻拂,文件內(nèi)容如下

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在spring-extensionPoint.xml配置如下

  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:extensionPoint/datasouce.properties"/>
    </bean>
  <bean id="dataSource"  class="com.spring.extensionPoint.DataSourceConfig">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

測(cè)試方法:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
 DataSourceConfig config=   context.getBean("dataSource",DataSourceConfig.class);
System.out.println(config);

測(cè)試結(jié)果如下:

DataSourceConfig{driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql:mydb', username='sa', password='root'}

Spring 2.5后引入了context命名空間程奠,可以專用的配置元素來(lái)配置屬性占位符。多個(gè)地址可以使用逗號(hào)分隔符祭钉。

<context:property-placeholder location="classpath:extensionPoint/override.properties"/>

PropertyPlaceholderConfigurer不僅僅查看開(kāi)發(fā)者指定的Properties文件瞄沙。默認(rèn)的話,它如果不能再指定的屬性文件中找到屬性的話,仍然會(huì)檢查Java的System配置距境。開(kāi)發(fā)者可以通過(guò)配置systemPropertiesMode屬性來(lái)修改這個(gè)行為申尼,這個(gè)屬性有如下三種值:
nerver(0):從不檢查系統(tǒng)屬性
fallback(1):如果沒(méi)有從指定的屬性文件中找到特定的屬性時(shí)檢查系統(tǒng)屬性,這個(gè)是默認(rèn)值垫桂。
override(2):優(yōu)先查找系統(tǒng)屬性师幕,而后才試著檢查指定配置文件。系統(tǒng)的屬性會(huì)覆蓋指定文件的屬性伪货。

接下來(lái)看看PropertyPlaceholderConfigurer的源碼们衙,先看下PropertyPlaceholderConfigurer的繼承關(guān)系钾怔,發(fā)現(xiàn)有beanFactoryPostProcessor


image.png

在PropertyResourceConfigurer實(shí)現(xiàn)了BeanFactoryPostProcessor碱呼,那么postProcessBeanFactory就是主入口了,后面的流程有興趣可以自己看看宗侦。

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            //從特定的文件加載屬性到mergedProps中
            Properties mergedProps = mergeProperties();
            
            // Convert the merged properties, if necessary.
            convertProperties(mergedProps);

            //這里的處理會(huì)properties屬性,會(huì)生成一個(gè)解析器愚臀,然后注入到容器中,裝載beand的時(shí)候再來(lái)回調(diào)這個(gè)解析器
            // Let the subclass process the properties.
            processProperties(beanFactory, mergedProps);
        }
        catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
        }
    }

另外一個(gè)例子是PropertyOverrideConfigurer
PropertyOverrideConfigurer矾利,是另一個(gè)bean的后置處理器姑裂,有些類似于PropertyPlaceholderConfigurer,但是區(qū)別于后者男旗,原有的定義可能有默認(rèn)值舶斧,或者沒(méi)有任何值。如果覆蓋的Properties文件沒(méi)有一個(gè)確切的Bean屬性察皇,就使用默認(rèn)的定義茴厉。

覆蓋的屬性如下,注意但是要求每一個(gè)路徑上的組件什荣,需要是非空的矾缓,也就是dataSource該組件不能為空

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

在具體的xml配置如下,那么dataSource.driverClassName和url將被覆蓋,沒(méi)有覆蓋的值保持原樣

   <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
        <property name="locations" value="classpath:extensionPoint/override.properties"/>
    </bean>

在Spring 2.5引入的context命名空間中稻爬,也可以指定配置覆蓋的文件屬性如下:

<context:property-override location="classpath:extensionPoint/override.properties"/>

3.自定義FactoryBean的初始化邏輯

FactoryBean接口是一種類似于Spring IoC容器的插件化的邏輯嗜闻。如果當(dāng)開(kāi)發(fā)者的代碼有復(fù)雜的初始化代碼,在配置上使用Java代碼比XML更有效時(shí)桅锄,開(kāi)發(fā)者可以考慮創(chuàng)建自己的FacotoryBean對(duì)象琉雳,將復(fù)雜的初始化操作放到類中,將自定義的FactoryBean擴(kuò)展到容器中友瘤。

FacotryBean接口提供如下三個(gè)方法

Object getObject():返回一個(gè)工廠創(chuàng)建的對(duì)象翠肘。實(shí)例可被共享,取決于返回Bean的作用域?yàn)樵瓦€是單例商佑。
boolean isSingleton():如果FactoryBean返回單例锯茄,為T(mén)rue,否則為False
Class getObjectType():返回由getObject()方法返回的對(duì)象的類型,如果對(duì)象類型未知肌幽,返回null晚碾。
FactoryBean概念和接口廣泛用預(yù)Spring框架,Spring本身就有多于50個(gè)FactoryBean的實(shí)現(xiàn)喂急。

當(dāng)開(kāi)發(fā)者需要一個(gè)FactoryBean實(shí)例而不是其產(chǎn)生的Bean的時(shí)候格嘁,在調(diào)用ApplicationContext的getBean()方法時(shí),在其id之前加上&符號(hào)廊移。也就是說(shuō)糕簿,對(duì)于一個(gè)給定的FactoryBean,其id為myBean狡孔,調(diào)用getBean("myBean")返回其產(chǎn)生的Bean對(duì)象懂诗,而調(diào)用getBean("&myBean")返回FactoryBean實(shí)例本身

下面進(jìn)行例子演示:

/**
 * @Project: spring
 * @description:  說(shuō)明是一個(gè)factroyBean    是一個(gè)bean苗膝,但是是一個(gè)工廠bean
 * @author: sunkang
 * @create: 2018-09-16 16:34
 * @ModificationHistory who      when       What
 **/
public class FactoryBeanDemo implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return "factoryBean";
    }

    @Override
    public Class<?> getObjectType() {
        return String.class;
    }
}

在spring-extensionPoint.xml具體的配置如下:

    <bean id="factoryBean" class="com.spring.extensionPoint.FactoryBeanDemo"/>

測(cè)試如下:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
//當(dāng)其id為factoryBean殃恒,調(diào)用getBean("factoryBean")返回其產(chǎn)生的Bean對(duì)象,
//而調(diào)用getBean("&factoryBean")返回FactoryBean實(shí)例本身辱揭。
FactoryBeanDemo factoryBeanDemo =   context.getBean("&factoryBean",FactoryBeanDemo.class);
System.out.println("得到工廠:factory"+factoryBeanDemo);
//得到bean對(duì)象离唐,其實(shí)是工廠bean調(diào)用了getObject方法產(chǎn)生的對(duì)象
System.out.println("得到具體的bean:"+context.getBean("factoryBean"));

結(jié)果如下: 結(jié)果很顯然

得到工廠:factorycom.spring.extensionPoint.FactoryBeanDemo@5025a98f
得到具體的bean:factoryBean
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市问窃,隨后出現(xiàn)的幾起案子亥鬓,更是在濱河造成了極大的恐慌,老刑警劉巖域庇,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嵌戈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡较剃,警方通過(guò)查閱死者的電腦和手機(jī)咕别,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)写穴,“玉大人惰拱,你說(shuō)我怎么就攤上這事“∷停” “怎么了偿短?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)馋没。 經(jīng)常有香客問(wèn)我昔逗,道長(zhǎng),這世上最難降的妖魔是什么篷朵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任勾怒,我火速辦了婚禮婆排,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘笔链。我一直安慰自己段只,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布鉴扫。 她就那樣靜靜地躺著赞枕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坪创。 梳的紋絲不亂的頭發(fā)上炕婶,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音莱预,去河邊找鬼柠掂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锁施,可吹牛的內(nèi)容都是我干的陪踩。 我是一名探鬼主播杖们,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悉抵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了摘完?” 一聲冷哼從身側(cè)響起姥饰,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孝治,沒(méi)想到半個(gè)月后列粪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谈飒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年岂座,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杭措。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡费什,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出手素,到底是詐尸還是另有隱情鸳址,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布泉懦,位于F島的核電站稿黍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崩哩。R本人自食惡果不足惜巡球,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一言沐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酣栈,春花似錦呢灶、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至跋涣,卻和暖如春缨睡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陈辱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工奖年, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沛贪。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓陋守,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親利赋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子水评,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容