徒手擼一個簡單的IOC

徒手擼一個簡單的IOC

Spring框架中最經典的兩個就是IOC和AOP,其中IOC(Inversion of Control)是什麼呢?控制反轉,簡單來講就是將控制實體Bean的動做交給了Spring容器進行管理。再簡單點來講就是例如以前想用一個類,必須new一個,可是使用了Spring那麼直接用@Autowired註解或者用xml配置的方式就能直接得到此對象,並且你也不用管它的生命週期啊等等之類的。就不用本身new一個對象了。git

若是是以前沒有使用IOC的話,那麼這些對象的建立以及賦值都是由咱們本身建立了,下面簡單的演示了若是有上面四個對象依賴的話,那麼沒有IOC咱們必需要建立對象而且賦值。僅僅四個對象就這麼多,那麼一旦項目大了,對象成百上千,若是還這樣寫的話,那麼絕對是一場災難。github

對象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的容器中進行管理,因此就簡單了不少。上面的實例化對象的代碼也不須要咱們寫了。框架

上面說了那麼多,其實就是一句話IOC很是重要,可是若是直接看Spring源碼的話會很是懵逼,因此就簡單的寫一個IOC的小例子來理解這種思想。dom

分析並編寫代碼

仍是編寫代碼前的分析階段,Spring的IOC其實就是將全部的Bean放在統一容器中進行管理起來,而後在在獲取的時候進行初始化,因此須要咱們在程序啓動的時候將被標記的類進行存儲在自定義的容器中管理。ide

  • 初始化階段:將被@MyIoc相似於Spring中@Service標記的類放入到自定義的容器中。
  • 使用:經過自定義的獲取Bean的類進行統一獲取。

如今咱們就以上面兩個步驟進行詳細點的分析函數

數據準備階段

首先初始化階段咱們要先創建兩個註解類用於類的發現(@MyIoc相似於@Service)。post

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyIoc {
    
}

而後要初始化信息進自定義容器的話用什麼類型的容器去存儲這些信息呢?這裏能夠想到是用Map來存,用key爲類名,value用什麼呢?value就是要放在容器中進行管理的類的信息了,那麼一個類有什麼信息呢即類是由什麼組成呢?有如下幾個信息this

  • 類名
  • 構造函數
  • 屬性值
  • 父類

因此根據上面的分析咱們能夠創建一個實體類來存儲這些信息,此時咱們就不考慮複雜的構造函數了,就都是初始化的無參構造函數。而後父類的屬性就不進行分析注入了。因此此時類實體類就簡單了。編碼

@Data
public class BeanDefinition {
    private String className;
    private String alias;
    private String superNames;
}

初始化階段

有了存儲類信息的類了,那麼咱們在程序啓動的時候就應該將這些信息給加載到Map中,此時創建一個啓動類用於初始化被@MyIoc標記的類的信息。code

@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;

}

完整項目地址

參考文章

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息