Spring boot實(shí)現(xiàn)一個(gè)簡單的ioc(2)
前言
跳過廢話,直接看正文
仿照spring-boot的項(xiàng)目結(jié)構(gòu)以及部分注解,寫一個(gè)簡單的ioc容器。
測試代碼完成后,便正式開始這個(gè)ioc容器的開發(fā)工作。
正文
項(xiàng)目結(jié)構(gòu)
實(shí)際上三四個(gè)類完全能搞定這個(gè)簡單的ioc容器,但是出于可擴(kuò)展性的考慮,還是寫了不少的類。
因篇幅限制,接下來只將幾個(gè)最重要的類的代碼貼出來并加以說明,完整的代碼請直接參考https://github.com/clayandgithub/simple-ioc。
SimpleAutowired
代碼
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的作用是用于注解需要自動(dòng)裝配的字段。
此類和spring的@Autowired的作用類似。但又有以下兩個(gè)區(qū)別:
- @SimpleAutowired只能作用于類字段,而不能作用于方法(這樣實(shí)現(xiàn)起來相對簡單些,不會(huì)用到aop)
- @SimpleAutowired中包括了required(是否一定需要裝配)和value(要裝配的bean的名字)兩個(gè)字段,實(shí)際上是將spring中的@Autowired以及Qualifier的功能簡單地融合到了一起
SimpleBean
代碼
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SimpleBean { String value() default ""; }
說明
@SimpleBean作用于方法,根據(jù)方法返回值來生成一個(gè)bean,對應(yīng)spring中的@Bean
用value來設(shè)置要生成的bean的名字
SimpleComponent
代碼
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SimpleBean { String value() default ""; }
說明
@SimpleComponent作用于類,ioc容器會(huì)為每一個(gè)擁有@SimpleComponent的類生成一個(gè)bean,對應(yīng)spring中的@Component。特殊說明,為了簡單起見,@SimpleComponent注解的類必須擁有一個(gè)無參構(gòu)造函數(shù),否則無法生成該類的實(shí)例,這個(gè)在之后的SimpleAppliationContext中的processSingleClass方法中會(huì)有說明。
SimpleIocBootApplication
代碼
import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SimpleIocBootApplication { String[] basePackages() default {}; }
說明
@SimpleIocBootApplication作用于應(yīng)用的入口類。
這個(gè)啟動(dòng)模式是照搬了spring-boot的啟動(dòng)模式,將啟動(dòng)任務(wù)委托給SimpleIocApplication來完成。ioc容器將根據(jù)注解@SimpleIocBootApplication的相關(guān)配置自動(dòng)掃描相應(yīng)的package,生成beans并完成自動(dòng)裝配。(如果沒有配置,默認(rèn)掃描入口類(測試程序中的SampleApplication)所在的package及其子package)
以上就是這個(gè)ioc容器所提供的所有注解,接下來講解ioc容器的掃描和裝配過程的實(shí)現(xiàn)。
SimpleIocApplication
代碼
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)用的啟動(dòng)會(huì)委托SimpleIocApplication來完成,通過將應(yīng)用入口類(測試程序中的SampleApplication)傳入SimpleIocApplication的構(gòu)造函數(shù),構(gòu)造出SimpleIocApplication的一個(gè)實(shí)例并運(yùn)行run方法。在run方法中,會(huì)首先生成一個(gè)applicationContext,并調(diào)用SimpleApplicationContextInitializer來完成applicationContext的初始化(bean的掃描、裝配)。然后調(diào)用processSpecialBeans來處理一些特殊的bean,如實(shí)現(xiàn)了SimpleIocApplicationRunner接口的bean會(huì)調(diào)用run方法來完成一些應(yīng)用程序的啟動(dòng)任務(wù)。
這就是這個(gè)ioc容器的整個(gè)流程。
SimpleApplicationContextInitializer
代碼
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中,會(huì)根據(jù)basePackages來構(gòu)造一個(gè)SimpleApplicationContextInitializer 的實(shí)例,進(jìn)而通過這個(gè)ApplicationContextInitializer來完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 簡單地調(diào)用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任務(wù)
SimpleApplicationContext
說明:
終于到了最重要的部分了,在SimpleApplicationContext中將真正完成掃描、生成bean以及自動(dòng)裝配的任務(wù)。這里scan即為SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化時(shí)調(diào)用。
代碼的調(diào)用邏輯簡單易懂,就不多加說明了。
這里只簡單列一下各個(gè)字段的含義以及幾個(gè)比較關(guān)鍵的方法的作用。
字段
- startupDate:啟動(dòng)時(shí)間記錄字段
- scannedPackages:已經(jīng)掃描的包的集合,保證不重復(fù)掃描
- registeredBeans:已經(jīng)完全裝配好并注冊好了的bean
- earlyBeans : 只是生成好了,還未裝配完成的bean,用于處理循環(huán)依賴的問題
- totalBeanCount : 所有bean的計(jì)數(shù)器,在生成bean的名字時(shí)會(huì)用到其唯一性
方法
- processEarlyBeans:用于最終裝配earlyBeans 中的bean,若裝配成功,則將bean移至registeredBeans,否則報(bào)錯(cuò)
- scan : 掃描并處理傳入的package集合
- processSingleClass:處理單個(gè)類,嘗試生成該類的bean并進(jìn)行裝配(前提是此類有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顧名思義,根據(jù)那些被@Bean注解的方法來生成bean
- autowireFields:嘗試裝配某個(gè)bean,lastChance代表是否在裝配失敗是報(bào)錯(cuò)(在第一次裝配時(shí),此值為false,在裝配失敗后會(huì)將bean移至earlyBeans,在第二次裝配時(shí),此值為true,實(shí)際上就是在裝配earlyBeans中的bean,因此若仍然裝配失敗,就會(huì)報(bào)錯(cuò))。在這個(gè)方法中,裝配相應(yīng)的bean時(shí)會(huì)從registeredBeans以及earlyBeans中去尋找符合條件的bean,只要找到,不管是來自哪里,都算裝配成功。
代碼
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 clayanddev@163.com * @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; } }
后記
至此,一個(gè)簡單的ioc容器就完成了,總結(jié)一下優(yōu)缺點(diǎn)。
優(yōu)點(diǎn):
小而簡單。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 來完成一些簡單但常用的依賴注入任務(wù).
缺點(diǎn):
很明顯,實(shí)現(xiàn)過于簡單,提供的功能太少。
如果你想了解ioc的實(shí)現(xiàn)原理,或者你想要開發(fā)一個(gè)小型個(gè)人項(xiàng)目但又嫌spring過于龐大,這個(gè)簡單的ioc容器或許可以幫到你。
如果你想做的不僅如此,那么你應(yīng)該將目光轉(zhuǎn)向spring-boot。
完整代碼參考:https://github.com/clayandgithub/simple-ioc。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 深入理解Java的Spring框架中的IOC容器
- 關(guān)于SpringBoot獲取IOC容器中注入的Bean(推薦)
- Spring學(xué)習(xí)筆記1之IOC詳解盡量使用注解以及java代碼
- Spring中IoC優(yōu)點(diǎn)與缺點(diǎn)解析
- 淺析Java的Spring框架中IOC容器容器的應(yīng)用
- 簡單實(shí)現(xiàn)Spring的IOC原理詳解
- 利用Spring IOC技術(shù)實(shí)現(xiàn)用戶登錄驗(yàn)證機(jī)制
- Spring核心IoC和AOP的理解
- 用java的spring實(shí)現(xiàn)一個(gè)簡單的IOC容器示例代碼
- 簡單談?wù)凷pring Ioc原理解析
相關(guān)文章
Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場景
這篇文章主要介紹了Java中的轉(zhuǎn)換流、壓縮流、序列化流、打印流及應(yīng)用場景,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06Java中線程的等待與喚醒_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當(dāng)前線程進(jìn)入等待狀態(tài),同時(shí),wait()也會(huì)讓當(dāng)前線程釋放它所持有的鎖。下面通過本文給大家介紹Java中線程的等待與喚醒知識(shí),感興趣的朋友一起看看吧2017-05-05Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之二手書商城系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+JSP+Springboot+maven+mysql+ThymeLeaf+FTP開發(fā)的二手書商城系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有在線書城該有的所有功能,感興趣的朋友快來看看吧2022-01-01Java應(yīng)用程序開發(fā)學(xué)習(xí)之static關(guān)鍵字應(yīng)用
今天小編就為大家分享一篇關(guān)于Java應(yīng)用程序開發(fā)學(xué)習(xí)之static關(guān)鍵字應(yīng)用,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12使用jxls自定義命令設(shè)置動(dòng)態(tài)行高
這篇文章主要介紹了使用jxls自定義命令設(shè)置動(dòng)態(tài)行高,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08詳解Java枚舉類在生產(chǎn)環(huán)境中的使用方式
本文主要介紹了Java枚舉類在生產(chǎn)環(huán)境中的使用方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02如何利用Java8 Stream API對Map按鍵或值排序
這篇文章主要給大家介紹了關(guān)于如何利用Java8 Stream API對Map按鍵或值排序的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Java8具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11