1.springboot-启用自动化配置工作原理

1.启用自动化配置

  • 在springboot中使用@EnableAutoConfiguration注解开启,而@EnableAutoConfiguration集成在了@SpringBootApplication中,所以springboot项目中不需要显示配置@EnableAutoConfiguration,默认开启了自动化配置;
  • @EnableAutoConfiguration集成了@AutoConfigurationPackage,收集spring扫描的包名;
  • @EnableAutoConfiguration导入了AutoConfigurationImportSelector类(springboot-EnableXX启用配置原理),AutoConfigurationImportSelector注册所有自动化配置相关的配置类(XXAutoConfiguration);

2.@AutoConfigurationPackage

作用:收集spring扫描的包名/类。由于没有配置basePackages和basePackageClasses,实际上是把标注@AutoConfigurationPackage类所在的包添加到了spring扫描的包中;

Registrar注册包名

  • AutoConfigurationPackage导入了AutoConfigurationPackages的内部类Registrar;
  • ImportBeanDefinitionRegistrar接口registerBeanDefinitions方法使用registry注册相关bean,通常是注册一些配置类;
  • DeterminableImports接口,与ImportSelector/ImportBeanDefinitionRegistrar一起使用时,描述ImportSelector/ImportBeanDefinitionRegistrar注册的bean名称;
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //**调用register方法注册包名
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	    //**根据注解元数据创建PackageImports获取包名,然后注册
		register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
	}

    //**描述ImportBeanDefinitionRegistrar注册的包名;
	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImports(metadata));
	}
}

PackageImports获取需注册包名

private static final class PackageImports {
    //**需要注册的包名
	private final List<String> packageNames;

    //**构造函数,获取需要注册的包名
	PackageImports(AnnotationMetadata metadata) {
	    //**获取AutoConfigurationPackage注解元数据
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
		List<String> packageNames = new ArrayList<>();
		//**获取配置的basePackages,实际上没有配置
		for (String basePackage : attributes.getStringArray("basePackages")) {
			packageNames.add(basePackage);
		}
		//**获取配置的basePackageClasses,实际上没有配置
		for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
			packageNames.add(basePackageClass.getPackage().getName());
		}
		//**如果basePackages和basePackageClasses都没有配置,则把标注@AutoConfigurationPackage类所在的包添加到packageNames中
		if (packageNames.isEmpty()) {
		    //**使用className.lastIndexOf(".")截取类所在的包名
			packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
		}
		this.packageNames = Collections.unmodifiableList(packageNames);
	}

	List<String> getPackageNames() {
		return this.packageNames;
	}

    //**DeterminableImports接口返回的对象必须实现hashCode和equals方法
	public boolean equals(Object obj) { ... }
	public int hashCode() { ... }
	public String toString() { ... }
}

register注册包名逻辑

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    //**如果已经注册了AutoConfigurationPackages
	if (registry.containsBeanDefinition(BEAN)) {
	    //**获取名称为AutoConfigurationPackages的bean定义,实际上注册的class是BasePackages
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		//**获取构造函数的参数值
		ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
		//**把packageNames与原有的包名合并,然后替换原有的构造函数参数值
		constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
	}
	//**如果没有注册AutoConfigurationPackages
	else {
	    //**创建bean定义
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		//**class为BasePackages
		beanDefinition.setBeanClass(BasePackages.class);
		//**设置packageNames为构造函数参数值
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		//**注册名称为AutoConfigurationPackages的bean
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

//**把新的包名与原有的包名合并
private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
	String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
	Set<String> merged = new LinkedHashSet<>();
	merged.addAll(Arrays.asList(existing));
	merged.addAll(Arrays.asList(packageNames));
	return StringUtils.toStringArray(merged);
}

//**BasePackages
static final class BasePackages {
	private final List<String> packages;
	private boolean loggedBasePackageInfo;
	
    //**构造函数
	BasePackages(String... names) {
		List<String> packages = new ArrayList<>();
		for (String name : names) {
			if (StringUtils.hasText(name)) {
				packages.add(name);
			}
		}
		this.packages = packages;
	}
}

3.AutoConfigurationImportSelector

调用

  • ConfigurationClassParser.getImports()调用了AutoConfigurationImportSelector内部类AutoConfigurationGroup.process(处理自动配置类),然后调用selectImports(导入自动配置类);
  • 调试时没有调用AutoConfigurationImportSelector.selectImports();
  • 上述两者功能基本一样,核心功能为getAutoConfigurationEntry(获取导入的自动配置类名,并封装为AutoConfigurationEntry对象);
public Iterable<Group.Entry> getImports() {
	for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
		this.group.process(deferredImport.getConfigurationClass().getMetadata(),
				deferredImport.getImportSelector());
	}
	return this.group.selectImports();
}

说明(重点)

  • 可通过spring.boot.enableautoconfiguration禁用自动配置;
  • 默认的自动配置为spring-boot-autoconfigure包META-INF/spring.factories中key=EnableAutoConfiguration的数据。可通过在项目创建resources/META-INF/spring.factories文件,并配置key=EnableAutoConfiguration的数据加载自定义的自动配置类,示例:
//**自定义自动配置,加载Bean Test
public class MyAutoConfiguration {

    @Bean
    public Test test(){
        Test t = new Test();
            t.setId("12");
            t.setName("pings");
        return t;
    }
}

//**resources/META-INF/spring.factories配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.pings.spring.demo.cache.MyAutoConfiguration
  • 上述方法件还可以配置AutoConfigurationImportFilter过滤器和AutoConfigurationImportListener监听器;
  • 默认的自动配置依赖关系在spring-boot-autoconfigure包META-INF/spring-autoconfigure-metadata.properties中。同样可通过创建文件添加自定义依赖关系;
  • 配置应排除的数据,除了设置@EnableAutoConfiguration的exclude和excludeName,还可以设置spring.autoconfigure.exclude;
  • 没有出现在spring.factories中的自动配置,也不能出现在应排除的配置中;

源码

  • 实现了BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware和EnvironmentAware接口,获取spring上下文中当前的一些运行环境;
  • 实现了Ordered接口,指定执行顺序;
  • 实现了DeferredImportSelector接口,DeferredImportSelector是延迟的ImportSelector,会在所有的@Configuration之后运行,导入指定的bean;
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    //**标记导入的自动配置为空
	private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
    //**标记不导入自动配置
	private static final String[] NO_IMPORTS = {};

	private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
	//**设置spring.autoconfigure.exclude,配置应排除的自动配置类名
	private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
	private ConfigurableListableBeanFactory beanFactory;
	private Environment environment;
	private ClassLoader beanClassLoader;
	private ResourceLoader resourceLoader;
    
    //**过滤器
	private ConfigurationClassFilter configurationClassFilter;
    
    //**获取需要导入到spring上下文中的自动配置类名
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
	    //**没用启用自动配置,则返回NO_IMPORTS,不导入自动配置
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		
		//**获取导入的自动配置AutoConfigurationEntry对象
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		//**返回AutoConfigurationEntry对象中包含的类名
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

    //**排除函数
	@Override
	public Predicate<String> getExclusionFilter() {
		return this::shouldExclude;
	}

    //**验证是否排除指定的自动配置
	private boolean shouldExclude(String configurationClassName) {
		return getConfigurationClassFilter().filter(Collections.singletonList(configurationClassName)).isEmpty();
	}

	//**获取导入的自动配置类名,并封装为AutoConfigurationEntry对象
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	    //**没用启用自动配置,则返回EMPTY_ENTRY,导入自动配置为空
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		
		//**获取@EnableAutoConfiguration的配置的exclude和excludeName
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//**获取spring-boot-autoconfigure包META-INF/spring.factories配置的自动配置类名
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		//**去重
		configurations = removeDuplicates(configurations);
		//**应排除的类名
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		//**验证不能出现exclusions存在 && configurations不存在的类名
		checkExcludedClasses(configurations, exclusions);
		//**删除所有应该排除的类名
		configurations.removeAll(exclusions);
		//**删除过滤器过滤掉的类名(相关依赖条件不满足)
		configurations = getConfigurationClassFilter().filter(configurations);
		//**触发AutoConfigurationImportEvent事件监听
		fireAutoConfigurationImportEvents(configurations, exclusions);
		//**把configurations和exclusions封装为AutoConfigurationEntry对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}

    //**获取DeferredImportSelector.Group(导入分组)
	@Override
	public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}

    //**是否启用自动配置
	protected boolean isEnabled(AnnotationMetadata metadata) {
	    //**获取spring.boot.enableautoconfiguration配置,如果不存在/值为true则启用
		if (getClass() == AutoConfigurationImportSelector.class) {
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		
		//**如果是AutoConfigurationImportSelector子类的实例,则默认启用
		return true;
	}

	//**获取@EnableAutoConfiguration的配置(exclude和excludeName)
	protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
		String name = getAnnotationClass().getName();
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
		Assert.notNull(attributes, () -> "No auto-configuration attributes ... + "?");
		return attributes;
	}

	//**AutoConfigurationImportSelector处理@EnableAutoConfiguration
	protected Class<?> getAnnotationClass() {
		return EnableAutoConfiguration.class;
	}

	//**获取spring-boot-autoconfigure包META-INF/spring.factories中key=EnableAutoConfiguration自动配置类名
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto ... in META-INF/spring.factories ...");
		return configurations;
	}

	//**从spring.factories中获取配置的key
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
    
    //**验证不能出现exclusions存在 && configurations不存在的类名
	private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
		List<String> invalidExcludes = new ArrayList<>(exclusions.size());
		for (String exclusion : exclusions) {
			if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
				invalidExcludes.add(exclusion);
			}
		}
		if (!invalidExcludes.isEmpty()) {
		    //**存在则抛出异常
			handleInvalidExcludes(invalidExcludes);
		}
	}

	//**应排除的类名
	protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		Set<String> excluded = new LinkedHashSet<>();
		//**@EnableAutoConfiguration中exclude应排除的类名
		excluded.addAll(asList(attributes, "exclude"));
		//**@EnableAutoConfiguration中excludeName应排除的类名
		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
		//**配置文件中spring.autoconfigure.exclude应排除的类名
		excluded.addAll(getExcludeAutoConfigurationsProperty());
		return excluded;
	}
    
    //**配置文件中spring.autoconfigure.exclude应排除的类名
	private List<String> getExcludeAutoConfigurationsProperty() {
		if (getEnvironment() instanceof ConfigurableEnvironment) {
			Binder binder = Binder.get(getEnvironment());
			return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
					.orElse(Collections.emptyList());
		}
		String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
		return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
	}
    
    //**从spring.factories中获取配置的key=AutoConfigurationImportFilter的类名,并实例化
	protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
	}

    //**获取过滤器
	private ConfigurationClassFilter getConfigurationClassFilter() {
	    //**如果过滤器为空,初始化
		if (this.configurationClassFilter == null) {
		    //**获取spring.factories中配置的过滤器(OnBeanCondition,OnClassCondition,OnWebApplicationCondition)
			List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
			for (AutoConfigurationImportFilter filter : filters) {
			    //**把所需的spring上下文中运行环境设置到过滤器实例中
				invokeAwareMethods(filter);
			}
			//**封装为ConfigurationClassFilter对象
			this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
		}
		
		//**如果过滤器不为空,表示已经初始化,直接返回
		return this.configurationClassFilter;
	}
    
    //**字符串去重(LinkedHashSet会保持顺序)
	protected final <T> List<T> removeDuplicates(List<T> list) {
		return new ArrayList<>(new LinkedHashSet<>(list));
	}

    //**获取注解配置的属性
	protected final List<String> asList(AnnotationAttributes attributes, String name) {
		String[] value = attributes.getStringArray(name);
		return Arrays.asList(value);
	}
    
    //**触发AutoConfigurationImportEvent事件监听
	private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
	    //**从spring.factories中获取配置的key=AutoConfigurationImportListener的类名,并实例化
		List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
	
		if (!listeners.isEmpty()) {
		    //**创建AutoConfigurationImportEvent事件
			AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
			//**触发事件监听
			for (AutoConfigurationImportListener listener : listeners) {
			    //**把所需的spring上下文中运行环境设置到监听器实例中
				invokeAwareMethods(listener);
				listener.onAutoConfigurationImportEvent(event);
			}
		}
	}
    
    //**从spring.factories中获取配置的key=AutoConfigurationImportListener的类名,并实例化
	protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
	}
    
    //**把所需的spring上下文中运行环境设置到对应的实例中
	private void invokeAwareMethods(Object instance) {
		if (instance instanceof Aware) {
			if (instance instanceof BeanClassLoaderAware) {
				((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
			}
			if (instance instanceof BeanFactoryAware) {
				((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
			}
			if (instance instanceof EnvironmentAware) {
				((EnvironmentAware) instance).setEnvironment(this.environment);
			}
			if (instance instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
			}
		}
	}

    ...Aware接口的getter and setter

	//**执行顺序很靠后
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE - 1;
	}
    
    //**过滤器
	private static class ConfigurationClassFilter {
        //**spring-boot-autoconfigure包META-INF/spring-autoconfigure-metadata.properties中每个自动配置类需要的依赖关系
		private final AutoConfigurationMetadata autoConfigurationMetadata;

		private final List<AutoConfigurationImportFilter> filters;

		ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
		    //**获取每个自动配置类需要的依赖关系
			this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
			//**spring.factories中配置的过滤器(OnBeanCondition,OnClassCondition,OnWebApplicationCondition)
			this.filters = filters;
		}
        
        //**过滤相关依赖条件不满足的自动配置类名
		List<String> filter(List<String> configurations) {
			long startTime = System.nanoTime();
			//**待过滤的自动配置类名数组
			String[] candidates = StringUtils.toStringArray(configurations);
			//**所有自动配置类都通过的标识(false表示通过)
			boolean skipped = false;
			
			//**使用过滤器(OnBeanCondition,OnClassCondition,OnWebApplicationCondition)匹配
			for (AutoConfigurationImportFilter filter : this.filters) {
			    //**匹配所有的自动配置类名
				boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
				for (int i = 0; i < match.length; i++) {
					if (!match[i]) {
					    //**不通过,则把当前的类型置为null
						candidates[i] = null;
						//**不通过,则把skipped置为true
						skipped = true;
					}
				}
			}
			
			//**都通过,直接返回configurations
			if (!skipped) {
				return configurations;
			}
			
			//**存在不通过的,则删除后返回
			List<String> result = new ArrayList<>(candidates.length);
			for (String candidate : candidates) {
				if (candidate != null) {
					result.add(candidate);
				}
			}
			//**存在不通过的,则打印日志
			if (logger.isTraceEnabled()) {
				int numberFiltered = configurations.size() - result.size();
				logger.trace("Filtered " + numberFiltered + " auto configuration ... ms");
			}
			return result;
		}

	}

    //**导入分组
	private static class AutoConfigurationGroup
			implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        //**自动配置类名及@EnableAutoConfiguration元数据
		private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
        //**封装的自动配置Entry
		private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

		private ClassLoader beanClassLoader;
		private BeanFactory beanFactory;
		private ResourceLoader resourceLoader;
        
        //**同ConfigurationClassFilter一样,每个自动配置类需要的依赖关系
		private AutoConfigurationMetadata autoConfigurationMetadata;

		...Aware接口setter
        
        //**处理导入类信息
		@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
		    //**deferredImportSelector必须是AutoConfigurationImportSelector的实例
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are ..."));
			//**获取导入的自动配置AutoConfigurationEntry对象
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(annotationMetadata);
			//**添加到autoConfigurationEntries中
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			//**添加到entries中
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

        //**获取导入的类名
		@Override
		public Iterable<Entry> selectImports() {
		    //**如果autoConfigurationEntries为空,返回空列表
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			
			//**所有应排除的类名
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			//**所有应导入的类名	
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			//**删除应排除的类名		
			processedConfigurations.removeAll(allExclusions);
            
            //**排序后,把@EnableAutoConfiguration元数据和类名封装为Entry对象
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

        //**获取每个自动配置类需要的依赖关系
		private AutoConfigurationMetadata getAutoConfigurationMetadata() {
			if (this.autoConfigurationMetadata == null) {
				this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
			}
			return this.autoConfigurationMetadata;
		}

        //**排序,排序规则包括英文字母、Ordered接口和注解(@AutoConfigureBefore @AutoConfigureAfter)
		private List<String> sortAutoConfigurations(Set<String> configurations,
				AutoConfigurationMetadata autoConfigurationMetadata) {
			return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
					.getInPriorityOrder(configurations);
		}
        
        //**获取internalCachingMetadataReaderFactory的实例
		private MetadataReaderFactory getMetadataReaderFactory() {
			try {
				return this.beanFactory.getBean(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
						MetadataReaderFactory.class);
			}
			catch (NoSuchBeanDefinitionException ex) {
				return new CachingMetadataReaderFactory(this.resourceLoader);
			}
		}

	}
    
    //**封装的自动配置Entry
	protected static class AutoConfigurationEntry {
        //**自动配置类名
		private final List<String> configurations;
        //**排除的类名
		private final Set<String> exclusions;

		...构造方法、getter and setter
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值