当前位置:   article > 正文

Spring Bean生命周期之(2)实例化( Instantiation)_spring instantiatebean

spring instantiatebean

1 Bean实例化( Instantiation)

点击了解 Spring bean生命周期之(1)BeanDefinition
在Bean创建阶段主要包括:Spring Bean 实例化前阶段、Spring Bean 实例化阶段、Spring Bean 实例化后阶段等阶段。

1.1 重要的后置处理器

  • InstantiationAwareBeanPostProcessor
    • Spring Bean 实例化前阶段:第一次调用后置处理器 postProcessBeforeInstantiation方法,默认实现是判断是否需要代理放入map中
    • Spring Bean 实例化后置阶段:第五次调用后置处理器 postProcessAfterInstantiation方法 ,属性赋值(Populate)判断是否需要属性填充
    • populateBean属性赋值 :第六次调用后置处理器:postProcessPropertyValuesbean填充属性包括依赖注入的属性
  • SmartInstantiationAwareBeanPostProcessor后置处理器:
    第二次调用后置处理器 determineCandidateConstructors 获取最优构造方法实例化对象
  • SmartInstantiationAwareBeanPostProcessor后置处理器
    第四次调用后置处理器getEarlyBeanReference解决循环依赖的问题

在这里插入图片描述

1.2 Bean实例化详细流程

相关入口:

根据类型获取Bean
org.springframework.context.support.AbstractApplicationContext.getBean(Class<T>)
根据名字获取Bean
org.springframework.context.support.AbstractApplicationContext.getBean(String)
实际获取Bean
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean
根据给出的Bean获取真实Bean(可能是factoryBean)
org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance
创建Bean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean
实际创建Bean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

下面是 的相关源码

AbstractApplicationContext.finishBeanFactoryInitialization()实例化bean入口方法

//创建Bean实例对象
	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
 
		if (logger.isDebugEnabled()) {
			logger.debug("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;
 
		//判断需要创建的Bean是否可以实例化,即是否可以通过当前的类加载器加载
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
 
		// 校验和准备Bean中的方法覆盖
		try {
			mbdToUse.prepareMethodOverrides();
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
					beanName, "Validation of method overrides failed", ex);
		}
 
		try {
			//如果Bean配置了初始化前和初始化后的处理器,则试图返回一个需要创建Bean的代理对象
			//TODO 第一次调用bean的后置处理器 主要判断bean需要被代理  bean一般都为空
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}
 
		try {
			//创建Bean的入口
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isDebugEnabled()) {
				logger.debug("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException ex) {
			// A previously detected exception with proper bean creation context already...
			throw ex;
		}
		catch (ImplicitlyAppearedSingletonException ex) {
			// An IllegalStateException to be communicated up to DefaultSingletonBeanRegistry...
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

1.3 Spring Bean实例化前阶段

第一次调用后置处理器
非主流生命周期 – Bean 实例化前阶段:InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
在这里插入图片描述

1.3.1 源码实现解析

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
 
<!--    <context:annotation-config/>-->
 
<!--    <context:component-scan base-package="org.acme" />-->
 
    <!-- Root BeanDefinition 不需要合并,不存在 parent -->
    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 经过合并后 GenericBeanDefinition 变成 RootBeanDefinition -->
    <bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
        <property name="id" value="1"/>
        <property name="name" value="小马哥"/>
        <property name="city" value="HANGZHOU"/>
        <property name="workCities" value="BEIJING,HANGZHOU"/>
        <property name="lifeCities">
            <list>
                <value>BEIJING</value>
                <value>SHANGHAI</value>
            </list>
        </property>
        <property name="configFileLocation" value="classpath:/META-INF/user-config.properties"/>
    </bean>
 
    <!-- 普通 beanDefinition GenericBeanDefinition -->
    <!-- 合并后 GenericBeanDefinition 变成 RootBeanDefinition,并且覆盖 parent 相关配置-->
    <!-- primary = true , 增加了一个 address 属性 -->
    <bean id="superUser" class="org.geekbang.thinking.in.spring.ioc.overview.domain.SuperUser" parent="user"
          primary="true">
        <property name="address" value="杭州"/>
    </bean>
  
 
</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

spring 内置的实现 (主要作用是判断 bean是否需要被代理 ,需要被代理的类 就放入map中)
代码位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation(beanName, mbdToUse)

@Nullable
	protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
		Object bean = null;
		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
			// Make sure bean class is actually resolved at this point.
			//TODO mbd.isSynthetic() 表示是否合成类
			// hasInstantiationAwareBeanPostProcessors() 判断系统是否有 InstantiationAwareBeanPostProcessors
			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
				Class<?> targetType = determineTargetType(beanName, mbd);
				if (targetType != null) {
					//这个一般都是为空
					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
					if (bean != null) {
						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
					}
				}
			}
			mbd.beforeInstantiationResolved = (bean != null);
		}
		return bean;
	}
 
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

主要实现类就一个AbstractAutoProxyCreator#postProcessBeforeInstantiation

主要作用是判断 bean是否需要被代理 ,需要被代理的类 就放入map中

@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(beanClass, beanName);
 
		if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
			//advisedBeans 不需要被代理的对象
			if (this.advisedBeans.containsKey(cacheKey)) {
				return null;
			}
			if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
				this.advisedBeans.put(cacheKey, Boolean.FALSE);
				return null;
			}
		}
 
		// Create proxy here if we have a custom TargetSource.
		// Suppresses unnecessary default instantiation of the target bean:
		// The TargetSource will handle target instances in a custom fashion.
		TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
		if (targetSource != null) {
			if (StringUtils.hasLength(beanName)) {
				this.targetSourcedBeans.add(beanName);
			}
			Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
			Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}
 
		return null;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

1.4 Spring Bean 实例化阶段

实例化阶段主要是 根据后置处理器 推断出 实例化bean的最优构造方法,实例化对象。

第二次调用bean的后置处理器:推断实例化构造方法—》SmartInstantiationAwareBeanPostProcessor #determineCandidateConstructors

路径:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance(beanName, mbd, args)

接口:
SmartInstantiationAwareBeanPostProcessor #determineCandidateConstructors(Class<?> beanClass, String beanName)

实现类:
AutowiredAnnotationBeanPostProcessor #determineCandidateConstructors

这个后置处理器 需要实现 SmartInstantiationAwareBeanPostProcessor接口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述
实例化方式:

  • 使用默认无参构造器:instantiateBean(beanName, mbd)
    实例化策略 - InstantiationStrategy— instantiationStrategy = new CglibSubclassingInstantiationStrategy()---- SimpleInstantiationStrategy
  • 使用依赖注入的构造器:autowireConstructor(beanName, mbd, ctors, args)

1.4.1 第二次调用后置处理器推断构造方法

接口:SmartInstantiationAwareBeanPostProcessor #determineCandidateConstructors(Class<?> beanClass, String beanName)

实现类:AutowiredAnnotationBeanPostProcessor #determineCandidateConstructors

其中推断构造器的规则

这个分两种类型

  • 手动注入
    合适的构造方法创建对象
    • 只有一个无参构造方法 ctors为 null 使用默认无参构造方法
    • 如果有多个构造方法 ctors为 null 使用默认无参构造方法
    • 如果只有一个有参构造方法 ctors不为null 因为只有一个有参数的 只能用这个了
    • 多个构造方法 且只有一个构造方法加了@Autowired(required = true) 用这个构造方法来创建对象
    • 多个构造方法 且多个构造方法加了@Autowired(required = true) spring ioc容器报错
    • 多个构造方法 且多个构造方法加了@Autowired(required = false) 就把构造方法都加到集合中 第二次推断
  • 自动注入 --通过构造方法自动注入
    • 如果有多个构造方法 找出最优的构造器 参数最多的为最优的
    • 多个构造方法 且只有一个构造方法加了@Autowired(required = true) 用这个构造方法来创建对象
    • 多个构造方法 且多个构造方法加了@Autowired(required = true) spring ioc容器报错

1.4.2 instantiateBean(beanName, mbd)使用默认无参构造器实例化对象

当推断出来使用默认无参构造

//使用默认的无参构造方法实例化Bean对象
	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			final BeanFactory parent = this;
			//获取系统的安全管理接口,JDK标准的安全管理API
			if (System.getSecurityManager() != null) {
				//这里是一个匿名内置类,根据实例化策略创建实例对象
				beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
						getInstantiationStrategy().instantiate(mbd, beanName, parent),
						getAccessControlContext());
			}
			else {
				//将实例化的对象封装起来
				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
			}
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
			initBeanWrapper(bw);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

1.4.3 使用依赖注入的构造器实例化对象:autowireConstructor(beanName, mbd, ctors, args)

使用条件:推断处理的 构造器方法不为null或者开启自动装配或者 使用指定入参的构造方法

if (ctors != null ||
				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || //这个表示自动装配
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
			//使用容器的自动装配特性,调用匹配的构造方法实例化
			//使用推断出来的构造方法找到一个可以用的 实例化bean
			return autowireConstructor(beanName, mbd, ctors, args);
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

具有实现:ConstructorResolver#autowireConstructor 这里会再次推断构造方法实例化对象。

1.5 处理合并BeanDefinition与解决循环依赖后置处理器

实例化后会第三和第四调用后置处理器
在这里插入图片描述

1.5.1 第三调用后置处理器MergedBeanDefinitionPostProcessor

这里主要解决 处理合并BeanDefinition的问题

入口:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName)

接口:这个后置处理器 需要实现 MergedBeanDefinitionPostProcessor 接口中的postProcessMergedBeanDefinition方法

合并相关介绍:

为什么会提前合并
通过类型找到名字—返回一个BeanFactoryPostProcessor的集合–完成了合并

不能用原始的BeanDefinition 去比较 必须合并
代码流程 —AbstractApplicationContext.invokeBeanFactoryPostProcessors(beanFactory);–>>PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());–>DefaultListableBeanFactory.getBeanNamesForType(@Nullable Class<?> type) -->RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

//通过类型找到名字---返回一个BeanFactoryPostProcessor的集合--完成了合并
//返回一个BeanDefinition 后置工厂的名字的集合--通过名字实例化BeanFactoryPostProcessor
String[] postProcessorNames =  
	beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

但是在实例化bean的时候会重新在去 合并bd 会把缓存中的合并后的BeanDefinition删除在重新合并

AbstractAutowireCapableBeanFactory applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);调用后置处理的源码

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof MergedBeanDefinitionPostProcessor) {
				MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
				bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
			}
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
1.5.1.1 实现类:AutowiredAnnotationBeanPostProcessor

findAutowiredMetadata----找出所有需要完成注入的“点”-----@Autowired @Value注解方法或者属性—为什么不需要构造方法
​​​​checkConfigMembers----injectedElements 做了一个复制

@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		//
		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
1.5.1.2 实现类:CommonAnnotationBeanPostProcessor

调用父类的方法,查找所有的生命周期回调方法—初始化和销毁
findResourceMetadata----找出所有需要完成注入的“点”-----@Resource注解
checkConfigMembers----injectedElements 做了一个复制

@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
		InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

两者使用的bean的后置处理器不一样的。

1.6 spring bean实例化后置阶段

这个阶段主要判断当前实例化的bean是否需要属性注入
在这里插入图片描述

1.6.1 第五次调用后置处理器 :属性是否注入InstantiationAwareBeanPostProcessor

路径:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean(beanName, mbd, instanceWrapper)

//todo 第五次---属性是否注入InstantiationAwareBeanPostProcessor
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						continueWithPropertyPopulation = false;
						break;
					}
				}
			}
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

接口:InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)

所有spring内置的实现类都为true

1.6.2 测试实例

我们当创建use bean的时候放回 false。这样这个bean就不会填充任何属性了。

/**
 * Bean 实例化生命周期示例
 *
 */
public class BeanInstantiationDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 方法一:添加 BeanPostProcessor 实现 MyInstantiationAwareBeanPostProcessor
        beanFactory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessorDome());
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "META-INF/dependency-lookup-context.xml";
        Resource resource = new ClassPathResource(location);
        // 指定字符编码 UTF-8
        EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
        beanDefinitionReader.loadBeanDefinitions(encodedResource);
        // 通过 Bean Id 和类型进行依赖查找
        User user = beanFactory.getBean("user", User.class);
        System.out.println(user);
        User superUser = beanFactory.getBean("superUser", User.class);
        System.out.println(superUser);
 
    }
 
    /**
     * 实现 后置处理器
     */
    static class  MyInstantiationAwareBeanPostProcessorDome implements InstantiationAwareBeanPostProcessor {
 
        @Override
        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            if(ObjectUtils.nullSafeEquals("superUser",beanName) && SuperUser.class.equals(beanClass)){
                //把配置完成的 superUser bean覆盖
                return  new SuperUser();
            }
            return null;//这里表示什么都不变化
        }
        @Override
        public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
            if(ObjectUtils.nullSafeEquals("superUser",beanName) && SuperUser.class.equals(beanClass)){
                //如果是 user 对象不允许属性的赋值
                return  false;
            }
            return true;//这里表示什么都不变化
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

1.7总结

在这里插入图片描述

参考链接:https://blog.csdn.net/qq_36697880/article/details/113836822

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/66337
推荐阅读
相关标签
  

闽ICP备14008679号