Spring原理分析-BeanFactory與ApplicationContext

零白热、本文綱要

  • 一、容器接口
    1粗卜、BeanFactory與ApplicationContext
    2屋确、BeanFactory接口
    ① DefaultListableBeanFactory類
    3、ApplicationContext接口
    ① 實(shí)現(xiàn)MessageSource接口
    ② 實(shí)現(xiàn)ResourcePatternResolver接口
    ③ 實(shí)現(xiàn)EnvironmentCapable接口
    ④ 實(shí)現(xiàn)ApplicationEventPublisher接口
  • 二续扔、容器實(shí)現(xiàn)
    1攻臀、BeanFactory實(shí)現(xiàn)
    ① DefaultListableBeanFactory類
    ② 注冊(cè)各類后置處理器
    ③ beanFactory后置處理器
    ④ bean后置處理器
    ⑤ preInstantiateSingletons初始化
    補(bǔ)充:AnnotationConfigUtils類
    2、ApplicationContext實(shí)現(xiàn)
    ① ClassPathXmlApplicationContext類
    ② FileSystemXmlApplicationContext類
    補(bǔ)充:XmlBeanDefinitionReader類
    ③ AnnotationConfigApplicationContext類
    補(bǔ)充:<context:annotation-config/>
    ④ AnnotationConfigServletWebServerApplicationContext類

一纱昧、容器接口

0刨啸、基礎(chǔ)準(zhǔn)備

添加最基礎(chǔ)的Spring Boot依賴,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

1识脆、BeanFactory與ApplicationContext

  • ① Spring Boot啟動(dòng)類

SpringApplication.run(A01.class, args);的返回值為ConfigurableApplicationContext對(duì)象设联,如下:

@SpringBootApplication
public class SpringOriginDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringOriginDemoApplication.class, args);
    }

}
BeanFactory接口.png

BeanFactory是:

Ⅰ ApplicationContext 的父接口善已;
Ⅱ 是 Spring 的核心容器, 主要的 ApplicationContext 實(shí)現(xiàn)都【組合】了它的功能。

2离例、BeanFactory接口

BeanFactory接口换团,最基礎(chǔ)的待實(shí)現(xiàn)方法是getBean();方法。

另外宫蛆,像控制反轉(zhuǎn)艘包、基本的依賴注入、直至 Bean 的生命周期的各種功能耀盗,都是依賴于BeanFactory接口實(shí)現(xiàn)的想虎。

  • ① DefaultListableBeanFactory類

是Spring項(xiàng)目中BeanFactory功能最完善的實(shí)現(xiàn)類,關(guān)系圖如下:

DefaultListableBeanFactory類.png
  • ② 通過(guò)反射獲取singletonObjects成員變量案例

DefaultSingletonBeanRegistry類袍冷,如下:

DefaultSingletonBeanRegistry類.png

通過(guò)反射獲取beanFactory中的singletonObjects磷醋,如下:

// 通過(guò)反射獲取 Field 對(duì)象singletonObjects
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
// 暴力反射
singletonObjects.setAccessible(true);
// 獲取beanFactory中的singletonObjects,并遍歷
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
map.entrySet()
        .stream().filter(e -> e.getKey().startsWith("component"))
        .forEach(e -> System.out.println(e.getKey() + " = " + e.getValue()));
反射輸出結(jié)果.png

3胡诗、ApplicationContext接口

  • ① 實(shí)現(xiàn)MessageSource接口

Ⅰ 在/resources目錄下編寫配置文件

語(yǔ)言代碼表:Language Code Table (lingoes.cn)邓线,此處設(shè)置配置文件地區(qū)可以省略不寫,一般寫成zh煌恢、ja骇陈、en就可以(推薦)。

在/resources目錄下編寫配置文件.png
逐個(gè)配置配置文件.png

Ⅱ 編寫測(cè)試代碼

System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.JAPAN));
輸出.png
  • ② 實(shí)現(xiàn)ResourcePatternResolver接口

Ⅰ 獲取類路徑下指定資源classpath:

Resource[] resources = context.getResources("classpath:application.properties");
for (Resource resource : resources) {
    System.out.println(resource); // 輸出 class path resource [application.properties]
}

Ⅱ 獲取jar包其他資源classpath*:

Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
獲取jar包其他資源.png
  • ③ 實(shí)現(xiàn)EnvironmentCapable接口

Ⅰ 獲取變量鍵值

該方法可以獲取配置文件(如:application.properties)內(nèi)的鍵值瑰抵,也可以拿到環(huán)境變量的鍵值你雌,如下:

System.out.println(context.getEnvironment().getProperty("java_home")); // 輸出 C:\Program Files\Java\jdk1.8.0_311
System.out.println(context.getEnvironment().getProperty("server.port")); // 輸出 8080
  • ④ 實(shí)現(xiàn)ApplicationEventPublisher接口

Ⅰ 編寫事件類(實(shí)現(xiàn)ApplicationEvent )

public class UserRegisterEvent extends ApplicationEvent {
    public UserRegisterEvent(Object source) {
        super(source);
    }
}

Ⅱ 編寫事件發(fā)布類

@Component
public class Component1 {
    private static final Logger log = LoggerFactory.getLogger(Component1.class);
    @Autowired
    private ApplicationEventPublisher context;
    public void register() {
        log.debug("用戶注冊(cè)");
        context.publishEvent(new UserRegisteredEvent(this));
    }
}

Ⅲ 編寫事件監(jiān)聽類

@Component
public class Component2 {
    private static final Logger log = LoggerFactory.getLogger(Component2.class);
    @EventListener
    public void aaa(UserRegisteredEvent event) {
        log.debug("{}", event);
        log.debug("發(fā)送短信");
    }
}

此處我們是debug輸出的,在application.properties中自定義一下日志輸出:logging.level.com.stone=debug二汛。
Ⅳ 測(cè)試

context.getBean(Component1.class).register();
實(shí)現(xiàn)ApplicationEventPublisher接口.png

實(shí)際使用場(chǎng)景中的作用:解耦合婿崭。

  • ⑤ 總結(jié)

ApplicationContext接口拓展功能:

Ⅰ 國(guó)際化支持(MessageSource);
Ⅱ 通配符獲取資源(ResourcePatternResolver)肴颊;
Ⅲ 獲取環(huán)境變量(EnvironmentCapable)氓栈;
Ⅳ 發(fā)送事件(ApplicationEventPublisher)。

二婿着、容器實(shí)現(xiàn)

1授瘦、BeanFactory實(shí)現(xiàn)

  • ① DefaultListableBeanFactory類

Ⅰ 準(zhǔn)備測(cè)試類

public class TestBeanFactory {
    @Configuration
    static class Config{
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }

        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    static class Bean1{
        private static final Logger log = LoggerFactory.getLogger(Bean1.class);

        public Bean1(){
            log.debug("構(gòu)造 Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }
    }

    static class Bean2{
        private static final Logger log = LoggerFactory.getLogger(Bean1.class);

        public Bean2(){
            log.debug("構(gòu)造 Bean2()");
        }
    }
}

Ⅱ 編寫main方法

public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    // Bean 的定義信息 class、scope竟宋、initMethod提完、destroyMethod 等
    AbstractBeanDefinition bd
            = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
    beanFactory.registerBeanDefinition("config", bd);

    for (String definitionName : beanFactory.getBeanDefinitionNames()) {
        System.out.println(definitionName);
    }
}

可以看到我們@Configuration注解的配置類并沒(méi)有被掃描,所以@Bean相關(guān)的Bean沒(méi)有輸出丘侠,如下:

控制臺(tái)輸出.png

至此徒欣,我們可以看出基礎(chǔ)的DefaultListableBeanFactory類并不直接支持此類注解掃描。

  • ② 注冊(cè)各類后置處理器
// 給 beanFactory 添加常用的后置處理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
添加常用的后置處理器.png

可以看到蜗字,此時(shí)bean1和bean2仍然沒(méi)有被掃描放入容器帚称。以為還沒(méi)有調(diào)用beanFactory的后置處理器官研。添加的后置處理器:
a秽澳、ConfigurationAnnotationProcessor闯睹;(BeanFactory后置處理器)
b、AutowiredAnnotationProcessor担神;
c楼吃、CommonAnnotationProcessor;
d妄讯、EventListenerProcessor孩锡;
e、EventListenerFactory亥贸。

  • ③ beanFactory后置處理器

獲取beanFactory的后置處理器躬窜,并調(diào)用,如下:

beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()
        .forEach(beanFactoryPostProcessor -> {
    beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});
獲取beanFactory的后置處理器.png

至此炕置,我們知道BeanFactoryPostProcessor是對(duì)BeanDifinition的一些補(bǔ)充荣挨。另外,我們嘗試獲取一下bean1朴摊、bean2默垄,如下:

System.out.println(beanFactory.getBean(Bean1.class).getBean2());
嘗試獲取bean1.png

Creating shared instance of singleton bean 'bean1'可以看到Bean對(duì)象在我們獲取的時(shí)候才去創(chuàng)建,并且bean1對(duì)象內(nèi)部并沒(méi)有注入構(gòu)造好的bean2甚纲。此時(shí)我們就需要用到Bean后置處理器口锭。

  • ④ bean后置處理器

其作用是對(duì)bean生命周期的各個(gè)階段提供拓展,例如:@Autowired...

beanFactory.getBeansOfType(BeanPostProcessor.class).values()
        .forEach(beanFactory::addBeanPostProcessor);
bean后置處理器.png

此時(shí)可以看到介杆,裝配bean1時(shí)需要bean2鹃操,容器就去溝通bean2完成注入。但是春哨,實(shí)際使用Spring的時(shí)候荆隘,其實(shí)bean都是在我們使用前已經(jīng)創(chuàng)建好的,我們還需處理悲靴。

  • ⑤ preInstantiateSingletons初始化
beanFactory.preInstantiateSingletons();
System.out.println("==== before we get these beans ====");
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
preInstantiateSingletons初始化.png
  • ⑥ 總結(jié)

Ⅰ 不會(huì)主動(dòng)調(diào)用 BeanFactory 后處理器臭胜;
Ⅱ 不會(huì)主動(dòng)添加 Bean 后處理器;
Ⅲ 不會(huì)主動(dòng)初始化單例癞尚;
Ⅳ 不會(huì)解析beanFactory 還不會(huì)解析 ${ } 與 #{ }(如:@Value注解內(nèi)使用)耸三。

補(bǔ)充:AnnotationConfigUtils類

  • ① 基礎(chǔ)準(zhǔn)備

Ⅰ Config類添加:

@Bean
public Bean3 bean3(){
    return new Bean3();
}
@Bean
public Bean4 bean4(){
    return new Bean4();
}

Ⅱ 編寫接口&內(nèi)部類:

interface Inter{}
static class Bean3 implements Inter{}
static class Bean4 implements Inter{}

Ⅲ Bean1內(nèi)注入:

a、同一接口不同實(shí)現(xiàn)浇揩,@Autowired按照字段名注入仪壮,如下:

@Autowired
private Inter bean3;
@Autowired按照字段名注入.png

b、同一接口不同實(shí)現(xiàn)胳徽,@Resource(name = "bean4")指定注入积锅,如下:

@Resource(name = "bean4")指定注入.png

c爽彤、同時(shí)開啟,按照@Autowired注入缚陷,如下:

同時(shí)開啟按照@Autowired注入.png

d适篙、通過(guò)比較器修改注入,如下:

Ⅰ 修改前

beanFactory.getBeansOfType(BeanPostProcessor.class).values()
        .forEach(beanPostProcessor -> {
            System.out.println("[!!!LOOK HERE!!!] Added BeanPostProcessor is ---> " + beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });
修改前.png

Ⅱ 修改后

添加了.stream().sorted(beanFactory.getDependencyComparator())箫爷;

beanFactory.getBeansOfType(BeanPostProcessor.class).values()
        .stream().sorted(beanFactory.getDependencyComparator())
        .forEach(beanPostProcessor -> {
            System.out.println("[!!!LOOK HERE!!!] Added BeanPostProcessor is ---> " + beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });
修改后.png

2嚷节、ApplicationContext實(shí)現(xiàn)

  • ① ClassPathXmlApplicationContext類

Ⅰ 編寫內(nèi)部類

public class Demo02 {
    static class Bean1{}

    static class Bean2{
        private Bean1 bean1;

        public void setBean1(Bean1 bean1) {
            this.bean1 = bean1;
        }

        public Bean1 getBean1() {
            return bean1;
        }
    }
}

Ⅱ 編寫b01.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-->
        <bean name="bean1" class="com.stone.demo02.Demo02.Bean1"/>
        <bean name="bean2" class="com.stone.demo02.Demo02.Bean2">
            <property name="bean1" ref="bean1"/>
        </bean>
</beans>

Ⅲ 編寫測(cè)試方法

public static void testClassPathXmlApplicationContext(){
    ClassPathXmlApplicationContext context = 
            new ClassPathXmlApplicationContext("b01.xml");
    for (String definitionName : context.getBeanDefinitionNames()) {
        System.out.println(definitionName);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}
ClassPathXmlApplicationContext類.png
  • ② FileSystemXmlApplicationContext類
public static void testFileSystemXmlApplicationContext(){
    FileSystemXmlApplicationContext context
            // 絕對(duì)路徑 D:\JavaStudy\Level1\spring_origin_demo\src\main\resources\b01.xml
            // 相對(duì)路徑 src/main/resources/b01.xml
            = new FileSystemXmlApplicationContext("src/main/resources/b01.xml");
    for (String definitionName : context.getBeanDefinitionNames()) {
        System.out.println(definitionName);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}
FileSystemXmlApplicationContext類.png

補(bǔ)充:XmlBeanDefinitionReader類

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("[!!!LOOK HERE!!!] Before reader works :");
for (String definitionName : beanFactory.getBeanDefinitionNames()) {
    System.out.println(definitionName);
}
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//        reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));
reader.loadBeanDefinitions(new FileSystemResource("src/main/resources/b01.xml"));
System.out.println("[!!!LOOK HERE!!!] After reader worked :");
for (String definitionName : beanFactory.getBeanDefinitionNames()) {
    System.out.println(definitionName);
}
XmlBeanDefinitionReader類.png
  • ③ AnnotationConfigApplicationContext類

Ⅰ 編寫Config類

@Configuration
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
    @Bean
    public Bean2 bean2(Bean1 bean1){
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}

Ⅱ 編寫測(cè)試方法

public static void testAnnotationConfigApplicationContext(){
    AnnotationConfigApplicationContext context
            = new AnnotationConfigApplicationContext(Config.class);
    for (String definitionName : context.getBeanDefinitionNames()) {
        System.out.println(definitionName);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}
AnnotationConfigApplicationContext類.png

補(bǔ)充:<context:annotation-config/>

可以看到AnnotationConfigApplicationContext類自動(dòng)添加了后置處理器,其作用類似于配置<context:annotation-config/>虎锚。

<!--添加注解驅(qū)動(dòng)-->
<context:annotation-config/>
注解驅(qū)動(dòng)在上.png
注解驅(qū)動(dòng)在下.png
  • ④ AnnotationConfigServletWebServerApplicationContext類

Ⅰ 編寫WebConfig類

@Configuration
static class WebConfig{
    @Bean // 必須有
    public ServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }
    @Bean // 必須有
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }
    @Bean // 必須有
    public DispatcherServletRegistrationBean registrationBean(
            DispatcherServlet dispatcherServlet){
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
    @Bean("/hello")
    public Controller controller(){
        // org.springframework.web.servlet.mvc.Controller
        return (request, response) -> {
            response.getWriter().println("Hello, context!");
            return null;
        };
    }
}

Ⅱ 編寫測(cè)試方法

public static void testAnnotationConfigServletWebServerApplicationContext(){
    AnnotationConfigServletWebServerApplicationContext context
            = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    for (String definitionName : context.getBeanDefinitionNames()) {
        System.out.println(definitionName);
    }
}
AnnotationConfigServletWebServerApplicationContext類.png

三硫痰、結(jié)尾

以上即為Spring原理分析-容器&Bean(一)的全部?jī)?nèi)容,感謝閱讀窜护。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末效斑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柱徙,更是在濱河造成了極大的恐慌缓屠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坐搔,死亡現(xiàn)場(chǎng)離奇詭異藏研,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)概行,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蠢挡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人凳忙,你說(shuō)我怎么就攤上這事业踏。” “怎么了涧卵?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵勤家,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我柳恐,道長(zhǎng)伐脖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任乐设,我火速辦了婚禮讼庇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘近尚。我一直安慰自己蠕啄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歼跟,像睡著了一般和媳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哈街,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天留瞳,我揣著相機(jī)與錄音,去河邊找鬼叹卷。 笑死撼港,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骤竹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼往毡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蒙揣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起开瞭,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤懒震,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嗤详,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體个扰,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年葱色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了递宅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苍狰,死狀恐怖办龄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淋昭,我是刑警寧澤俐填,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站翔忽,受9級(jí)特大地震影響英融,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歇式,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一驶悟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贬丛,春花似錦撩银、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)够庙。三九已至,卻和暖如春抄邀,著一層夾襖步出監(jiān)牢的瞬間耘眨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工境肾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剔难,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓奥喻,卻偏偏與公主長(zhǎng)得像偶宫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子环鲤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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