自动读取配置文件,装配配置类

从刷新容器开始:
AbstractApplicationContext

public void refresh() throws BeansException, IllegalStateException {//...invokeBeanFactoryPostProcessors(beanFactory);//...}protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {//开始执行beanFactoryPostProcessor对应实现类PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}}

PostProcessorRegistrationDelegate先执行beanDefinitionRegistryPostProcessor方法,后执行beanFactoryPostProcessor方法
先执行PriorityOrdered后处理器,后执行Ordered后处理器,最后执行常规后处理器

 public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {// Invoke BeanDefinitionRegistryPostProcessors first, if any.Set<String> processedBeans = new HashSet<>();if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<>();List<BeanDefinitionRegistryPostProcessor> registryProcessors = new LinkedList<>();//这里开始遍历上面三个内部类,如果属于BeanDefinitionRegistryPostProcessor 子类,//加入到bean注册的集合,否则加入到 regularPostProcessors中,从名字可以看出是有规律集合。for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {BeanDefinitionRegistryPostProcessor registryProcessor =(BeanDefinitionRegistryPostProcessor) postProcessor;registryProcessor.postProcessBeanDefinitionRegistry(registry);registryProcessors.add(registryProcessor);}else {regularPostProcessors.add(postProcessor);}}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let the bean factory post-processors apply to them!// Separate between BeanDefinitionRegistryPostProcessors that implement// PriorityOrdered, Ordered, and the rest.List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.String[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);//首先执行类型为PriorityOrdered的BeanDefinitionRegistryPostProcessor//PriorityOrdered类型表明为优先执行for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {//获取对应的beancurrentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));//用来存储已经执行过的`BeanDefinitionRegistryPostProcessor`               processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);//其次执行类型为Ordered的BeanDefinitionRegistryPostProcessor//Ordered表明按顺序执行for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.boolean reiterate = true;//循环中执行类型不为PriorityOrdered,Ordered类型的BeanDefinitionRegistryPostProcessorwhile (reiterate) {reiterate = false;postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate = true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();}// Now, invoke the postProcessBeanFactory callback of all processors handled so far.//执行父类方法,优先执行注册处理类invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);//执行有规则处理类invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}
...
}

执行ConfigurationClassPostProcessor后处理

 @Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);}this.registriesPostProcessed.add(registryId);processConfigBeanDefinitions(registry);}public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application contextSingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);candidates.clear();if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

首先获得ConfigurationClassParser,这个是所有配置类的解析类,比较核心。所有的解析逻辑在parser.parse(candidates);中,解析@Component注解。@ComponentScan注解,扫描所有@Component注解,注册bean

ConfigurationClassParser

public void parse(Set<BeanDefinitionHolder> configCandidates) {this.deferredImportSelectors = new LinkedList<>();for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}processDeferredImportSelectors();}protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {processConfigurationClass(new ConfigurationClass(metadata, beanName));}protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}ConfigurationClass existingClass = this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else {// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass = asSourceClass(configClass);do {sourceClass = doProcessConfigurationClass(configClass, sourceClass);}while (sourceClass != null);this.configurationClasses.put(configClass, configClass);}//递归解析@Configuration配置类等注解protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {//处理内部类逻辑,由于传来的参数是我们的启动类,不含内部类,所以跳过。processMemberClasses(configClass, sourceClass);// Process any @PropertySource annotations//针对属性配置的解析for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}//这里是根据启动类 @ComponentScan 注解来扫描项目中的beanAnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class);if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {// The config class is annotated with @ComponentScan -> perform the scan immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if necessary//遍历我们项目中的bean,如果是注解定义的bean,则进一步解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {//判断是否是注解beanif (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {//这里是关键,递归解析。所有的bean,如果有注解,会进一步解析注解中包含的beanparse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());}}}// Process any @Import annotations//这里又是一个递归解析,获取导入的配置类。很多情况下,导入的配置类中会同样包含导入类注解。processImports(configClass, sourceClass, getImports(sourceClass), true);// Process any @ImportResource annotations//解析导入的 xml 配置类if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methodsSet<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 获取接口中的默认方法,1.8以上的处理逻辑for (SourceClass ifc : sourceClass.getInterfaces()) {beanMethods = ifc.getMetadata().getAnnotatedMethods(Bean.class.getName());for (MethodMetadata methodMetadata : beanMethods) {if (!methodMetadata.isAbstract()) {// A default method or other concrete method on a Java 8+ interface...configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}}}// Process superclass, if any//如果该类有父类,则继续返回。上层方法判断不为空,则继续递归执行。if (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is complete//递归实现,superclass为空,则结束递归中的循环return null;}

导入配置类的逻辑,@Import注解的类,注册bean

 private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {Set<SourceClass> imports = new LinkedHashSet<>();Set<SourceClass> visited = new LinkedHashSet<>();collectImports(sourceClass, imports, visited);return imports;}//递归方法导入,因为导入的类上可能还有@Import注解private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)throws IOException {if (visited.add(sourceClass)) {for (SourceClass annotation : sourceClass.getAnnotations()) {String annName = annotation.getMetadata().getClassName();if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {collectImports(annotation, imports, visited);}}imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));}}

开始执行 SpringBoot 默认配置逻辑

private void processDeferredImportSelectors() {List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;this.deferredImportSelectors = null;if (deferredImports == null) {return;}deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();for (DeferredImportSelectorHolder deferredImport : deferredImports) {Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent((group != null ? group : deferredImport),key -> new DeferredImportSelectorGrouping(createGroup(group)));grouping.add(deferredImport);configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getConfigurationClass());}for (DeferredImportSelectorGrouping grouping : groupings.values()) {grouping.getImports().forEach(entry -> {ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());try {processImports(configurationClass, asSourceClass(configurationClass),asSourceClasses(entry.getImportClassName()), false);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configurationClass.getMetadata().getClassName() + "]", ex);}});}}private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,Collection<SourceClass> importCandidates, boolean checkForCircularImports) {if (importCandidates.isEmpty()) {return;}if (checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));}else {this.importStack.push(configClass);try {for (SourceClass candidate : importCandidates) {if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector -> delegate to it to determine importsClass<?> candidateClass = candidate.loadClass();ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));}else {String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);processImports(configClass, currentSourceClass, importSourceClasses, false);}}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?> candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar =BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else {// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass));}}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configClass.getMetadata().getClassName() + "]", ex);}finally {this.importStack.pop();}}}

springboot2源码3-自动配置相关推荐

  1. Spring Boot 基于注解驱动源码分析--自动配置

    Spring作为Java开发最常用的容器管理框架,使用注解为我们提供很多便捷,下面通过源码分析Spring基于注解驱动自动配置的原理 首先介绍两个关键类: ConfigurationClassPost ...

  2. STL源码剖析 空间配置器 查漏补缺

    ptrdiff_t含义 减去两个指针的结果的带符号整数类型 ptrdiff_t (Type support) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 std::set_new_ ...

  3. C++、VC++、MFC网页自动注册、登陆、发帖、留言,QQ注册、QQ申请器源码、注册邮箱源码、自动发帖源码...

    C++.VC++.MFC网页自动注册.登陆.发帖.留言,QQ注册.QQ申请器源码.注册邮箱源码.自动发帖源码   参考资料: 自动登录yahoo邮箱http://blog.csdn.net/suisu ...

  4. 盖章php源码,php源码企业自动发卡程序源码

    php源码企业自动发卡程序源码 php企业自动发卡程序源码,无问题与Bug,已通过安全狗等平台验证,提供新版UI模板,全新体验,后台有多款页面模板切换! 本程序已对接了易商付银行支付接口(www.es ...

  5. CentOS上PHP源码安装和配置

    CentOS上PHP源码安装和配置 此文是在CentOS 7上已经部署了Nginx的基础上进行的 关于CentOS7上安装Nginx,可参考我之前的文章: CentOS上Nginx安装记录 我们现在在 ...

  6. Kubernetes Node Controller源码分析之配置篇

    2019独角兽企业重金招聘Python工程师标准>>> Author: xidianwangtao@gmail.com Kubernetes Node Controller源码分析之 ...

  7. 微信小程序:开心锤锤超火动态表情包微信小程序源码下载自动采集

    这是一款表情包小程序源码 大家刷抖音的时候应该都刷过开心锤锤这个网红卡通短视频吧 现在这一款小程序就是和它有关的 里面的表情包呢大部分都是动态表情包(斗图的时候是不是更炫) 至于里面的表情包人物的就都 ...

  8. python电玩城源码_2019最新最全价值2W的微信H5电玩城游戏全套源码+架设教程+配置文档...

    2019最新最全价值2W的微信H5电玩城游戏全套源码+架设教程+配置文档由小鱼H5游戏源码精心整理,并分享给大家.喜欢该资源的小伙伴请下载使用,注册.回复.每日签到.点赞送大量积分,满足您免费下载的愿 ...

  9. 精尽 Dubbo 源码分析 —— API 配置

    1. 概述 Dubbo 的配置目前提供了四种配置方式:1. API 配置 2. 属性配置 3. XML 配置 4. 注解配置 2. 配置一览 我们来看看 dubbo-config-api 的项目结构, ...

最新文章

  1. 排序算法7---快速排序算法
  2. 跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?(源码感悟)
  3. css加载会造成阻塞吗
  4. C++内联函数(inline)
  5. Apache Doris : 一个开源 MPP 数据库的架构与实践
  6. vi(vim)常用命令汇总
  7. Entity FrameWork 操作使用详情
  8. 机器学习降维算法二:LDA(Linear Discriminant Analysis)
  9. Python项目实践:文本进度条
  10. Bailan4142 二分法求函数的零点【二分法】
  11. 关于ADO.Net连接池(Connection Pool)的一些个人见解
  12. QoBean的元语言系统(一)
  13. 有关热敏打印机接口程序
  14. 04oracle单表查询、连接查询、子查询
  15. 修改elementui 的datepicker日期选择器自然周从周一至周日
  16. 802.11a data rate
  17. speedoffice使用方法——Word如何设置段落背景颜色
  18. 常用算法解析------二分法
  19. 基于java的格式转换,word 转 pdf、word 转图片、office 格式转换、在线文件预览
  20. 修改WSL的Ubuntu环境下ls显示的文件夹文字颜色和背景色

热门文章

  1. 【零基础学Java】—ArrayList集合概述和基本使用(十四)
  2. java 缓冲流 刷新_java – 缓冲和刷新Apache Beam流数据
  3. redis 远程主机强迫关闭了一个现有的连接_如何在 Debian 10 上安装和配置 Redis 服务...
  4. 玩游戏该怎么选择硬盘
  5. 请问基友,基金转换需要多长时间?
  6. 每天快走一小时,身体会有什么变化?
  7. 盼望的意思是什么,怎么用盼望造句?
  8. 做餐饮,要会算细账,要少折腾
  9. 查看堆内存(histogram)中的对象数量及大小
  10. 中国有了北斗系统,为什么手机上还是GPS?