徒手擼一個簡單的IOC
徒手擼一個簡單的IOC
Spring框架中最經典的兩個就是IOC和AOP,其中IOC(Inversion of Control)是什麼呢?控制反轉,簡單來說就是將控制實體Bean的動作交給了Spring容器進行管理。再簡單點來說就是例如之前想用一個類,必須new一個,但是使用了Spring那麼直接用 @Autowired
註解或者用xml配置的方式就能直接獲得此物件,而且你也不用管它的生命週期啊等等之類的。就不用自己new一個物件了。

image
如果是之前沒有使用IOC的話,那麼這些物件的建立以及賦值都是由我們自己建立了,下面簡單的演示瞭如果有上面四個物件依賴的話,那麼沒有IOC我們必須要建立物件並且賦值。僅僅四個物件就這麼多,那麼一旦專案大了,物件成百上千,如果還這樣寫的話,那麼絕對是一場災難。
物件A a = new 物件A(); 物件B b = new 物件B(); 物件C c = new 物件C(); 物件D d = new 物件D(); a.setB(b); a.setC(c); b.setD(d); c.setD(d);
因此在Spring中通過IOC將所有的物件統一放到Spring的容器中進行管理,所以就簡單了很多。上面的例項化物件的程式碼也不需要我們寫了。

image
上面說了那麼多,其實就是一句話IOC非常重要,但是如果直接看Spring原始碼的話會非常懵逼,所以就簡單的寫一個IOC的小例子來理解這種思想。
分析並編寫程式碼
還是編寫程式碼前的分析階段,Spring的IOC其實就是將所有的Bean放在統一容器中進行管理起來,然後在在獲取的時候進行初始化,所以需要我們在程式啟動的時候將被標記的類進行儲存在自定義的容器中管理。
- 初始化階段:將被
@MyIoc
類似於Spring中@Service
標記的類放入到自定義的容器中。 - 使用:通過自定義的獲取Bean的類進行統一獲取。
現在我們就以上面兩個步驟進行詳細點的分析
資料準備階段
首先初始化階段我們要先建立兩個註解類用於類的發現( @MyIoc
類似於 @Service
)。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyIoc { }
然後要初始化資訊進自定義容器的話用什麼型別的容器去儲存這些資訊呢?這裡可以想到是用Map來存,用key為類名,value用什麼呢?value就是要放在容器中進行管理的類的資訊了,那麼一個類有什麼資訊呢即類是由什麼組成呢?有以下幾個資訊
- 類名
- 建構函式
- 屬性值
- 父類
所以根據上面的分析我們可以建立一個實體類來儲存這些資訊,此時我們就不考慮複雜的構造函數了,就都是初始化的無參建構函式。然後父類的屬性就不進行分析注入了。所以此時類實體類就簡單了。
@Data public class BeanDefinition { private String className; private String alias; private String superNames; }
初始化階段
有了儲存類資訊的類了,那麼我們在程式啟動的時候就應該將這些資訊給載入到Map中,此時建立一個啟動類用於初始化被 @MyIoc
標記的類的資訊。
@Component @Order(value = 1) public class IoCInitConifg implements CommandLineRunner{ @Override public void run(String... args){ ConcurrentHashMap<String,BeanDefinition> concurrentHashMap = new ConcurrentHashMap<>(); Reflections reflections = new Reflections(); //獲得專案中所有被MyIoc標記得類 Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(MyIoc.class); //將其資訊初始進自定義容器MyBeanFactory中 for (Class clazz : typesAnnotatedWith){ BeanDefinition beanDefinition = new BeanDefinition(); String className = clazz.getName(); String superclassName = clazz.getSuperclass().getName(); beanDefinition.setClassName(className); beanDefinition.setSuperNames(superclassName); beanDefinition.setAlias(getClassName(className)); concurrentHashMap.put(className,beanDefinition); } MyBeanFactoryImpl.setBeanDineMap(concurrentHashMap); } private String getClassName(String beanClassName) { String className = beanClassName.substring(beanClassName.lastIndexOf(".") + 1); className = className.substring(0, 1).toLowerCase() + className.substring(1); return className; } }
此時得說一下自定義的統一容器管理的類 MyBeanFactory
此類用作統一獲得類的途徑
public interface MyBeanFactory { Object getBeanByName(String name) throws Exception; }
此時還有其實現類
@Log4j public class MyBeanFactoryImpl implements MyBeanFactory{ //儲存物件名稱和已經例項化的物件對映 private static ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>(); //儲存物件名稱和對應物件資訊的對映 private static ConcurrentHashMap<String,BeanDefinition> beanDefineMap= new ConcurrentHashMap<>(); //儲存儲存在容器中物件的名稱 private static Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>()); @Override public Object getBeanByName(String name) throws Exception { //看有沒有已經例項化的物件,有的話就直接返回 Object object = beanMap.get(name); if (object != null){ return object; } //沒有的話就例項化一個物件 object = getObject(beanDefineMap.get(name)); if (object != null){ //對例項化中物件的注入需要的引數 setFild(object); //將例項化的物件放入Map中,便於下次使用 beanMap.put(name,object); } return object; } public void setFild(Object bean) throws Exception { Field[] declaredFields = bean.getClass().getDeclaredFields(); for (Field field: declaredFields){ String filedAllName = field.getType().getName(); if (beanNameSet.contains(filedAllName)){ Object findBean = getBeanByName(filedAllName); //為物件中的屬性賦值 field.setAccessible(true); field.set(bean,findBean); } } } public Object getObject(BeanDefinition beanDefinition) throws Exception { String className = beanDefinition.getClassName(); Class<?> clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { log.info("can not find bean by beanName: "+className); throw new Exception("can not find bean by beanName: "+className); } return clazz; } public static void setBeanDineMap(ConcurrentHashMap<String,BeanDefinition> beanDefineMap){ MyBeanFactoryImpl.beanDefineMap = beanDefineMap; } public static void setBeanNameSet(Set<String> beanNameSet){ MyBeanFactoryImpl.beanNameSet = beanNameSet; } }
此時初始化的階段已經完成了,即已經將所有被 @MyIoc
標記的類已經被全部存放在了自定義的容器中了。其實在這裡我們已經能使用自己的自定義的容器進行獲得Bean了。
@MyIoc @Data public class User { private Student student; }
@MyIoc public class Student { public String play(){ return "student"+ this.toString(); } }
此時我們在啟動類中寫如下
User user1 = (User)beanFactory.getBeanByName("com.example.ioc.domain.User"); User user2 = (User)beanFactory.getBeanByName("com.example.ioc.domain.User"); Student student1 = user1.getStudent(); Student student2 = user1.getStudent(); Student student3 = (Student)beanFactory.getBeanByName("com.example.ioc.domain.Student"); System.out.println(user1); System.out.println(user2); System.out.println(student1); System.out.println(student2); System.out.println(student3);
發現控制檯中輸出的物件都是同一個物件,並且在User中也自動注入了Student物件。此時一個簡單的IOC就完成了。
User(student=com.example.ioc.domain.Student@705e7b93) User(student=com.example.ioc.domain.Student@705e7b93) com.example.ioc.domain.Student@705e7b93 com.example.ioc.domain.Student@705e7b93 com.example.ioc.domain.Student@705e7b93
總結
本來一開始的想法的是想要寫一個類似於 @Autowired
註解的自定義註解,但是在編碼過程中遇到了一個困難,就是例如下面的程式碼,例項化B容易,但是如何將B注入到每一個例項化的A中,這個問題困擾了我好幾天,也查找了許多的資料,至今還是沒有解決,估計是隻有研究Spring原始碼才能夠了解是如何做到的。
@MyIoc public class A{ @MyIocUse private B b; }