Spring Boot作为目前最流行的Java开发框架,秉承“约定优于配置”原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目。

本文基于Spring Boot 2.1.0.RELEASE版本了解Spring Boot如何启动

首先让我们看一下最简单的Spring Boot启动代码

  1. @SpringBootApplication
  2. public class DemoApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(DemoApplication.class, args);
  5. }
  6. }

每一个使用过Spring Boot的同学对于上面的代码应该都非常熟悉了,通过这段代码即可启动Spring Boot应用。那么SpringApplication.run(DemoApplication.class, args)内部到底做了什么事情呢?

在查看具体代码之前,我们先了解一下SpringApplication内部大概的执行流程,如下图

从上图中可以看出run()是整个应用的入口,接着初始化SpringApplicationRunListenerEnvironment等实例,然后创建应用上下文对象,“准备”并“刷新”上下文,到这里Spring容器已基本启动完成,最后发送事件通知各个组件作出相应动作。

源码分析

在了解完大概的流程之后,下面开始深入源码分析Spring Boot具体的启动过程,首先进入入口方法run

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. ConfigurableApplicationContext context = null;
  5. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  6. configureHeadlessProperty();
  7. SpringApplicationRunListeners listeners = getRunListeners(args);
  8. listeners.starting();
  9. // ...

StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。

Started DemoApplication in 4.241 seconds (JVM running for 5.987)

getRunListeners()完成了SpringApplicationRunListener实例化工作,如何完成的呢?进入方法内部查看

  1. private SpringApplicationRunListeners getRunListeners(String[] args) {
  2. Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  3. return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
  4. SpringApplicationRunListener.class, types, this, args));
  5. }

SpringApplicationRunListenersSpringApplicationRunListener不是同一个类,它们名称非常相似

查看SpringApplicationRunListeners源码

  1. SpringApplicationRunListeners(Log log,
  2. Collection<? extends SpringApplicationRunListener> listeners) {
  3. this.log = log;
  4. this.listeners = new ArrayList<>(listeners);
  5. }
  6. public void starting() {
  7. for (SpringApplicationRunListener listener : this.listeners {
  8. listener.starting();
  9. }
  10. }
  11. public void environmentPrepared() {
  12. // ....
  13. }
  14. public void contextPrepared() {
  15. // ....
  16. }
  17. public void contextLoaded() {
  18. // ....
  19. }
  20. public void started() {
  21. // ....
  22. }
  23. public void running() {
  24. // ....
  25. }

它是SpringApplicationRunListener的一个集合

观察SpringApplicationRunListeners所有方法,可以看出,它实际是一个用来发送SpringApplicationRunListener相关事件的工具类

接着继续观察getSpringFactoriesInstances源码,看它是如何实例化对象的(此方法后续多处使用)

  1. private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
  2. Class<?>[] parameterTypes, Object... args) {
  3. ClassLoader classLoader = getClassLoader();
  4. // 加载对象名称
  5. Set<String> names = new LinkedHashSet<>(
  6. SpringFactoriesLoader.loadFactoryNames(type,classLoader));
  7. List<T> instances = createSpringFactoriesInstances(type parameterTypes,
  8. classLoader, args, names);
  9. AnnotationAwareOrderComparator.sort(instances);
  10. return instances;
  11. }

这里通过SpringFactoriesLoader.loadFactoryNames获取type对应的FactoryNames,不明白有什么用处?进入方法内部查看

  1. public static List<String> loadFactoryNames(Class<?>factoryClass, @Nullable ClassLoader classLoader) {
  2. String factoryClassName = factoryClass.getName();
  3. return loadSpringFactories(classLoader).getOrDefaul(factoryClassName, Collections.emptyList());
  4. }

继续进入loadSpringFactories方法内部

  1. public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
  2. private static Map<String, List<String>> loadSpringFactorie(@Nullable ClassLoader classLoader) {
  3. MultiValueMap<String, String> result = cache.ge(classLoader);
  4. if (result != null) {
  5. return result;
  6. }
  7. try {
  8. // 获取 META-INF/spring.factories 对应的资源
  9. Enumeration<URL> urls = (classLoader != null ?
  10. classLoader.getResource(FACTORIES_RESOURCE_LOCATION) :
  11. ClassLoader.getSystemResource(FACTORIES_RESOURCE_LOCATION));
  12. result = new LinkedMultiValueMap<>();
  13. while (urls.hasMoreElements()) {
  14. URL url = urls.nextElement();
  15. UrlResource resource = new UrlResource(url);
  16. // 读取文件内容
  17. Properties properties =PropertiesLoaderUtils.loadProperties(resource);
  18. for (Map.Entry<?, ?> entry : properties.entrySe()) {
  19. String factoryClassName = ((String)entry.getKey()).trim();
  20. for (String factoryName :StringUtils.commaDelimitedListToStringArray(String) entry.getValue())) {
  21. // 获取 factoryClassName 对应的多个valu(多个value用逗号分隔)
  22. result.add(factoryClassName,factoryName.trim());
  23. }
  24. }
  25. }
  26. // 缓存已经读取到的内容
  27. cache.put(classLoader, result);
  28. return result;
  29. }
  30. catch (IOException ex) {
  31. throw new IllegalArgumentException("Unable to loadfactories from location [" +
  32. FACTORIES_RESOURCE_LOCATION + "]", ex);
  33. }
  34. }

看到这里可能会疑惑META-INF/spring.factories文件在哪里?文件里面有什么内容?

其实这个文件存放在Spring BootSpring Boot autoconfigure的jar包内部(有兴趣的同学可以自行下载jar包并解压查看),Spring Boot中的文件内容如下:

  1. # 完整内容请查看原文件
  2. # Run Listeners
  3. org.springframework.boot.SpringApplicationRunListener=\
  4. org.springframework.boot.context.event.EventPublishingRunListener
  5. # Application Listeners
  6. org.springframework.context.ApplicationListener=\
  7. org.springframework.boot.ClearCachesApplicationListener,\
  8. org.springframework.boot.builder.ParentContextCloserApplicationListener,\
  9. org.springframework.boot.context.FileEncodingApplicationListener,\
  10. org.springframework.boot.context.config.AnsiOutputApplicationListener,\
  11. org.springframework.boot.context.config.ConfigFileApplicationListener,\
  12. org.springframework.boot.context.config.DelegatingApplicationListener,\
  13. org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
  14. org.springframework.boot.context.logging.LoggingApplicationListener,\
  15. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到SpringApplicationRunListener对应的值是EventPublishingRunListener

回到SpringFactoriesLoader.loadFactoryNames方法内部,可以发现方法获取的值实际上是factoryClassMETA-INF/spring.factories中对应的实现类的集合

明白这个方法之后,再回到getSpringFactoriesInstances方法

  1. private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
  2. Class<?>[] parameterTypes, Object... args) {
  3. ClassLoader classLoader = getClassLoader();
  4. // 获取 SpringApplicationRunListener 对应的实现类的名称集合
  5. Set<String> names = new LinkedHashSet<>(
  6. SpringFactoriesLoader.loadFactoryNames(type,classLoader));
  7. // 通过反射实例化对象
  8. List<T> instances = createSpringFactoriesInstances(type parameterTypes,
  9. classLoader, args, names);
  10. AnnotationAwareOrderComparator.sort(instances);
  11. return instances;
  12. }

到此为止getRunListeners完成了SpringApplicationRunListener对应实现类的实例化,并回调其starting方法

  1. SpringApplicationRunListeners listeners getRunListeners(args);
  2. listeners.starting();

从上面分析得知,实际上调用的是EventPublishingRunListenerstarting方法,那么方法内部做了什么呢?

  1. public void starting() {
  2. this.initialMulticaster.multicastEvent(
  3. new ApplicationStartingEvent(this.application,this.args));
  4. }

发送了一个ApplicationStartingEvent事件

继续查找ApplicationStartingEvent事件的消费者,从spring.factories中可以找到所有预定义的事件消费者

  1. # Application Listeners
  2. org.springframework.context.ApplicationListener=\
  3. org.springframework.boot.ClearCachesApplicationListener,\
  4. org.springframework.boot.builder.ParentContextCloserApplicationListener,\
  5. org.springframework.boot.context.FileEncodingApplicationListener,\
  6. org.springframework.boot.context.config.AnsiOutputApplicationListener,\
  7. org.springframework.boot.context.config.ConfigFileApplicationListener,\
  8. org.springframework.boot.context.config.DelegatingApplicationListener,\
  9. org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
  10. org.springframework.boot.context.logging.LoggingApplicationListener,\
  11. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
  1. # Application Listeners
  2. org.springframework.context.ApplicationListener=\
  3. org.springframework.boot.autoconfigure.BackgroundPreinitializer

接下来要做的就是从这些消费者中找出ApplicationStartingEvent事件的消费者(查找过程省略),找到以下两个消费者

  • LoggingApplicationListener
    初始化日志系统

  • LiquibaseServiceLocatorApplicationListener
    (参数liquibase.servicelocator.ServiceLocator)如果存在,则使用springboot相关的版本进行替代

了解完ApplicationStartingEvent事件之后,回到run方法继续往下探究prepareEnvironment

  1. private ConfigurableEnvironment prepareEnvironment(
  2. SpringApplicationRunListeners listeners,
  3. ApplicationArguments applicationArguments) {
  4. // 创建Environment对象
  5. ConfigurableEnvironment environment =getOrCreateEnvironment();
  6. configureEnvironment(environment,applicationArguments.getSourceArgs());
  7. // 发布ApplicationEnvironmentPreparedEvent事件
  8. listeners.environmentPrepared(environment);
  9. bindToSpringApplication(environment);
  10. if (!this.isCustomEnvironment) {
  11. environment = new EnvironmentConverte(getClassLoader())
  12. .convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
  13. }
  14. ConfigurationPropertySources.attach(environment);
  15. return environment;
  16. }

这里又发布了一个ApplicationEnvironmentPreparedEvent事件,继续查找事件监听对象

  • FileEncodingApplicationListener
    检查系统文件编码格式是否符合环境变量中配置的文件编码格式(如果存在相关设置 - spring.mandatory-file-encoding),如果编码不符合,则抛出异常阻止Spring启动
  • AnsiOutputApplicationListener
    是否开启AnsiOutput
  • DelegatingApplicationListener
    代理context.listener.classes配置的监听者
  • ClasspathLoggingApplicationListener
    日志输出classpath
  • LoggingApplicationListener
    配置日志系统,logging.config, logging.level...等
  • ConfigFileApplicationListener
    这是一个比较重要的监听对象,具体的方法实现如下
  1. private void onApplicationEnvironmentPreparedEvent(
  2. ApplicationEnvironmentPreparedEvent event) {
  3. List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();
  4. postProcessors.add(this);
  5. AnnotationAwareOrderComparator.sort(postProcessors);
  6. for (EnvironmentPostProcessor postProcessor :postProcessors) {
  7. postProcessor.postProcessEnvironmen(event.getEnvironment(),
  8. event.getSpringApplication());
  9. }
  10. }
  11. List<EnvironmentPostProcessor> loadPostProcessors() {
  12. return SpringFactoriesLoader.loadFactorie(EnvironmentPostProcessor.class,
  13. getClass().getClassLoader());
  14. }

通过spring.factories,可以看到这里加载以下EnvironmentPostProcessor对象

  • CloudFoundryVcapEnvironmentPostProcessor
  • SpringApplicationJsonEnvironmentPostProcessor
  • SystemEnvironmentPropertySourceEnvironmentPostProcessor
  • ConfigFileApplicationListener

很多同学可能会疑问ConfigFileApplicationListener并不存在spring.factories文件中,这里为什么会有它呢?

实际上ConfigFileApplicationListeneronApplicationEnvironmentPreparedEvent方法中,将自身添加到EnvironmentPostProcessor对象列表中。

我们主要关注ConfigFileApplicationListenerpostProcessEnvironment方法

  1. public void postProcessEnvironment(ConfigurableEnvironmentenvironment,
  2. SpringApplication application) {
  3. addPropertySources(environment,application.getResourceLoader());
  4. }
  5. protected void addPropertySources(ConfigurableEnvironmentenvironment,
  6. ResourceLoader resourceLoader) {
  7. RandomValuePropertySource.addToEnvironment(environment);
  8. // 读取applicaiton.yml, application.properties等配置文件
  9. new Loader(environment, resourceLoader).load();
  10. }

ConfigFileApplicationListener监听到ApplicationEnvironmentPreparedEvent事件之后开始读取本地配置文件

关于Spring如何读取本地配置文件,请前往Spring Boot源码分析-配置文件加载原理

创建ApplicationContext对象

  1. protected ConfigurableApplicationContextcreateApplicationContext() {
  2. Class<?> contextClass = this.applicationContextClass;
  3. if (contextClass == null) {
  4. try {
  5. // 根据webApplicationType创建对应上下文对象
  6. switch (this.webApplicationType) {
  7. case SERVLET:
  8. contextClass = Class.forNam(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
  9. break;
  10. case REACTIVE:
  11. contextClass = Class.forNam(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
  12. break;
  13. default:
  14. contextClass = Class.forNam(DEFAULT_CONTEXT_CLASS);
  15. }
  16. }
  17. catch (ClassNotFoundException ex) {
  18. throw new IllegalStateException(
  19. "Unable create a defaultApplicationContext, "
  20. + "please specify anApplicationContextClass",
  21. ex);
  22. }
  23. }
  24. return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
  25. }

这里是根据webApplicationType决定创建什么类型的ApplicationContext对象,那么webApplicationType是何时赋值的呢?

  1. public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. Assert.notNull(primarySources, "PrimarySources must notbe null");
  4. this.primarySources = new LinkedHashSet<>(Arrays.asLis(primarySources));
  5. // 初始化webApplicationType
  6. this.webApplicationType =WebApplicationType.deduceFromClasspath();
  7. setInitializers((Collection) getSpringFactoriesInstance(
  8. ApplicationContextInitializer.class));
  9. setListeners((Collection) getSpringFactoriesInstance(ApplicationListener.class));
  10. this.mainApplicationClass = deduceMainApplicationClass(;
  11. }

从上面可以看出是通过WebApplicationType.deduceFromClasspath方法初始化的webApplicationType,继续跟踪代码

  1. private static final String WEBFLUX_INDICATOR_CLASS = "org."
  2. + "springframework.web.reactive.DispatcherHandler";
  3. private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
  4. + "web.servlet.DispatcherServlet";
  5. private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
  6. private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
  7. "org.springframework.web.context.ConfigurableWebApplicationContext" };
  8. static WebApplicationType deduceFromClasspath() {
  9. if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
  10. && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS null)
  11. && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS null)) {
  12. return WebApplicationType.REACTIVE;
  13. }
  14. for (String className : SERVLET_INDICATOR_CLASSES) {
  15. if (!ClassUtils.isPresent(className, null)) {
  16. return WebApplicationType.NONE;
  17. }
  18. }
  19. return WebApplicationType.SERVLET;
  20. }

从上面代码中可以看出Spring是通过当前classpath下是否存在相应的类,从而决定webApplicationType类型

初始化ApplicationContext对象

  1. private void prepareContext(ConfigurableApplicationContextcontext,
  2. ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,
  3. ApplicationArguments applicationArguments, BannerprintedBanner) {
  4. // 初始化context
  5. context.setEnvironment(environment);
  6. postProcessApplicationContext(context);
  7. applyInitializers(context);
  8. // 发送ApplicationContextInitializedEvent消息
  9. listeners.contextPrepared(context);
  10. if (this.logStartupInfo) {
  11. logStartupInfo(context.getParent() == null);
  12. logStartupProfileInfo(context);
  13. }
  14. // Add boot specific singleton beans
  15. ConfigurableListableBeanFactory beanFactory =context.getBeanFactory();
  16. beanFactory.registerSingleto("springApplicationArguments", applicationArguments);
  17. if (printedBanner != null) {
  18. beanFactory.registerSingleton("springBootBanner",printedBanner);
  19. }
  20. if (beanFactory instanceof DefaultListableBeanFactory) {
  21. ((DefaultListableBeanFactory) beanFactory)
  22. .setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
  23. }
  24. Set<Object> sources = getAllSources();
  25. Assert.notEmpty(sources, "Sources must not be empty");
  26. // 注册DemoApplication
  27. load(context, sources.toArray(new Object[0]));
  28. listeners.contextLoaded(context);
  29. }

这里注册了DemoApplicationSpring容器中,为后续bean扫描做准备

接下来继续深入refreshContext方法,可以发现实际上是执行了AbstractApplicationContext.refresh方法

  1. public void refresh() throws BeansException,IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. prepareRefresh();
  4. ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
  5. prepareBeanFactory(beanFactory);
  6. try {
  7. postProcessBeanFactory(beanFactory);
  8. // 完成bean的加载
  9. invokeBeanFactoryPostProcessors(beanFactory);
  10. registerBeanPostProcessors(beanFactory);
  11. initMessageSource();
  12. initApplicationEventMulticaster();
  13. onRefresh();
  14. registerListeners();
  15. finishBeanFactoryInitialization(beanFactory);
  16. finishRefresh();
  17. }
  18. catch (BeansException ex) {
  19. if (logger.isWarnEnabled()) {
  20. logger.warn("Exception encountered duringcontext initialization - " +
  21. "cancelling refresh attempt: " + ex;
  22. }
  23. destroyBeans();
  24. cancelRefresh(ex);
  25. throw ex;
  26. }
  27. finally {
  28. resetCommonCaches();
  29. }
  30. }
  31. }

refresh方法内部做了很多事情。比如:完成BeanFactory设置,BeanFactoryPostProcessorBeanPostProcessor接口回调,Bean加载,国际化配置等。

到此为止Spring基本完成了容器的初始化工作,最后在调用callRunners方法,执行ApplicationRunnerCommandLineRunner接口。

  1. private void callRunners(ApplicationContext context,ApplicationArguments args) {
  2. List<Object> runners = new ArrayList<>();
  3. runners.addAll(context.getBeansOfTyp(ApplicationRunner.class).values());
  4. runners.addAll(context.getBeansOfTyp(CommandLineRunner.class).values());
  5. AnnotationAwareOrderComparator.sort(runners);
  6. for (Object runner : new LinkedHashSet<>(runners)) {
  7. if (runner instanceof ApplicationRunner) {
  8. callRunner((ApplicationRunner) runner, args);
  9. }
  10. if (runner instanceof CommandLineRunner) {
  11. callRunner((CommandLineRunner) runner, args);
  12. }
  13. }
  14. }

整个启动过程的核心方法是refresh,此方法内部承载大部分容器启动所需的工作。由于篇幅原因,后续再进行refresh内部源码分析,了解Spring Boot加载Bean的整个过程。

本文由博客一文多发平台 OpenWrite 发布!

Spring Boot源码分析-启动过程的更多相关文章

  1. Spring Boot源码分析-配置文件加载原理

    在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...

  2. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. 精尽Spring Boot源码分析 - Jar 包的启动实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  4. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 文章导读

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. mysql: error while loading shared libraries: libnuma.so

    安装mysql后,执行初始化配置脚本,创建系统自带的数据库和表时报异常: [root@VM_0_12_centos mysql]# scripts/mysql_install_db --basedir ...

  2. 如何知道,当前redis实例是处于阻塞状态?

    随便get一个key,然后卡着不动就行,简单粗暴.优雅一点是看latency的延迟,blocked_clients的数量,rejected_connections的数量等 或者 方法一:登录 Redi ...

  3. Remote Ubuntu VM from Windows

    Need to install the xrdp tool on Ubuntu. To do this, open a Terminal window (Ctrl + Alt + T) and ent ...

  4. 在debian下安装QT 5.10 32位

    准备工作: 在开始之前最好把GCC升级到5.0以上. 如果升级后出现“libstdc++.so.6: version `CXXABI_1.3.9' not found”错误,可以参考https://b ...

  5. ffmpeg编码h264设置规格

    ffmpeg -i demo.ts -profile:v baseline -vcodec h264 -acodec aac -f flv demo.flv

  6. 设置Fedora能够使用root用户登录

    1. 切换到root工作环境,因为一下操作必须拥有root权限 [ha@localhost ~]$ su root密码: 2. 编辑/etc/pam.d/gdm [root@localhost ha] ...

  7. Android网络编程之——文件断点下载

    一:关于断点下载所涉及到的知识点 1.对SQLite的增删改查(主要用来保存当前任务的一些信息) 2.HttpURLConnection的请求配置 HttpURLConnection connecti ...

  8. GIT管理以及运行规范

    继前天看分享的前后端分离后,又重新研究了GIT分支与各个环境的应用. 从开始使用git就一直有在网上查各种资料,查他的运行规范.但不知道是自己理解不够还是怎么的,一直用得不是很好. 根据自己的摸索,整 ...

  9. 数据中心网络架构的问题与演进 — 云网融合与 SD-WAN

    目录 文章目录 目录 前文列表 云网融合 云网融合的应用场景 SD-WAN SD-WAN 的应用场景 企业组网互联 SD-EN 数据中心互联 SD-DCI 云间互联 SD-CX 企业用户接入云 数据中 ...

  10. 几句简单的python代码完成周公解梦功能

    <周公解梦>是靠人的梦来卜吉凶的一本于民间流传的解梦书籍,共有七类梦境的解述.这是非常传统的中国文化体系的一部分,但是如何用代码来获取并搜索周公解梦的数据呢?一般情况下,要通过爬虫获取数据 ...