spring ioc原理

原理:一般指的是代码的实现 以及涉及到的技术 

一:以xml的形式来说 


1.主要涉及到xml解析技术 以及 反射机制 还有实例存储的数据结构(容器) 
1)xml解析技术: 包括dom4j 以及jdom支持xpath… 功能更强大 推荐使用 
2)反射机制: 首先得获得类的全路径,该类还得有构造器,如果你要是写了有参构造器的话,必须得显示写无参构造器。默认情况下,该类是自带无参构造器。 
然后Class.forName(“xxx.xxx.xxx.User”).newInstance 会构建一个实例 利用无参构造器。 
3)容器: 
特点:根据唯一标示 获取该实例 
容器底层的数据结构:采用Map集合构架承载实例与ID的角色 


二 :以注解的形式来说 


利用反射机制获得注解 进行对该类实例化 
IOC 和 D I 
IOC:控制反转 控制指的是什么?反转指的是什么? 
控制:指的是构建实例的控制权 以前构建实例的权利由我们开发人员来new出来,包括依赖关系的注入,此时实例的控制权为引用类,现在有了spring,构建实例的权利交给spring来接管,反转控制就是反转了对象的创建方式,从我们自己创建反转给力程序(spring) 
反转:控制权由其他类(引用类)转为spring了 
D I:依赖注入 依赖是什么?注入又是什么? 
依赖:指的是A类 要引用B类 那么我们就说A依赖B 
注入:A既然依赖B 那么B类何时初始化呢?为了解决耦合度的问题 不能再A类中new B了 需要靠spring将B类的实例传递给A,该过程叫做注入 
耦合度问题:A依赖B才能完成特定的功能。该B为一个借口,实现类有多个Bimpl_1 Bimpl_2各有不同。 
以前A可以直接使用Bimpl_1 那么我们就说A与Bimpl_1 紧密联系在一起了,耦合度很高 
现在A依赖Bimpl_1 的接口即B,B接口的实现由Spring来控制注入Bimpl_1 或者Bimpl_2.即A与Bimpl_1 实现了解耦(多态 多种形式表现) 
IOC 和 DI的区别: 
先有了IOC 将实例构建出来 才能DI,就是去ioc容器里即concurrentHashMap里根据依赖类的 Bean 以 beanName 为键去保存实例的HashMap 中去取该类的实例化对象,注入

(原来就这些,spring的代码实现,看看下面的就好了,不用去费心记,你也记不住,没必要,不过还是写下,方便理解)

IOC容器的设计与实现

1,IoC容器设计中,两个主要的接口

1)BeanFactory:可以理解为IoC容器的抽象,提供了ioc容器的最基本API。
2)ApplicationContext:IoC容器的高级形态,在基础IoC容器上加了许多特性。
从继承图来理解:

DefaultListableBeanFactory是IoC容器的最基础实现,是一个最基础最简单的IoC容器对象,其高级容器ApplicationContext也是通过持有DefaultListableBeanFactory引用,在基础IoC容器之上进行特性增强。

容器设计原理

XmlBeanFactory这个IoC容器便是在DefaultListableBeanFactory基础容器之上,添加了如xml读取的附加功能。
编程式使用IoC容器示例

ClassPathResource res = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

大致过程如下几步:
1)读取bean.xml配置文件资源
2)创建基础BeanFactory容器(DefaultListableBeanFactory)
3)创建资源读取器(XmlBeanDefinitionReader),并将其与BeanFactory容器对象进行关联
4)将bean.xml配置文件中的信息解析成BeanDefinition元数据信息对象,并注册到BeanFactory(IoC容器)中

ApplicationContext容器特点

ApplicationContext通过对不同特性接口的实现为基础IoC容器添加了许多特性
1)MessageSource接口:支持不同的信息源。
2)ResourceLoader和Resource接口:支持从不同的途径获取资源信息。
3)ApplicationEventPublisher接口:支持应用事件。
4)ApplicationContext接口:在基本IoC容器基础上增加附加服务。

IoC容器的初始化过程

IoC容器启动的三个基本过程
1)Resource资源信息定位
2)BeanDefinition的载入(将资源中的信息初始化为BeanDefinition对象)
3)向IoC容器注册生成的BeanDefinition对象

下面我们通过FileSystemXmlApplicationContext为例,进行IoC容器初始化过程的解析

 

FileSystemXmlApplicationContext是一个支持xml定义BeanDefinition的ApplicationContext,我们看一下
FileSystemXmlApplicationContext的具体实现

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

    public FileSystemXmlApplicationContext() {
    }

    public FileSystemXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }

    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }

    // 调用refresh函数载入和注册BeanDefinition对象,完成IoC容器的初始化过程
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }
    // 构造一个FileSystemResource来获取资源文件,此方法是在BeanDefinitionReader的loadBeanDefintion中被调用
    // loadBeanDefintion方法是一个抽象,具体的资源定位的实现由具体的子类来完成
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }
}

这里的refresh方法是IoC容器初始化的唯一入口
初始化调用过程大致如下:IoC容器执行refresh的过程

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // 初始化准备工作
      prepareRefresh();
      // 获取BeanFactory
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // BeanFactory的预准备工作
      prepareBeanFactory(beanFactory);

      try {
         // beanFactory准备工作完成之后进行的后置处理工作。
         postProcessBeanFactory(beanFactory);

         // 调用BeanFactoryPostProcessor各个实现类的postProcessFactory(factory)方法。
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册BeanPostProcessor的实现类
         registerBeanPostProcessors(beanFactory);

         // 初始化当前ApplicationContext的MessageSource
         initMessageSource();

         //初始化ApplicationContext事件广播器
         initApplicationEventMulticaster();

         // 钩子方法,在这里可以初始化一些特殊的bean
         onRefresh();

         // 注册事件监听器
         registerListeners();

         // 初始化所有的singleton beans
         finishBeanFactoryInitialization(beanFactory);

         //广播初始化完成
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

其中最主要的BeanDefintion对象的载入和注册过程是在
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()中refreshBeanFactory方法中完成

 @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 创建ioc容器
            // new DefaultListableBeanFactory(getInternalParentBeanFactory())
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            // 启动BeanDefinition的载入和注册过程
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

这里的loadBeanDefinitions方法中会创建XmlBeanDefinitionReader对象,并启动BeanDefinitionReader的getResourceLoader方法

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // 以Resource的方式获取配置文件的资源位置
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        // 以String的方式获取配置文件的资源位置
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

BeanDefinitionReader抽象中会完成配置文件的资源的加载

 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            // 调用具体实现容器的getResourceByPath方法获取Resource handle对象并根据具体实现加载配置资源
            Resource resource = resourceLoader.getResource(location);
            // 加载和注册BeanDefinition元信息对象,具体由XmlBeanDefinitionReader实现
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

XmlBeanDefinitionReader中loadBeanDefinitions的具体实现

 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 {
            // 准备IO流来获取配置资源中的数据
            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();
            }
        }
    }

    /**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
     // 真正开始从指定的资源文件中获取信息,加载并注册BeanDefinition到IoC中
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            // 通过DefaultDocumentLoader对象获取xml配置文件的Document对象
            Document doc = doLoadDocument(inputSource, resource);
            // 将获取的Document对象解析成BeanDefinition对象,并注册到IoC容器中
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

将获取的Document对象解析成BeanDefinition对象的具体实现由BeanDefinitionParserDelegate解析器具体实现完成

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 将Document对象解析成BeanDefinition对象的具体方法
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                //将解析好的BeanDefinition注册到DefaultListableBeanFactory容器对象中
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

具体的Document解析成BeanDefinition的过程是通过不断的递归调用完成,具体实现这里不做分析

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

        this.parseState.push(new BeanEntry(beanName));
        // 获取定义的<bean>中设置的bean的name,设置到BeanDefinition对象中去
        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }

        try {
            String parent = null;
            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                parent = ele.getAttribute(PARENT_ATTRIBUTE);
            }
            // 创建BeanDefinition对象
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            // 对bean元素的属性进行解析
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            parseMetaElements(ele, bd);
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
            // 解析<bean>的构造函数设置
            parseConstructorArgElements(ele, bd);
            // 解析<bean>的property设置
            parsePropertyElements(ele, bd);
            parseQualifierElements(ele, bd);

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

            return bd;
        }
        catch (ClassNotFoundException ex) {
            error("Bean class [" + className + "] not found", ele, ex);
        }
        catch (NoClassDefFoundError err) {
            error("Class that bean class [" + className + "] depends on not found", ele, err);
        }
        catch (Throwable ex) {
            error("Unexpected failure during bean definition parsing", ele, ex);
        }
        finally {
            this.parseState.pop();
        }

        return null;
    }

而注册则是将BeanDefinition对象放入到DefaultListableBeanFactory容器中维护的一个map中
this.beanDefinitionMap.put(beanName, beanDefinition);

至此,IoC容器的初始化过程就完成了。

总结:IoC容器的初始化过程就是将xml配置资源的信息抽象到BeanDefinition信息对象中,再将BeanDefinition设置到基本容器的map中,BeanDefinition中的信息是容器建立依赖反转的基础,IoC容器的作用就是对这些信息进行处理和维护。

IoC容器的依赖注入

依赖注入是用户第一次向IoC容器索要Bean时触发的,入口为DefaultListableBeanFactory基类AbstractBeanFactory中的getBean方法

具体依赖注入过程

总结

Spring IOC容器主要有继承体系底层的BeanFactory、高层的ApplicationContext和WebApplicationContext

Bean有自己的生命周期

容器启动原理:Spring应用的IOC容器通过tomcat的Servlet或Listener监听启动加载;Spring MVC的容器由DispatchServlet作为入口加载;Spring容器是Spring MVC容器的父容器

容器加载Bean原理:

BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作;使用BeanWrapper完成Bean属性的设置工作;

单例Bean缓存池:Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以 beanName 为键保存在这个HashMap 中。

spring的依赖注入原理

 

我们可以看出,对属性的注入过程分以下两种情况:
1)、属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
2)、属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
对属性值的解析是在 BeanDefinitionValueResolver 类中的 resolveValueIfNecessary()方法中进行的,对属性值的依赖注入是通过 bw.setPropertyValues()方法实现的,在分析属性值的依赖注入之前,我们先分析一下对属性值的解析过程 

解析属性注入规则

当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个 Bean实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由 resolveValueIfNecessary()方法实现。

Spring IOC 容器是如何将属性的值注入到 Bean 实例对象中去的:
1)、对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
2)、对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 getter()方法获取指定属性注入以前的值,同时调用属性的 setter()方法为属性设置注入后的值。看到这里相信很多人都明白了 Spring的 setter()注入原理

依赖注入流程补充(这个比较细)

1、getBean第一次调用lazy-init的bean

      是以BeanFactory的getBean方法为入口触发的(实现在AbstractBeanFactory实现类中)。如果是单例会缓存起来只加载一次,如果是FactoryBean这种特殊的bean会把这个bean的实例传入getObjectForBeanInstance方法获得FactoryBean产生的bean(调用FactoryBean的getObject方法,这就是自定义的FactoryBean要重写的方法,AOP也是这个原理,详情见下方AOP分析)。在第一次载入时要先判断这个BeanDefinition在当前BeanFactory有没有,没有就从双亲BeanFactory中找,一直递归。

      找到后要验证是否存在递归依赖,有则报错无则设置当前bean依赖bean的依赖关系到两个map中(一个是被依赖map,一个是依赖map),其中:
      (1)如果是单例第一次载入就调用getSingleton方法(方法中回调了参数中ObjectFactory的getObject方法,这里重写了这个方法调用createBean)获得实例用getObjectForBeanInstance获得FactoryBean产生的bean(如果它是FactoryBean的话)。
      (2)如果是prototype加载调用createBean后调用getObjectForBeanInstance。
      (3)如果是其他scope类型:request、session和global session,这三种就用scope.get获取实例(和getSingleton类似回调重写的getObject也就是调用createBean)后调用getObjectForBeanInstance。

      最后如果getBean指定了requiredType要检验获取的bean能不能转化成指定的类型不能的话就报错。

      createBean方法就是生成bean的方法并对一些比如init-method属性、后置处理器等一些初始化进行了处理。方法中在实例化之前判断是否有post-processor,如果有这样的processor则短路指定bean的创建,直接返回一个proxy而不是指定的bean(这种processor可以指定生成一个其他类型的对象)没有的话用doCreateBean创建bean返回。

      doCreateBean是用createBeanInstance生成BeanWrapper(包装bean)之后用populateBean向其中的bean完成依赖bean的注入(autowire等)。

      createBeanInstance创建beanWrapper时分三类进行处理:
      (1)如果有工厂方法,调用instantiateUsingFactoryMethod创建。
      (2)如果是构造器注入的方式调用autowireConstructor。
      (3)简单方式调用instantiateBean。调用的是策略类(默认SimpleInstantiationStrategy)的instantiate而其中又是通过bean方法是否有跟IOC容器同名的(会被覆盖)来分两类处理(没同名方法的从BeanDefinition中拿出class直接用jdk的反射拿构造器来newinstance一个实例,如果有同名的则是用CGLIB的方式来new一个实例)。

      populateBean为生成的bean依赖注入,先对非简单类型属性有autowire的进行处理,判断这个属性在之前解析载入beanDefinition时property里有没有,有的话进行getBean初始化后放入PropertyValue集合中(这个就是propertyname和value的封装),然后更新依赖map,再对非autowire的或一般属性进行注入,有要转化的要经过valueResolver的转化(如果是RuntimeBeanReference之前载入时XML中配置是ref的就getBean(如果在双亲BeanFactory中就从双亲中取)获得后也放到PropertyValue集合中,也要更新依赖map)。最后再注入到bean中,这里说的注入其实真实发生在最后的BeanWraper的setPropertyValue(propertyValue集合)方法,具体实现就是通过反射的方式获得setter方法赋值。

2、lazy-init==false初始化(只对singleton,也是默认方式)

      在refresh方法中的finishBeanFactoryInitialization方法中进行初始化(实际也是调用getBean方法)。

IoC(控制反转)思想(其实ioc是个思想)

控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系

spring的ioc就是spring对ioc原理的实现,就是创建对象和索取对象都交给了ioc容器。就是通过bean初始化入后refresh()方法入口,进行一系列的处理,最终通过beanName这一属性,反射实例化bean对象,在放到concurrentHashMap中缓存起来,这里可以看出spring的ioc容器就是个map-存储bean信息的map,用的时候,通过beanName去获取bean,这里需要注意,通过beanName获取bean的时候,是已经初始化的,没初始化的,还要走一边bean的初始化流程

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章