面试准备.md
[toc]
操作系统与计算机网络
进程与线程
首先需要掌握进程与线程的区别和联系:
进程是系统资源分配的最小单位,线程是程序执行的最小单位;
进程使用独立的数据空间,而线程共享进程的数据空间。
线程调度,简单了解线程的几种调度算法就可以了。比如时间片轮转调度、先来先服务调度、优先级调度、多级反馈队列调度以及高响应比优先调度。
在进程与线程部分还有一个比较常见的考察点,就是进程间通信,也就是 IPC。这部分在面试中间件研发的相关职位时经常会考察。如上面知识点汇总图中所示,需要了解这 6 种进程通信方式的原理与适用场景。例如,进程间数据共享的场景可以使用共享内存;进程间数据交换的场景可以使用 Unix Socket 或者消息队列。
Linux 常用命令
AWK、top、netstat、grep
计算机网络知识点
TCP/IP协议族四层网络模型
从协议的角度对网络传输做出的模型分类,是基于网络7层模型的。
1)数据链路层 :代表协议ARP,可解析IP为MAC地址
2)网络层:代表协议IP,32位ip地址
3)传输层:代表协议TCP/UDP,建立网络连接,进行数据传输
4)应用层:代表协议FTP文件传输协议/DNS域名系统/HTTP网络数据
7层模型
第一层 物理层
第二层 数据链路层
第三层 网络层
第四层 传输层
第五层 会话层
第六层 表示层
第七层 应用层
TCP 协议特点
TCP 是传输层协议,对应 OSI 网络模型的第四层传输层,特点如下。
TCP 协议是基于链接的,也就是传输数据前需要先建立好链接,然后再进行传输。
TCP 链接一旦建立,就可以在链接上进行双向的通信。
TCP 的传输是基于字节流而不是报文
TCP 还能提供流量控制能力,通过滑动窗口来控制数据的发送速率。
TCP 协议还考虑到了网络问题可能会导致大量重传,进而导致网络情况进一步恶化,因此 TCP 协议还提供拥塞控制。TCP 处理拥塞控制主要用到了慢启动、拥塞避免、拥塞发生、快速恢复四个算法
三次握手建连
简述:初始客户端状态syn_sent,服务端状态listen;客户端发送syn,服务端收到后返回ack和syn,服务端状态syn_rcvd,客户端收到后状态变为established。客户端返回ack,服务端再次接收,服务端更新状态为established。
SYN 洪水攻击发生的原因,就是 Server 端收到 Client 端的 SYN 请求后,发送了 ACK 和 SYN,但是 Client 端不进行回复,导致 Server 端大量的链接处在 SYN_RCVD 状态,进而影响其他正常请求的建连。可以设置 tcp_synack_retries = 0 加快半链接的回收速度,或者调大 tcp_max_syn_backlog 来应对少量的 SYN 洪水攻击
四次挥手断连
简述:客户端,服务端初始状态都为established;客户端发送fin到服务端,服务端收到返回ack,进入close_wait状态。客户端收到ack,状态变为fin_wait_2状态;等待服务端发送数据结束,再次向客户端发送fin,服务端状态变为last_ack,客户端收到后状态变为time_wait,等待 2 倍的最大报文段生存时间,来保证链接的可靠关闭,客户端状态变为closed;客户端返回ack,服务端收到状态变为closed;
这里面试官可能会问为什么需要等待 2 倍最大报文段生存时间之后再关闭链接,原因有两个:
保证 TCP 协议的全双工连接能够可靠关闭; 保证这次连接的重复数据段从网络中消失,防止端口被重用时可能产生数据混淆。
语言特性与设计模式
设计模式知识点
设计模式分为 3 大类型共 23 种
创建型:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
最常见的设计模式
单例模式有几种实现方式
- 第一种是静态初始化方式,也叫作饿汉方式。实现的思路就是在类初始化时完成单例实例的创建,因此不会产生并发问题,在这种方式下不管是否会使用到这个单例,都会创建这个单例。
- 第二种实现方式是双重检查,也叫作懒汉方式,只有在真正用到这个单例实例的时候才会去创建,如果没有使用就不会创建。这个方式必然会面对多个线程同时使用实例时的并发问题。为了解决并发访问问题,通过 synchronized 或者 lock 进行双重检查,保证只有一个线程能够创建实例。这里要注意内存可见性引起的并发问题,必须使用 volatile 关键字修饰单例变量。
- 第三种是单例注册表方式,Spring 中 Bean 的单例模式就是通过单例注册表方式实现的。
单例模式、工厂模式、代理模式、构造者模式、责任链模式、适配器模式、观察者模式等
场景:
- 单例模式:数据源链接信息,spring中bean注入
- 工厂模式:mybatis中sqlsession工厂,spring中不同的bean工厂
- 代理模式:spring中的aop思想实现,jdk动态代理和cglib动态代理
- 适配器模式:不同适配器将 SLF4J 与 Log4j 等实现框架进行适配
- 构造者模式:适用于一个对象有很多复杂的属性,mybatis中的sqlsessionFactory.builder ()方法
Java 语言特性知识点
Java 的集合类中部分需要重点了解类的实现。例如,HashMap、TreeMap 是如何实现的等。
动态代理与反射是 Java 语言的特色,需要掌握动态代理与反射的使用场景,例如在 ORM 框架中会大量使用代理类。而 RPC 调用时会使用到反射机制调用实现类方法。
Java 基础数据类型也常常会在面试中被问到,例如各种数据类型占用多大的内存空间、数据类型的自动转型与强制转型、基础数据类型与 wrapper 数据类型的自动装箱与拆箱等。
Java 对对象的引用分为强引用、软引用、弱引用、虚引用四种,这些引用在 GC 时的处理策略不同,强引用不会被 GC 回收;软引用内存空间不足时会被 GC 回收;弱引用则在每次 GC 时被回收;虚引用必须和引用队列联合使用,主要用于跟踪一个对象被垃圾回收的过程。
Java 的异常处理机制就是 try-catch-finally 机制,需要知道异常时在 try catch 中的处理流程;需要了解 Error 和 Exception 的区别。
详解 Map
HashMap
先来看 HashMap 的实现,简单来说,Java 的 HashMap 就是数组加链表实现的,数组中的每一项是一个链表。通过计算存入对象的 HashCode,来计算对象在数组中要存入的位置,用链表来解决散列冲突,。
除了实现的方式,我们还需要知道填充因子的作用与 Map 扩容时的 rehash 机制,需要知道 HashMap 的容量都是 2 的幂次方,是因为可以通过按位与操作来计算余数,比求模要快。另外需要知道 HashMap 是非线程安全的,在多线程 put 的情况下,有可能在容量超过填充因子时进行 rehash,因为 HashMap 为了避免尾部遍历,在链表插入元素时使用头插法,多线程的场景下有可能会产生死循环。
ConcurrentHashMap
从 HashMap 的非线程安全,面试官很自然地就会问到线程安全的 ConcurrentHashMap。ConcurrentHashMap 采用分段锁的思想来降低并发场景下的锁定发生频率,在 JDK1.7 与 1.8 中的实现差异非常大,1.7 中使用 Segment 进行分段加锁,降低并发锁定;1.8 中使用 CAS 自旋锁的乐观锁来提高性能,但是在并发度较高时性能会比较一般。另外 1.8 中的 ConcurrentHashMap 引入了红黑树来解决 Hash 冲突时链表顺序查找的问题。红黑树的启用条件与链表的长度和 Map 的总容量有关,默认是链表大于 8 且容量大于 64 时转为红黑树。
Java 版本特性
在 8 版本中 Java 增加了对 lambda 表达式的支持,使 Java 代码的编写可以更简洁,也更方便支持并行计算。并且提供了很多 Stream 流式处理的 API。8 版本还支持了方法引用的能力,可以进一步简化 lambda 表达式的写法。
11 版本是 Java 最新的长期支持版本,也将会是未来一段时间的主要版本,11 版本中提供的最激动人心的功能是 ZGC 这个新的垃圾回收器,ZGC 为大内存堆设计,有着非常强悍的性能,能够实现 10ms 以下的 GC 暂停时间。关于 ZGC 会在下一课中进一步介绍。除此之外,11 版本对字符串处理 API 进行了增强,提供了字符复制等功能。11 版本还内置了 HttpClient。
深入浅出JVM
JVM 内存模型
栈也叫方法栈,是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。
本地方法栈与栈类似,也是用来保存线程执行方法时的信息,不同的是,执行 Java 方法使用栈,而执行 native 方法使用本地方法栈。
程序计数器保存着当前线程所执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器为执行 Java 方法服务,执行 native 方法时,程序计数器为空。
栈、本地方法栈、程序计数器这三个部分都是线程独占的。
堆是 JVM 管理的内存中最大的一块,堆被所有线程共享,目的是为了存放对象实例,几乎所有的对象实例都在这里分配。当堆内存没有可用的空间时,会抛出 OOM 异常。根据对象存活的周期不同,JVM 把堆内存进行分代管理,由垃圾回收器来进行对象的回收管理。
方法区也是各个线程共享的内存区域,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,JDK 1.7 中的永久代和 JDK 1.8 中的 Metaspace 都是方法区的一种实现。
类加载机制
类的加载指将编译好的 Class 类文件中的字节码读入内存中,将其放在方法区内并创建对应的 Class 对象。类的加载分为加载、链接、初始化,其中链接又包括验证、准备、解析三步。如下图所示。
加载是文件到内存的过程。通过类的完全限定名查找此类字节码文件,并利用字节码文件创建一个 Class 对象。
验证是对类文件内容验证。目的在于确保 Class 文件符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备阶段是进行内存分配。为类变量也就是类中由 static 修饰的变量分配内存,并且设置初始值。这里要注意,初始值是 0 或者 null,而不是代码中设置的具体值,代码中设置的值是在初始化阶段完成的。另外这里也不包含用 final 修饰的静态变量,因为 final 在编译的时候就会分配。
解析主要是解析字段、接口、方法。主要是将常量池中的符号引用替换为直接引用的过程。直接引用就是直接指向目标的指针、相对偏移量等。
初始化,主要完成静态块执行与静态变量的赋值。这是类加载最后阶段,若被加载类的父类没有初始化,则先对父类进行初始化。
只有对类主动使用时,才会进行初始化,初始化的触发条件包括在创建类的实例时、访问类的静态方法或者静态变量时、Class.forName() 反射类时、或者某个子类被初始化时。
类加载器
如上图所示,Java 自带的三种类加载器分别是:BootStrap 启动类加载器、扩展类加载器和应用加载器(也叫系统加载器)。图右边的桔黄色文字表示各类加载器对应的加载目录。启动类加载器加载 java home 中 lib 目录下的类,扩展加载器负责加载 ext 目录下的类,应用加载器加载 classpath 指定目录下的类。除此之外,可以自定义类加载器。
Java 的类加载使用双亲委派模式,即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器,如上图中蓝色向上的箭头。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。如图中的桔黄色向下的箭头。
这种双亲委派模式的好处,可以避免类的重复加载,另外也避免了 Java 的核心 API 被篡改。
GC分代回收
根据年轻代与老年代的特点,JVM 提供了不同的垃圾回收算法。垃圾回收算法按类型可以分为引用计数法、复制法和标记清除法。
引用计数法是通过对象被引用的次数来确定对象是否被使用,缺点是无法解决循环引用的问题。
复制算法需要 from 和 to 两块相同大小的内存空间,对象分配时只在 from 块中进行,回收时把存活对象复制到 to 块中,并清空 from 块,然后交换两块的分工,即把 from 块作为 to 块,把 to 块作为 from 块。缺点是内存使用率较低。
标记清除算法分为标记对象和清除不在使用的对象两个阶段,标记清除算法的缺点是会产生内存碎片。
JVM 中提供的年轻代回收算法 Serial、ParNew、Parallel Scavenge 都是复制算法,而 CMS、G1、ZGC 都属于标记清除算法。
CMS 算法如下图所示
第一个阶段是初始标记,这个阶段会 stop the world,标记的对象只是从 root 集最直接可达的对象;
第二个阶段是并发标记,这时 GC 线程和应用线程并发执行。主要是标记可达的对象;
第三个阶段是重新标记阶段,这个阶段是第二个 stop the world 的阶段,停顿时间比并发标记要小很多,但比初始标记稍长,主要对对象进行重新扫描并标记;
第四个阶段是并发清理阶段,进行并发的垃圾清理;
最后一个阶段是并发重置阶段,为下一次 GC 重置相关数据结构。
并发与多线程
线程状态转换
线程是 JVM 执行任务的最小单元,理解线程的状态转换是理解后续多线程问题的基础。在 JVM 运行中,线程一共有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,这些状态对应 Thread.State 枚举类中的状态。
当创建一个线程时,线程处在 NEW 状态,运行 Thread 的 start 方法后,线程进入 RUNNABLE 可运行状态。
这时,所有可运行状态的线程并不能马上运行,而是需要先进入就绪状态等待线程调度,如图中间所示的 READY 状态。在获取 CPU 后才能进入运行状态,如图中所示的 RUNNING。运行状态可以随着不同条件转换成除 NEW 以外的其他状态。
在运行态中的线程进入 synchronized 同步块或者同步方法时,如果获取锁失败,则会进入到 BLOCKED 状态。当获取到锁后,会从 BLOCKED 状态恢复到就绪状态。
运行中的线程还会进入等待状态,这两个等待一个是有超时时间的等待,例如调用 Object.wait、Thread.join 等;另外一个是无超时的等待,例如调用 Thread.join 或者 Locksupport.park 等。这两种等待都可以通过 notify 或 unpark 结束等待状态并恢复到就绪状态。
线程运行完成结束时,线程状态变成 TERMINATED。
CAS 与 ABA 问题
线程的同步与互斥,解决线程同步与互斥的主要方式是 CAS、synchronized 和 lock。
CAS
CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。CAS 操作的流程如下图所示,线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。这是一种乐观策略,认为并发操作并不总会发生。
ABA
CAS 容易出现 ABA 问题,就是如下面时序图所示,如果线程 T1 读取值 A 之后,发生两次写入,先由线程 T2 写回了 B,又由 T3 写回了 A,此时 T1 在写回比较时,值还是 A,就无法判断是否发生过修改。
synchronized
synchronized 是最常用的线程同步手段之一,它是如何保证同一时刻只有一个线程可以进入临界区呢?
synchronized 对对象进行加锁,在 JVM 中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。在对象头中保存了锁标志位和指向 monitor 对象的起始地址,如下图所示,右侧就是对象对应的 Monitor 对象。当 Monitor 被某个线程持有后,就会处于锁定状态,如图中的 Owner 部分,会指向持有 Monitor 对象的线程。另外 Monitor 中还有两个队列,用来存放进入及等待获取锁的线程。
synchronized 应用在方法上时,在字节码中是通过方法的 ACC_SYNCHRONIZED 标志来实现的,synchronized 应用在同步块上时,在字节码中是通过 monitorenter 和 monitorexit 实现的。
针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。
线程池
线程池通过复用线程,避免线程频繁地创建和销毁。Java 的 Executors 工具类中提供了 5 种类型的线程池创建方法
线程池初始化的时候有那些参数
第一个参数设置核心线程数。默认情况下核心线程会一直存活。
第二个参数设置最大线程数。决定线程池最多可以创建的多少线程。
第三个参数和第四个参数用来设置线程空闲时间,和空闲时间的单位,当线程闲置超过空闲时间就会被销毁。可以通过 allowCoreThreadTimeOut 方法来允许核心线程被回收。
第五个参数设置缓冲队列,上图中左下方的三个队列是设置线程池时常使用的缓冲队列。其中 ArrayBlockingQueue 是一个有界队列,就是指队列有最大容量限制。LinkedBlockingQueue 是无界队列,就是队列不限制容量。最后一个是 SynchronousQueue,是一个同步队列,内部没有缓冲区。
第六个参数设置线程池工厂方法,线程工厂用来创建新线程,可以用来对线程的一些属性进行定制,例如线程的 group、线程名、优先级等。一般使用默认工厂类即可。
第七个参数设置线程池满时的拒绝策略。如上图右下方所示有四种策略,Abort 策略在线程池满后,提交新任务时会抛出 RejectedExecutionException,这个也是默认的拒绝策略。Discard 策略会在提交失败时对任务直接进行丢弃。CallerRuns 策略会在提交失败时,由提交任务的线程直接执行提交的任务。DiscardOldest 策略会丢弃最早提交的任务。
线程池执行流程
向线程池提交任务时,会首先判断线程池中的线程数是否大于设置的核心线程数,如果不大于,就创建一个核心线程来执行任务。
如果大于核心线程数,就会判断缓冲队列是否满了,如果没有满,则放入队列,等待线程空闲时执行任务。
如果队列已经满了,则判断是否达到了线程池设置的最大线程数,如果没有达到,就创建新线程来执行任务。
如果已经达到了最大线程数,则执行指定的拒绝策略。
数据结构与算法
二叉树
二叉搜索树满足这样的条件,每个节点包含一个值,每个节点至多有两个子树。每个节点左子树节点的值都小于自身的值,每个节点右子树节点的值都大于自身的值。二叉树的查询时间复杂度是 log(N),但是随着不断的插入、删除节点,二叉树的树高可能会不断变大,当一个二叉搜索树所有节点都只有左子树或者都只有右子树时,其查找性能就退化成线性的了。
平衡二叉树
平衡二叉树可以解决上面这个问题,平衡二叉树保证每个节点左右子树的高度差的绝对值不超过 1,例如 AVL 树。AVL 树是严格的平衡二叉树,插入或删除数据时可能经常需要旋转来保持平衡,比较适合插入、删除比较少的场景。
红黑树
红黑树是一种更加实用的非严格的平衡二叉树。红黑树更关注局部平衡而非整体平衡,确保没有一条路径会比其他路径长出 2 倍,所以是接近平衡的,但减少了许多不必要的旋转操作,更加实用。前面提到过,Java 8 的 HashMap 中就应用了红黑树来解决散列冲突时的查找问题。TreeMap 也是通过红黑树来保证有序性的。
每个节点不是红色就是黑色。
根节点是黑色。
每个叶子节点都是黑色的空节点,如图中的黑色三角。
红色节点的两个子节点都是黑色的。
任意节点到其叶节点的每条路径上,包含相同数量的黑色节点。
B 树
B 树是一种多叉树,也叫多路搜索树。B 树中每个节点可以存储多个元素,非常适合用在文件索引上,可以有效减少磁盘 IO 次数。B 树中所有结点的最大子节点数称为 B 树的阶,如下图所示是一棵 3 阶 B 树
非叶节点最多有 m 棵子树;
根节点最少有两个子树,非根、非叶节点最少有 m/2 棵子树;
非叶子结点中保存的关键字个数,等于该节点子树个数−1,就是说一个节点如果有 3棵子树,那么其中必定包含 2 个关键字;
B+ 树
B+ 树的定义与 B 树基本相同,除了下面这几个特点。
节点中的关键字与子树数目相同,比如节点中有 3 个关键字,那么就有 3 棵子树;
关键字对应的子树中的节点都大于或等于关键字,子树中包括关键字自身;
所有关键字都出现在叶子节点中;
所有叶子节点都有指向下一个叶子节点的指针。
与 B 树不同,B+ 树在搜索时不会在非叶子节点命中,一定会查询到叶子节点;另外一个,叶子节点相当于数据存储层,保存关键字对应的数据,而非叶子节点只保存关键字和指向叶节点的指针,不保存关键字对应的数据,所以同样数量关键字的非叶节点,B+ 树比 B 树要小很多。
B+ 树更适合索引系统,MySQL 数据库的索引就提供了 B+ 树实现。
原因有三个:
由于叶节点之间有指针相连,B+ 树更适合范围检索;
由于非页节点只保存关键字和指针,同样大小非叶节点,B+ 树可以容纳更多的关键字,可以降低树高,查询时磁盘读写代价更低;
B+ 树的查询效率比较稳定。任何关键字的查找必须走一条从根结点到叶子结点的路,所有关键字查询的路径长度相同,效率相当。
Git 常用命令
spring框架
图中红框框住的是比较重要的组件,Core 组件是 Spring 所有组件的核心;Bean 组件和 Context 组件我刚才提到了,是实现 IoC 和依赖注入的基础;AOP 组件用来实现面向切面编程;Web 组件包括 SpringMVC,是 Web 服务的控制层实现。
关于 AOP 还需要了解一下对应的 Aspect、pointcut、advice 等注解和具体使用方式。
核心接口/类
再来看图右上方需要重点掌握的核心类。
ApplicationContext 保存了 IoC 的整个应用上下文,可以通过其中的 BeanFactory 获取到任意到 Bean;
BeanFactory 主要的作用是根据 Bean Definition 来创建具体的 Bean;
BeanWrapper 是对 Bean 的包装,一般情况下是在 Spring IoC 内部使用,提供了访问 Bean 的属性值、属性编辑器注册、类型转换等功能,方便 IoC 容器用统一的方式来访问 Bean 的属性;
FactoryBean 通过 getObject 方法返回实际的 Bean 对象,例如 Motan 框架中 referer 对 service 的动态代理就是通过 FactoryBean 来实现的。
Scope
Bean 的 Scope 是指 Bean 的作用域,默认情况下是单例模式,这也是使用最多的一种方式;多例模式,即每次从 BeanFactory 中获取 Bean 都会创建一个新的 Bean。Request、Session、Global-session 是在 Web 服务中使用的 Scope。
Request 每次请求都创建一个实例;
Session 是在一个会话周期内保证只有一个实例;
Global-session 在 5.x 版本中已经不再使用,同时增加了 Application 和 Websocket 两种Scope,分别保证在一个 ServletContext 与一个 WebSocket 中只创建一个实例。
@Component 注解在类上使用表明这个类是个组件类,需要 Spring 为这个类创建 Bean。
@Bean 注解使用在方法上,告诉 Spring 这个方法将会返回一个 Bean 对象,需要把返回的对象注册到 Spring 的应用上下文中。
Bean 生命周期
调用 Bean 的构造方法创建 Bean;
通过反射调用 setter 方法进行属性的依赖注入;
如果实现 BeanNameAware 接口的话,会设置 Bean 的 name;
如果实现了 BeanFactoryAware,会把 BeanFactory 设置给 Bean;
如果实现了 ApplicationContextAware,会给 Bean 设置 ApplictionContext;
如果实现了 BeanPostProcessor 接口,则执行前置处理方法;
实现了 InitializingBean 接口的话,执行 afterPropertiesSet 方法;
执行自定义的 init 方法;
执行 BeanPostProcessor 接口的后置处理方法。
Spring Boot
Spring Boot 的几个特色模块。
Starter 是 Spring Boot 提供的无缝集成功能的一种方式,使用某个功能时开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由 Spring Boot 自动配置进行 Bean的创建。例如需要使用 Web 功能时,只需要在依赖中引入 Spring-boot-starter-web 即可。
Actuator 是用来对应用程序进行监视和管理,通过 RESTful API 请求来监管、审计、收集应用的运行情况。
DevTools 提供了一系列开发工具的支持,来提高开发效率。例如热部署能力等。
CLI 就是命令行接口,是一个命令行工具,支持使用 Groovy 脚本,可以快速搭建 Spring 原型项目。
MyBatis
能描述一下Spring Context初始化的整个流程吗?
简单介绍一下Bean的生命周期及作用域。
如何保证分布式系统下的一致性
spring boot启动流程
设计规则
Redisssion脚本了解过没
jdk新特性
分布式锁