zoukankan      html  css  js  c++  java
  • Spring5源码分析(024)——IoC篇之bean加载:parentBeanFactory和依赖处理

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总


      前面已经分析了从单例缓存中获取到有 bean 实例时的处理流程,那从缓存单例中没有获取到 bean 缓存时,Spring 是如何处理的呢?这也是 Spring 统一加载的,而且肯定都是需要去实例化,可以想到的应该有以下两种场景:
    • 1、从父工厂 parentBeanFactory 中进行加载 :如果当前 BeanFactory 的 beanDefinitionMap 中没有相关的 BeanDefinition 定义且存在父工厂时,则通过父工厂 parentBeanFactory 来获取 bean 实例,此时是递归调用 #getBean 的各个重载方法来处理
    • 2、在当前 BeanFactory 中,根据各种 scope 进行 bean 实例化,此时还需要处理的就是相关的依赖 bean ,他们都需要提前实例化。也即是先实例化依赖,然后再根据不同的 scope 来进行实际的实例化。
     
      鉴于根据各种 scope 进行 bean 实例化的过程相对复杂且篇幅较多,接下来的介绍会拆分为两大部分:
    • 第一部分,主要是一些前置检测、通过 parentBeanFactory 获取 bean 实例、依赖 bean 处理,前文中提到的 2.4 - 2.8
    • 第二部分,则是根据各种 scope 进行 bean 的实例化
      本篇主要对第一部分进行分析,也即是在《Spring5源码分析(021)——IoC篇之bean加载》中提到的 2.4 - 2.8 这几个小节所提到的处理,代码如下:
    /// org.springframework.beans.factory.support.AbstractBeanFactory
    /// protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly)
    
    // 其他处理代码。。。
    
    // Fail if we're already creating this bean instance:
    // We're assumably within a circular reference.
    // 4、原型模式的依赖检查
    // 只有在单例情况下才会尝试解决循环依赖,原型模式情况下,如果存在 A 中有 B 的属性, B 中有 A 的属性,
    // 那么当依赖注入的时候,就会产生A还未创建完的时候因为对于B的创建再次返回创建A,造成循环依赖,也就是下面的情况。
    // 原型模式下如果存在循环依赖则会抛出异常
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
    
    // 5、检查 parentBeanFactory 是否存在对应的 bean
    // Check if bean definition exists in this factory.
    BeanFactory parentBeanFactory = getParentBeanFactory();
    // 当前容器中没有找到,则从父类容器中加载
    // 如果 beanDefinitionMap 中也就是在所有已经加载的类中不包括 beanName 指定的 bean,
    // 则尝试从 parentBeanFactory 中检测
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
        // Not found -> check parent.
        String nameToLookup = originalBeanName(name);
        // 递归到 BeanFactory 中寻找
        if (parentBeanFactory instanceof AbstractBeanFactory) {
            return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                    nameToLookup, requiredType, args, typeCheckOnly);
        }
        else if (args != null) {
            // Delegation to parent with explicit args.
            return (T) parentBeanFactory.getBean(nameToLookup, args);
        }
        else if (requiredType != null) {
            // No args -> delegate to standard getBean method.
            return parentBeanFactory.getBean(nameToLookup, requiredType);
        }
        else {
            return (T) parentBeanFactory.getBean(nameToLookup);
        }
    }
    
    // 6、如果不是仅仅做类型检查则是创建 bean ,这里需要进行记录
    if (!typeCheckOnly) {
        markBeanAsCreated(beanName);
    }
    
    try {
        // 7、将存储 XML 配置的 GenericBeanDefinition 转换成 RootBeanDefinition ,
        // 如果指定的 beanName 是子 bean 的话同时会合并父类的相关属性
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        checkMergedBeanDefinition(mbd, beanName, args);
    
        // 8、处理依赖的 bean
        // Guarantee initialization of beans that the current bean depends on.
        // 若存在依赖则需要递归实例化依赖的 bean
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
            for (String dep : dependsOn) {
                // 循环依赖的情况,depends-on 是强制依赖
                if (isDependent(beanName, dep)) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                }
                // 缓存依赖调用
                registerDependentBean(dep, beanName);
                try {
                    // 递归调用获取依赖的 bean
                    getBean(dep);
                }
                catch (NoSuchBeanDefinitionException ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                }
            }
        }
    /// 其他处理代码。。。

      结合注释可以看出,这段代码主要处理以下这几个部分:

     
    本文目录如下:

    1、原型模式的依赖检测

      众所周知, Spring 只解决单例模式下的循环依赖,而原型模式下检测到循环依赖则会抛出异常(要不然会陷入无限创建的循环中),这就是 #isPrototypeCurrentlyInCreation(String beanName) 所做的处理,具体代码如下:
    /// org.springframework.beans.factory.support.AbstractBeanFactory
    
    // Fail if we're already creating this bean instance:
    // We're assumably within a circular reference.
    // 4、原型模式的依赖检查
    // 只有在单例情况下才会尝试解决循环依赖,原型模式情况下,如果存在 A 中有 B 的属性, B 中有 A 的属性,
    // 那么当依赖注入的时候,就会产生A还未创建完的时候因为对于B的创建再次返回创建A,造成循环依赖,也就是下面的情况。
    // 原型模式下如果存在循环依赖则会抛出异常
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
    
    ///
    /**
     * Return whether the specified prototype bean is currently in creation
     * (within the current thread).
     * @param beanName the name of the bean
     */
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
    
    /** Names of beans that are currently in creation. */
    private final ThreadLocal<Object> prototypesCurrentlyInCreation =
            new NamedThreadLocal<>("Prototype beans currently in creation");

      可以看到循环依赖的检测判断有些似曾相识,都是通过正在创建中的集合来判断的,也就是创建时记录和检查,这里是通过 prototypesCurrentlyInCreation 这个 ThreadLocal 来进行记录相关的创建中原型集合的,而这个记录则是在 beforePrototypeCreation(String beanName)afterPrototypeCreation(String beanName) ,这个可以在稍后面的原型实例化创建时看到有相关调用,这里的默认实现就是直接将 beanName 丢到 prototypesCurrentlyInCreation 中。

    2、检查 parentBeanFactory 是否存在对应的 bean

      如果当前 BeanFactory 的 beanDefinitionMap 中没有相关的 BeanDefinition 定义,也就是说通过 # containsBeanDefinition(String beanName) 方法获不到对应的bean定义,且存在父工厂时,则通过父工厂 parentBeanFactory 来获取 bean 实例,代码如下:

    /// org.springframework.beans.factory.support.AbstractBeanFactory
    
    // 5、检查 parentBeanFactory 是否存在对应的 bean
    // Check if bean definition exists in this factory.
    BeanFactory parentBeanFactory = getParentBeanFactory();
    // 当前容器中没有找到,则从父类容器中加载
    // 如果 beanDefinitionMap 中也就是在所有已经加载的类中不包括 beanName 指定的 bean,
    // 则尝试从 parentBeanFactory 中检测
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
        // Not found -> check parent.
        String nameToLookup = originalBeanName(name);
        // 递归到 BeanFactory 中寻找
        if (parentBeanFactory instanceof AbstractBeanFactory) {
            return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                    nameToLookup, requiredType, args, typeCheckOnly);
        }
        else if (args != null) {
            // Delegation to parent with explicit args.
            return (T) parentBeanFactory.getBean(nameToLookup, args);
        }
        else if (requiredType != null) {
            // No args -> delegate to standard getBean method.
            return parentBeanFactory.getBean(nameToLookup, requiredType);
        }
        else {
            return (T) parentBeanFactory.getBean(nameToLookup);
        }
    }

      这过程其实也好理解,就是递归调用 #getBean 方法来获取实例。需要注意的是,这里的 beanName 是需要做转换的,调用 #originalBeanName(String name) 来处理,代码如下:

    /**
     * Determine the original bean name, resolving locally defined aliases to canonical names.
     * @param name the user-specified name
     * @return the original bean name
     */
    protected String originalBeanName(String name) {
        String beanName = transformedBeanName(name);
        if (name.startsWith(FACTORY_BEAN_PREFIX)) {
            beanName = FACTORY_BEAN_PREFIX + beanName;
        }
        return beanName;
    }
      这里先调用 transformedBeanName(name)(前面文章已经提到过) 来获取真正的 beanName ,然后(如果有的话)再补上 FactoryBean 引用前缀 & 。
     

    3、类型检查

      如果不是仅仅做类型检查则是创建 bean ,则需要调用 #markBeanAsCreated(String beanName) 方法,将该 bean 标记为已创建或即将创建。代码如下:
    /// org.springframework.beans.factory.support.AbstractBeanFactory
    
    /**
     * Mark the specified bean as already created (or about to be created).
     * <p>This allows the bean factory to optimize its caching for repeated
     * creation of the specified bean.
     * <p>将指定的 bean 标记为已创建或即将创建。
     * <p>这允许 beanFactory 优化其缓存,以重复创建指定的Bean。
     * @param beanName the name of the bean
     */
    protected void markBeanAsCreated(String beanName) {
        // 还没有创建
        if (!this.alreadyCreated.contains(beanName)) {
            // 加锁
            synchronized (this.mergedBeanDefinitions) {
                // DCL 双重检查
                if (!this.alreadyCreated.contains(beanName)) {
                    // Let the bean definition get re-merged now that we're actually creating
                    // the bean... just in case some of its metadata changed in the meantime.
                    clearMergedBeanDefinition(beanName);
                    // 添加到已创建 bean 集合中
                    this.alreadyCreated.add(beanName);
                }
            }
        }
    }
    
    /**
     * Remove the merged bean definition for the specified bean,
     * recreating it on next access.
     * <p>从 mergedBeanDefinitions 中删除指定的 bean ,并在下次访问时重新创建它。
     * @param beanName the bean name to clear the merged definition for
     */
    protected void clearMergedBeanDefinition(String beanName) {
        RootBeanDefinition bd = this.mergedBeanDefinitions.get(beanName);
        if (bd != null) {
            bd.stale = true;
        }
    }
     

    4、将存储 XML 配置的 GenericBeanDefinition 转换成 RootBeanDefinition

      代码如下:
    /// org.springframework.beans.factory.support.AbstractBeanFactory
    
    // 7、将存储 XML 配置的 GenericBeanDefinition 转换成 RootBeanDefinition ,
    // 如果指定的 beanName 是子 bean 的话同时会合并父类的相关属性
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    checkMergedBeanDefinition(mbd, beanName, args);
    • 1、这里首先调用 #getMergedLocalBeanDefinition(String beanName) 方法,获取 BeanDefinition 对象,代码如下:
    /// org.springframework.beans.factory.support.AbstractBeanFactory
    
    /**
     * Return a merged RootBeanDefinition, traversing the parent bean definition
     * if the specified bean corresponds to a child bean definition.
     * <p>将存储 XML 配置的 GenericBeanDefinition 转换成 RootBeanDefinition ,
     *    如果指定的 beanName 是子bean的话同时会合并父类的相关属性
     * @param beanName the name of the bean to retrieve the merged definition for
     * @return a (potentially merged) RootBeanDefinition for the given bean
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @throws BeanDefinitionStoreException in case of an invalid bean definition
     */
    protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
        // Quick check on the concurrent map first, with minimal locking.
        RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
        if (mbd != null && !mbd.stale) {
            return mbd;
        }
        return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
    }
    
    /**
     * Return a RootBeanDefinition for the given top-level bean, by merging with
     * the parent if the given bean's definition is a child bean definition.
     * <p>返回给定顶层 bean 的 RootBeanDefinition ,如果给定的 bean 定义是
     * 子 bean 定义,则会同时合并父bean定义
     * @param beanName the name of the bean definition
     * @param bd the original bean definition (Root/ChildBeanDefinition)
     * @return a (potentially merged) RootBeanDefinition for the given bean
     * @throws BeanDefinitionStoreException in case of an invalid bean definition
     */
    protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd)
            throws BeanDefinitionStoreException {
    
        return getMergedBeanDefinition(beanName, bd, null);
    }
    
    /**
     * Return a RootBeanDefinition for the given bean, by merging with the
     * parent if the given bean's definition is a child bean definition.
     * @param beanName the name of the bean definition
     * @param bd the original bean definition (Root/ChildBeanDefinition)
     * @param containingBd the containing bean definition in case of inner bean,
     * or {@code null} in case of a top-level bean
     * @return a (potentially merged) RootBeanDefinition for the given bean
     * @throws BeanDefinitionStoreException in case of an invalid bean definition
     */
    protected RootBeanDefinition getMergedBeanDefinition(
            String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
            throws BeanDefinitionStoreException {
    
        // 同步锁
        synchronized (this.mergedBeanDefinitions) {
            RootBeanDefinition mbd = null;
            RootBeanDefinition previous = null;
    
            // Check with full lock now in order to enforce the same merged instance.
            // 同步锁下重新获取,确保是同一个合并实例对象
            if (containingBd == null) {
                mbd = this.mergedBeanDefinitions.get(beanName);
            }
            // 需要合并/重新合并
            if (mbd == null || mbd.stale) {
                previous = mbd;
                // 没有父bean
                if (bd.getParentName() == null) {
                    // Use copy of given root bean definition.
                    if (bd instanceof RootBeanDefinition) {
                        mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
                    }
                    else {
                        mbd = new RootBeanDefinition(bd);
                    }
                }
                else {
                    // 需要合并父bean定义
                    // Child bean definition: needs to be merged with parent.
                    BeanDefinition pbd;
                    try {
                        String parentBeanName = transformedBeanName(bd.getParentName());
                        if (!beanName.equals(parentBeanName)) {
                            pbd = getMergedBeanDefinition(parentBeanName);
                        }
                        else {
                            BeanFactory parent = getParentBeanFactory();
                            if (parent instanceof ConfigurableBeanFactory) {
                                pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
                            }
                            else {
                                throw new NoSuchBeanDefinitionException(parentBeanName,
                                        "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
                                        "': cannot be resolved without an AbstractBeanFactory parent");
                            }
                        }
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
                                "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
                    }
                    // Deep copy with overridden values.
                    mbd = new RootBeanDefinition(pbd);
                    mbd.overrideFrom(bd);
                }
    
                // Set default singleton scope, if not configured before.
                // 设置默认 scope
                if (!StringUtils.hasLength(mbd.getScope())) {
                    mbd.setScope(SCOPE_SINGLETON);
                }
    
                // A bean contained in a non-singleton bean cannot be a singleton itself.
                // Let's correct this on the fly here, since this might be the result of
                // parent-child merging for the outer bean, in which case the original inner bean
                // definition will not have inherited the merged outer bean's singleton status.
                if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
                    mbd.setScope(containingBd.getScope());
                }
    
                // Cache the merged bean definition for the time being
                // (it might still get re-merged later on in order to pick up metadata changes)
                if (containingBd == null && isCacheBeanMetadata()) {
                    this.mergedBeanDefinitions.put(beanName, mbd);
                }
            }
            if (previous != null) {
                copyRelevantMergedBeanDefinitionCaches(previous, mbd);
            }
            return mbd;
        }
    }
      • 先从缓存 mergedBeanDefinitions 中获取 RootBeanDefinition 对象,如果存在且不需要重新合并,则直接返回;
      • 否则调用 #getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) 获取。前面从 bean 的解析过程中我们可以知道,从 xml 配置文件中读取到的 bean 信息是存储在 GenericBeanDefinition 中的,而 Spring 中所有的 bean 后续的处理都是针对 RootBeanDefinition 的,因此这里需要进行一个转换,转换的同时如果父类 bean 不为空的话,则会一并合并父类的属性,感兴趣的可以进一步深入研究。
    • 2、调用 #checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) 方法,对 RootBeanDefinition 进行校验,这里只是检查是否是抽象类。
    /**
     * Check the given merged bean definition,
     * potentially throwing validation exceptions.
     * @param mbd the merged bean definition to check
     * @param beanName the name of the bean
     * @param args the arguments for bean creation, if any
     * @throws BeanDefinitionStoreException in case of validation failure
     */
    protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args)
            throws BeanDefinitionStoreException {
    
        if (mbd.isAbstract()) {
            throw new BeanIsAbstractException(beanName);
        }
    }

    5、依赖处理

      如果需要获取的 bean 有依赖,则需要确保相关依赖先初始化,代码如下:

    // 8、处理依赖的 bean
    // Guarantee initialization of beans that the current bean depends on.
    // 若存在依赖则需要递归实例化依赖的 bean
    String[] dependsOn = mbd.getDependsOn();
    if (dependsOn != null) {
        for (String dep : dependsOn) {
            // 循环依赖的情况,depends-on 是强制依赖
            if (isDependent(beanName, dep)) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
            }
            // 缓存依赖调用
            registerDependentBean(dep, beanName);
            try {
                // 递归调用获取依赖的 bean
                getBean(dep);
            }
            catch (NoSuchBeanDefinitionException ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
            }
        }
    }

      主要逻辑还是循环依赖检测、缓存注册,然后递归调用 #getBean 方法进行 bean 依赖的实例化。

    5.1、isDependent

      该方法用于确定指定的依赖Bean是否已注册为依赖于给定Bean或依赖于其任何传递依赖项,递归检测,其实就是循环依赖检测,因为 depends-on 是强制依赖,不能有循环依赖

    /// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
    
    /** Map between dependent bean names: bean name to Set of dependent bean names.
     * <p>存放映射关系(依赖谁集合): beanName --> 依赖的 beanNames 集合,即 [canonicalName --> dependentBeanName set] */
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
    
    /**
     * Determine whether the specified dependent bean has been registered as
     * dependent on the given bean or on any of its transitive dependencies.
     * <p>确定指定的依赖Bean是否已注册为依赖于给定Bean或依赖于其任何传递依赖项。
     * @param beanName the name of the bean to check
     * @param dependentBeanName the name of the dependent bean
     * @since 4.0
     */
    protected boolean isDependent(String beanName, String dependentBeanName) {
        synchronized (this.dependentBeanMap) {
            return isDependent(beanName, dependentBeanName, null);
        }
    }
    
    private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
        if (alreadySeen != null && alreadySeen.contains(beanName)) {
            return false;
        }
        // 获取真正的 beanName
        String canonicalName = canonicalName(beanName);
        // 当前 beanName 依赖的bean集合
        Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
        if (dependentBeans == null) {
            return false;
        }
        // 存在则说明存在循环依赖
        if (dependentBeans.contains(dependentBeanName)) {
            return true;
        }
        // 对所有依赖进行递归检测
        for (String transitiveDependency : dependentBeans) {
            if (alreadySeen == null) {
                alreadySeen = new HashSet<>();
            }
            // 添加到 alreadySeen 中
            alreadySeen.add(beanName);
            // 递归调用检测
            if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
                return true;
            }
        }
        return false;
    }

    5.2、registerDependentBean

      为给定的 bean 注册一个依赖 bean,在销毁指定的 bean 之前销毁该 bean,其实就是记录“谁依赖哪些谁”、“谁被哪些谁依赖”之间的映射。需要说明的是, depends-on 是一种强制依赖初始化,不能有循环依赖,因此这里进行了注册判断。代码如下:

    /**
     * Register a dependent bean for the given bean,
     * to be destroyed before the given bean is destroyed.
     * <p>为给定的 bean 注册一个依赖 bean,在销毁指定的 bean 之前销毁该 bean
     * @param beanName the name of the bean
     * @param dependentBeanName the name of the dependent bean
     */
    public void registerDependentBean(String beanName, String dependentBeanName) {
        // 获取真正的 beanName
        String canonicalName = canonicalName(beanName);
    
        // 添加 [canonicalName --> dependentBeanName set] 到 dependentBeanMap 中
        synchronized (this.dependentBeanMap) {
            Set<String> dependentBeans =
                    this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
            if (!dependentBeans.add(dependentBeanName)) {
                return;
            }
        }
    
        // 添加 [dependentBeanName --> canonicalName set] 到 dependenciesForBeanMap 中
        synchronized (this.dependenciesForBeanMap) {
            Set<String> dependenciesForBean =
                    this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
            dependenciesForBean.add(canonicalName);
        }
    }

     

    5.3、getBean

      这里就是通过递归调用 #getBean 方法实例化依赖的 bean 。 

    6、参考

  • 相关阅读:
    纯数学教程 Page 325 例LXVIII (12)
    纯数学教程 Page 325 例LXVIII (11) Math.Trip.1927,1928
    纯数学教程 Page 325 例LXVIII (11) Math.Trip.1927,1928
    纯数学教程 Page 325 例LXVIII (9)
    纯数学教程 Page 325 例LXVIII (14) $\frac{1}{1^2}+\frac{1}{3^2}+\frac{1}{5^2}+\cdots=\frac{3}{4}(\frac{1}{1^2}+\frac{1}{2^2}+\frac{1}{3^2}+\cdots)$
    纯数学教程 Page 325 例LXVIII (14) $\frac{1}{1^2}+\frac{1}{3^2}+\frac{1}{5^2}+\cdots=\frac{3}{4}(\frac{1}{1^2}+\frac{1}{2^2}+\frac{1}{3^2}+\cdots)$
    纯数学教程 Page 325 例LXVIII (13)
    纯数学教程 Page 325 例LXVIII (12)
    纯数学教程 Page 325 例LXVIII (9)
    纯数学教程 Page 325 例LXVIII (13)
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14968540.html
Copyright © 2011-2022 走看看