概述

学习自2025年Java春招手写Spring源码,1000分钟让你彻底搞懂Spring底层原理与源码实现,挑战7天打卡春招上岸!_哔哩哔哩_bilibili

记录手写Spring相关的知识点,本部分主要为手写模拟Spring的Bean的生命周期,包括容器初始化、BeanDefinition扫描、单例和多例Bean、依赖注入、Aware回调、初始化以及BeanPostProcessor

思维导图

基础概念

Spring的生命周期如下:

实例化:Spring容器根据配置文件或注解实例化Bean对象

属性注入:Spring将依赖(通过构造器、setter方法或字段注入)注入到Bean实例中

初始化前的扩展机制:如果Bean实现了BeanNameAware等aware接口,则执行aware注入

初始化前(BeanPostProcessor):在Bean初始化前,通过BeanPostProcessor接口对Bean进行一些额外的处理

初始化:调用InitializingBean接口的afterPropertiesSet方法或通过init-method属性指定的初始化方法

初始化后(BeanPostProcessor):在Bean初始化后,通过BeanPostProcessor接口进行进一步的处理

使用Bean:Bean已初始化完成,可以被容器中的其他Bean使用

销毁:当容器关闭时,Spring调用DisposableBean接口的destroy方法或通过destroy-method属性指定的销毁方法

流程图如下所示:

Bean生命周期

  • 实例化时,会有一个推断构造方法的过程,对于一个Bean,如果没有构造方法,会报错,如果有一个构造方法,Spring会使用这个无参构造方法,如果有多个构造方法,如果多个构造方法中有无参,则Spring用无参,如果没有无参,则报错,因为Spring也不知道会用哪个构造方法
  • 推断构造方法中,如果使用的有参构造方法,注入的Bean的注入过程中(单例Bean),先ByType在ByName,即先根据类型去单例池中去找,如果有多个再根据名称去找
  • 依赖注入同推断构造方法,先ByType在ByName
  • 初始化前,如果Bean对象的方法上有一个注解叫@PostConstruct,那么这个方法会在初始化前运行
  • AOP在初始化后这个环节,如果是一个单例Bean,那么放到单例池里的是代理对象,如果不是AOP,那么放到单例池里的就是原本的Bean

手写模拟

初始化

新建一个普通的java项目,创建两个目录,一个service,一个spring,前者测试,后者保存手写的Spring的代码

Spring代码

  • ApplicationContext容器类
  1. 首先即为Spring的容器类,容器类里需要有一个配置类,和对应的构造方法

  2. 容器类中有一个经典的getBean方法,在此处也先定义上

1
2
3
4
5
6
7
8
9
10
11
public class CrossacidApplicationContext {
private Class configClass;

public CrossacidApplicationContext(Class appConfigClass) {
this.configClass = appConfigClass;
}

public Object getBean(String beanName) {
return null;
}
}
  • ComponentScan注解

配置类上要有Bean的扫描路径,故需要写一个注解,来表明需要扫描哪个路径下的Bean

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 只扫描类
public @interface ComponentScan {
String value() default "";
}
  • Component注解

那么对应的,就需要标识Bean,此处就定义一个新的注解

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 只扫描类
public @interface Component {

String value() default "";
}

测试代码

  • 配置类

配置类如下所示:

1
2
3
4
@ComponentScan("crossacid.service")
public class AppConfig {

}

显示扫描crossacid.service这个包下面的Bean

  • Bean

定义一个UserService

1
2
3
4
@Component("userService")
public class UserService{

}
  • Test类

AppConfig这一配置类作为参数传入自定义容器中,并设置一个UserServicegetBean测试,此处肯定为null,因为还什么都没有写

1
2
3
4
5
6
7
public class Test {

public static void main(String[] args) {
CrossacidApplicationContext applicationContext = new CrossacidApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
}
}

BeanDefinition扫描

Spring代码

Spring扫描Bean时,不会直接创建一个完整的Bean,这样是为了避免循环依赖,因此,首先定义一个BeanDefinition

1
2
3
4
5
6
7
8
9
10
11
public class BeanDefinition {
private Class type;

public Class getType() {
return type;
}

public void setType(Class type) {
this.type = type;
}
}

为此,需要在容器类CrossacidApplicationContext中添加一个字段,即beanDefinitionMap

1
2
3
4
5
public class CrossacidApplicationContext {   

private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

}

Spring扫描Bean的步骤如下:

  1. 获取配置类上的ComponentScan注解
  2. 获取ComponentScan注解上的扫描路径
  3. 获取容器类的类加载器
  4. 使用getResource方法获取当前路径的URL(IDEA默认配置下,此时即编译后的out路径下对应的class文件夹)
  5. 通过URLgetFile方法获取文件夹
  6. 遍历文件夹,针对每个文件,判断是否为.class结尾
  7. 对于以.class结尾的文件,获取其全类名
  8. 接下来,需要进一步分析,对应的类是否存在Component注解

代码添加在CrossacidApplicationContext的初始化方法中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public CrossacidApplicationContext(Class appConfigClass) {
this.configClass = appConfigClass;

// 判断配置类上是否有ComponentScan注解
if (configClass.isAnnotationPresent(ComponentScan.class)) {
if (configClass.isAnnotationPresent(ComponentScan.class)) {
// 获取注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);

// 获取扫描路径crossacid.service-> crossacid/service
String path = componentScanAnnotation.value();
path = path.replace(".", "/"); //

// 获取当前的类加载器
ClassLoader classLoader = CrossacidApplicationContext.class.getClassLoader();

// 获取URL
URL resource = classLoader.getResource(path);

// 遍历文件夹
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
// 对.class结尾的文件做操作
if (fileName.endsWith(".class")) {
// 获取全类名
String basePath = "crossacid-spring\\out\\production\\crossacid-spring\\";
String className = fileName.substring(fileName.indexOf(basePath) + basePath.length(), fileName.indexOf(".class"));
className = className.replace("\\", ".");
System.out.println(className);

try {
// 根据类名进行加载
Class<?> clazz = classLoader.loadClass(className);
// 判断是否是Component
if (clazz.isAnnotationPresent(Component.class)) {
// 获取beanName
Component component = clazz.getAnnotation(Component.class);
String beanName = component.value();

// 如果Component注解没有设置beanName,则给一个默认名,逻辑为如果首字母和第二个字母均大写则保留,如果只有第一个字母大写,则修改为小写
// Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays as "URL"
if (beanName.equals("")) {
beanName = Introspector.decapitalize(clazz.getSimpleName());
}

BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);

// 保存对应的beanDefinition
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}

单例和多例Bean实例化

Spring代码

单例Bean和多例Bean涉及到Spring的Bean的作用域问题(Scope),此处手写,只简单实现singletonprototype,前者为单例,即整个IOC容器内部仅此一个,而后者则为多实例。

完整Spring作用域如下引用

singleton:默认,单例,整个IOC容器内部仅此一个

prototype:原型,会存在多个Bean实例

request:每个请求都会新建一个属于自己的Bean实例,这种作用域只存在于Spring Web中

session:一个http session中只有一个bean实例,这种作用域只存在于Spring Web中

application:整个SevelretContext生命周期内,只有一个bean,这种作用域仅存在于Spring Web中

websocket:一个WebSocket生命周期内一个bean实例,这种作用域仅存在于Spring Web中

首先定义一个Scope注解,使用方法同其他自定义注解,不写则默认为单例

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "";
}

在Spring的BeanDefinition这块,也需要添加上对应的操作和判断

1
2
3
4
5
6
7
8
9
10
11
12
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);

if (clazz.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
beanDefinition.setScope("singleton");
}

// 保存对应的beanDefinition
beanDefinitionMap.put(beanName, beanDefinition);

为了区分单例Bean和多例Bean,此时需要一个新的属性,单例Bean的Map,同样写在容器类的属性中,代码如下

1
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

在扫描完BeanDefinition,将所有的BeanDefinition都创建好放到beanDefinitionMap中后,再将所有的单例Bean放到singletonObjects

1
2
3
4
5
6
7
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
Object bean = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, bean);
}
}

其中有个createBean方法,需要手动实现,即创建Bean的方法

1
2
3
4
5
6
7
8
9
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
try {
// 无参构造方法
Object instance = clazz.getConstructor().newInstance();
return instance;
}
return null;
}

从而,在获取Bean对象时,即执行getBean时,即可根据作用域来进行区分,步骤如下:

  1. 获取beanDefinitionMap中的Bean
  2. 如果为空则报空指针
  3. 否则获取作用域
  4. 如果为单例,则去单例的Map进行查找,不是则跳转6
  5. 如果为空,则创建该单例(实际上在扫描的时候就应该创建过了)并返回
  6. 如果不是单例,则创建Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

if (beanDefinition == null) {
throw new NullPointerException();
} else {
String scope = beanDefinition.getScope();
if (scope.equals("singleton")) {
Object bean = singletonObjects.get(beanName);
if (bean == null) {
Object o = createBean(beanName, beanDefinition);
singletonObjects.put(beanName, o);
}
return bean;
} else {
// 多例
return createBean(beanName, beanDefinition);
}
}
}

测试代码

此处可以测试一下多例,修改下UserServiceScope

1
2
3
4
5
6
7
@Scope("prototype")
@Component("userService")
public class UserService implements BeanNameAware, InitializingBean, UserInterface{
public void test() {
System.out.println(orderService);
}
}

那么就可以修改测试代码

1
2
3
4
5
6
7
8
9
10
11
public class Test {

public static void main(String[] args) {
CrossacidApplicationContext applicationContext = new CrossacidApplicationContext(AppConfig.class);

System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("orderService"));
}
}

如果是prototype,那么此时打印的结果应该是不一样的,反之不写这个,或者是singleton的话,则打印的结果应该是一样的

依赖注入

依赖注入是一个很经典的Spring的环节,在Spring中解决依赖注入主要是基于三级缓存的,此处只是简单实现一下

Spring代码

首先是定义一个依赖注入的注解Autowried(类型注入,Spring提供,多个同类型的Bean需要使用@Qualifier指定具体的Bean)

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {

}

手写这块,主要是在createBean方法中进行实现

首先实例化一个Instace,然后通过反射获取其属性,看看属性上是否有Autowired注解,如果有,则根据那个属性的名称去获取对应的Bean进行注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
try {
// 无参构造方法实例化
Object instance = clazz.getConstructor().newInstance();

// 依赖注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}
return instance;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return null;
}

测试代码

新建一个OrderService的Bean

1
2
3
@Component
public class OrderService {
}

修改原来的UserService,加入一个OrderService的Bean,并创建一个test方法进行测试

1
2
3
4
5
6
7
8
9
10
@Component("userService")
public class UserService {

@Autowired
private OrderService orderService;

public void test() {
System.out.println(orderService);
}
}

则测试类方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package crossacid.service;

import crossacid.spring.CrossacidApplicationContext;

public class Test {

public static void main(String[] args) {
CrossacidApplicationContext applicationContext = new CrossacidApplicationContext(AppConfig.class);

UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
}
}

会发现成功输出了OrderService这个属性的信息

Aware回调

此处的理解为一个扩展的机制,可以在Bean初始化前,获取Bean名称、BeanFactory等容器资源,此处仅实现获取Bean名称的接口

Spring代码

首先写一个BeanNameAware的接口

1
2
3
public interface BeanNameAware {
public void setBeanName(String beanName);
}

然后再createBean方法中进行补充,如果实现了这个接口,则执行这个接口的setBeanName方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try {
// 实例化
Object instance = clazz.getConstructor().newInstance();

// 依赖注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}

// Aware回调(BeanName)
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}

return instance;
} catch (Exception e) {
throw new Exception(e);
}

测试代码

UserService实现这个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component("userService")
public class UserService implements BeanNameAware {

@Autowired
private OrderService orderService;

private String beanName;

@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}

public void test() {
System.out.println(orderService);
}
}

初始化

Spring代码

初始化机制,Spring会提供一个InitializingBean的接口,会提供一个afterPropertiesSet方法,首先也写一一个InitializingBean接口

1
2
3
public interface InitializingBean {
public void afterPropertiesSet();
}

createBean中也补充对应逻辑,如果某个Bean实现了InitializingBean接口,则执行它的afterPropertiesSet方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try {
// 实例化
Object instance = clazz.getConstructor().newInstance();

// 依赖注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}

// Aware回调(BeanName)
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}

// 初始化
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}

return instance;
} catch (Exception e) {
throw new Exception(e);
}

测试代码

更新UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component("userService")
public class UserService implements BeanNameAware, InitializingBean {

@Autowired
private OrderService orderService;

private String beanName;

@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}

@Override
public void afterPropertiesSet() {
System.out.println("xxxxxxxxxxxxxxxxxxxxxx");
}

public void test() {
System.out.println(orderService);
}
}

BeanPostProcessor

BeanPostProcessor这块就和AOP相关了,就是一个后置处理器,Spring就提供了一个BeanPostProcessor接口

Spring代码

首先写一个BeanPostProcessor接口,里面包含两个方法,postProcessBeforeInitializationpostProcessAfterInitialization,一个前置,一个后置,即初始化前和初始化后,其中初始化后的方法一般和AOP关联

1
2
3
4
public interface BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName);
public Object postProcessAfterInitialization(Object bean, String beanName);
}

针对这个BeanPostProcessor接口,Spring中也有一个单独的Map来存,即在容器类中添加该属性

1
private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

在扫描到实现了这个接口的Bean时,就也会有一些额外的逻辑(这段在容器类的构造方法中),如果实现了这个接口,就会创建一个对应的BeanPostProcessor放到beanPostProcessorList中去

1
2
3
4
5
6
7
8
9
10
11
12
13
if (clazz.isAnnotationPresent(Component.class)) {
// 类实现了BeanPostProcessor接口
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
beanPostProcessorList.add(instance);
}

/**
* other code;
*/

beanDefinitionMap.put(beanName, beanDefinition);
}

那么在creatBean方法中,也需要扩展对应的逻辑,初始化前,执行这个对应的instance的postProcessBeforeInitialization方法,初始化后,执行postProcessAfterInitialization方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
try {
// 实例化
Object instance = clazz.getConstructor().newInstance();

// 依赖注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}

// Aware回调(BeanName)
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}

// 初始化前
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
}

// 初始化
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}

// 初始化后
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}

return instance;
} catch (Exception e) {
throw new Exception(e);
}

测试代码

就和实现AOP一样,这块也写一个方法,实现BeanPostProcessor接口,就类似一个切面吧

这里面在初始化前加了一些逻辑,初始化后执行代理类的一些方法

UserService在初始化前时,就会执行createBean中的这一段代码beanPostProcessor.postProcessBeforeInitialization(instance, beanName)因为它符合postProcessBeforeInitialization中的判断,就会打印一个111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class CrossacidBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (beanName.equals("userService")) {
System.out.println(111);
}
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (beanName.equals("userService")) {
Object proxyInstance = Proxy.newProxyInstance(CrossacidBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("切面逻辑");
return method.invoke(bean, args);
}
});
return proxyInstance;
}
return bean;
}
}

如果此处UserService需要实现AOP,那么初始化后,需要返回一个代理类,即执行postProcessAfterInitialization方法,此处的逻辑是创建一个代理类并执行相应方法并返回这个代理类

因为上述使用的是jdk动态代理,所以需要让UserService实现一个接口

1
2
3
public interface UserInterface {
public void test();
}
1
2
3
4
5
6
7
8
9
10
@Component("userService")
public class UserService implements UserInterface{

@Autowired
private OrderService orderService;

public void test() {
System.out.println(orderService);
}
}

那么,此时就要修改测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {

public static void main(String[] args) {
CrossacidApplicationContext applicationContext = new CrossacidApplicationContext(AppConfig.class);

UserInterface userService = (UserInterface) applicationContext.getBean("userService");
userService.test();

// System.out.println(applicationContext.getBean("userService"));
// System.out.println(applicationContext.getBean("userService"));
// System.out.println(applicationContext.getBean("userService"));
// System.out.println(applicationContext.getBean("orderService"));

}
}

就可以看到”切面逻辑“在控制台输出