前言
連假實在有點無聊,打算來手寫一個SprintBoot專案,來加深自己對這種IoC框架的理解,我打算把這個框架叫做Winter,象徵我每況愈下的人生,唉
SprintBoot 單例池
本章所用到的程式碼存放在這
https://github.com/Hoxton019030/Winter/tree/bean-scan-and-beandefinition
一切開始的地方,Main
啟動類的寫法如下
1
2
3
4
5
| public class Main {
public static void main(String[] args) {
WinterApplicationContext applicationContext = new WinterApplicationContext(AppConfig.class);
}
}
|
這邊看到兩個陌生的Class,WinterApplicationContext,以及AppConfig。先講AppConfig是什麼,它是Winter的配置類,我們會在這邊去配置我們Winter Bean的位置在哪邊,所以我們要加上@ComponentScan這個註解,讓我們的Winter框架知道它要去什麼package底下找到Bean。這邊我們就先創這個Class就好,目前先不會寫到它。
AppConfig
1
2
3
4
5
6
7
| /**
* Winter的配置文件
*/
@ComponentScan("org.hoxton.service")
public class AppConfig {
}
|
@ComponentScan
1
2
3
4
5
| @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "" ;
}
|
用來示意我們的Winter框架,要去哪邊找到我們的Bean,我們的Bean都會寫在org.hoxton.service這個路徑下面。
@Component
這個大家熟的吧,將當前類變成一個Spring Bean的註解
1
2
3
4
5
6
| @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
|
@Scope
標示當前類所要創建的Bean是一個單例還是一個Prototype的Bean
1
2
3
4
5
| @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value();
}
|
UserService
用來假設MVC架構中的Service層
1
2
3
4
| @Component("userService")
@Scope("singleton")
public class UserService {
}
|
WinterApplicationContext
這個類就是SpringBoot框架中的容器類,我們接下來要來手寫一個容器類,首先先創這個類出來,並且它要吃一個Class參數作為Constructor
1
2
3
4
5
6
7
| public class WinterApplicationContext {
private Class configClass;
public WinterApplicationContext(Class configClass){
this.configClass=configClass;
}
}
|
接下來我們寫一個方法,叫做Scan,這個Scan方法會去得到@ComponentScan這個註解的值,取得要掃描的Package路徑,取得路徑之後,會去掃描路徑底下有哪些類有被@Component這個註解所修飾,並且會去找尋@Scope這個註解,得知哪些Bean是Singleton,哪些Bean是prototype,將這些資訊放入
1
| private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
|
這個ConcurrentHashMap中
步驟
- 從Config類中取得ComponentScan註解的值,其值即為路徑
1
2
3
4
5
6
| private void scan(Class configClass) {
// 從Config類中取得ComponetScan註解的值,其值即為路徑
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
}
|
- 使用類加載,讀出Class檔案的資訊
1
2
3
4
5
6
7
8
9
10
11
| private void scan(Class configClass) {
// 從Config類中取得ComponentScan註解的值,其值即為路徑
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
//掃描 org.hoxton.service
// 類加載器 (Class Loader)
// Java中有三種類加載器,以及對應的加載路徑
// BootStrap ---> jre/lib
// Ext ---> jre/ext/lib
// App ---> classpath ---> "C:\Program Files\Java\jdk-11\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\lib\idea_rt.jar=5445:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\hoxton\Desktop\Winter\target\classes org.hoxton.Test 編譯器顯示的資訊
}
|
- BeanDefinition
我們先來討論一個東西叫做BeanDefinition,這個類用來描述一個Bean的資訊,我們目前讓這個類盡量簡單一點,我們只需要紀錄一個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
26
27
28
29
30
31
32
33
34
35
36
37
| public class BeanDefinition {
/**
* 當前Bean類型
*/
private Class clazz;
/**
* 當前Bean作用範圍
*/
private String scope;
public BeanDefinition() {
}
public BeanDefinition(Class clazz, String scope) {
this.clazz = clazz;
this.scope = scope;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
|
- 使用類加載器,依照@ComponentScan的值,去掃描底下的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
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
| private void scan(Class configClass) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value(); //掃描路徑
//掃描 org.hoxton.service
// 類加載器 (Class Loader)
// Java中有三種類加載器,以及對應的加載路徑
// BootStrap ---> jre/lib
// Ext ---> jre/ext/lib
// App ---> classpath ---> "C:\Program Files\Java\jdk-11\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\lib\idea_rt.jar=5445:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\za546\Desktop\Winter\target\classes org.hoxton.Test 編譯器顯示的資訊
ClassLoader classLoader = WinterApplicationContext.class.getClassLoader(); //app加載器
URL resource = classLoader.getResource("org/hoxton/service"); //使用類加載,掃苗檔案下的.class檔
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
if (fileName.endsWith(".class")) { //若檔案以.class結尾即為.class檔,則進入流程判斷
String className = fileName.substring(fileName.indexOf("org"), fileName.indexOf(".class"));
className = className.replace("\\", ".");
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
if (clazz.isAnnotationPresent(Component.class)) { //類上若有@Component註解,代表為一個Bean
//表示這個類是個Bean
// ...? Class -- > bean ?
// 解析類,判斷當前Bean是單例Bean還是Prototype的Bean,生成BeanDefinition
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value(); //取得@Component的值(bean名)
//BeanDefinition- Bean定義
BeanDefinition beanDefinition = new BeanDefinition(); //取得一個BeanDefinition物件
beanDefinition.setClazz(clazz);
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
beanDefinition.setScope(SINGLETON); //沒加Scope註解,預設為單例
}
beanDefinitionMap.put(beanName, beanDefinition); // 將beanDefinition放進ConcurrentHashMap中
}
}
}
}
}
|
- 我們先來稍微預覽一下整個Class完成後會長什麼樣子,讓大家更有一個概念
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
| public class WinterApplicationContext {
final String SINGLETON ="singleton";
private Class configClass;
/**
* 一個單例池,存放Spring Bean
*/
private ConcurrentHashMap<String, Object> singletonPool = new ConcurrentHashMap<>();
/**
* 存放所有Spring Bean的定義
*/
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
/**
* @param configClass Winter的配置文件
*/
public WinterApplicationContext(Class configClass) {
this.configClass = configClass;
//解析配置類
// ComponentScan註解解析 -> 掃描路徑 -> 掃描 ---> BeanDefinition ---> BeanDefinitionMap
scan(configClass);
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//如果bean scope是單例,則放進單例池中
if(beanDefinition.getScope().equals(SINGLETON)){
Object bean = createBean(beanDefinition);
singletonPool.put(beanName, bean);
}
}
}
public Object createBean(BeanDefinition beanDefinition){
Class clazz = beanDefinition.getClazz();
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
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);
}
}
private void scan(Class configClass) {
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value(); //掃描路徑
//掃描 org.hoxton.service
// 類加載器 (Class Loader)
// Java中有三種類加載器,以及對應的加載路徑
// BootStrap ---> jre/lib
// Ext ---> jre/ext/lib
// App ---> classpath ---> "C:\Program Files\Java\jdk-11\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\lib\idea_rt.jar=5445:C:\Program Files\JetBrains\IntelliJ IDEA 2022.2.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\za546\Desktop\Winter\target\classes org.hoxton.Test 編譯器顯示的資訊
ClassLoader classLoader = WinterApplicationContext.class.getClassLoader(); //app加載器
URL resource = classLoader.getResource("org/hoxton/service"); //使用類加載,掃苗檔案下的.class檔
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
String fileName = f.getAbsolutePath();
if (fileName.endsWith(".class")) {
String className = fileName.substring(fileName.indexOf("org"), fileName.indexOf(".class"));
className = className.replace("\\", ".");
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
if (clazz.isAnnotationPresent(Component.class)) {
//表示這個類是個Bean
// ...? Class -- > bean ?
// 解析類,判斷當前Bean是單例Bean還是Prototype的Bean,生成BeanDefinition
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
//BeanDefinition- Bean定義
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(clazz);
if (clazz.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
beanDefinition.setScope(SINGLETON);
}
beanDefinitionMap.put(beanName, beanDefinition);
}
}
//
}
}
}
public Object getBean(String beanName) {
// 依照beanName去判斷是單例Bean還是Prototype Bean
if (beanDefinitionMap.containsKey(beanName)) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals(SINGLETON)) {
Object bean = singletonPool.get(beanName);
return bean;
} else {
//創建bean對象嗎?
Object bean = createBean(beanDefinition);
return bean;
}
} else {
throw new NullPointerException("沒有這個Bean");
}
}
}
|
- 接下來我們繼續回到WinterApplicationContext這個Constructor,我們已經把我們Package底下的Bean掃描完,放進beanDefinitionMap中,那我們接下來要依照這個Map,創建SINGLETON的bean,接著把SINGLETON的Bean放進singletonPool中,這個singletonPool也就是Spring中的單例池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public WinterApplicationContext(Class configClass) {
this.configClass = configClass;
//解析配置類
// ComponentScan註解解析 -> 掃描路徑 -> 掃描 ---> BeanDefinition ---> BeanDefinitionMap
scan(configClass);
for (String beanName : beanDefinitionMap.keySet()) { //遍整個beanDefinitionMap,找出單例的Bean,放進singletonPool中
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//如果bean scope是單例,則放進單例池中
if(beanDefinition.getScope().equals(SINGLETON)){
Object bean = createBean(beanDefinition);
singletonPool.put(beanName, bean);
}
}
}
|
- 撰寫createBean方法會吃一個BeanDefinition為參數,這個方法會用BeanDefinition的Clazz創建一個Class,接著調用class底下的
getDeclaredConstructor()
取得建構子,並用newInstance()
方法創造出一個那個bean的物件出來
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public Object createBean(BeanDefinition beanDefinition){
Class clazz = beanDefinition.getClazz();
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
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);
}
}
|
- 到Main中,使用getBean來取得singletonpool中的Bean
1
2
3
4
5
6
7
8
9
10
| public class Main {
public static void main(String[] args) {
WinterApplicationContext applicationContext = new WinterApplicationContext(AppConfig.class);
System.out.println("可以看到這三個的值是一模一樣的,代表這幾個物件都是同一個");
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
}
}
|
這樣就是一個基礎的SprintBoot 單例池的創建
Autowired之實現原理
To Be Continued …