数据仓库&面试总结

一、数据仓库分为几层?负责什么职责?为什么要分层?

1、数据仓库分为4层:

  • ODS层 (原始数据层)
  • DWD层 (明细数据层)
  • DWS层 (服务数据层)
  • ADS层 (数据应用层)

2、主要负责职责,如下:

ODS层(原始数据层):存放原始数据,直接加载原始日志、数据,数据保存原貌不做处理。

DWD层(明细数据层):结构与粒度原始表保持一致,对ODS层数据进行清洗(去除空值、脏数据、超过极限范围的数据)

DWS层 (服务数据层):以DWD为基础,进行轻度汇总

ADS层 (数据应用层):为各种统计报表提供数据

3、为什么要分层?

空间换时间:通过建设多层次的数据模型供用户使用,避免用户直接使用操作型数据,可以更高效的访问数据

把复杂问题简单化:一个复杂的任务分解成多个步骤来完成,每一层只处理单一的步骤,比较简单和容易理解。而且便于维护数据的准确性,当数据出现问题之后,可以不用修复所有的数据,只需要从有问题的步骤开始修复

便于处理业务的变化:随着业务的变化,只需要调整底层的数据,对应用层对业务的调整零感知

4、如何理解数仓?

官方定义:

数据仓库是一个面向主题的、集成的、随时间变化的、但信息本身相对稳定的数据集合,用于对管理决策过程的支持。

个人理解:

数据仓库就是整合多个数据源的历史数据进行细粒度的、多维的分析,帮助高层管理者或者业务分析人员做出商业战略决策或商业报表

5、数据集市和数据仓库的主要区别?

数据仓库是企业级的,能为整个企业各个部门的运行提供决策支持手段;

数据集市则是一种微型的数据仓库,它通常有更少的数据,更少的主题区域,以及更少的历史数据,因此是部门级的,一般只能为某个局部范围内的管理人员服务,因此也称之为部门级数据仓库。

6、数据仓库和数据库的区别?

数据库:是一种软件,用来实现数据库逻辑过程,属于物理层;

数据库:是一种逻辑概念,用来存放数据的仓库;通过数据库软件来实现

数据仓库:是数据库概念的升级,从数据量来说,数据仓库要比数据库更庞大德多,主要用于数据挖掘和数据分析,辅助领导做决策

数据库: 随着时间变化不断删去旧的数据内容 。

数据仓库: 内的数据也有存储期限,一旦过了这一期限,过期数据就要被删除。

只是数据库内的数据时限要远远的长于操作型环境中的数据时限。在操作型环境中一般只保存有60~90天的数据,而在数据仓库中则要需要保存较长时限的数据(例如:5~10年),以适应DSS进行趋势分析的要求。

7、数仓中每层表的建模?怎么建模?

(1)ODS: 特点是保持原始数据的原貌,不作修改!

原始数据怎么建模,ODS就怎么建模!举例: 用户行为数据特征是一条记录就是一行!

ODS层表(line string) 业务数据,参考Sqoop导入的数据类型进行建模!

(2)DWD层:特点从ODS层,将数据进行ETL(清洗),轻度聚合,再展开明细!

  • 在展开明细时,对部分维度表进行降维操作

例如:将商品一二三级分类表,sku商品表,spu商品表,商品品牌表合并汇总为一张维度表!

  • 对事实表,参考星型模型的建模策略,按照选择业务过程→声明粒度→确认维度→确认事实思路进行建模

选择业务过程: 选择感兴趣的事实表
声明粒度: 选择最细的粒度!可以由最细的粒度通过聚合的方式得到粗粒度!
确认维度: 根据3w原则确认维度,挑选自己感兴趣的维度
确认事实: 挑选感兴趣的度量字段,一般是从事实表中选取!

  • DWS层: 根据业务需求进行分主题建模!一般是建宽表!
  • DWT层: 根据业务需求进行分主题建模!一般是建宽表!
  • ADS层: 根据业务需求进行建模!

二、hadoop相关的技术问题

1、 HDFS文件读流程?

(1)客户端通过调用FileSystemopen方法获取需要读取的数据文件,对HDFS来说该FileSystem就是DistributeFileSystem

(2)DistributeFileSystem通过RPC来调用NameNode,获取到要读的数据文件对应的bock存储在哪些NataNode之上

(3)客户端先到最佳位置(距离最近)的DataNode上调用FSDataInputStreamread方法,通过反复调用read方法,可以将数据从DataNode传递到客户端

(4)当读取完所有的数据之后,FSDataInputStream会关闭与DataNode的连接,然后寻找下一块的最佳位置,客户端只需要读取连续的流。

(5)一旦客户端完成读取操作之后,就对FSDataInputStream调用close方法来完成资源的关闭操作

2、HDFS文件写操作

(1)客户端通过调用DistributeFileSystemcreate方法来创建一个文件

(2)DistributeFileSystem会对NameNode发起RPC请求,在文件系统的名称空间中创建一个新的文件,此时会进行各种检查,比如:检查要创建的文件是否已经存在,如果该文件不存在,NameNode就会为该文件创建一条元数据记录

(3)客户端调用FSDataOututStreamwrite方法将数据写到一个内部队列中。假设副本数为3,那么将队列中的数据写到3个副本对应的存储的DataNode上。

(4)FSDataOututStream内部维护着一个确认队列,当接收到所有DataNode确认写完的消息后,数据才会从确认队列中删除

(5)当客户端完成数据的写入后,会对数据流调用close方法来关闭相关资源

3、Hive与HBase的对比区别?

Hive

(1)数据仓库

Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询。

(2) 用于数据分析、清洗

Hive适用于离线的数据分析和清洗,延迟较高。

(3) 基于HDFS、MapReduce

Hive存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行。

HBase

(1) 数据库

是一种面向列存储的非关系型数据库。

(2) 用于存储结构化和非结构化的数据

适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作。

(3) 基于HDFS

数据持久化存储的体现形式是Hfile,存放于DataNode中,被ResionServer以region的形式进行管理。

(4) 延迟较低,接入在线业务使用

面对大量的企业数据,HBase可以直线单表大量数据的存储,同时提供了高效的数据访问速度。

3、Hive内部表和外部表的区别?

创建表时:创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径, 不对数据的位置做任何改变。

删除表时:在删除表的时候,内部表的元数据和数据会被一起删除, 而外部表只删除元数据,不删除数据。这样外部表相对来说更加安全些,数据组织也更加灵活,方便共享源数据。

4、mapReduce中shuffle阶段的工作流程,如何优化shuffle阶段?

分区,排序,溢写,拷贝到对应reduce机器上,增加combiner,压缩溢写的文件。

5、保证kafka数据的容错性、一致性,在集群运行环境过程中,若机器发生宕机了,如何解决?

涉及检查点,而这个检查点(Checkpoints),它原理机制:就是存盘,当我们使用kafka把数据读进来后,它 的偏移量就发生改变,若想把kakfa的数据进行再消费一遍。

在spark中

第一种方式:在整个处理完成之后,加一个机制,不要让它消费,直接把它的偏移量更改,到最后整个完成之后,再把它的偏移量做更改

第二种方式:在恢复之前后的状态的保存点的数据都要存下来,恢复的时候在之前kafka的偏移量手动更改,重新提交一次,相当于告诉kafka,重新再消费一次

在Flink中

与spark有所不同,区别:spark是一批一批的读取数据,而flink是一条一条的读取数据;

另外,flink的特性是:有状态的流处理,在这种过程中可以保证状态,所以可以把我们kafka的偏移量作为状态保存下来,言下之意:等到恢复的时候有检查点机制,后面处理的过程中也挂了,在之前存盘的重新读取、重新恢复,就相当于把之前偏移量恢复出来,然后flink本身的kafka连接器实现了,相当于重新手动提交的偏移量

6、解释一下,在数据制作过程中,你如何能从Kafka得到准确的信息?

在数据中,为了精确地获得Kafka的消息,你必须遵循两件事: 在数据消耗期间避免重复,在数据生产过程中避免重复。

这里有两种方法,可以在数据生成时准确地获得一个语义:

(1)每个分区使用一个单独的写入器,每当你发现一个网络错误,检查该分区中的最后一条消息,以查看您的最后一次写入是否成功

(2)在消息中包含一个主键(UUID或其他),并在用户中进行反复制

7、Kafka为什么需要复制?

Kafka的信息复制确保了任何已发布的消息不会丢失,并且可以在机器错误、程序错误或更常见些的软件升级中使用。

8、Kafka 在什么情况下会出现消息丢失?

kafka使用精确一次可以保证不丢数据,ack-1,也可以上层或下层做事物处理,kafka不做任何处理,业务数据和日志数据都会洛盘到hdfs。在flume保存30天,kafka会保留3-7天

9、Hive分区表和分桶表的区别?

分区在HDFS上的表现形式是一个目录, 分桶是一个单独的文件

分区: 细化数据管理,直接读对应目录,缩小mapreduce程序要扫描的数据量

分桶: 1、提高join查询的效率(用分桶字段做连接字段)

2、提高采样的效率

10、ElasticSearch索引机制问题

Elasticsearch会对所有输入的文本进行处理,建立索引放入内存中,从而提高搜索效率。在这一点上ES要优于MySQL的B+树的结构,MySQL需要将索引放入磁盘,每次读取需要先从磁盘读取索引然后寻找对应的数据节点,但是ES能够直接在内存中就找到目标文档对应的大致位置,最大化提高效率。并且在进行组合查询的时候MySQL的劣势更加明显,它不支持复杂的组合查询比如聚合操作,即使要组合查询也要事先建好索引,但是ES就可以完成这种复杂的操作,默认每个字段都是有索引的,在查询的时候可以各种互相组合。

(1)在ES中每个字段都是被索引的,所以不会像MySQL中那样需要对字段进行手动的建立索引。

(2) ES在建立索引的时候采用了一种叫做倒排索引的机制,保证每次在搜索关键词的时候能够快速定位到这个关键词所属的文档。

(3)ES为了提高搜索效率、优化存储空间做了很多工作(如:由于索引数量巨大,ES无法直接把全部索引放入内存,转而建立词典索引,构建有限状态转换器(FST)放入内存,进一步提高搜索效率。)

(4) 数据文档的id在词典内的空间消耗也是巨大的,ES使用了索引帧(Frame of Reference)技术压缩posting list,带来的压缩效果十分明显。

(5)ES的filter语句采用了Roaring Bitmap技术来缓存搜索结果,保证高频filter查询速度的同时降低存储空间消耗。

11、kafka重复或丢失数据如何处理

生产者和broke阶段消息丢失场景:

ack=0,不重试
producer发送消息完,不管结果了,如果发送失败也就丢失了。
ack=1,leader crash
producer发送消息完,只等待lead写入成功就返回了,leader crash了,这时follower没来及同步,消息丢失。
unclean.leader.election.enable 配置true
允许选举ISR以外的副本作为leader,会导致数据丢失,默认为false。producer发送异步消息完,只等待lead写入成功就返回了,leader crash了,这时ISR中没有follower,leader从OSR中选举,因为OSR中本来落后于Leader造成消息丢失。

对于kafkka的重复消费,根本原因:数据消费完没有及时提交offset到broke。

场景:消息消费端在消费过程中挂掉没有及时提交offset到broke,另一个消费端启动拿之前记录的offset开始消费,由于offset的滞后性可能会导致新启动的客户端有少量重复消费。

解决方案:

(1)取消自动自动提交

每次消费完或者程序退出时手动提交。这可能也没法保证一条重复。

(2)下游做幂等

一般的解决方案是让下游做幂等或者尽量每消费一条消息都记录offset,对于少数严格的场景可能需要把offset或唯一ID,例如订单ID和下游状态更新放在同一个数据库里面做事务来保证精确的一次更新或者在下游数据表里面同时记录消费offset,然后更新下游数据的时候用消费位点做乐观锁拒绝掉旧位点的数据更新。

12、ReduceByKey 和GroupByKey的区别

reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].
groupByKey:按照key进行分组,直接进行shuffle

在大的数据集上,reduceByKey()的效果比groupByKey()的效果更好一些。因为reduceByKey()会在shuffle之前对数据进行合并。

而当我们调用groupByKey()的时候,所有的键值对都会被shuffle到下一个stage,传输的数据比较多,自然效率低一些。

13、Hive调优及优化

(1)请慎重使用COUNT(DISTINCT col)

原因:

distinct会将b列所有的数据保存到内存中,形成一个类似hash的结构,速度是十分的块;但是在大数据背景下,因为b列所有的值都会形成以key值,极有可能发生OOM

解决方案:

可以考虑使用Group By 或者 ROW_NUMBER() OVER(PARTITION BY col)方式代替COUNT(DISTINCT col)

(2)小文件会造成资源的过度占用以及影响查询效率

原因:

  • 众所周知,小文件在HDFS中存储本身就会占用过多的内存空间,那么对于MR查询过程中过多的小文件又会造成启动过多的Mapper Task, 每个Mapper都是一个后台线程,会占用JVM的空间
  • 在Hive中,动态分区会造成在插入数据过程中,生成过多零碎的小文件(请回忆昨天讲的动态分区的逻辑)
  • 不合理的Reducer Task数量的设置也会造成小文件的生成,因为最终Reducer是将数据落地到HDFS中的
  • Hive中分桶表的设置

解决方案:

在数据源头HDFS中控制小文件产生的个数,比如

  • 采用Sequencefile作为表存储格式,不要用textfile,在一定程度上可以减少小文件(常见于在流计算的时候采用Sequencefile格式进行存储)
  • 减少reduce的数量(可以使用参数进行控制)
  • 慎重使用动态分区,最好在分区中指定分区字段的val值
  • 最好数据的校验工作,比如通过脚本方式检测hive表的文件数量,并进行文件合并
  • 合并多个文件数据到一个文件中,重新构建表

(3)请慎重使用SELECT *

原因:

在大数据量多字段的数据表中,如果使用 SELECT * 方式去查询数据,会造成很多无效数据的处理,会占用程序资源,造成资源的浪费

解决方案:

在查询数据表时,指定所需的待查字段名,而非使用 * 号

(4)不要在表关联后面加WHERE条件

原因:

比如以下语句:

SELECT * FROM stu as t 
LEFT JOIN course as t1
ON t.id=t2.stu_id
WHERE t.age=18;

上面语句是否具有优化的空间?如何优化?

解决方案:

采用谓词下推的技术,提早进行过滤有可能减少必须在数据库分区之间传递的数据量

谓词下推的解释:

所谓谓词下推就是通过嵌套的方式,将底层查询语句尽量推到数据底层去过滤,这样在上层应用中就可以使用更少的数据量来查询,这种SQL技巧被称为谓词下推(Predicate pushdown)

SELECT * FROM (SELECT * FROM stu WHERE age=18) as t LEFT JOIN course AS t1 on t.id=t1.stu_id

(5)处理掉字段中带有空值的数据

原因:

一个表内有许多空值时会导致MapReduce过程中,空成为一个key值,对应的会有大量的value值, 而一个key的value会一起到达reduce造成内存不足

解决方式:

在查询的时候,过滤掉所有为NULL的数据,比如:

create table res_tbl as  
select n.* from 
(select * from res where id is not null ) n 
left join org_tbl o on n.id = o.id;

查询出空值并给其赋上随机数,避免了key值为空(数据倾斜中常用的一种技巧)

create table res_tbl as
select n.* from res n 
full join org_tbl o on 
case when n.id is null then concat('hive', rand()) else n.id end = o.id;

(6)设置并行执行任务数

通过设置参数 hive.exec.parallel 值为 true,就可以开启并发执行。不过,在共享集群中,需要注意下,如果 job 中并行阶段增多,那么集群利用率就会增加。

//打开任务并行执行

set hive.exec.parallel=true;

//同一个 sql 允许最大并行度,默认为 8

set hive.exec.parallel.thread.number=16;

(7)设置合理的Reducer个数

原因:

  • 过多的启动和初始化 reduce 也会消耗时间和资源
  • 有多少个Reduer就会有多少个文件产生,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题

解决方案:

Reducer设置的原则:

每个Reduce处理的数据默认是256MB

hive.exec.reducers.bytes.per.reducer=256000000

每个任务最大的reduce数,默认为1009

hive.exec.reducers.max=1009

计算reduce数的公式

N=min(每个任务最大的reduce数,总输入数据量/reduce处理数据量大小)

设置Reducer的数量

set mapreduce.job.reduces=n 

(8)JVM重用

JVM重用是Hadoop中调优参数的内容,该方式对Hive的性能也有很大的帮助,特别对于很难避免小文件的场景或者Task特别多的场景,这类场景大数据书执行时间都很短

Hadood的默认配置通常是使用派生JVM来执行map和reduce任务的,会造成JVM的启动过程比较大的开销,尤其是在执行Job包含有成百上千个task任务的情况。
JVM重用可以使得JVM实例在同一个job中重新使用N次,N的值可以在hadoop的mapred-site.xml文件中进行设置

<property>
<name>mapred.job.reuse.jvm.num.tasks</name>
<value>10</value>
</property>

(9)为什么任务执行的时候只有一个reduce?

原因:

  • 使用了Order by (Order By是会进行全局排序)
  • 直接COUNT(1),没有加GROUP BY,比如:

有笛卡尔积操作

SELECT COUNT(1) FROM tbl WHERE pt=201909

解决方案

避免使用全局排序,可以使用sort by进行局部排序

使用GROUP BY进行统计,不会进行全局排序,比如:

SELECT pt,COUNT(1) FROM tbl WHERE pt=201909group by pt;

(10)选择使用Tez引擎

Tez: 是基于Hadoop Yarn之上的DAG(有向无环图,Directed Acyclic Graph)计算框架。它把Map/Reduce过程拆分成若干个子过程,同时可以把多个Map/Reduce任务组合成一个较大的DAG任务,减少了Map/Reduce之间的文件存储。同时合理组合其子过程,也可以减少任务的运行时间

设置

hive.execution.engine = tez;

通过上述设置,执行的每个HIVE查询都将利用Tez

当然,也可以选择使用spark作为计算引擎

(11)选择使用本地模式

有时候Hive处理的数据量非常小,那么在这种情况下,为查询出发执行任务的时间消耗可能会比实际job的执行时间要长,对于大多数这种情况,hive可以通过本地模式在单节点上处理所有任务,对于小数据量任务可以大大的缩短时间

可以通过

hive.exec.mode.local.auto=true 

(12)选择使用严格模式

Hive提供了一种严格模式,可以防止用户执行那些可能产生意想不到的不好的影响查询

比如:

  • 对于分区表,除非WHERE语句中含有分区字段过滤条件来限制数据范围,否则不允许执行,也就是说不允许扫描所有分区
  • 使用ORDER BY 语句进行查询是,必须使用LIMIT语句,因为ORDER BY 为了执行排序过程会将所有结果数据分发到同一个reduce中进行处理,强制要求用户添加LIMIT可以防止reducer额外的执行很长时间

严格模式的配置:

hive.mapred.mode=strict

java方面问题

1、Java中HashMap如何解决Hash冲突的?

(1)HashMap存储数据执行原理:

当程序执行map.put(String, Object)方法时,系统将调用String的hashCode()方法得到其hashCode值,每一个Java对象都有hashCode()方法,都可以通过该方法获取它的hashCode值。

系统会根据该hashCode值来决定该元素的存储位置。
当系统决定存储HashMap中的key-value对时,仅仅只是根据key来计算每个Entry(key-value)的存储位置。我们完全可以把Map集合中的value当成key的附属,当系统决定了key的存储位置之后,value随之保存在那里即可。

(2)什么是hash冲突?

如果存在相同的hashCode值,那么它们确定的索引位置就相同,这时判断它们的key是否相同,如果不相同,那么就产生了hash冲突

(3)解决hash冲突

散列表/哈希表要解决的一个问题就是散列值的冲突问题,通常有两种方法:链表法和开放地址法。
链表法,将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法,通过一个探测算法,当某一个槽位已经被占据的情况下,继续查找下一个可以使用槽位。
而java.util.HashMap采用的是链表法的方式,链表是单向链表。

形成单链表的核心代码如下:

void addEntry(int hash, K key, V value, int bucketIndex) {  
    Entry<K,V> e = table[bucketIndex];  
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    if (size++ >= threshold)  
        resize(2 * table.length);  
bsp;  

HashMap里面没有出现hash冲突时,没有形成单链表时,hashMap查找元素很快,get()方法能够直接定位到元素。但是出现单链表后,单个buchet里存储的不是一个Entry,而是一个Entry链,系统只能必须按顺序遍历每个Entry,直到找到想搜索的Entry为止——如果恰好要搜索的Entry位于该Entry链的最末端(该Entry是最早放入该buchet中的),那么系统必须循环到最后才能找到该元素。

当创建HashMap时,有一个默认的负载因子(load factor),其默认值为0.75,这是时间和空间成本上一种折中:增大负载因子可以减少Hash表(也就是那个Entry数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(hashMap的get()方法和put()方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加hash表所占用的内存空间。

2、HashMap的工作原理

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

当两个不同的键对象的hashcode相同时会发生什么?

它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

理由:

因为HashMap的好处非常多,我曾经在电子商务的应用中使用HashMap作为缓存。因为金融领域非常多的运用Java,也出于性能的考虑,我们会经常用到HashMap和ConcurrentHashMap。

3、HashMap 的数据结构?

哈希表结构(链表散列:数组+链表)实现,结合数组和链表的优点。当链表长度超过 8 时,链表转换为红黑树。

transient Node<K,V>\[\] table; 

4、jdk8中对HashMap做了哪些改变?

在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)

发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入

在java 1.8中,Entry被Node替代(换了一个马甲)。

5、HashMap,LinkedHashMap,TreeMap 有什么区别?

LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;

TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)

6、HashMap 和 HashTable 有什么区别?

①、HashMap 是线程不安全的,HashTable 是线程安全的;

②、由于线程安全,所以 HashTable 的效率比不上 HashMap;

③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode

7、HashMap & ConcurrentHashMap 的区别?

除了加锁,原理上无太大区别。另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。

8、SpringBoot启动过程

分为两部分:

第一部分进行SpringBootApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器
另一部分实现了SpringApplication.run() 方法,主要调用了spring容器启动方法扫描配置,加载bean到spring容器中,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块,自动注入等功能

整体流程:

(1)SpringApplication的实例化

推断应用类型是否是Web环境
这里通过判断是否存在 Servlet 和 ConfigurableWebApplicationContext 类来判断是否是Web环境,上文提到的 @Conditional 注解也有基于 class 来判断环境
设置初始化器(Initializer)
因此,所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类,这个接口是这样定义的:ApplicationContextInitializer是一个回调接口,它会在 ConfigurableApplicationContext 容器 refresh() 方法调用之前被调用,做一些容器的初始化工作
设置监听器(Listener)
设置完了初始化器,下面开始设置监听器:个接口基于JDK中的 EventListener 接口,实现了观察者模式。对于 Spring 框架的观察者模式实现,它限定感兴趣的事件类型需要是 ApplicationEvent 类型的子类,而这个类同样是继承自JDK中的 EventObject 类
推断应用入口类(Main)
它通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到入口类的名字

(2)SpringApplication.run方法

完成了实例化,下面开始调用run方法:
获取RunListeners
从 META-INF/spring.factories 中读取Key为 org.springframework.boot.SpringApplicationRunListener 的Values,从类文档可以看出,它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理
准备Environment环境
配置 Property Sources
配置 Profiles,为应用程序环境配置哪些配置文件处于active(活动)状态。
对于Web应用而言,得到的environment变量是一个StandardServletEnvironment的实例。得到实例后,SpringApplicationRunListeners 的用途和目的也比较明显了,它实际上是一个事件中转器,它能够感知到Spring Boot启动过程中产生的事件,然后有选择性的将事件进行中转
创建Spring Context
Spring Context前置处理
配置Bean生成器以及资源加载器(如果它们非空):
Spring Context刷新
Spring Context后置处理
CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/172334.html

(0)
上一篇 2021年9月27日
下一篇 2021年9月27日

相关推荐

发表回复

登录后才能评论