Spring Boot 的入口类

@SpringBootApplication
public class SpringBootBestPracticeApplication {public static void main(String[] args) {SpringApplication.run(SpringBootBestPracticeApplication.class, args);}
}

做过 Spring Boot 项目的都知道,上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。

来看一下这个类的run方法调用关系源码:

public static ConfigurableApplicationContext run( Class<?> primarySource,String... args) {return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {return new SpringApplication(primarySources).run(args);
}

第一个参数primarySource:加载的主要资源类

第二个参数 args:传递给应用的应用参数

先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来分析这个启动源码。

SpringApplication 的实例化过程

接着上面的 SpringApplication 构造方法进入以下源码:

public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 1、资源初始化资源加载器为 nullthis.resourceLoader = resourceLoader;// 2、断言主要加载资源类不能为 null,否则报错Assert.notNull(primarySources, "PrimarySources must not be null");// 3、初始化主要加载资源类集合并去重this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 4、推断当前 WEB 应用类型this.webApplicationType = deduceWebApplicationType();// 5、设置应用上线文初始化器setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      // 6、设置监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 7、推断主入口应用类this.mainApplicationClass = deduceMainApplicationClass();}

可知这个构造器类的初始化包括以下 7 个过程。
1. 资源初始化资源加载器为 null

this.resourceLoader = resourceLoader;

2. 断言主要加载资源类不能为 null,否则报错

Assert.notNull(primarySources, "PrimarySources must not be null");

3. 初始化主要加载资源类集合并去重

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

4. 推断当前 WEB 应用类型

this.webApplicationType = deduceWebApplicationType();

来看下 deduceWebApplicationType 方法和相关的源码:

private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {return WebApplicationType.REACTIVE;}for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;
}private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."+ "web.reactive.DispatcherHandler";private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."+ "web.servlet.DispatcherServlet";private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"};      public enum WebApplicationType {/*** 非 WEB 项目*/NONE,/*** SERVLET WEB 项目*/SERVLET,/*** 响应式 WEB 项目*/REACTIVE}

这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型。

5. 设置应用上线文初始化器

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

ApplicationContextInitializer 的作用是什么?源码如下。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {/*** Initialize the given application context.* @param applicationContext the application to configure*/void initialize(C applicationContext);}
}

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。

来看下 setInitializers 方法源码,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {this.initializers = new ArrayList<>();this.initializers.addAll(initializers);
}

再来看下这个初始化 getSpringFactoriesInstances 方法和相关的源码:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

设置应用上下文初始化器可分为以下 5 个步骤。

  • 5.1 获取当前线程上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

5.2 获取 `ApplicationContextInitializer 的实例名称集合并去重

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 方法相关的源码如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));result.addAll((String) entry.getKey(), factoryClassNames);}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 的初始化器相关配置内容如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

5.3 根据以上类路径创建初始化器实例列表

List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);private <T> List<T> createSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,Set<String> names) {List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;
}

5.4 初始化器实例列表排序

AnnotationAwareOrderComparator.sort(instances);

5.5 返回初始化器实例列表

return instances;

6. 设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

ApplicationListener 的作用是什么?源码如下。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {/*** Handle an application event.* @param event the event to respond to*/void onApplicationEvent(E event);}

看源码,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为: getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

可以看出目前只有一个 BackgroundPreinitializer 监听器。
7.推断主入口应用类

this.mainApplicationClass = deduceMainApplicationClass();private Class<?> deduceMainApplicationClass() {try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// Swallow and continue}return null;
}

这个推断入口应用类的方式有点特别,通过构造一个运行时异常,再遍历异常栈中的方法名,获取方法名为 main 的栈帧,从来得到入口类的名字再返回该类。

总结

源码分析内容有点多,也很麻烦,本章暂时分析到 SpringApplication 构造方法的初始化流程,下章再继续分析其 run 方法

第二篇

SpringApplication 实例 run 方法运行过程

上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续来看下这个 SpringApplication 对象的 run 方法的源码和运行流程。

public ConfigurableApplicationContext run(String... args) {// 1、创建并启动计时监控类StopWatch stopWatch = new StopWatch();stopWatch.start();// 2、初始化应用上下文和异常报告集合ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 3、设置系统属性 `java.awt.headless` 的值,默认值为:trueconfigureHeadlessProperty();// 4、创建所有 Spring 运行监听器并发布应用启动事件SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {// 5、初始化默认应用参数类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 6、根据运行监听器和应用参数来准备 Spring 环境ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);// 7、创建 Banner 打印类Banner printedBanner = printBanner(environment);// 8、创建应用上下文context = createApplicationContext();// 9、准备异常报告器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 10、准备应用上下文prepareContext(context, environment, listeners, applicationArguments,printedBanner);// 11、刷新应用上下文refreshContext(context);// 12、应用上下文刷新后置处理afterRefresh(context, applicationArguments);// 13、停止计时监控类stopWatch.stop();// 14、输出日志记录执行主类名、时间信息if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 15、发布应用上下文启动完成事件listeners.started(context);// 16、执行所有 Runner 运行器callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {// 17、发布应用上下文就绪事件listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}// 18、返回应用上下文return context;
}

所以,我们可以按以下几步来分解 run方法的启动过程。

1、创建并启动计时监控类

StopWatch stopWatch = new StopWatch();
stopWatch.start();

来看下这个计时监控类 StopWatch 的相关源码:

public void start() throws IllegalStateException {start("");
}public void start(String taskName) throws IllegalStateException {if (this.currentTaskName != null) {throw new IllegalStateException("Can't start StopWatch: it's already running");}this.currentTaskName = taskName;this.startTimeMillis = System.currentTimeMillis();
}

首先记录了当前任务的名称,默认为空字符串,然后记录当前 Spring Boot 应用启动的开始时间。
2、初始化应用上下文和异常报告集合

ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

3、设置系统属性 java.awt.headless 的值

configureHeadlessProperty();

设置该默认值为:trueJava.awt.headless = true 有什么作用?

对于一个 Java 服务器来说经常要处理一些图形元素,例如地图的创建或者图形和图表等。这些API基本上总是需要运行一个X-server以便能使用AWT(Abstract Window Toolkit,抽象窗口工具集)。然而运行一个不必要的 X-server 并不是一种好的管理方式。有时你甚至不能运行 X-server,因此最好的方案是运行 headless 服务器,来进行简单的图像处理。

参考:www.cnblogs.com/princessd8251/p/4000016.html

4、创建所有 Spring 运行监听器并发布应用启动事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

来看下创建 Spring 运行监听器相关的源码:


private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}SpringApplicationRunListeners(Log log,Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList<>(listeners);
}

创建逻辑和之前实例化初始化器和监听器的一样,一样调用的是 getSpringFactoriesInstances 方法来获取配置的监听器名称并实例化所有的类。

SpringApplicationRunListener 所有监听器配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

5、初始化默认应用参数类

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

6、根据运行监听器和应用参数来准备 Spring 环境

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);

下面我们主要来看下准备环境的 prepareEnvironment 源码:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// 6.1) 获取(或者创建)应用环境ConfigurableEnvironment environment = getOrCreateEnvironment();// 6.2) 配置应用环境configureEnvironment(environment, applicationArguments.getSourceArgs());listeners.environmentPrepared(environment);bindToSpringApplication(environment);if (this.webApplicationType == WebApplicationType.NONE) {environment = new EnvironmentConverter(getClassLoader()).convertToStandardEnvironmentIfNecessary(environment);}ConfigurationPropertySources.attach(environment);return environment;
}

6.1 获取(或者创建)应用环境

private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}if (this.webApplicationType == WebApplicationType.SERVLET) {return new StandardServletEnvironment();}return new StandardEnvironment();
}

这里分为标准 Servlet 环境和标准环境。

  • 6.2 配置应用环境
protected void configureEnvironment(ConfigurableEnvironment environment,String[] args) {configurePropertySources(environment, args);configureProfiles(environment, args);
}

这里分为以下两步来配置应用环境。

  • 配置 property sources
  • 配置 Profiles

这里主要处理所有 property sources 配置和 profiles 配置。

7、创建 Banner 打印类

Banner printedBanner = printBanner(environment);

这是用来打印 Banner 的处理类,这个没什么好说的。

8、创建应用上下文

context = createApplicationContext();

来看下 createApplicationContext() 方法的源码:

protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

其实就是根据不同的应用类型初始化不同的上下文应用类。

9、准备异常报告器

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);

逻辑和之前实例化初始化器和监听器的一样,一样调用的是 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。

该异常报告处理类配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面。

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

10、准备应用上下文

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

来看下 prepareContext() 方法的源码:

private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 10.1)绑定环境到上下文context.setEnvironment(environment);// 10.2)配置上下文的 bean 生成器及资源加载器postProcessApplicationContext(context);// 10.3)为上下文应用所有初始化器applyInitializers(context);// 10.4)触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法listeners.contextPrepared(context);// 10.5)记录启动日志if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 10.6)注册两个特殊的单例beancontext.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);if (printedBanner != null) {context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);}// 10.7)加载所有资源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));// 10.8)触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法listeners.contextLoaded(context);
}

11、刷新应用上下文

refreshContext(context);

这个主要是刷新 Spring 的应用上下文,源码如下,不详细说明。

private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}
}

这个主要是刷新 Spring 的应用上下文,源码如下,不详细说明。

private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}
}

12、应用上下文刷新后置处理

afterRefresh(context, applicationArguments);

看了下这个方法的源码是空的,目前可以做一些自定义的后置处理操作。

/*** Called after the context has been refreshed.* @param context the application context* @param args the application arguments*/
protected void afterRefresh(ConfigurableApplicationContext context,ApplicationArguments args) {
}

13、停止计时监控类

stopWatch.stop();
public void stop() throws IllegalStateException {if (this.currentTaskName == null) {throw new IllegalStateException("Can't stop StopWatch: it's not running");}long lastTime = System.currentTimeMillis() - this.startTimeMillis;this.totalTimeMillis += lastTime;this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);if (this.keepTaskList) {this.taskList.add(this.lastTaskInfo);}++this.taskCount;this.currentTaskName = null;
}
```java
计时监听器停止,并统计一些任务执行信息。**14、输出日志记录执行主类名、时间信息**
```java
if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

15、发布应用上下文启动完成事件

listeners.started(context);

触发所有 SpringApplicationRunListener 监听器的 started 事件方法。

16、执行所有 Runner 运行器

callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);for (Object runner : new LinkedHashSet<>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}}
}

执行所有 ApplicationRunnerCommandLineRunner 这两种运行器,不详细展开了。

17、发布应用上下文就绪事件
listeners.running(context);
触发所有 SpringApplicationRunListener 监听器的 running 事件方法。

18、返回应用上下文

return context;

总结

Spring Boot 的启动全过程源码分析至此,分析 Spring 源码真是一个痛苦的过程,希望能给大家提供一点参考和思路,也希望能给正在 Spring Boot 学习路上的朋友一点收获。

SpringBoot2.x启动原理概述相关推荐

  1. CGB2005 JT-1(jt概述 SqlYog 物理模型图PD 表结构 pom文件标签说明 jt环境搭建 创建项目2种,创建各种文件 idea导入,打包,删除项目,启动原理)

    注意事项: 1.京淘项目概述和动吧项目缺点描述 2.把sql文件导入数据库:通过SqlYog可视化工具或者Dos命令窗口. 3.SqlYog说明,和制作物理模型图的工具pd用发. 4.表结构学习 5. ...

  2. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  3. selenium webdriver 原理概述

    背景 虽然掌握selenium webdriver提供的各种方法,就可以做web的UI自动化,但是本着知其然,也要知其所以然的态度,也为了提升自己.了解下selenium webdriver的原理是必 ...

  4. 【Spark】网络原理概述

    网络原理概述 一.关键词 二.应用层 1. DNS:Domain Name System(域名系统) 2. DHCP协议:Dynamic Host Configuration Protocol: 动态 ...

  5. SSD固态硬盘的结构和基本工作原理概述

    展开 我们都知道,早期的电脑CPU是可以直接从硬盘上面读取数据进行处理的,随着科技的进步,时代的发展,计算机硬件的发展速度也是极其迅猛.CPU主频的不断提升,从单核到双核,再到多核:CPU的处理速度越 ...

  6. harbor https镜像仓库安装及原理概述

    harbor https镜像仓库一键安装及原理概述(基于官网2023年架构) 1.简介 Harbor是VMware公司开源的企业级DockerRegistry项目,其目标是帮助用户迅速搭建一个企业级的 ...

  7. nvGRAPH原理概述

    nvGRAPH原理概述 nvGRAPH的API参考分析. 简介 数据分析是高性能计算的不断增长的应用.许多高级数据分析问题可以称为图形问题.反过来,当今许多常见的图形问题也可以称为稀疏线性代数.这是N ...

  8. golang源码分析-启动过程概述

    golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...

  9. Vue底层实现原理概述

    Vue是一个典型的MVVM框架,模型(Model)只是普通的JavaScript对象,修改它则视图(View)会自动更新.这种设计让状态管理变得非常简单而直观.那么Vue是如何把模型和视图建立起关联的 ...

最新文章

  1. 计算硼原子基态能级B---动能和势能
  2. unicode字符编码表下载_详解字符编码(上)
  3. MFC之COleVariant类
  4. 计算机的键盘如何保养,知识每天涨一点:快捷键2 键盘键位知识 电脑小保养
  5. CentOS 安装WildFly Jboss10
  6. Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题
  7. mysql 主从机器 触发器 的测试,完全正常 没有问题
  8. mysql 版本_mysql各个版本介绍
  9. 通过一个简单例子看懂遗传算法,附MATLAB代码
  10. 利用python绘制太阳花
  11. Android 源码编译make的错误处理
  12. 网络安全-江湖高手专用的“隐身术”:图片隐写技术
  13. 【转贴】常识普及:广府人是越佬族
  14. 【ubuntu系统下装win10双系统】
  15. python获取模块的名称_Python获取模块名称
  16. python基础总结:1.8、输入输出
  17. 模型计算算力_8核人工智能开源主板_基于 BITMAIN AI 计算模组_3.5寸
  18. 关于常成员函数对非常成员函数的调用问题
  19. 数据库复习——单表使用where子句和分组(groud by)筛选查询(or、and、like、isnull)
  20. 南京16家需要转诊才能享受统筹的三甲医院

热门文章

  1. 网络编程: 基于UDP协议的socket
  2. Verilog中的UDP
  3. 斗地主(深搜+贪心+剪枝)
  4. [BZOJ1030] [JSOI2007]文本生成器
  5. [Swift] 使用Playground
  6. 译Step-by-Step Guide on Configuring Django-Userena
  7. poj 3517
  8. protobuf生成as文件
  9. 容器内存释放问题(STL新手笔记)
  10. asp.net用标签递归一颗树