天天看點

IOC容器模拟實作

運用反射機制和自定義注解模拟實作IOC容器,使其具有自動加載、自動裝配和根據全限定類名擷取Bean的功能。

一. 實作原理

1-1 IOC容器的本質

IOC容器可了解為是一個map,其中的一個entry可了解為一個component(元件),entry中的key為beanId(全限定類名),entry中的value為bean(類對應的對象);

IOC容器模拟實作

具體的展現為:

IOC容器模拟實作

1-2 自動加載

  • 自動加載是指IOC容器會自動加載被@Component等(以下用@Component為例)注解标記的類,使其成為IOC容器中的一個元件;
  • @Component注解加在一個類前,表示此類被IOC容器管理,成為IOC容器中的一個元件;
  • 在自動加載時,容器會自動掃描給定包路徑所對應的包及其子包下的所有類,判斷類是否是接口,如果不是接口就再判斷是否被@Component标記,如果被标記了就将其添加到IOC容器中,key為該類的全限定類名,value為該類的對象。具體流程如圖:
IOC容器模拟實作

1-3 自動裝配

  • 自動裝配是指IOC容器會自動裝配容器中各個bean中被@Autowired注解标記屬性的屬性值;
  • @Autowired注解加在類中的一個屬性前,表示此屬性的值需要被自動裝配;
  • 待自動加載完成後,容器會根據keySet中的全限定類名周遊容器中各個類的各個屬性,判斷屬性是否被@Autowired注解标記,如果被标記了,就會根據屬性的類型的全限定類名(beanId)從容器中找到對應的bean,然後将找到的bean 的引用指派給對應屬性(模拟bean的scope為singleton)。具體的流程如圖:
IOC容器模拟實作

二. 具體實作

2-1 模拟情形

IOC容器自動加載

com.hutao.springioc

包及其子包下的所有類,并自動完成各類對應bean的屬性裝配。

2-2 目錄結構

IOC容器模拟實作
  • Autowired, Component為自定義注解;
  • Cat, Dog, User為實體類;
  • IocContainer為IOC容器;
  • Demo為測試。

2-3 實作

Autowired.java

package com.hutao.springioc.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

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

Component.java

package com.hutao.springioc.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}      

Cat.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Component;

@Component
public class Cat {
    public void mew(){
        System.out.println("Meow Meow Meow...");
    }
}      

Dog.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Component;

@Component
public class Dog {
    public void bark(){
        System.out.println("Wow wow wow...");
    }
}      

User.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Autowired;
import com.hutao.springioc.annotation.Component;

@Component
public class User {
    @Autowired
    private Dog dog;

    @Autowired
    private Cat cat;

    public void chat(){
        System.out.println("This is my dog and cat.");
        dog.bark();
        cat.mew();
    }
}      

IocContainer.java

package com.hutao.springioc;

import com.hutao.springioc.annotation.Autowired;
import com.hutao.springioc.annotation.Component;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

/**
 * 模拟Ioc容器,實作自動加載元件、自動裝配、根據類的全限定名擷取Bean
 *
 * @author Hutao
 * @createDate 2020/11/14
 */
public class IocContainer {
    //Ioc容器 存儲的鍵值對為 <類的完全限定名稱,該類的一個對象>
    private Map<String, Object> container = new HashMap<String, Object>();

    //Ioc容器可掃描到該包及其子包下的所有類
    private String packageName;

    public IocContainer(String packageName) {
        this.packageName = packageName;

        try{
            //添加元件到容器
            loadComponent();

            //裝配元件
            assemble();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 将制定包及其子包下的所有元件加載到容器中
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    private void loadComponent() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        //用于擷取包對應的URL,而URL中的分隔符為“/”, 是以将包路徑的分隔符“.” 用“/”代替
        String packagePath = packageName.replace(".","/");

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL resource = classLoader.getResource(packagePath);

        //通過擷取此資源的協定名稱,判斷包名對應的資源類型
        String protocol = resource.getProtocol();

        if(!"file".equals(protocol)){
            //隻測試protocol為file的情況,其他情況還有jar等
            return;
        }

        //擷取了指定包及其子包下所對應的所有以suffix(.class)結尾的檔案路徑
        List<String> filePathList = listFilePath(resource.getPath());

        //擷取類的完全限定名稱
        List<String> fullClassNameList = listFullClassName(filePathList);

        //加載component到容器中
        for (String fullClassName : fullClassNameList) {
            addToContainer(fullClassName);
        }
    }

    /**
     * 擷取指定檔案夾下所有以.class結尾檔案的抽象路徑名的規範路徑
     * @param directoryPath 指定檔案夾的路徑
     * @return 如擷取到:包含符合條件的檔案的規範路徑的List
     *         如未擷取到:空的List
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    private List<String> listFilePath(String directoryPath) throws IOException {
        List<String> filePathList = new ArrayList<String>();

        //參數校驗
        if(null==directoryPath){
            return filePathList;
        }

        File directoryFile = new File(directoryPath);
        if(!directoryFile.isDirectory()){
            return filePathList;
        }

        String filePath = null;
        File[] files = directoryFile.listFiles();
        for (File file : files) {
            if(!file.isDirectory()){
                filePath = file.getCanonicalPath();
                if(filePath.endsWith(".class")){
                    filePathList.add(filePath);
                }
            }
            else{
                //遞歸調用
                filePathList.addAll(listFilePath(file.getCanonicalPath()));
            }
        }

        return filePathList;
    }

    /**
     * 根據.class檔案的規範路徑擷取其類的全限定名
     * @param filePathList .class檔案的規範路徑List
     * @return 如擷取到:包含類的全限定名的的List
     *         如未擷取到:空的List
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    private List<String> listFullClassName(List<String> filePathList){
        List<String> fullClassNameList = new ArrayList<String>();
        if(null==packageName||null==filePathList){
            return fullClassNameList;
        }

        String packagePath = packageName.replace(".","\\");

        for (String filePath : filePathList) {
            fullClassNameList.add(filePath.substring(filePath.indexOf(packagePath),filePath.indexOf(".class")).replace("\\","."));
        }
        return fullClassNameList;
    }

    /**
     * 根據類的全限定名判斷該類是否被标記為容器的元件,如果是則将元件添加到容器中
     * @param fullClassName 類的全限定名
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    private void addToContainer(String fullClassName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class classObject = Class.forName(fullClassName);
        if(!classObject.isInterface()
                &&null!=classObject.getAnnotation(Component.class)){
            //如果掃描的Class對象不是接口類型且有@Component注解,就将對應的component裝配到容器中
            container.put(fullClassName,classObject.newInstance());
        }
    }

    /**
     * 自動裝配元件的屬性值
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    private void assemble() throws IllegalAccessException, ClassNotFoundException {
        Set<Map.Entry<String, Object>> entrySet = container.entrySet();
        for (Map.Entry<String, Object> entry : entrySet) {
            Class classObj = Class.forName(entry.getKey());
            Field[] declaredFieldArray = classObj.getDeclaredFields();
            for (Field field : declaredFieldArray) {
                if(null!=field.getAnnotation(Autowired.class)){
                    //如果屬性被@Autowired注解标注,則根據屬性的類型名進行自動裝配
                    field.setAccessible(true);
                    String beanId = field.getType().getName();
                    field.set(entry.getValue(),container.get(beanId));
                }
            }
        }
    }

    /**
     * 根據類的全限定名從Ioc容器中擷取對應的Bean
     * @param fullClassName
     * @return
     *
     * @author Hutao
     * @createDate 2020/11/14
     */
    public Object getBean(String fullClassName){
        if(null==fullClassName){
            return null;
        }

        return container.get(fullClassName);
    }
}      

Demo.class

package com.hutao.springioc;

import com.hutao.springioc.model.User;

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        IocContainer iocContainer = new IocContainer("com.hutao.springioc");
        User user = (User)iocContainer.getBean(User.class.getName());
        user.chat();
    }
}      

測試結果為:

//console輸出:
This is my dog and cat.
Wow wow wow...
Meow Meow Meow...

Process finished with exit code 0      

 注意:

  1. 以上IOC容器的實作原理隻是基本的原理,甚至未檢視源碼進行驗證,僅用于初步了解;