Spring源码之IOC机制梳理
如何理解IOC在JAVA的古早时期,程序员自己既要定义对象,然后还要通过new的方式创建出对象。而在Spring中则我们只需要定义Bean,然后交给Spring来创建对象(从甲乙方的角度,定义对象是提需求的甲方,实现对象是执行的乙方。那么new的方式是即做甲方又做乙方。而Spring让程序员当了一回甲方)。
而这个动作就是IOC(Inversion of Control)控制反转。不过IOC不是一种具体的技术,而是一种设计思想,用来解决的是层和层之间, 类和类之间的耦合问题。
举个例子来说,现在有Cat、Dog两个类,Cat中引用了Dog类。如果这时Dog需要被替换为Pig类,我们会如何做呢?一点点改倒是可以,但如果Dog类被引用了100次,我们也要修改100次吗?
现在假设我们做如下调整,我们将Dog包装起来,然后Cat间接调用它;如果后面需要将Dog改为Pig,只要在包装类中奖Dog改为Pig就行,完全不需要修改Cat类中的引用了,而这就是控制反转。
Spring使用了IOC机制会将Cat、Dog等类的引用存在IOC中,Spring会帮我们维护好他们的生命周期,我们完全不用担心。
...
Spring Boot启动Tomcat原理
前言在Spring的时期如果我们想部署一个JAVA服务,通常是在服务器上安装一个Tomcat服务,然后将JAVA项目打好的war包丢到相应位置就好,麻烦且容易出错。所以Spring Boot中简化了这个操作,可以直接启动一个项目而不用单独部署Tomcat,那么Spring Boot是如何实现这一切的呢?
自动引入Tomcat依赖Tomcat也是基于JAVA开发的,它本质上也是一个jar包,所以Spring Boot为它添加了默认依赖,当我们创建一个web项目时会自动将tomcat的依赖引入进来。
1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>
然后点进去spring-boot-starter-web,可以看到它引入了spring-boot-starter-tomcat依赖,所以后面也就可以使用Tomcat的 ...
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 ...