1. 程式人生 > 其它 >簡單實現Spring依賴注入以及控制反轉

簡單實現Spring依賴注入以及控制反轉

執行流程

從上一章分析可得,spring建立bean的流程如下所示
獲取class資訊 -> 根據class資訊呼叫構造方法建立物件 -> 判斷成員變數中是否有依賴注入註解並進行注入操作 -> 初始化前(@PostConstruct) -> 初始化(實現InitializingBean介面) -> 初始化後(AOP) -> 生成代理物件 -> Bean

掃描bean

掃描bean主要根據包名獲取類資訊以及判斷使用者建立的類是否繼承BeanProcessor介面以及將該介面放入到spring容器中,並將該資訊儲存到map集合中。
類資訊包含bean名稱、單例還是原型、是否懶載入。
BeanProcessor介面用於處理初始化前和初始化後的方法。

首先根據傳入的類class判斷該類是否有ComponentScan註解,如果有就根據該註解獲取要掃描的包。
掃描類時載入的並不是.java檔案而是編譯後的二進位制位元組碼class檔案,想要載入該檔案需要找到編譯後的檔案目錄,可以根據應用程式類載入器獲取該目錄,呼叫resouces方法獲取包目錄下的url,獲取前得先將包名.替換成'/',這樣才能表示目錄,可以根據該url來建立file物件,之後判斷file物件是否為目錄,如果是目錄就掃描該目錄下的所有檔案,如下程式碼所示

        if (aClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) aClass.getAnnotation(ComponentScan.class);
            String packagePath = componentScan.value();
            packagePath = packagePath.replace(".", "/");

            URL resource = LyraApplicationContext.class.getClassLoader().getResource(packagePath);

            assert resource != null;
            File file = new File(URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8));

            if (file.isDirectory()) {
			}
	}

之後遍歷該目錄下的所有檔案,然後判斷檔案中是否有Component註解,如果有該註解那麼就表示這個類是一個bean,在然後判斷包中的類是否實現BeanProcessor介面,如果有類實現了則將該類例項化並存儲到map中。

                 if (aClass1.isAnnotationPresent(Component.class)) {
                            if (BeanProcessor.class.isAssignableFrom(aClass1)) {
                                BeanProcessor instance = (BeanProcessor) aClass1.getDeclaredConstructor().newInstance();
                                beanProcessorList.add(instance);
                            }
                        }

之後就該獲取類資訊了,類資訊所在類具體由下所示,類中儲存的資料由bean的class、是否為單例和bean名稱,單獨抽出BeanDefinition的原因是在掃描階段和獲取bean階段都需要掃描類從而獲取類是否為單例,這樣使程式碼重複且效率較低,不如建立個類資訊類,在掃描類時獲取類資訊儲存,之後獲取bean時直接從BeanDefinition中獲取即可。

public class BeanDefinition {
    private Class<?> clazz;
    private BeanScopeConstant scope;

    private String beanName;

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "clazz=" + clazz +
                ", scope=" + scope +
                ", beanName='" + beanName + '\'' +
                '}';
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    public BeanScopeConstant getScope() {
        return scope;
    }

    public void setScope(BeanScopeConstant scope) {
        this.scope = scope;
    }
}

獲取類資訊時判斷是否有Scope註解,如果有該註解則將該註解的資訊儲存到BeanDefinition中,如果沒有則預設為單例,之後將bean名稱和class資訊儲存到BeanDefinition中即可,再然後將BeanDefinition儲存到map中,map的key為beanName。bean掃描的整體程式碼如下所示

    private void scanPackage() {
        // 判斷類是否有componentScan註解
        if (aClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) aClass.getAnnotation(ComponentScan.class);
            String packagePath = componentScan.value();
            packagePath = packagePath.replace(".", "/");

            URL resource = LyraApplicationContext.class.getClassLoader().getResource(packagePath);

            assert resource != null;
            File file = new File(URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8));

            // 判斷類檔案是否為目錄
            if (file.isDirectory()) {
                for (File file1 : Objects.requireNonNull(file.listFiles())) {
                    try {
                        // 將包名.替換為/
                        String componentPackagePath = file1.getAbsolutePath().substring(file1.getAbsolutePath().indexOf("com"), file1.getAbsolutePath().indexOf(".class")).replace("\\", ".");
                        BeanDefinition beanDefinition = new BeanDefinition();
                        // 獲取類編譯目錄
                        Class<?> aClass1 = LyraApplicationContext.class.getClassLoader().loadClass(componentPackagePath);
                        // 載入類 判斷類是否有component註解
                        if (aClass1.isAnnotationPresent(Component.class)) {
                            // 判斷當前類是否實現了BeanProcessor介面
                            if (BeanProcessor.class.isAssignableFrom(aClass1)) {
                                BeanProcessor instance = (BeanProcessor) aClass1.getDeclaredConstructor().newInstance();
                                beanProcessorList.add(instance);
                            }
                            // 在類資訊中儲存類scope 單例/原型
                            if (aClass1.isAnnotationPresent(Scope.class)) {
                                Scope annotation = aClass1.getAnnotation(Scope.class);

                                beanDefinition.setScope(annotation.value());
                            } else {
                                beanDefinition.setScope(BeanScopeConstant.SINGLETON);
                            }
                            // 設定類資訊
                            beanDefinition.setBeanName(aClass1.getAnnotation(Component.class).value());
                            beanDefinition.setClazz(aClass1);

                            beanDefinitionMap.put(aClass1.getAnnotation(Component.class).value(), beanDefinition);
                        }
                    } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
                             InstantiationException | InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

bean建立

單例

掃描類完畢之後會根據掃描類的scope來建立bean,如果類的scope為單例則立即建立則儲存到儲存到單例池中。

    public LyraApplicationContext(Class<ApplicationConfig> applicationConfigClass) {
        this.aClass = applicationConfigClass;

        scanPackage();

        for (Map.Entry<String, BeanDefinition> stringBeanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = stringBeanDefinitionEntry.getKey();
            BeanScopeConstant scope = stringBeanDefinitionEntry.getValue().getScope();

            if (scope == BeanScopeConstant.SINGLETON) {
                Object singletonBean = createBean(beanName, stringBeanDefinitionEntry.getValue());
                singletonMap.put(beanName, singletonBean);
            }
        }

    }

原型

如果類的scope為原型則每次呼叫 getBean方法都會建立不同的bean返回
獲取bean中首先從bean資訊map中根據bean名稱獲取bean資訊,如果不存在則表示該類沒有被掃描到容器中,獲取單例bean時,如果單例bean還未建立則呼叫getbean建立bean並掃描到單例池中

    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if (beanDefinition == null) {
            throw new NullPointerException();
        }

        if (beanDefinition.getScope() == BeanScopeConstant.PROTOTYPE) {
            return createBean(beanName, beanDefinition);
        } else {
            Object bean = singletonMap.get(beanName);
            if (bean == null) {
                bean = createBean(beanName, beanDefinition);
                singletonMap.put(beanName, bean);
            }
            return bean;
        }

    }

建立bean

建立bean的流程為
獲取class資訊 -> 根據class資訊呼叫構造方法建立物件 -> 判斷成員變數中是否有依賴注入註解並進行注入操作 -> 初始化前(@PostConstruct) -> 初始化(實現InitializingBean介面) -> 初始化後(AOP) -> 生成代理物件 -> Bean

  1. 獲取類資訊
    掃描時已經將類資訊儲存到beanDefinition中,直接從map中獲取beanDefinition即可
       Class<?> clazz = beanDefinition.getClazz();
  1. 利用反射呼叫構造方法建立bean
    這裡呼叫的是無參構造方法,沒有構造方法推理,還略微有些問題
            Object instance = clazz.getConstructor().newInstance();
  1. 檢視bean欄位中有沒有被Autowired註解標識,如果有則呼叫geatBean方法為欄位注入值,這裡有個坑,如果掃描的bean為單例bean,雖然標識了Component註解,建立bean在注入之前,容器還沒來得及建立就已經執行注入操作,肯定會注入失敗的,這就在getBean方法獲取單例bean時獲取,如果單例bean還未建立則呼叫getbean建立bean並掃描到單例池中
        for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getName()));
                }
            }
  1. 呼叫初始化前方法
    BeanProcessor中封裝了兩個方法,初始化前和初始化後的方法,這個介面很重要,因為AOP代理物件就是從這個介面的實現類建立的,通過對bean的處理,然後返回代理物件
public interface BeanProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("before");
        return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("after");
        return bean;
    }
}

執行初始化前方法,這個list填充是由掃描bean時完成的,所以不為空

            beanProcessorList.forEach((beanProcessor -> {
                beanProcessor.postProcessBeforeInitialization(instance, beanName);
            }));
  1. 呼叫初始化方法
    初始化方法是由實現InitializingBean介面的bean完成的,直接判斷當前bean是否實現了InitializingBean介面即可,如果實現了之江將bean強轉為InitializingBean然後呼叫初始化方法即可。
        if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
  1. 呼叫初始化後方法

            beanProcessorList.forEach((beanProcessor -> {
                beanProcessor.postProcessAfterInitialization(instance, beanName);
            }));

建立bean完整程式碼

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class<?> clazz = beanDefinition.getClazz();

        try {
            Object instance = clazz.getConstructor().newInstance();

            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getName()));
                }
            }

            beanProcessorList.forEach((beanProcessor -> {
                beanProcessor.postProcessBeforeInitialization(instance, beanName);
            }));

            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            beanProcessorList.forEach((beanProcessor -> {
                beanProcessor.postProcessAfterInitialization(instance, beanName);
            }));


            return instance;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }