请尊重作者劳动成果,转载请标明原文链接:http://www.cnblogs.com/dylan-java/p/7455699.html

上一篇分析了SpringApplication的初始化,接下来看一下它的run方法

1 publicConfigurableApplicationContext run(String... args) {2     StopWatch stopWatch = newStopWatch();3 stopWatch.start();4     ConfigurableApplicationContext context = null;5     FailureAnalyzers analyzers = null;6 configureHeadlessProperty();7     SpringApplicationRunListeners listeners =getRunListeners(args);8 listeners.starting();9     try{10         ApplicationArguments applicationArguments = newDefaultApplicationArguments(11 args);12         ConfigurableEnvironment environment =prepareEnvironment(listeners,13 applicationArguments);14         Banner printedBanner =printBanner(environment);15         context =createApplicationContext();16         analyzers = newFailureAnalyzers(context);17 prepareContext(context, environment, listeners, applicationArguments,18 printedBanner);19 refreshContext(context);20 afterRefresh(context, applicationArguments);21         listeners.finished(context, null);22 stopWatch.stop();23         if (this.logStartupInfo) {24             new StartupInfoLogger(this.mainApplicationClass)25 .logStarted(getApplicationLog(), stopWatch);26 }27         returncontext;28 }29     catch(Throwable ex) {30 handleRunFailure(context, listeners, analyzers, ex);31         throw newIllegalStateException(ex);32 }33 }

首先创建一个StopWatch对象并调用它的start方法,该类是Spring提供的一个计时器类,与本篇要讨论的东西无关,所在在这里不对它进行分析

第6行调用了configureHeadlessProperty方法,该方法只有一行代码,就是设置系统属性java.awt.headless,这里设置为true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能

第7行通过getRunListeners方法获取SpringApplicationRunListeners对象,这个对象是一个SpringBoot事件广播器的管理者,它可以包含多个SpringApplicationRunListener,SpringApplication类中使用它们来间接调用ApplicationListener

在classpath下的JAR文件中包含的/META/spring.factories文件里找到org.springframework.boot.SpringApplicationRunListener对应的属性,然后实例化并排序

在jar:file:/C:/Users/guiqingqing/.m2/repository/org/springframework/boot/spring-boot/1.5.6.RELEASE/spring-boot-1.5.6.RELEASE.jar!/META-INF/spring.factories文件找到如下配置

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

这里只有一个,就是EventPublishingRunListener,然后作为参数调用SpringApplicationRunListeners的构造函数,创建对象并返回,EventPublishingRunListener对象的实例被添加到SpringApplicationRunListeners对象的listeners属性中

看一下EventPublishingRunListener的构造函数

1 publicEventPublishingRunListener(SpringApplication application, String[] args) {2     this.application =application;3     this.args =args;4     this.initialMulticaster = newSimpleApplicationEventMulticaster();5     for (ApplicationListener<?>listener : application.getListeners()) {6         this.initialMulticaster.addApplicationListener(listener);7 }8 }

内部会创建一个Spring广播器SimpleApplicationEventMulticaster对象,它实际上是一个事件广播器,EventPublishingRunListener实现了SpringApplicationRunListener接口,我们看一下SpringApplicationRunListener接口的定义

1 public interfaceSpringApplicationRunListener {2     voidstarting();3
4     voidenvironmentPrepared(ConfigurableEnvironment environment);5
6     voidcontextPrepared(ConfigurableApplicationContext context);7
8     voidcontextLoaded(ConfigurableApplicationContext context);9
10     voidfinished(ConfigurableApplicationContext context, Throwable exception);11 }

该接口规定了SpringBoot的生命周期,在各个生命周期广播相应的事件,调用实际的ApplicationListener的onApplicationEvent方法

回到SpringApplication的run方法

第8行调用listeners的starting方法

1 public voidstarting() {2     for (SpringApplicationRunListener listener : this.listeners) {3 listener.starting();4 }5 }

该方法遍历所有的listeners,当然这里只有一个(EventPublishingRunListener),调用它的starting方法

1 @Override2 @SuppressWarnings("deprecation")3 public voidstarting() {4     this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));5 }

这里会使用广播器(SimpleApplicationEventMulticaster)去广播事件,创建一个ApplicationStartedEvent对象作为参数

1 @Override2 public voidmulticastEvent(ApplicationEvent event) {3 multicastEvent(event, resolveDefaultEventType(event));4 }5
6 @Override7 public void multicastEvent(finalApplicationEvent event, ResolvableType eventType) {8     ResolvableType type = (eventType != null ?eventType : resolveDefaultEventType(event));9     for (final ApplicationListener<?>listener : getApplicationListeners(event, type)) {10         Executor executor =getTaskExecutor();11         if (executor != null) {12             executor.execute(newRunnable() {13 @Override14                 public voidrun() {15 invokeListener(listener, event);16 }17 });18 }19         else{20 invokeListener(listener, event);21 }22 }23 }

可以看出multicastEvent方法会找到ApplicationListener的集合,然后依次调用invokeListener方法,而invokeListener方法内部则会调用ApplicationListener对象的onApplicationEvent方法

第10行创建一个DefaultApplicationArguments对象,它持有着args参数,就是main函数传进来的参数

第12行调用prepareEnvironment方法,该方法准备运行的环境,比如开发环境dev,测试环境test,还是生产环境prd,然后根据环境解析不同的配置文件

1 privateConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {2     //Create and configure the environment
3     ConfigurableEnvironment environment =getOrCreateEnvironment();4 configureEnvironment(environment, applicationArguments.getSourceArgs());5 listeners.environmentPrepared(environment);6     if (!this.webEnvironment) {7         environment = newEnvironmentConverter(getClassLoader())8 .convertToStandardEnvironmentIfNecessary(environment);9 }10     returnenvironment;11 }

getOrCreateEnvironment方法会检查之前设置的webEnvironment属性,如果是web程序,那么创建一个StandardServletEnvironment对象并返回,如果不是web程序,那么创建一个StandardEnvironment对象并返回

接下来是configureEnvironment方法,该方法会对上一步返回的environment做进一步的配置,调用configurePropertySources和configureProfiles,比如main函数传进来参数"--spring.profiles.active=dev",那么会在configurePropertySources方法里被添加到CommandLineArgs对象的optionArgs属性,不是以"--"开头的参数会被添加到nonOptionArgs属性。而configureProfiles方法这个时候就会获取到dev作为当前有效的profile并添加到environment的activeProfiles属性中,本例不传参数,所以基本上configureEnvironment方法什么都不做

然后调用listeners的environmentPrepared方法,发布一个ApplicationEnvironmentPreparedEvent事件,通过事件广播器,依次调用每个ApplicationListener对象的onApplicationEvent方法,这里我们重点分析ConfigFileApplicationListener的onApplicationEvent方法

1 @Override2 public voidonApplicationEvent(ApplicationEvent event) {3     if (event instanceofApplicationEnvironmentPreparedEvent) {4 onApplicationEnvironmentPreparedEvent(5 (ApplicationEnvironmentPreparedEvent) event);6 }7     if (event instanceofApplicationPreparedEvent) {8 onApplicationPreparedEvent(event);9 }10 }

1 private voidonApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {2     List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();3     postProcessors.add(this);4 AnnotationAwareOrderComparator.sort(postProcessors);5     for(EnvironmentPostProcessor postProcessor : postProcessors) {6 postProcessor.postProcessEnvironment(event.getEnvironment(),7 event.getSpringApplication());8 }9 }

首先loadPostProcessors方法会去classpath下的JAR文件中包含的/META/spring.factories文件里找到org.springframework.boot.env.EnvironmentPostProcessor对应的属性,然后实例化并排序,设置到postProcessors变量,在把当前对象(ConfigFileApplicationListener的实例)也添加到postProcessors变量中,然后对postProcessors排序,遍历postProcessors,依次调用它们的postProcessEnvironment方法。我们来看ConfigFileApplicationListener的postProcessEnvironment方法

1 @Override2 public voidpostProcessEnvironment(ConfigurableEnvironment environment,3 SpringApplication application) {4 addPropertySources(environment, application.getResourceLoader());5 configureIgnoreBeanInfo(environment);6 bindToSpringApplication(environment, application);7 }

addPropertySources方法最终会调用到ConfigFileApplicationListener的内部类Loader的load方法,这个方法是解析我们配置的application.yml和application-dev.yml文件的实现所在

1 public voidload() {2     this.propertiesLoader = newPropertySourcesLoader();3     this.activatedProfiles = false;4     this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());5     this.processedProfiles = new LinkedList<Profile>();6
7     //Pre-existing active profiles set via Environment.setActiveProfiles()8     //are additional profiles and config files are allowed to add more if9     //they want to, so don't call addActiveProfiles() here.
10     Set<Profile> initialActiveProfiles =initializeActiveProfiles();11     this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));12     if (this.profiles.isEmpty()) {13         for (String defaultProfileName : this.environment.getDefaultProfiles()) {14             Profile defaultProfile = new Profile(defaultProfileName, true);15             if (!this.profiles.contains(defaultProfile)) {16                 this.profiles.add(defaultProfile);17 }18 }19 }20
21     //The default profile for these purposes is represented as null. We add it22     //last so that it is first out of the queue (active profiles will then23     //override any settings in the defaults when the list is reversed later).
24     this.profiles.add(null);25
26     while (!this.profiles.isEmpty()) {27         Profile profile = this.profiles.poll();28         for(String location : getSearchLocations()) {29             if (!location.endsWith("/")) {30                 //location is a filename already, so don't search for more31                 //filenames
32                 load(location, null, profile);33 }34             else{35                 for(String name : getSearchNames()) {36 load(location, name, profile);37 }38 }39 }40         this.processedProfiles.add(profile);41 }42
43     addConfigurationProperties(this.propertiesLoader.getPropertySources());44 }

25行之前基本上是一些变量的实例化,会往profiles集合中添加default和null,直接跳过,接下来从profiles集合中取出对应的值进行解析,首先取出null

第28行getSearchLocations方法,首先会检查当前环境是否配置了spring.config.location,如果没有,那么会使用默认的搜索路径classpath:/,classpath:/config/,file:./,file:./config/,这里调用了Collections.reverse方法进行倒序,也就是先查找file:./config/,在查找file:./,再是classpath:/config/,最后查找classpath:/。笔者的application.yml和application-dev.yml都是直接在src/main/resources目录下,所以对应的是在location为classpath:/时调用load(String location, String name, Profile profile)方法(在没有配置spring.config.name的情况下,name默认为application)

1 private voidload(String location, String name, Profile profile) {2     String group = "profile=" + (profile == null ? "": profile);3     if (!StringUtils.hasText(name)) {4         //Try to load directly from the location
5 loadIntoGroup(group, location, profile);6 }7     else{8         //Search for a file with the given name
9         for (String ext : this.propertiesLoader.getAllFileExtensions()) {10             if (profile != null) {11                 //Try the profile-specific file
12                 loadIntoGroup(group, location + name + "-" + profile + "." +ext,13                         null);14                 for (Profile processedProfile : this.processedProfiles) {15                     if (processedProfile != null) {16                         loadIntoGroup(group, location + name + "-"
17                                 + processedProfile + "." +ext, profile);18 }19 }20                 //Sometimes people put "spring.profiles: dev" in21                 //application-dev.yml (gh-340). Arguably we should try and error22                 //out on that, but we can be kind and load it anyway.
23                 loadIntoGroup(group, location + name + "-" + profile + "." +ext,24 profile);25 }26             //Also try the profile-specific section (if any) of the normal file
27             loadIntoGroup(group, location + name + "." +ext, profile);28 }29 }30 }

调用this.propertiesLoader.getAllFileExtensions()方法获取配置文件后缀名,分别是在PropertiesPropertySourceLoader类和YamlPropertySourceLoader类中getFileExtensions方法定义的

1 public class PropertiesPropertySourceLoader implementsPropertySourceLoader {2 @Override3     publicString[] getFileExtensions() {4         return new String[] { "properties", "xml"};5 }6 }

1 public class YamlPropertySourceLoader implementsPropertySourceLoader {2 @Override3     publicString[] getFileExtensions() {4         return new String[] { "yml", "yaml"};5 }6 }

第23行调用的loadIntoGroup方法里又调用doLoadIntoGroup方法,这个方法会检查配置文件是否存在,比如file:./config/下不存在,或是classpath:/下的application.properties不存在,最后发现classpath:/下的application.yml存在,然后对其进行解析

1 public PropertySource<?>load(Resource resource, String group, String name,2             String profile) throwsIOException {3     if(isFile(resource)) {4         String sourceName =generatePropertySourceName(name, profile);5         for (PropertySourceLoader loader : this.loaders) {6             if(canLoadFileExtension(loader, resource)) {7                 PropertySource<?> specific =loader.load(sourceName, resource,8 profile);9 addPropertySource(group, specific, profile);10                 returnspecific;11 }12 }13 }14     return null;15 }

这里有2个loader,PropertiesPropertySourceLoader和YamlPropertySourceLoader,先检查是否该由此loader去解析,检查的规则就是看文件后缀名是否在getFileExtensions方法返回的数组中存在,很显然,这里应该由YamlPropertySourceLoader去解析,笔者的application.yml很简单,内容如下

spring:profiles:active: dev

解析完成之后,调用了一个handleProfileProperties方法,这个方法里又会调用maybeActivateProfiles方法,此方法的addProfiles方法会把解析到的dev添加到profiles中去,removeUnprocessedDefaultProfiles方法会删除之前添加的default(此时只剩下dev),如果配置了include,也会被解析

然后再次遍历file:./config/,file:./,classpath:/config/,classpath:/,这次是查找到application-dev.yml并解析,过程就不重复分析了

回到SpringApplication的run方法

第14行调用printBanner方法,打印SpringBoot的LOGO

1 privateBanner printBanner(ConfigurableEnvironment environment) {2     if (this.bannerMode ==Banner.Mode.OFF) {3         return null;4 }5     ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader6             : newDefaultResourceLoader(getClassLoader());7     SpringApplicationBannerPrinter bannerPrinter = newSpringApplicationBannerPrinter(8             resourceLoader, this.banner);9     if (this.bannerMode ==Mode.LOG) {10         return bannerPrinter.print(environment, this.mainApplicationClass, logger);11 }12     return bannerPrinter.print(environment, this.mainApplicationClass, System.out);13 }

方法内部会创建一个SpringApplicationBannerPrinter的对象,并调用print方法

1 public Banner print(Environment environment, Class<?>sourceClass, PrintStream out) {2     Banner banner = getBanner(environment, this.fallbackBanner);3 banner.printBanner(environment, sourceClass, out);4     return newPrintedBanner(banner, sourceClass);5 }

通过getBanner方法获取Banner对象

1 privateBanner getBanner(Environment environment, Banner definedBanner) {2     Banners banners = newBanners();3 banners.addIfNotNull(getImageBanner(environment));4 banners.addIfNotNull(getTextBanner(environment));5     if(banners.hasAtLeastOneBanner()) {6         returnbanners;7 }8     if (this.fallbackBanner != null) {9         return this.fallbackBanner;10 }11     returnDEFAULT_BANNER;12 }

第一步,调用Banners的无参构造创建一个Banners的对象,然后我们先看一下SpringApplicationBannerPrinter类里定义的几个静态属性

1 static final String BANNER_LOCATION_PROPERTY = "banner.location";2
3 static final String BANNER_IMAGE_LOCATION_PROPERTY = "banner.image.location";4
5 static final String DEFAULT_BANNER_LOCATION = "banner.txt";6
7 static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };

先看是否配置了系统属性banner.image.location,没有配置则在classpath中查找banner.gif,banner.jpg,banner.png,如果找到,则创建一个ImageBanner对象并添加到Banners对象的banners属性中,该属性是一个List类型,接下来看是否配置了banner.location属性,如果没有,则用默认的banner.txt,然后在classpath中查找banner.txt,如果找到,则创建一个ResourceBanner对象并添加到Banners对象的banners属性中,最后,如果Banners对象的banners不为空,也就是至少找到了banner.gif,banner.jpg,banner.png,banner.txt其中的一个,那么返回该Banners对象,否则返回默认的SpringBootBanner对象

接下来根据返回的不同Banners类型的对象调用具体的printBanner实现方法,所以如果要想打印自定义的LOGO,要么你在classpath下添加图片,要么添加banner.txt的文本文件,默认的SpringBootBanner会打印如下图

回到SpringApplication的run方法

第15行调用createApplicationContext方法,该方法创建SpringBoot的上下文

1 protectedConfigurableApplicationContext createApplicationContext() {2     Class<?> contextClass = this.applicationContextClass;3     if (contextClass == null) {4         try{5             contextClass = Class.forName(this.webEnvironment6                     ?DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);7 }8         catch(ClassNotFoundException ex) {9             throw newIllegalStateException(10                     "Unable create a default ApplicationContext, "
11                             + "please specify an ApplicationContextClass",12 ex);13 }14 }15     return(ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);16 }

通过判断当前是否是web环境决定创建什么类,如果是web程序,那么创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext的实例,否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext的实例,这些都是定义在SpringApplication类的静态属性中

第16行创建FailureAnalyzers的对象,FailureAnalyzers的构造函数里调用了loadFailureAnalyzers方法,还是老规矩,在classpath下的JAR文件中包含的/META/spring.factories文件中找到org.springframework.boot.diagnostics.FailureAnalyzer对应的属性,实例化并排序,赋值给FailureAnalyzers对象的analyzers属性,主要是用来处理启动时发生一些异常时的一些分析

第17行调用prepareContext方法,准备上下文

1 private voidprepareContext(ConfigurableApplicationContext context,2 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,3 ApplicationArguments applicationArguments, Banner printedBanner) {4 context.setEnvironment(environment);5 postProcessApplicationContext(context);6 applyInitializers(context);7 listeners.contextPrepared(context);8     if (this.logStartupInfo) {9         logStartupInfo(context.getParent() == null);10 logStartupProfileInfo(context);11 }12
13     //Add boot specific singleton beans
14     context.getBeanFactory().registerSingleton("springApplicationArguments",15 applicationArguments);16     if (printedBanner != null) {17         context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);18 }19
20     //Load the sources
21     Set<Object> sources =getSources();22     Assert.notEmpty(sources, "Sources must not be empty");23     load(context, sources.toArray(newObject[sources.size()]));24 listeners.contextLoaded(context);25 }

先给上下文设置环境,然后调用postProcessApplicationContext方法设置上下文的beanNameGenerator和resourceLoader(如果SpringApplication有的话)

接下来调用applyInitializers方法,拿到之前实例化SpringApplication对象的时候设置的ApplicationContextInitializer,调用它们的initialize方法,对上下文做初始化

介绍几个ApplicationContextInitializer,其他的不过多介绍,有兴趣的可以自己DEBUG

DelegatingApplicationContextInitializer: 从环境中(配置的context.initializer.classes)取出所有的ApplicationContextInitializer并执行

ContextIdApplicationContextInitializer: 设置上下文的id(name + ":" + profiles + ":" + index),笔者调试的id为(application:dev:8080)

下面一行调用listeners的contextPrepared方法,跟之前调用listeners的starting方法一样,只是EventPublishingRunListener的contextPrepared方法是个空实现

然后打印启动日志

2017-09-02 22:08:07.196 [INFO ][main]:com.dylan.java.springboot.template.Application[logStarting:48] - Starting Application on Dylan-PC with PID 5764 (F:\Dylan\workspace\template\target\classes started by Dylan in F:\Dylan\workspace\template)
2017-09-02 22:08:12.388 [INFO ][main]:com.dylan.java.springboot.template.Application[logStartupProfileInfo:597] - The following profiles are active: dev

打印完日志往上下文的beanFactory中注册一个singleton的bean,bean的名字是springApplicationArguments,bean的实例是之前实例化的ApplicationArguments对象

如果之前获取的printedBanner不为空,那么往上下文的beanFactory中注册一个singleton的bean,bean的名字是springBootBanner,bean的实例就是这个printedBanner

prepareContext方法的倒数第2行调用load方法注册启动类的bean定义,也就是调用SpringApplication.run(Application.class, args);的类,SpringApplication的load方法内会创建BeanDefinitionLoader的对象,并调用它的load()方法

1 public intload() {2     int count = 0;3     for (Object source : this.sources) {4         count +=load(source);5 }6     returncount;7 }

对所有的source(这里只有一个: class com.dylan.java.springboot.template.Application)都执行一次load(Object source)方法,这个方法又会调用load(Class<?> source)方法

1 private int load(Class<?>source) {2     if(isGroovyPresent()) {3         //Any GroovyLoaders added in beans{} DSL can contribute beans here
4         if (GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {5             GroovyBeanDefinitionSource loader =BeanUtils.instantiateClass(source,6                     GroovyBeanDefinitionSource.class);7 load(loader);8 }9 }10     if(isComponent(source)) {11         this.annotatedReader.register(source);12         return 1;13 }14     return 0;15 }

由于@SpringBootApplication注解继承了@SpringBootConfiguration注解,@SpringBootConfiguration注解继承了@Configuration注解,@Configuration注解又继承了@Component注解,所以上面代码的第10行返回true,于是执行第11行和第12行代码

1 classBeanDefinitionLoader {2     private finalObject[] sources;3
4     private finalAnnotatedBeanDefinitionReader annotatedReader;5
6     private finalXmlBeanDefinitionReader xmlReader;7
8     privateBeanDefinitionReader groovyReader;9
10 ...11 }

可以看出BeanDefinitionLoader中有多个加载BeanDefinition的Reader类,这里针对@SpringBootApplication注解使用了annotatedReader,调用register方法,因为启动类没有@Conditional注解,所以不能跳过注册的步骤

那么就老老实实的注册该BeanDefinition吧,没有设置bean是singleton还是prototype,那么默认使用singleton,而bean的名字,则默认是把类名的首字母变小写,也就是application,然后检查是否有@Lazy、@Primary、@DependsOn注解并设置AnnotatedBeanDefinition的属性,如果是AbstractBeanDefinition,还要检查是否有@Role、@Description注解并设置其属性,最后通过BeanDefinitionReaderUtils类的registerBeanDefinition方法注册BeanDefinition

最后调用listeners的contextLoaded方法,说明上下文已经加载,该方法先找到所有的ApplicationListener,遍历这些listener,如果该listener继承了ApplicationContextAware类,那么在这一步会调用它的setApplicationContext方法,设置context

遍历完ApplicationListener之后,创建ApplicationPreparedEvent事件对象,并广播出去,也就是调用所有ApplicationListener的onApplicationEvent方法

最后再回到SpringApplication的run方法,第19行调用refreshContext方法,该方法内容较多,我们放下一篇再分析

转载于:https://www.cnblogs.com/dylan-java/p/7455699.html

SpringBoot(1.5.6.RELEASE)源码解析(三)相关推荐

  1. SpringBoot(1.5.6.RELEASE)源码解析(一)

    启动SpringBoot,需要在入口函数所在的类上添加@SpringBootApplication注解 1 @SpringBootApplication 2 public class Applicat ...

  2. Disruptor源码解析三 RingBuffer解析

    目录 系列索引 前言 主要内容 RingBuffer的要点 源码解析 系列索引 Disruptor源码解析一 Disruptor高性能之道 Disruptor源码解析二 Sequence相关类解析 D ...

  3. OkHttp3源码解析(三)——连接池复用

    OKHttp3源码解析系列 OkHttp3源码解析(一)之请求流程 OkHttp3源码解析(二)--拦截器链和缓存策略 本文基于OkHttp3的3.11.0版本 implementation 'com ...

  4. 并发编程与源码解析 (三)

    并发编程 (三) 1 Fork/Join分解合并框架 1.1 什么是fork/join ​ Fork/Join框架是JDK1.7提供的一个用于并行执行任务的框架,开发者可以在不去了解如Thread.R ...

  5. ReactiveSwift源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  6. 前端入门之(vuex源码解析三)

    上两节前端入门之(vuex源码解析二)我们把vuex的源码大概的撸了一遍,还剩下(插件.getters跟module),我们继续哈~ 插件童鞋们可以去看看vuex在各个浏览器的状态显示插件,小伙伴可以 ...

  7. 拆轮子-RxDownload2源码解析(三)

    本文为博主原创文章,未经允许不得转载 造轮子者:Season_zlc 轮子用法请戳作者链接 ↑ 前言 本文主要讲述 RxDownload2 的多线程断点下载技术. 断点下载技术前提 服务器必须支持按 ...

  8. 深度理解springboot集成cache缓存之源码解析

    一.案例准备 1.创建数据表(employee表) 2.创建Employee实体类封装数据库中的数据 @AllArgsConstructor @NoArgsConstructor @Data @ToS ...

  9. Tomcat源码解析三:tomcat的启动过程

    Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context ...

最新文章

  1. Google、Facebook、亚马逊、Uber等硅谷顶尖AI专家团北京聚首 ,这场AI开发者盛会不可错过
  2. unet_3plus
  3. java后端 返回json_Java后端返回Json数据
  4. 用Excel制作改进前后漏斗模型图比较
  5. hdu3594 强连通 tarjan
  6. Netflix Curator 使用 Zookeeper 编程
  7. python小结_python简单小结
  8. 12v小型电机型号大全_伊藤8KW静音柴油发电机YT8100T型号规格
  9. Ruby编程语言学习笔记4
  10. 机器学习之几个好用的数据下载网站
  11. spring boot配置虚拟路径(替代docBase配置)访问本地图片
  12. SDOI2017R1(姑且是游记)
  13. 【MaxScript】删除所选择的骨骼的位置和缩放
  14. Python爬虫学习框架介绍
  15. useful websites for constructing your own website
  16. 南邮转专业计算机科学与技术,南京邮电大学本科生转专业管理办法(2020年10月9日修订)...
  17. 多图预警~ 华为 ECS 与 阿里云 ECS 对比实战
  18. WIN10彻底禁用UAC(用户账户控制)
  19. Unity Shader入门精要学习笔记 - 第11章 让画面动起来
  20. 解决SublimeText中文注释乱码

热门文章

  1. flask实现后台java实现前端页面_java实现telnet功能,待实现windows下远程多机自动化发布软件后台代码...
  2. linux版本信息i686,Linux下如何查看版本信息
  3. 投影仪幕布增益_当贝投影带你看投影仪使用幕布和白墙的区别实测!
  4. 小程序与UC浏览器打通
  5. Buffer内存分配
  6. 关于php正则表达式得选择题,经典PHP笔试题
  7. vfp 调用接口取数据_2分钟教你调用全国天气预报数据接口
  8. oracle数据库静态启动,ORACLE数据库的连接
  9. VB提取字符串中的日期
  10. 消耗性缺口_衰竭缺口分析