從零開始實現一個簡易的Java MVC框架(三)--實現IOC

Spring中的IOC

IoC全稱是Inversion of Control,就是控制反轉,他其實不是spring獨有的特性或者說也不是java的特性,他是一種設計思想。而DI(Dependency Injection),即依賴注入就是Ioc的一種實現方式。關於Ioc和DI的具體定義和優缺點等你們能夠自行查找資料瞭解一下,這裏就不詳細贅述,總之spring的IoC功能很大程度上便捷了咱們的開發工做。java

在實現咱們的Ioc以前,咱們先了解一下spring的依賴注入,在spring中依賴注入有三種方式,分別是:git

  1. 接口注入(Interface Injection)
  2. 設值方法注入(Setter Injection)
  3. 構造注入(Constructor Injection)
@Component
public class ComponentA {
    @Autowired // 1.接口注入
    private ComponentB componentB;
    
    @Autowired // 2.設值方法注入
    public void setComponentB(ComponentB componentB) {
        this.componentB = componentB;
    }

    @Autowired // 3.構造注入
    public ComponentA(ComponentB componentB) {
        this.componentB = componentB;
    }
}

循環依賴注入

若是隻是實現依賴注入的話實際上很簡單,只要利用java的反射原理將對應的屬性‘注入’進去就能夠了。可是必需要注意一個問題,那就是循環依賴問題。循環依賴就是類之間相互依賴造成了一個循環,好比A依賴於B,同時B又依賴於A,這就造成了相互循環。github

// ComponentA
@Component
public class ComponentA {
    @Autowired
    private ComponentB componentB;
}

// ComponentB
@Component
public class ComponentB {
    @Autowired
    private ComponentA componentA;
}

那麼在spring中又是如何解決循環依賴問題的呢,咱們大體說一下原理。spring

若是要建立一個類,先把這個類放進'正在建立池'中,經過反射等建立實例,建立成功的話就把這個實例放入建立池中,並移除'正在建立池'中的這個類。每當實例中有依賴須要注入的話,就從建立池中找對應的實例注入進去,若是沒有找到實例,則先建立這個依賴。緩存

利用了這個正在建立的中間狀態緩存,讓Bean的建立的時候即便有依賴尚未實例化,能夠先把Bean放進這個中間狀態,而後跑去建立那個依賴,假如那個依賴的類又依賴與這個Bean,那麼只要在'正在建立池'中再把這個Bean拿出來,注入到這個依賴中,就能夠保證Bean的依賴可以實例化完成。再回頭來把這個依賴注入到Bean中,那麼這個Bean也實例化完成了,就把這個Bean從'正在建立池'移到'建立完成池'中,就解決了循環依賴問題。框架

雖然spring巧妙的避免了循環依賴問題,可是事實上構造注入是沒法避免循環依賴問題的。由於在實例化ComponentA的構造函數的時候必須獲得ComponentB的實例,可是實例化ComponentB的構造函數的時候又必須有ComponentA的實例。這兩個Bean都不能經過反射實例化而後放到'正在建立池',因此沒法解決循環依賴問題,這時候spring就會主動拋出BeanCurrentlyInCreationException異常避免死循環。ide

* 注意,前面講的這些都是基於spring的單例模式下的,若是是多例模式會有所不一樣,你們有興趣能夠自行了解。函數

實現IOC

如今能夠開始實現IOC功能了測試

增長註解

先在zbw.ioc包下建立一個annotation包,而後再建立一個Autowired的註解。這個註解的Target只有一個ElementType.FIELD,就是隻能註解在屬性上。意味着咱們目前只實現接口注入的功能。這樣能夠避免構造注入形成的循環依賴問題沒法解決,並且接口注入也是用的最多的方式了。若是想要實現設值方式注入你們能夠本身去實現,實現原理幾乎都同樣。優化

package com.zbw.ioc.annotation;

import ...

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

實現IOC類

package com.zbw.ioc;

import ...

@Slf4j
public class Ioc {

    /**
     * Bean容器
     */
    private BeanContainer beanContainer;

    public Ioc() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 執行Ioc
     */
    public void doIoc() {
        for (Class<?> clz : beanContainer.getClasses()) { //遍歷Bean容器中全部的Bean
            final Object targetBean = beanContainer.getBean(clz);
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) { //遍歷Bean中的全部屬性
                if (field.isAnnotationPresent(Autowired.class)) {// 若是該屬性被Autowired註解,則對其注入
                    final Class<?> fieldClass = field.getType();
                    Object fieldValue = getClassInstance(fieldClass);
                    if (null != fieldValue) {
                        ClassUtil.setField(field, targetBean, fieldValue);
                    } else {
                        throw new RuntimeException("沒法注入對應的類,目標類型:" + fieldClass.getName());
                    }
                }
            }
        }
    }

    /**
     * 根據Class獲取其實例或者實現類
     */
    private Object getClassInstance(final Class<?> clz) {
        return Optional
                .ofNullable(beanContainer.getBean(clz))
                .orElseGet(() -> {
                    Class<?> implementClass = getImplementClass(clz);
                    if (null != implementClass) {
                        return beanContainer.getBean(implementClass);
                    }
                    return null;
                });
    }

    /**
     * 獲取接口的實現類
     */
    private Class<?> getImplementClass(final Class<?> interfaceClass) {
        return beanContainer.getClassesBySuper(interfaceClass)
                .stream()
                .findFirst()
                .orElse(null);
    }

}

在知道IOC的原理以後發現其實真的是很是簡單,這裏用了幾十行的代碼就實現了IOC的功能。

首先在Ioc類構造的時候先獲取到咱們以前已經單例化的BeanContainer容器。

而後在doIoc()方法中就是正式實現IOC功能的了。

  • 遍歷在BeanContainer容器的全部Bean
  • 對每一個Bean的Field屬性進行遍歷
  • 若是某個Field屬性被Autowired註解,則調用getClassInstance()方法對其進行注入
  • getClassInstance()會根據Field的Class嘗試從Bean容器中獲取對應的實例,若是獲取到則返回該實例,若是獲取不到,則咱們認定該Field爲一個接口,咱們就調用getImplementClass()方法來獲取這個接口的實現類Class,而後再根據這個實現類Class在Bean容器中獲取對應的實現類實例。

測試用例

爲了測試咱們的Ioc和以前寫的BeanContainer編寫正確,咱們寫一下測試用例測試一下。

先在pom.xml添加junit的依賴

<properties>
    ...
    <junit.version>4.12</junit.version>
</properties>
<dependencies>
    ...
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

而後在test包下添加DoodleControllerDoodleServiceDoodleServiceImpl三個類方便測試

// DoodleController
package com.zbw.bean;
@Controller
@Slf4j
public class DoodleController {
    @Autowired
    private DoodleService doodleService;

    public void hello() {
        log.info(doodleService.helloWord());
    }
}

// DoodleService
package com.zbw.bean;
public interface DoodleService {
    String helloWord();
}

// DoodleServiceImpl
package com.zbw.bean;
@Service
public class DoodleServiceImpl implements DoodleService{
    @Override
    public String helloWord() {
        return "hello word";
    }
}

再編寫IocTest的測試用例

package com.zbw.ioc;

import ...

@Slf4j
public class IocTest {
    @Test
    public void doIoc() {
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zbw");
        new Ioc().doIoc();
        DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
        controller.hello();
    }
}

看到在DoodleController中輸出了DoodleServiceImplhelloWord()方法裏的字符串,說明DoodleController中的DoodleService已經成功注入了DoodleServiceImpl。那麼咱們的IOC的功能也完成了。


源碼地址:doodle

原文地址:從零開始實現一個簡易的Java MVC框架--實現IOC

相關文章
相關標籤/搜索