前言
跳過廢話,直接看正文
仿照spring-boot的項目結(jié)構(gòu)以及部分注解,寫一個簡單的ioc容器。
測試代碼完成后,便正式開始這個ioc容器的開發(fā)工作。
正文
項目結(jié)構(gòu)
實際上三四個類完全能搞定這個簡單的ioc容器,但是出于可擴(kuò)展性的考慮,還是寫了不少的類。
因篇幅限制,接下來只將幾個最重要的類的代碼貼出來并加以說明,完整的代碼請直接參考https://github.com/clayandgithub/simple-ioc。
SimpleAutowired
代碼
1
2
3
4
5
6
7
8
9
10
|
import java.lang.annotation.*; @Target ({ElementType.FIELD}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface SimpleAutowired { boolean required() default true ; String value() default "" ; // this field is moved from @Qualifier to here for simplicity } |
說明
@SimpleAutowired的作用是用于注解需要自動裝配的字段。
此類和spring的@Autowired的作用類似。但又有以下兩個區(qū)別:
- @SimpleAutowired只能作用于類字段,而不能作用于方法(這樣實現(xiàn)起來相對簡單些,不會用到aop)
- @SimpleAutowired中包括了required(是否一定需要裝配)和value(要裝配的bean的名字)兩個字段,實際上是將spring中的@Autowired以及Qualifier的功能簡單地融合到了一起
SimpleBean
代碼
1
2
3
4
5
6
7
8
|
import java.lang.annotation.*; @Target ({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface SimpleBean { String value() default "" ; } |
說明
@SimpleBean作用于方法,根據(jù)方法返回值來生成一個bean,對應(yīng)spring中的@Bean
用value來設(shè)置要生成的bean的名字
SimpleComponent
代碼
1
2
3
4
5
6
7
8
|
import java.lang.annotation.*; @Target ({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface SimpleBean { String value() default "" ; } |
說明
@SimpleComponent作用于類,ioc容器會為每一個擁有@SimpleComponent的類生成一個bean,對應(yīng)spring中的@Component。特殊說明,為了簡單起見,@SimpleComponent注解的類必須擁有一個無參構(gòu)造函數(shù),否則無法生成該類的實例,這個在之后的SimpleAppliationContext中的processSingleClass方法中會有說明。
SimpleIocBootApplication
代碼
1
2
3
4
5
6
7
8
|
import java.lang.annotation.*; @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface SimpleIocBootApplication { String[] basePackages() default {}; } |
說明
@SimpleIocBootApplication作用于應(yīng)用的入口類。
這個啟動模式是照搬了spring-boot的啟動模式,將啟動任務(wù)委托給SimpleIocApplication來完成。ioc容器將根據(jù)注解@SimpleIocBootApplication的相關(guān)配置自動掃描相應(yīng)的package,生成beans并完成自動裝配。(如果沒有配置,默認(rèn)掃描入口類(測試程序中的SampleApplication)所在的package及其子package)
以上就是這個ioc容器所提供的所有注解,接下來講解ioc容器的掃描和裝配過程的實現(xiàn)。
SimpleIocApplication
代碼
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
|
import com.clayoverwind.simpleioc.context.*; import com.clayoverwind.simpleioc.util.LogUtil; import java.util.Arrays; import java.util.Map; import java.util.logging.Logger; public class SimpleIocApplication { private Class<?> applicationEntryClass; private ApplicationContext applicationContext; private final Logger LOGGER = LogUtil.getLogger( this .getClass()); public SimpleIocApplication(Class<?> applicationEntryClass) { this .applicationEntryClass = applicationEntryClass; } public static void run(Class<?> applicationEntryClass, String[] args) { new SimpleIocApplication(applicationEntryClass).run(args); } public void run(String[] args) { LOGGER.info( "start running......" ); // create application context and application initializer applicationContext = createSimpleApplicationContext(); ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass); // initialize the application context (this is where we create beans) initializer.initialize(applicationContext); // here maybe exist a hidden cast // process those special beans processSpecialBeans(args); LOGGER.info( "over!" ); } private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) { // get base packages SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication. class ); String[] basePackages = annotation.basePackages(); if (basePackages.length == 0 ) { basePackages = new String[]{entryClass.getPackage().getName()}; } // create context initializer with base packages return new SimpleApplicationContextInitializer(Arrays.asList(basePackages)); } private SimpleApplicationContext createSimpleApplicationContext() { return new SimpleApplicationContext(); } private void processSpecialBeans(String[] args) { callRegisteredRunners(args); } private void callRegisteredRunners(String[] args) { Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner. class ); try { for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) { applicationRunner.run(args); } } catch (Exception e) { throw new RuntimeException(e); } } } |
說明
前面說到應(yīng)用的啟動會委托SimpleIocApplication來完成,通過將應(yīng)用入口類(測試程序中的SampleApplication)傳入SimpleIocApplication的構(gòu)造函數(shù),構(gòu)造出SimpleIocApplication的一個實例并運(yùn)行run方法。在run方法中,會首先生成一個applicationContext,并調(diào)用SimpleApplicationContextInitializer來完成applicationContext的初始化(bean的掃描、裝配)。然后調(diào)用processSpecialBeans來處理一些特殊的bean,如實現(xiàn)了SimpleIocApplicationRunner接口的bean會調(diào)用run方法來完成一些應(yīng)用程序的啟動任務(wù)。
這就是這個ioc容器的整個流程。
SimpleApplicationContextInitializer
代碼
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
|
import java.io.IOException; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> { private Set<String> basePackages = new LinkedHashSet<>(); public SimpleApplicationContextInitializer(List<String> basePackages) { this .basePackages.addAll(basePackages); } @Override public void initialize(SimpleApplicationContext applicationContext) { try { applicationContext.scan(basePackages, true ); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } applicationContext.setStartupDate(System.currentTimeMillis()); } } |
說明
在SimpleIocApplication的run中,會根據(jù)basePackages來構(gòu)造一個SimpleApplicationContextInitializer 的實例,進(jìn)而通過這個ApplicationContextInitializer來完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 簡單地調(diào)用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任務(wù)
SimpleApplicationContext
說明:
終于到了最重要的部分了,在SimpleApplicationContext中將真正完成掃描、生成bean以及自動裝配的任務(wù)。這里scan即為SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化時調(diào)用。
代碼的調(diào)用邏輯簡單易懂,就不多加說明了。
這里只簡單列一下各個字段的含義以及幾個比較關(guān)鍵的方法的作用。
字段
- startupDate:啟動時間記錄字段
- scannedPackages:已經(jīng)掃描的包的集合,保證不重復(fù)掃描
- registeredBeans:已經(jīng)完全裝配好并注冊好了的bean
- earlyBeans : 只是生成好了,還未裝配完成的bean,用于處理循環(huán)依賴的問題
- totalBeanCount : 所有bean的計數(shù)器,在生成bean的名字時會用到其唯一性
方法
- processEarlyBeans:用于最終裝配earlyBeans 中的bean,若裝配成功,則將bean移至registeredBeans,否則報錯
- scan : 掃描并處理傳入的package集合
- processSingleClass:處理單個類,嘗試生成該類的bean并進(jìn)行裝配(前提是此類有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顧名思義,根據(jù)那些被@Bean注解的方法來生成bean
- autowireFields:嘗試裝配某個bean,lastChance代表是否在裝配失敗是報錯(在第一次裝配時,此值為false,在裝配失敗后會將bean移至earlyBeans,在第二次裝配時,此值為true,實際上就是在裝配earlyBeans中的bean,因此若仍然裝配失敗,就會報錯)。在這個方法中,裝配相應(yīng)的bean時會從registeredBeans以及earlyBeans中去尋找符合條件的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
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired; import com.clayoverwind.simpleioc.context.annotation.SimpleBean; import com.clayoverwind.simpleioc.context.annotation.SimpleComponent; import com.clayoverwind.simpleioc.context.factory.Bean; import com.clayoverwind.simpleioc.util.ClassUtil; import com.clayoverwind.simpleioc.util.ConcurrentHashSet; import com.clayoverwind.simpleioc.util.LogUtil; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; /** * @author clayoverwind * @E-mail [email protected] * @version 2017/4/5 */ public class SimpleApplicationContext implements ApplicationContext { private long startupDate; private Set<String> scannedPackages = new ConcurrentHashSet<>(); private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>(); private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>(); private final Logger LOGGER = LogUtil.getLogger( this .getClass()); AtomicLong totalBeanCount = new AtomicLong(0L); AtomicLong nameConflictCount = new AtomicLong(0L); @Override public Object getBean(String name) { return registeredBeans.get(name); } @Override public <T> T getBean(String name, Class<T> type) { Bean bean = (Bean)getBean(name); return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null ); } @Override public <T> T getBean(Class<T> type) { Map<String, T> map = getBeansOfType(type); return map.isEmpty() ? null : type.cast(map.values().toArray()[ 0 ]); } @Override public boolean containsBean(String name) { return getBean(name) != null ; } @Override public <T> Map<String, T> getBeansOfType(Class<T> type) { Map<String, T> res = new HashMap<>(); registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject()))); return res; } @Override public void setStartupDate( long startupDate) { this .startupDate = startupDate; } @Override public long getStartupDate() { return startupDate; } /** * try to autowire those beans in earlyBeans * if succeed, remove it from earlyBeans and put it into registeredBeans * otherwise ,throw a RuntimeException(in autowireFields) */ private synchronized void processEarlyBeans() { for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) { Bean myBean = entry.getValue(); try { if (autowireFields(myBean.getObject(), myBean.getClazz(), true )) { registeredBeans.put(entry.getKey(), myBean); earlyBeans.remove(entry.getKey()); } } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } /** * scan base packages and create beans * @param basePackages * @param recursively * @throws ClassNotFoundException */ public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException { LOGGER.info( "start scanning......" ); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // get all classes who haven't been registered Set<Class<?>> classes = new LinkedHashSet<>(); for (String packageName : basePackages) { if (scannedPackages.add(packageName)) { classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively)); } } // autowire or create bean for each class classes.forEach( this ::processSingleClass); processEarlyBeans(); LOGGER.info( "scan over!" ); } /** * try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans * @param clazz */ private void processSingleClass(Class<?> clazz) { LOGGER.info(String.format( "processSingleClass [%s] ..." , clazz.getName())); Annotation[] annotations = clazz.getDeclaredAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof SimpleComponent) { Object instance; try { instance = clazz.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } long beanId = totalBeanCount.getAndIncrement(); SimpleComponent component = (SimpleComponent) annotation; String beanName = component.value(); if (beanName.isEmpty()) { beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId); } try { if (autowireFields(instance, clazz, false )) { registeredBeans.put(beanName, new Bean(instance, clazz)); } else { earlyBeans.put(beanName, new Bean(instance, clazz)); } } catch (IllegalAccessException e) { throw new RuntimeException(e); } try { createBeansByMethodsOfClass(instance, clazz); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException { List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean. class ); for (Method method : methods) { method.setAccessible( true ); Object methodBean = method.invoke(instance); long beanId = totalBeanCount.getAndIncrement(); Class<?> methodBeanClass = methodBean.getClass(); //bean name SimpleBean simpleBean = method.getAnnotation(SimpleBean. class ); String beanName = simpleBean.value(); if (beanName.isEmpty()) { beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId); } // register bean registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass)); } } private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) { List<Method> res = new LinkedList<>(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { if (annotation.annotationType() == annotationClass) { res.add(method); break ; } } } return res; } /** * try autowire all fields of a certain instance * @param instance * @param clazz * @param lastChance * @return true if success, otherwise return false or throw a exception if this is the lastChance * @throws IllegalAccessException */ private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Annotation[] annotations = field.getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof SimpleAutowired) { SimpleAutowired autowired = (SimpleAutowired) annotation; String beanName = autowired.value(); Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true ); if (bean == null ) { if (lastChance) { if (!autowired.required()) { break ; } throw new RuntimeException(String.format( "Failed in autowireFields : [%s].[%s]" , clazz.getName(), field.getName())); } else { return false ; } } field.setAccessible( true ); field.set(instance, bean.getObject()); } } } return true ; } /** * only used in autowireFields * @param beanName * @param type * @param allowEarlyBean * @return */ private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) { // 1. by name Bean res = registeredBeans.get(beanName); if (res == null && allowEarlyBean) { res = earlyBeans.get(beanName); } // 2. by type if (type != null ) { if (res == null ) { res = getSimpleBeanByType(type, registeredBeans); } if (res == null && allowEarlyBean) { res = getSimpleBeanByType(type, earlyBeans); } } return res; } /** * search bean by type in certain beans map * @param type * @param beansMap * @return */ private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) { List<Bean> beans = new LinkedList<>(); beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue())); if (beans.size() > 1 ) { throw new RuntimeException(String.format( "Autowire by type, but more than one instance of type [%s] is founded!" , beans.get( 0 ).getClazz().getName())); } return beans.isEmpty() ? null : beans.get( 0 ); } private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) { String beanName = clazz.getName() + "_" + beanId; while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) { beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement(); } return beanName; } } |
后記
至此,一個簡單的ioc容器就完成了,總結(jié)一下優(yōu)缺點。
優(yōu)點:
小而簡單。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 來完成一些簡單但常用的依賴注入任務(wù).
缺點:
很明顯,實現(xiàn)過于簡單,提供的功能太少。
如果你想了解ioc的實現(xiàn)原理,或者你想要開發(fā)一個小型個人項目但又嫌spring過于龐大,這個簡單的ioc容器或許可以幫到你。
如果你想做的不僅如此,那么你應(yīng)該將目光轉(zhuǎn)向spring-boot。
完整代碼參考:https://github.com/clayandgithub/simple-ioc。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。