【pbkiller源码】【个人发卡源码2020】【7ghost源码】Joblauncher源码

时间:2024-11-23 13:38:57 来源:呼叫中心源码下载 分类:探索

1.spring batch joboperator和joblauncher的区别
2.Spring boot Batch 的启动原理- Configuration
3.一文搞懂大数据批量处理框架Spring Batch的完美解析方案是什么。
4.springbatch和springboot的区别
5.springboot启动类原理?

Joblauncher源码

spring batch joboperator和joblauncher的区别

       JobExecution 接口的示例,提供job的“运行、结束、失败”等状态的查询。

       ç”±äºŽJob运行可能消耗非常长的时间,Spring batch同步和异步两种launcher。

       å¼‚步的情况,用于类似于mit-interval设置read多少条记录后进行一次提交。通过设置commit-interval的间隔值,减少提交频次,降低资源使用率。Step的pbkiller源码每一次提交作为一个完整的事务存在。默认采用Spring提供的声明式事务管理模式,事务编排非常方便。如下是一个声明事务的示例:

       框架对于事务的支持能力包括:

       Chunk支持事务管理,通过commit-interval设置每次提交的记录数;支持对每个Tasklet设置细粒度的事务配置:隔离界别、传播行为、超时;支持rollback和norollback,通过skippable-exception-classes和no-rollback-exception-classes进行支撑;支持JMSQueue的事务级别配置;

       另外,在框架资深的模型抽象方面,SpringBatch也做了极为精简的抽象。

       仅仅使用六张业务表存储了所有的元数据信息(包括Job、Step的实例,上下文,执行器信息,为后续的监控、重启、重试、状态恢复等提供了可能)。

       BATCH_JOB_INSTANCE:作业实例表,用于存放Job的个人发卡源码2020实例信息BATCH_JOB_EXECUTION_PARAMS:作业参数表,用于存放每个Job执行时候的参数信息,该参数实际对应Job实例的。BATCH_JOB_EXECUTION:作业执行器表,用于存放当前作业的执行信息,比如创建时间,执行开始时间,执行结束时间,执行的那个Job实例,执行状态等。BATCH_JOB_EXECUTION_CONTEXT:作业执行上下文表,用于存放作业执行器上下文的信息。BATCH_STEP_EXECUTION:作业步执行器表,用于存放每个Step执行器的信息,比如作业步开始执行时间,执行完成时间,执行状态,读写次数,跳过次数等信息。BATCH_STEP_EXECUTION_CONTEXT:作业步执行上下文表,用于存放每个作业步上下文的信息。实现作业的健壮性与扩展性

       批处理要求Job必须有较强的健壮性,通常Job是批量处理数据、无人值守的,这要求在Job执行期间能够应对各种发生的7ghost源码异常、错误,并对Job执行进行有效的跟踪。

       一个健壮的Job通常需要具备如下的几个特性:

       1.容错性

       在Job执行期间非致命的异常,Job执行框架应能够进行有效的容错处理,而不是让整个Job执行失败;通常只有致命的、导致业务不正确的异常才可以终止Job的执行。

       2.可追踪性

       Job执行期间任何发生错误的地方都需要进行有效的记录,方便后期对错误点进行有效的处理。例如在Job执行期间任何被忽略处理的记录行需要被有效的记录下来,应用程序维护人员可以针对被忽略的记录后续做有效的处理。

       3.可重启性

       Job执行期间如果因为异常导致失败,应该能够在失败的点重新启动Job;而不是从头开始重新执行Job。

       框架提供了支持上面所有能力的特性,包括Skip(跳过记录处理)、Retry(重试给定的操作)、Restart(从错误点开始重新启动失败的Job):

       Skip,在对数据处理期间,如果数据的某几条的格式不能满足要求,可以通过Skip跳过该行记录的处理,让Processor能够顺利的处理其余的记录行。Retry,将给定的操作进行多次重试,在某些情况下操作因为短暂的异常导致执行失败,如网络连接异常、会员云端软件源码并发处理异常等,可以通过重试的方式避免单次的失败,下次执行操作时候网络恢复正常,不再有并发的异常,这样通过重试的能力可以有效的避免这类短暂的异常。Restart,在Job执行失败后,可以通过重启功能来继续完成Job的执行。在重启时候,批处理框架允许在上次执行失败的点重新启动Job,而不是从头开始执行,这样可以大幅提高Job执行的效率。

       对于扩展性,框架提供的扩展能力包括如下的四种模式:

       MultithreadedStep多线程执行一个Step;ParallelStep通过多线程并行执行多个Step;RemoteChunking在远端节点上执行分布式Chunk操作;PartitioningStep对数据进行分区,并分开执行;

       我们先来看第一种的实现MultithreadedStep:

       批处理框架在Job执行时默认使用单个线程完成任务的执行,同时框架提供了线程池的支持(MultithreadedStep模式),可以在Step执行时候进行并行处理,这里的并行是指同一个Step使用线程池进行执行,同一个Step被并行的执行。使用tasklet的属性task-executor可以非常容易的将普通的Step变成多线程Step。

       MultithreadedStep的实现示例:

       需要注意的是SpringBatch框架提供的大部分的ItemReader、ItemWriter等操作都是线程不安全的。

       可以通过扩展的方式显现线程安全的Step。

       下面为大家展示一个扩展的带expma指标源码实现:

       需求:针对数据表的批量处理,实现线程安全的Step,并且支持重启能力,即在执行失败点可以记录批处理的状态。

       对于示例中的数据库读取组件JdbcCursorItemReader,在设计数据库表时,在表中增加一个字段Flag,用于标识当前的记录是否已经读取并处理成功,如果处理成功则标识Flag=true,等下次重新读取的时候,对于已经成功读取且处理成功的记录直接跳过处理。

       MultithreadedStep(多线程步)提供了多个线程执行一个Step的能力,但这种场景在实际的业务中使用的并不是非常多。

       更多的业务场景是Job中不同的Step没有明确的先后顺序,可以在执行期并行的执行。

       ParallelStep:提供单个节点横向扩展的能力

       使用场景:StepA、StepB两个作业步由不同的线程执行,两者均执行完毕后,StepC才会被执行。

       框架提供了并行Step的能力。可以通过Split元素来定义并行的作业流,并制定使用的线程池。

       ParallelStep模式的执行效果如下:

       每个作业步并行处理不同的记录,示例中三个作业步,处理同一张表中的不同数据。

       并行Step提供了在一个节点上横向处理,但随着作业处理量的增加,有可能一台节点无法满足Job的处理,此时我们可以采用远程Step的方式将多个机器节点组合起来完成一个Job的处理。

       RemoteChunking:远程Step技术本质上是将对Item读、写的处理逻辑进行分离;通常情况下读的逻辑放在一个节点进行操作,将写操作分发到另外的节点执行。

       远程分块是一个把step进行技术分割的工作,不需要对处理数据的结构有明确了解。

       任何输入源能够使用单进程读取并在动态分割后作为块发送给远程的工作进程。

       远程进程实现了监听者模式,反馈请求、处理数据最终将处理结果异步返回。请求和返回之间的传输会被确保在发送者和单个消费者之间。

       在Master节点,作业步负责读取数据,并将读取的数据通过远程技术发送到指定的远端节点上,进行处理,处理完毕后Master负责回收Remote端执行的情况。

       在SpringBatch框架中通过两个核心的接口来完成远程Step的任务,分别是ChunkProvider与ChunkProcessor。

       ChunkProvider:根据给定的ItemReader操作产生批量的Chunk操作;

       ChunkProcessor:负责获取ChunkProvider产生的Chunk操作,执行具体的写逻辑;

       SpringBatch中对远程Step没有默认的实现,但我们可以借助SI或者AMQP实现来实现远程通讯能力。

       Step本地节点负责读取数据,并通过MessagingGateway将请求发送到远程Step上;远程Step提供了队列的监听器,当请求队列中有消息时候获取请求信息并交给ChunkHander负责处理。

       接下来我们看下最后一种分区模式;PartitioningStep:分区模式需要对数据的结构有一定的了解,如主键的范围、待处理的文件的名字等。

       这种模式的优点在于分区中每一个元素的处理器都能够像一个普通SpringBatch任务的单步一样运行,也不必去实现任何特殊的或是新的模式,来让他们能够更容易配置与测试。

       通过分区可以实现以下的优点:

       分区实现了更细粒度的扩展;基于分区可以实现高性能的数据切分;分区比远程通常具有更高的扩展性;分区后的处理逻辑,支持本地与远程两种模式;分区作业典型的可以分成两个处理阶段,数据分区、分区处理;

       数据分区:根据特殊的规则(例如:根据文件名称,数据的唯一性标识,或者哈希算法)将数据进行合理的数据切片,为不同的切片生成数据执行上下文ExecutionContext、作业步执行器StepExecution。可以通过接口Partitioner生成自定义的分区逻辑,SpringBatch批处理框架默认实现了对多文件的实现org.springframework.batch.core.partition.support.MultiResourcePartitioner;也可以自行扩展接口Partitioner来实现自定义的分区逻辑。

       分区处理:通过数据分区后,不同的数据已经被分配到不同的作业步执行器中,接下来需要交给分区处理器进行作业,分区处理器可以本地执行也可以远程执行被划分的作业。接口PartitionHandler定义了分区处理的逻辑,SpringBatch批处理框架默认实现了本地多线程的分区处理org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler;也可以自行扩展接口PartitionHandler来实现自定义的分区处理逻辑。

       SpringBatch框架提供了对文件分区的支持,实现类org.springframework.batch.core.partition.support.MultiResourcePartitioner提供了对文件分区的默认支持,根据文件名将不同的文件处理进行分区,提升处理的速度和效率,适合有大量小文件需要处理的场景。

       示例展示了将不同文件分配到不同的作业步中,使用MultiResourcePartitioner进行分区,意味着每个文件会被分配到一个不同的分区中。如果有其它的分区规则,可以通过实现接口Partitioner来进行自定义的扩展。有兴趣的TX,可以自己实现基于数据库的分区能力哦。

       总结一下,批处理框架在扩展性上提供了4中不同能力,每种都是各自的使用场景,我们可以根据实际的业务需要进行选择。

       批处理框架的不足与增强

       SpringBatch批处理框架虽然提供了4种不同的监控方式,但从目前的使用情况来看,都不是非常的友好。

       通过DB直接查看,对于管理人员来讲,真的不忍直视;通过API实现自定义的查询,这是程序员的天堂,确实运维人员的地狱;提供了Web控制台,进行Job的监控和操作,目前提供的功能太,无法直接用于生产;提供JMX查询方式,对于非开发人员太不友好;

       但在企业级应用中面对批量数据处理,仅仅提供批处理框架仅能满足批处理作业的快速开发、执行能力。

       企业需要统一的批处理平台来处理复杂的企业批处理应用,批处理平台需要解决作业的统一调度、批处理作业的集中管理和管控、批处理作业的统一监控等能力。

       那完美的解决方案是什么呢?

       关注我:转发私信回复“架构资料”获取Java高级架构资料、源码、笔记、视频

       Dubbo、Redis、设计模式、Netty、zookeeper、Springcloud、分布式、微服务

       高并发等架构技术

       企业级批处理平台需要在SpringBatch批处理框架的基础上,集成调度框架,通过调度框架可以将任务按照企业的需求进行任务的定期执行;

       丰富目前SpringBatchAdmin(SpringBatch的管理监控平台,目前能力比较薄弱)框架,提供对Job的统一管理功能,增强Job作业的监控、预警等能力;

       通过与企业的组织机构、权限管理、认证系统进行合理的集成,增强平台对Job作业的权限控制、安全管理能力。

       由于时间关系,今天的分享就到这里,很多内容未能展开讨论。欢迎大家在实际业务中使用SpringBatch框架。

       最后的话

       觉得还不错可以转发关注支持一波~私信架构资料获取一些我私人整理的Java进阶资料!

       为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀。而你是不是还在满足于现状且内心在窃喜?“对于程序员来说,如果哪一天开始他停止了学习,那么他的职业生涯便开始宣告消亡。”所以行动起来,学习起来!

springbatch和springboot的区别

       Spring Batch:

       é«˜æ•ˆçš„批处理应用,能够支持简单和复杂以及庞大数据量的批处理作业;

       æœ‰å¤§é‡çš„可重用组件,包括日志、追踪、事务、任务作业统计、任务重启、跳过、重复、资源管理;

       æä¾›äº†é«˜çº§åŠŸèƒ½å’Œç‰¹æ€§æ¥æ”¯æŒï¼Œæ¯”如区分功能、远程功能;

Spring Batch框架的组成部分

       1)JobRepository:用来注册Job容器,设置数据库相关属性。

       2)JobLauncher:用来启动Job的接口

       3)Job:我们要实际执行的任务,包含一个或多个

       4)Step:即步骤,包括:ItemReader->ItemProcessor->ItemWriter

       5)ItemReader:用来读取数据,做实体类与数据字段之间的映射。比如读取csv文件中的人员数据,之后对应实体person的字段做mapper

       6)ItemProcessor:用来处理数据的接口,同时可以做数据校验(设置校验器,使用JSR-(hibernate-validator)注解),比如将中文性别男/女,转为M/F。同时校验年龄字段是否符合要求等

       7)ItemWriter:用来输出数据的接口,设置数据库源。编写预处理SQL插入语句

Spring Boot:

       Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的创建、运行、调试、部署等。

       Spring Boot是开发者和Spring 本身框架的中间层,帮助开发者统筹管理应用的配置,提供基于实际开发中常见配置的默认处理。

Spring Batch框架的组成部分

       1)JobRepository:用来注册Job容器,设置数据库相关属性。

       2)JobLauncher:用来启动Job的接口

       3)Job:我们要实际执行的任务,包含一个或多个

       4)Step:即步骤,包括:ItemReader->ItemProcessor->ItemWriter

       5)ItemReader:用来读取数据,做实体类与数据字段之间的映射。比如读取csv文件中的人员数据,之后对应实体person的字段做mapper

       6)ItemProcessor:用来处理数据的接口,同时可以做数据校验(设置校验器,使用JSR-(hibernate-validator)注解),比如将中文性别男/女,转为M/F。同时校验年龄字段是否符合要求等

       7)ItemWriter:用来输出数据的接口,设置数据库源。编写预处理SQL插入语句

springboot启动类原理?

       SpringbootBatch的启动原理-Configuration

       Springboot整合了web和batch,但是他们肯定不是同一条路,在springboot中,会推断当前的运行环境。this.webApplicationType=WebApplicationType.deduceFromClasspath();

       ä»Žä¸Šæ–‡å¯ä»¥çœ‹å‡ºï¼ŒSpring尝试从classpath里找到特征类,来判断当前app是什么类型。当然这种判断是有局限性的,有可能是transitive带进来一个带有servlet的类被当成了WebApplicationType.SERVLET,实际上是个WebApplicationType.NONE;。如果不想以web运行就是想运行batch可以在application.properties强行指定WebApplicationType

       å…·ä½“发生作用的请看下面的stacktrace

       å½“一个batchapplication需要启动,需要配置JobRepository,Datasource等等,所有的开始都来自一个annotation@EnableBatchProcessing

       å½“加入@EnableBatchProcessing时,BatchConfigurationSelector开始启动,怎么启动的大家可以参考下面的stacktrace。

       import类主要是由ConfigurationClassPostProcessor来实现的。当BatchConfigurationSelector被调用的时候,我们可以看到他有两条支路。

       é‚£ä¹ˆè¿™ä¸¤æ¡è·¯æœ‰å•¥ä¸åŒå‘¢ã€‚主要是job定义的方式不同。

       modular=true的情况下,下面是一个例子

       å¯ä»¥æœ‰å¤šä¸ªå­ApplicationContextFactory,这样好处是在除了job大家不可以重复,因为是在不同的context里,其他的step,reader,writer,processor,mapper,以及所有的bean等等都可以重名。

       é‚£ä¸ºä»€ä¹ˆJob不可以重复,是因为虽然可以重复,但是如果job也重复,对用户来讲太不友好了。用户可能不知道自己配的是哪个context的job。具体为什么可以重名要看看GenericApplicationContextFactory的实现。

       å½“GenericApplicationContextFactory::createApplicationContext,会触发ApplicationContextHelper的构造函数从而调用loadConfiguration(config)把定义的bean加入到context里。那么有个问题,parent在哪里设置,createApplicationContext是什么时候调用的。

       æˆ‘们继续看ModularConfiguration

       ä»ŽModular的角度来看首先他可以外部注入一个Configurer,如果没有就选择DefaultBatchConfigurer,如果有多个选择则会抛出。

       å½“然Datasource也可以选择外部注入,或者由DefaultBatchConfigurer::initialize方法SimpleJobLauncher,JobRepository和JobExplorer都是由FactoryBean的方式实现的。

       åœ¨è¿™ä¸ªDefaultBatchConfigurer中可以看到JobLauncher的类型是

       initialize里MapJobRepositoryFactoryBean这个可以重点读一下。首先用FactoryBean的模式实现了一个ProxyBean,如果想了解FactoryBean的用法,这是个典型的例子。但是这个FactoryBean是以api行为直接调用的,并没有注册到Spring的context中。

       é…ç½®å¥½job需要的jobRepository,jobLauncher等那么重点来了,下面的AutomaticJobRegistrar就是来处理ApplicationContextFactory

       è¿™ä¸ªé€»è¾‘是把所有的ApplicationContextFactory的beaninstance放入到AutomaticJobRegistrar里去。这就回到了第一个问题,parentcontext是什么时候放进去的。就是在

       context.getBeansOfType(ApplicationContextFactory.class)请看下面的调用栈,在ApplicationContextAwareProcessor里会自动把parentcontext注入。

       ä½†æ˜¯æ”¾è¿›åŽ»å•¥æ—¶å€™ç”¨å‘¢ï¼Ÿæˆ‘们看一下AutomaticJobRegistrar

       åŽŸæ¥AutomaticJobRegistrar是个Smartlifecycle,从Smartlifecycle的细节可以从SpringbootSmartlifecycle来得知。它就是在所有bean都初始化结束后开始进行的一个阶段。在这个start方法中,开始遍历所有的ApplicationContextFactory,来进行加载。从上文这个jobLoader是DefaultJobLoader。

       é‚£ä¹ˆå¯ä»¥çœ‹çœ‹DefaultJobLoader::doLoad方法

       è¿™é‡Œæœ‰å‡ ä¸ªå…³é”®è°ƒç”¨ç¬¬ä¸€ä¸ªæ˜¯createApplicationContext,把context里定义的全部加载到Springcontext里去,这就满足了GenericApplicationContextFactory工作的两个条件。第二个是doRegister(context,job)里jobRegistry.register(jobFactory);

       æˆ‘们看一下JobRepository的实现MapJobRegistry::register方法,在这里就把jobname和jobFactory的键值对存储起来了。

       è¿™æ ·å°±å¯ä»¥é€šè¿‡JobRepository这个bean拿到所有注册的job了。

       å’±ä»¬å†å›žæ¥çœ‹@EnableBatchProcessing这个annotation,当没有设定modular的时候是比较简单的,只是实现了一个proxybased的Job的bean。

       åŒæ ·ä¹Ÿåªæœ‰BatchConfigurer来配置。这个逻辑和Modular是一样的。从这两个配置知道,Modular注册了在子context的配置,并且加载。但是当以正常bean的方式存在的,是怎么读进来的呢。这时候就要看JobRegistryBeanPostProcessor

       è¿™ä¸ªpostProcessAfterInitialization方法里,对每个job类型的bean,jobRegistry加入了ReferenceJobFactory。这样所有的以bean的方式定义的都可以通过jobRegistry获得。

SpringBootStater原理

       ä¸€.SpringBoot的好处

1.依赖管理:可插拔式的组件管理,当需要某个组件时,只需要引入相关stater即可,不需要再手动引入各个jar包,避免了包遗漏、包冲突等不必要的问题。开发人员可以专注于业务开发,

2.自动配置:遵从"约定优于配置"的原则,开发人员可以在少量配置或者不配置的情况下,使用某组件。

大大降低项目搭建及组件引入的成本,开发人员可以专注于业务开发,避免繁杂的配置和大量的jar包管理。

       äºŒ.实现原理

要引入某组件,无非要做两件事。一是引入jar包即pom文件引入stater;二就是编写配置文件,使用Java配置的情况下就是编写一系列@Configuration注解标注的类。那么SpringBoot是怎么来引入这些配置类的呢?就需要我们深入SpringBoot启动类一探究竟。

SpringBoot启动类上面会有@SpringBootApplication注解,这是SpringBoot中最重要的一个注解,是实现自动配置的关键。@SpringBootApplication是一个租合注解,主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三部分组成。

       @SpringBootConfiguration表明该类是一个配置类。

       @EnableAutoConfiguration由@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)组成。@AutoConfigurationPackage由@Import(AutoconfigurationPackages.Registrar.class)组成,向Bean容器中注册一个AutoConfigurationPackages类,该类持有basePackage,目前我发现的作用是在MyBatis扫描注册Mapper时作为包扫描路径。

       @AutoConfigurationPackage的执行流程如下图:

       @Import(AutoConfigurationImportSelector.class)是启动自动配置的核心。这里还有一个小插曲,一直在看AutoConfigurationImportSelector的selectImports方法,却发现没有被调用,以为是demo项目问题,去真实项目中debug断点,发现也没有进入,瞬间懵逼。。。继续跟踪发现执行流程如下:

       SpringBoot启动原理分析

       è‡ªåŠ¨é…ç½®æ ¸å¿ƒç±»SpringFactoriesLoader

       ä¸Šé¢åœ¨è¯´@EnableAutoConfiguration的时候有说META-INF下的spring.factories文件,那么这个文件是怎么被spring加载到的呢,其实就是SpringFactoriesLoader类。

       SpringFactoriesLoader是一个供Spring内部使用的通用工厂装载器,SpringFactoriesLoader里有两个方法,

       åœ¨è¿™ä¸ªSpringBoot应用启动过程中,SpringFactoriesLoader做了以下几件事:

       åŠ è½½æ‰€æœ‰META-INF/spring.factories中的Initializer

       åŠ è½½æ‰€æœ‰META-INF/spring.factories中的Listener

       åŠ è½½EnvironmentPostProcessor(允许在Spring应用构建之前定制环境配置)

       æŽ¥ä¸‹æ¥åŠ è½½Properties和YAML的PropertySourceLoader(针对SpringBoot的两种配置文件的加载器)

       å„种异常情况的FailureAnalyzer(异常解释器)

       åŠ è½½SpringBoot内部实现的各种AutoConfiguration

       æ¨¡æ¿å¼•æ“ŽTemplateAvailabilityProvider(如Freemarker、Thymeleaf、Jsp、Velocity等)

       æ€»å¾—来说,SpringFactoriesLoader和@EnableAutoConfiguration配合起来,整体功能就是查找spring.factories文件,加载自动配置类。

       æ•´ä½“启动流程

       åœ¨æˆ‘们执行入口类的main方法之后,运行SpringApplication.run,后面new了一个SpringApplication对象,然后执行它的run方法。

       åˆå§‹åŒ–SpringApplicationç±»

       åˆ›å»ºä¸€ä¸ªSpringApplication对象时,会调用它自己的initialize方法

       æ‰§è¡Œæ ¸å¿ƒrun方法

       åˆå§‹åŒ–initialize方法执行完之后,会调用run方法,开始启动SpringBoot。

       é¦–先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/spring.factories中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的starting()方法,通知这些监听器SpringBoot应用启动。

       åˆ›å»ºå¹¶é…ç½®å½“前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。

       éåŽ†è°ƒç”¨æ‰€æœ‰çš„SpringApplicationRunListeners的environmentPrepared()的方法,通知这些监听器SpringBoot应用的Environment已经完成初始化。

       æ‰“印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在banner.txt文件,则打印其内容,否则打印默认banner。

       æ ¹æ®å¯åŠ¨æ—¶è®¾ç½®çš„applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。

       åˆ›å»ºå¼‚常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。

       è®¾ç½®SpringBoot的Environment,注册SpringBean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。

       è°ƒç”¨æ‰€æœ‰çš„SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。

       æœ€æ ¸å¿ƒçš„一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

       è°ƒç”¨æ‰€æœ‰çš„SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。

       è°ƒç”¨refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)

       æ³¨ï¼šé’©å­å¯ä»¥åœ¨ä»¥ä¸‹å‡ ç§åœºæ™¯ä¸­è¢«è°ƒç”¨ï¼š

       1)程序正常退出

       2)使用System.exit()

       3)终端使用Ctrl+C触发的中断

       4)系统关闭

       5)使用Killpid命令杀死进程

       èŽ·å–当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法

       éåŽ†æ‰€æœ‰çš„SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。

SpringBoot应用启动原理(二)扩展URLClassLoader实现嵌套jar加载

       åœ¨ä¸Šç¯‡æ–‡ç« ã€ŠSpringBoot应用启动原理(一)将启动脚本嵌入jar》中介绍了SpringBoot如何将启动脚本与RunnableJar整合为ExecutableJar的原理,使得生成的jar/war文件可以直接启动

       æœ¬ç¯‡å°†ä»‹ç»SpringBoot如何扩展URLClassLoader实现嵌套jar的类(资源)加载,以启动我们的应用。

       é¦–先,从一个简单的示例开始

       build.gradle

       WebApp.java

       æ‰§è¡Œgradlebuild构建jar包,里面包含应用程序、第三方依赖以及SpringBoot启动程序,其目录结构如下

       æŸ¥çœ‹MANIFEST.MF的内容(MANIFEST.MF文件的作用请自行GOOGLE)

       å¯ä»¥çœ‹åˆ°ï¼Œjar的启动类为org.springframework.boot.loader.JarLauncher,而并不是我们的com.manerfan.SpringBoot.theory.WebApp,应用程序入口类被标记为了Start-Class

       jar启动并不是通过应用程序入口类,而是通过JarLauncher代理启动。其实SpringBoot拥有3中不同的Launcher:JarLauncher、WarLauncher、PropertiesLauncher

       SpringBoot使用Launcher代理启动,其最重要的一点便是可以自定义ClassLoader,以实现对jar文件内(jarinjar)或其他路径下jar、class或资源文件的加载

       å…³äºŽClassLoader的更多介绍可参考《深入理解JVM之ClassLoader》

       SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),可以是一个文件目录(ExplodedArchive),可以抽象为统一访问资源的逻辑层。

       ä¸Šä¾‹ä¸­ï¼Œspring-boot-theory-1.0.0.jar既为一个JarFileArchive,spring-boot-theory-1.0.0.jar!/BOOT-INF/lib下的每一个jar包也是一个JarFileArchive

       å°†spring-boot-theory-1.0.0.jar解压到目录spring-boot-theory-1.0.0,则目录spring-boot-theory-1.0.0为一个ExplodedArchive

       æŒ‰ç…§å®šä¹‰ï¼ŒJarLauncher可以加载内部/BOOT-INF/lib下的jar及/BOOT-INF/classes下的应用class

       å…¶å®žJarLauncher实现很简单

       å…¶ä¸»å…¥å£æ–°å»ºäº†JarLauncher并调用父类Launcher中的launch方法启动程序

       å†åˆ›å»ºJarLauncher时,父类ExecutableArchiveLauncher找到自己所在的jar,并创建archive

       åœ¨Launcher的launch方法中,通过以上archive的getNestedArchives方法找到/BOOT-INF/lib下所有jar及/BOOT-INF/classes目录所对应的archive,通过这些archives的url生成LaunchedURLClassLoader,并将其设置为线程上下文类加载器,启动应用

       è‡³æ­¤ï¼Œæ‰æ‰§è¡Œæˆ‘们应用程序主入口类的main方法,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载

       åœ¨åˆ†æžLaunchedURLClassLoader前,首先了解一下URLStreamHandler

       java中定义了URL的概念,并实现多种URL协议(见URL)*http**file**ftp**jar*等,结合对应的URLConnection可以灵活地获取各种协议下的资源

       å¯¹äºŽjar,每个jar都会对应一个url,如

       jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/

       jar中的资源,也会对应一个url,并以'!/'分割,如

       jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

       å¯¹äºŽåŽŸå§‹çš„JarFileURL,只支持一个'!/',SpringBoot扩展了此协议,使其支持多个'!/',以实现jarinjar的资源,如

       jar:file:/data/spring-boot-theory.jar!/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

       è‡ªå®šä¹‰URL的类格式为[pkgs].[protocol].Handler,在运行Launcher的launch方法时调用了JarFile.registerUrlProtocolHandler()以注册自定义的Handler

       åœ¨å¤„理如下URL时,会循环处理'!/'分隔符,从最上层出发,先构造spring-boot-theory.jar的JarFile,再构造spring-aop-5.0.4.RELEASE.jar的JarFile,最后构造指向SpringProxy.class的

       JarURLConnection,通过JarURLConnection的getInputStream方法获取SpringProxy.class内容

       ä»Žä¸€ä¸ªURL,到读取其中的内容,整个过程为

       URLClassLoader可以通过原始的jar协议,加载jar中从class文件

       LaunchedURLClassLoader通过扩展的jar协议,以实现jarinjar这种情况下的class文件加载

       æž„建war包很简单

       æž„建出的war包,其目录机构为

       MANIFEST.MF内容为

       æ­¤æ—¶ï¼Œå¯åŠ¨ç±»å˜ä¸ºäº†org.springframework.boot.loader.WarLauncher,查看WarLauncher实现,其实与JarLauncher并无太大差别

       å·®åˆ«ä»…在于,JarLauncher在构建LauncherURLClassLoader时,会搜索BOOT-INF/classes目录及BOOT-INF/lib目录下jar,WarLauncher在构建LauncherURLClassLoader时,则会搜索WEB-INFO/classes目录及WEB-INFO/lib和WEB-INFO/lib-provided两个目录下的jar

       å¦‚此依赖,构建出的war便支持两种启动方式

       PropretiesLauncher的实现与JarLauncherWarLauncher的实现极为相似,通过PropretiesLauncher可以实现更为轻量的thinjar,其实现方式可自行查阅源码