SpringBoot是如何加载配置文件的
前言日常开发中,我们会在配置文件中配置一些属性,然后通过诸如@Value注解来使用它,我们这次就分析下Spring Boot是如何加载、解析的。
如何加载文件的?在Spring Boot启动过程中会构建SpringApplication,这个过程中会加载一个监听器:EnvironmentPostProcessorApplicationListener,然后SpringApplication的prepareEnvironment()方法会发布一个事件,而这个时间会被EnvironmentPostProcessorApplicationListener监听到从而开始执行onApplicationEvent()方法,然后经过层层调用最终会调用Spring的方法去解析和加载资源文件中的属性,并最终将解析到的属性放入到Environment中,供后续使用。我们还是先通过一张图来对整个加载流程有个大概的认识。
下面我们对每个步骤,都看一下他们的源码。
EnvironmentPostProcessorApplicationListener这里的主要作用是根据接收到ApplicationEvent类 ...
Spring Boot是如何实现自动扫描的?
前言在我们使用Spring Boot的过程中会发现Spring Boot会将启动类同级目录以及子目录下符合条件的类自动注册成Bean,比如带有@Controller,@Service等注解的类。我们今天就看下默认配置在的自动扫描是如何实现的。
简介Spring Boot在启动过程中会先找出一个基础类,在默认配置下就是我们启动类所在的目录,然后将这个目录下面所有的资源都加载进来,依次遍历每个资源,看他们是否符合特定条件,大体是一个正常的类,并且本类或元注解上带有@Component注解,然后创建并返回这些类的BeanDefinition,供后续创建Bean动作使用。
扫描的过程忽略掉前置的流程,我们直接看关键部分的代码:
12345678910111213141516171819202122232425262728293031323334private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = ...
Spring Boot是如何启动的?
前言从今天开始我们进入Spring Boot的源码解读系列,本次使用的源码版本为3.2.X,后续默认都用这个版本的源码做解读。
我们先看一个最简单的Spring Boot启动代码:
12345678@SpringBootApplicationpublic class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); }}
就这么几行代码就成功启动了一个Spring Boot项目,我们今天就盘一下它是如何启动的。我们从run方法点进去,最终可以看到这样一个方法,总结来说就是先创建一个SpringApplication的实例,然后调用这个实例的run方法。
123public static ConfigurableApplicationContext run(Class<?>[] primarySour ...
JAVA源码解读之ThreadPoolExecutor
前言ThreadPoolExecutor用于管理和控制线程,提供了线程池的功能,可以执行Runnable或Callable任务,它具有以下特性
线程池管理:ThreadPoolExecutor 可以帮助您管理线程池,包括创建、启动、关闭线程池以及管理线程的生命周期。
任务执行:它可以执行提交给线程池的任务,这些任务可以是 Runnable 或 Callable 接口的实现。
灵活的配置选项:ThreadPoolExecutor 提供了丰富的配置选项,可以通过设置核心线程数、最大线程数、任务队列、拒绝策略等参数来适应不同的场景和需求。
任务队列:线程池使用任务队列来存储等待执行的任务。ThreadPoolExecutor 支持多种类型的任务队列,例如无界队列、有界队列、同步移交队列等。
拒绝策略:当任务无法被接受执行时,ThreadPoolExecutor 提供了多种拒绝策略,例如抛出异常、丢弃任务、阻塞等待等,可以根据需要选择合适的策略。
线程池状态管理:ThreadPoolExecutor 提供了方法来管理线程池的状态,例如启动线程池、关闭线程池、等待线程池中所有任务执行完成等。
...
JAVA源码解读之LinkedBlockingQueue
前言今天就开始分析JAVA中的队列了,先从LinkedBlockingQueue开始吧。不同于LinkedList,它是 基于链表实现的线程安全的队列实现,可以用来在多线程环境中安全地传递数据,总结来说,它具有以下特点:
链表实现:内部使用单向链表来存储元素,这使得它在插入和移除操作时的性能表现较好。
线程安全:它可以在多个线程之间安全地进行操作,而不需要额外的同步手段。
阻塞操作:它支持阻塞操作,包括阻塞式的插入和移除操作。当队列为空时,试图从队列中获取元素的操作会被阻塞,直到队列中有可用元素为止;当队列已满时,试图向队列中插入元素的操作会被阻塞,直到队列有足够的空间为止。
容量可选:可以选择是否限制队列的容量。如果指定了容量限制,那么队列的大小将受限于该容量,当队列达到容量上限时,插入操作将被阻塞,直到有元素被移除为止。如果不指定容量限制,则队列的大小理论上可以无限增长(Integer.MAX_VALUE)。
先进先出:保证了元素的插入和移除顺序是先进先出的。
类图如下:
关键属性123456789101112131415161718192021222324252627282 ...
JAVA源码解读之ConcurrentHashMap
前言在我们日常开发中,HashMap无疑是一个被频繁使用的类,但是它本身也有自己的不足。HashMap被设计为一个线程不完全的类,这意味着它在并发读写时会有问题,所以我们日常开发时不会在并发场景使用它。而这次要分析的ConcurrentHashMap不仅具有和HashMap类似的功能,而且在并发场景下也完全没有问题。我们先看一下它的类图。
ConcurrentHashMap同时具有以下特性:
线程安全:ConcurrentHashMap内部使用分段锁技术,不同的线程可以同时操作不同的段,提高并发性。
高并发:ConcurrentHashMap允许多个线程同时读,而不需要任何锁。写操作(如put,remove等)需要锁定部分数据,而不是整个数据结构。
非阻塞算法:ConcurrentHashMap使用一种新的方法CAS(Compare and Swap),它是硬件对于并发操作的直接支持,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知失败,并允许再次尝试。
弱一致性:ConcurrentHa ...
JAVA源码解读之ReentrantLock
前言ReentrantLock(可重入锁),它提供了与synchronized关键字类似的同步功能,但比synchronized更加灵活。
从类图上可以看到,它实现了Lock接口与Serializable接口,这意味着它有锁的特性,并且可以被序列化。同时它有三个内部类,而且其中的两个子类FairSync和NofairSync又继承了Sync,它俩的区别是FairSync会先检查同步队列中是否有在等待的线程,有就将当前线程加入队列的尾部;NofairSync则是直接尝试竞争锁,得到锁就执行自己的逻辑。
ReentrantLock的一些主要特性:
可重入性:和synchronized一样,ReentrantLock也支持可重入性。也就是说,一个线程可以多次获取同一个锁,而不会造成死锁
公平性和非公平性:ReentrantLock可以在创建时指定公平性。如果设置为公平锁,那么等待时间最长的线程将优先获取锁。如果设置为非公平锁(默认),那么哪个线程获取锁是不确定的。
条件变量:ReentrantLock提供了一个Condition类,可以用于多线程之间的精确唤醒。这是synchronize ...
JAVA源码解读之CountDownLatch
前言CountDownLatch的主要用途是同步一个或多个任务,使得这些任务在继续执行前必须等待其他任务完成。从类图上可以看到它的内部很简单,只有一个Sync的内部类。其中Sync是继承了AQS的,这意味着CountDownLatch底层还是基于AQS实现的。注意的是CountDownLatch只能等待countDown()前面的逻辑,countDown()后面的逻辑可能会在await()方法后执行,要看系统的调度。
小demo在这个例子中,我们创建了一个初始计数值为2的CountDownLatch。然后,我们创建了两个线程,每个线程在完成其任务后都会调用countDown()方法。主线程调用await()方法,等待所有其他线程完成任务。当所有其他线程都调用了countDown()方法,计数器的值变为0,主线程才会继续执行
123456789101112131415161718192021222324252627public class CountDownLatchTest { public static void main(String[] args) throws ...
JAVA源码解读之Semaphore
前言Semaphore,中文翻译为信号量,常用来限制某些资源的访问数量。就好像一个十个座位的咖啡厅最多只能接纳10个客户一样。由下面类图可以看出,Semaphore实现了Serializable接口,意味着它可以被虚拟化,同时它有三个子类:Sync,NofairSync,FairSync;其中Sync是Semaphore的核心,NofairSync,FairSync均继承自Sync,它俩的主要区别是在tryAcquireShared(int acquires)方法的实现,FairSync在尝试获取许可前会检查是否有等待的线程,以保证公平性。
使用demo下面例子中,我们定义了一个同时只能有1个线程进行逻辑处理的信号量,线程2我们先休眠10毫秒,这意味着线程1会先执行逻辑然后休眠1秒再释放资源,此时线程2只有等待线程1释放完后才能继续执行。
1234567891011121314151617181920212223242526public static void main(String[] args) { Semaphore semaphore = new Semaph ...
JAVA源码解读之AbstractQueuedSynchronizer
前言AbstractQueuedSynchronizer(AQS),翻译成中文为抽象队列同步器,是JAVA中很多类处理锁机制的底层方法,比如我们后面要讲的ReentrantLock、CountDownLatch、Semaphore等都是基于AQS来实现的。
关键属性1234567891011121314151617181920212223242526// 等待队列的头节点。当一个线程获取同步状态成功后,该线程所对应的Node会成为head。当head的waitStatus不再需要阻塞后续的线程时,可以将其waitStatus设置为SIGNAL,这样新的节点就可以安全的加入队列。private transient volatile Node head;// 等待队列的尾节点。新的节点通过CAS操作加入到队列的尾部。private transient volatile Node tail;// 同步状态,它的属性含义由子类定义,ReentrantLock中表示重入次数,Semaphore中表示可用的许可证数量,CountDownLatch中表示倒计数的数量。private volatil ...