Spring源碼------手寫體驗IOC與DI

Spring源碼------手寫體驗IOC與DI

目錄

Spring源碼------手寫體驗IOC與DI

1、前言

2、Spring IOC和DI原理流程

3、核心代碼

4、總結


1、前言

本篇博客並非是對Spring源碼的深入研究。而是對上一篇博客《Spring源碼------手寫體驗MVC》結構的優化。過程中主要是體驗Spring IOC和DI的初始化過程。那麼這篇博客涉及到的知識點大致有以下幾點:

  • 如何自定義註解,如何通過反射機制去賦予註解強大的功能(說白了,就是體驗在反射機制下,註解功能是多麼的強大)
  • Spring Ioc容器的實現原理
  • Spring DI 註解注入
  • Java反射機制
  • Java I/O流(加載配置文件,讀取配置文件信息) 

2、Spring IOC和DI原理流程

Spring IOC的基本流程:

  • 讀取配置文件
  • 解析配置文件,並封裝成BeanDefinition
  • 把BeanDefinition對應的實例放到容器進行緩存

Spring DI的基本流程:

  • 循環讀取BeanDefinition的緩存信息
  • 調用getBean()方法創建對象實例
  • 將創建好的對象實例包裝爲BeanWrapper對象
  • 將BeanWrapper對象緩存到IOC容器中
  • 循環IOC容器執行來進行注入

大致流圖:

image.png

3、核心代碼

代碼結構圖:

核心上下文applicationContext 代碼:

**
 * @description: 自定義上下文
 * @author: zps
 * @create: 2020-05-08 15:06
 **/
public class ZPSApplicationContext {

    private ZPSBeanDefinitionReader reader ;

    //保存BeanDefinition信息
    private Map<String , ZPSBeanDefinition> beanDefinitionMap = new HashMap<String , ZPSBeanDefinition>();

    private Map<String, ZPSBeanWrapper> factoryBeanInstanceCache = new HashMap<String, ZPSBeanWrapper>();

    private Map<String,Object> factoryBeanObjectCache = new HashMap<String, Object>();

    public ZPSApplicationContext(String... contextConfigLocation) {

        //加載配置文件
        reader = new ZPSBeanDefinitionReader(contextConfigLocation);

        //解析配置文件,封裝成BeanDefinition
        List<ZPSBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //把BeanDefinition緩存起來
        try {
            doRegistBeanDefinition(beanDefinitions);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //依賴注入,這裏默認沒有延遲加載
        doAutowired();

    }

    private void doAutowired() {
        //調用getBean()
        //這一步,所有的Bean並沒有真正的實例化,還只是配置階段
        for(Map.Entry<String , ZPSBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()){
            //根據類名進行初始化
            getBean(beanDefinitionEntry.getKey());
        }

    }

    //把BeanDefinition緩存起來
    private void doRegistBeanDefinition(List<ZPSBeanDefinition> beanDefinitions) throws Exception {
        for (ZPSBeanDefinition beanDefinition : beanDefinitions) {
//            if(this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
//                throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
//            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
            beanDefinitionMap.put(beanDefinition.getBeanClassName(),beanDefinition);
        }
    }

    //Bean的實例化,DI是從而這個方法開始的
    public Object getBean(String beanName){
        //1、先拿到BeanDefinition配置信息
        ZPSBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2、反射實例化newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);
        //3、封裝成一個叫做BeanWrapper
        ZPSBeanWrapper beanWrapper = new ZPSBeanWrapper(instance);
        //4、保存到IoC容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);
        //5、執行依賴注入
        populateBean(beanName,beanDefinition,beanWrapper);

        return beanWrapper.getWrapperInstance();
    }

    //執行依賴注入
    private void populateBean(String beanName, ZPSBeanDefinition beanDefinition, ZPSBeanWrapper zpsBeanWrapper) {
        

        Object instance = zpsBeanWrapper.getWrapperInstance();

        Class<?> clazz = zpsBeanWrapper.getWrappedClass();

        //在Spring中@Component
        if(!(clazz.isAnnotationPresent(ZPSController.class) || clazz.isAnnotationPresent(ZPSService.class))){
            return;
        }

        //把所有的包括private/protected/default/public 修飾字段都取出來
        for (Field field : clazz.getDeclaredFields()) {
            if(!field.isAnnotationPresent(ZPSAutowired.class)){ continue; }

            ZPSAutowired autowired = field.getAnnotation(ZPSAutowired.class);

            //如果用戶沒有自定義的beanName,就默認根據類型注入
            String autowiredBeanName = autowired.value().trim();
            if("".equals(autowiredBeanName)){
                //field.getType().getName() 獲取字段的類型
                autowiredBeanName = field.getType().getName();
            }

            //暴力訪問
            field.setAccessible(true);

            try {
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){
                    continue;
                }
                //ioc.get(beanName) 相當於通過接口的全名拿到接口的實現的實例
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    private Object instantiateBean(String beanName, ZPSBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if(this.factoryBeanObjectCache.containsKey(beanName)){
                instance = this.factoryBeanObjectCache.get(beanName);
            }else {
                Class<?> clazz = Class.forName(className);
                //2、默認的類名首字母小寫
                instance = clazz.newInstance();
                this.factoryBeanObjectCache.put(beanName, instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }

    public Object getBean(Class<?> clazz){
        return getBean(clazz.getName());
    }

    public int getBeanDefinitionCounts() {
        return this.beanDefinitionMap.size();
    }

    public String[] gteBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }
}

對應上圖的BeanDefinition:

**
 * @description: 讀取配置文件工具類
 * @author: zps
 * @create: 2020-05-08 15:18
 **/
public class ZPSBeanDefinitionReader {

    Properties properties = new Properties();

    //保存掃描的bean的類名信息
    private List<String> regitryBeanClasses = new ArrayList<String>();

    public ZPSBeanDefinitionReader(String... configLocations){

        //讀取配置文件信息
        doConfig(configLocations[0]);

        //對文件進行掃描,篩選出需要的類文件
        doScanner(properties.getProperty("scanPackage"));

    }

    //對文件進行掃描
    private void doScanner(String scanPackage) {
        //jar 、 war 、zip 、rar
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));
        File classPath = new File(url.getFile());

        //當成是一個ClassPath文件夾
        for (File file : classPath.listFiles()) {
            if(file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            }else {
                if(!file.getName().endsWith(".class")){continue;}
                //全類名 = 包名.類名
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                //Class.forName(className);
                regitryBeanClasses.add(className);
            }
        }
    }

    //加載配置文件
    private void doConfig(String configLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation.replaceAll("classpath:",""));
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public List<ZPSBeanDefinition> loadBeanDefinitions(){
        List<ZPSBeanDefinition> result = new ArrayList<ZPSBeanDefinition>();

        try {
            for (String className : regitryBeanClasses) {
                Class<?> beanClass = Class.forName(className);

                //保存類對應的ClassName(全類名)
                //還有beanName
                //1、默認是類名首字母小寫
                result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
                //2、自定義
                //3、接口注入
                for (Class<?> i : beanClass.getInterfaces()) {
                    result.add(doCreateBeanDefinition(i.getName(),beanClass.getName()));
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    private ZPSBeanDefinition doCreateBeanDefinition(String beanName, String beanClassName) {
        ZPSBeanDefinition beanDefinition = new ZPSBeanDefinition();
        beanDefinition.setFactoryBeanName(beanName);
        beanDefinition.setBeanClassName(beanClassName);
        return beanDefinition;
    }

    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}

對應上圖的BeanDefinition(該類主要是封裝從配置文件中讀取的類信息):

**
 * @description: 封裝類的信息
 * @author: zps
 * @create: 2020-05-08 15:23
 **/
public class ZPSBeanDefinition {

    private String factoryBeanName;  //簡單類名
    private String beanClassName;    //全類名

    public String getFactoryBeanName() {
        return factoryBeanName;
    }

    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    public String getBeanClassName() {
        return beanClassName;
    }

    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
    }
}

對應上圖的BeanWrapper :

/**
 * @description: bean的包裝類
 * @author: zps
 * @create: 2020-05-08 17:39
 **/
public class ZPSBeanWrapper {
    private Object wrapperInstance;
    private Class<?> wrappedClass;

    public ZPSBeanWrapper(Object instance) {
        this.wrapperInstance = instance;
        this.wrappedClass = instance.getClass();
    }

    public Object getWrapperInstance() {
        return wrapperInstance;
    }

    public Class<?> getWrappedClass() {
        return wrappedClass;
    }
}

運行截圖:

4、總結

對Spring Ioc 和DI 大致工作流程的描述:

Spring IOC的基本流程:

  • 讀取配置文件
  • 解析配置文件,並封裝成BeanDefinition
  • 把BeanDefinition對應的實例放到容器進行緩存

Spring DI的基本流程:

  • 循環讀取BeanDefinition的緩存信息
  • 調用getBean()方法創建對象實例
  • 將創建好的對象實例包裝爲BeanWrapper對象
  • 將BeanWrapper對象緩存到IOC容器中
  • 循環IOC容器執行來進行注入
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章