天天看點

手寫Spring-IOC

🎉🎉本文是基于尚矽谷的Spring6教程的筆記,希望對正在學習的你有一定幫助。

03spring6-ioc-demo

開發環境

  • Java17
  • Maven3.8.5

前言:

Spring架構的IOC是基于Java反射機制實作,是以要自己實作一個Spring-IOC容器,需要了解反射。此文篇幅有限,不再讨論反射。

目錄結構:

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─coding
│  │  │          ├─annotation
│  │  │          ├─bean
│  │  │          ├─dao
│  │  │          ├─service
│  │  │          └─test
│  │  └─resources
│  └─test
│      └─java
           

Task

  • 自己定義@Bean注解
  • 自己定義@DI注解

準備

dao

package com.coding.dao;

/**
 * @time 2023-02-21-20:38
 */
public interface UserDao {
     void add();
}

           
package com.coding.dao;


import com.coding.annotation.Bean;

/**
 * @time 2023-02-21-20:39
 */
@Bean
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("自定義bean dao.......");
    }
}

           

service

package com.coding.service;

/**
 * @time 2023-02-21-20:39
 */
public interface UserService {
    void add();
}

           
package com.coding.service;


import com.coding.annotation.Bean;
import com.coding.annotation.DI;
import com.coding.dao.UserDao;


/**
 * @time 2023-02-21-20:39
 */
@Bean
public class UserServiceImpl implements UserService {

    @DI
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("自定義bean service........");
        userDao.add();
    }
}

           

自定義注解

annotation

package com.coding.annotation;

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

/**
 * @time 2023-02-22-15:20
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}

           
package com.coding.annotation;

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

/**
 * @time 2023-02-22-15:20
 */

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

           

定義bean容器接口

package com.coding.bean;

/**
 * @time 2023-02-22-15:22
 */

public interface ApplicationContext {

    Object getBean(Class clazz);
}

           
public class AnnotationApplicationContext implements ApplicationContext {

    private Map<Class, Object> beanFactoryMap = new HashMap<>();

    @Override
    public Object getBean(Class clazz) {
        return beanFactoryMap.get(clazz);
    }
}
           

構造器基本邏輯

//掃描規則
    public AnnotationApplicationContext(String basePackagePath) {
        //包掃描
        try {
            //把.替換成斜杠\
            String packagePath = basePackagePath.replaceAll("\\.", "\\\\");
            //擷取包的絕對路徑
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().
                    getResources(packagePath);
            //擷取包的絕對路徑
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                //将轉義字元轉回來
                String filepath = URLDecoder.decode(url.getFile(), "utf-8");
                rootPath = filepath.substring(0, filepath.length() - basePackagePath.length());
                //掃描包
                loadBean(new File(filepath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //屬性注入
        loadDI();
    }
           

主要是實作loadBean方法和loadDI方法

思路:
private void loadBean(File file) {
        //1判斷是否是檔案夾
        //2擷取檔案夾中所有内容
        //3判斷檔案夾裡面為空,直接傳回
        //4如果檔案夾裡面不為空,周遊檔案夾所有内容
            //4.1 周遊得到每個FiLe對象,繼續判斷,如果還是檔案夾,遞歸
            //4.2周遊得到FiLe對象不是檔案夾,是檔案,
            //4.3得到包路徑+類名稱部分-字元串截取
            //4.4判斷目前檔案類型是否.cLass
            //4.5如果是.cLass類型,把路徑\替換成. 把 .class去掉
            //4.6判斷類上是否有@Bean注解,有就執行個體化
            	   //4.6.1擷取類的Class
                   //4.6.2判斷是不是接口
                     //4.6.3判斷類上是否有@Bean注解
                     //4.6.4執行個體化
            //4.7執行個體化後放到map集合beanFactoryMap裡面
                //如果有接口把接口的class當成key,執行個體對象當成value
                //如果沒有接口把自己的class當成key,執行個體對象當成value

    }
    
    
//屬性注入
private void loadDI(){
    //執行個體化對象在 beanFactoryMap 的map集合裡面
    //1 周遊 beanFactoryMap map集合
    //2擷取map集合每個對象(value) ,每個對象屬性擷取到
    //3 周遊得到每個對象屬性數組,得到每個屬性
    //4判斷屬性上面是否有@DI注解
     	//私有屬性  ,設定可以通路
    //5如果有@DI注解,把對象進行設定(注入)

}
           
實作:
//包掃描
    private void loadBean(File file) throws Exception {
        //1判斷是否是檔案夾
        if (file.isDirectory()) {
            //2擷取檔案夾中所有内容
            File[] childrenFiles = file.listFiles();
            //3判斷檔案夾裡面為空,直接傳回
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }
            //4如果檔案夾裡面不為空,周遊檔案夾所有内容
            for (File childFile : childrenFiles) {
                //4.1 周遊得到每個FiLe對象,繼續判斷,如果還是檔案夾,遞歸
                if (childFile.isDirectory()) {
                    loadBean(childFile);
                } else {
                    //4.2周遊得到FiLe對象不是檔案夾,是檔案,
                    //4.3得到包路徑+類名稱部分-字元串截取
                    String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
                    //4.4判斷目前檔案類型是否.class
                    if (pathWithClass.endsWith(".class")) {
                        //4.5如果是.cLass類型,把路徑\替換成. 把 .class去掉
                        String allName = pathWithClass.substring(0, pathWithClass.length() - 6).replaceAll("\\\\", ".");
                        //4.6判斷類上是否有@Bean注解,有就執行個體化
                        //4.6.1擷取類的Class
                        Class<?> clazz = Class.forName(allName);
                        //4.6.2判斷是不是接口
                        if (!clazz.isInterface()) {
                            //4.6.3判斷類上是否有@Bean注解
                            Bean annotation = clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                //4.6.4執行個體化
                                Object instance = clazz.getConstructor().newInstance();
                                //4.7執行個體化後放到map集合beanFactoryMap裡面
                                if (clazz.getInterfaces().length > 0) {
                                    //如果有接口把接口的class當成key,執行個體對象當成value
                                    beanFactoryMap.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    //如果沒有接口把自己的class當成key,執行個體對象當成value
                                    beanFactoryMap.put(clazz, instance);
                                }
                            }

                        }

                    }
                }

            }

        }
    }
           
//屬性注入
    private void loadDI() {
        //執行個體化對象在 beanFactoryMap 的map集合裡面

        //1 周遊 beanFactoryMap map集合
        Set<Map.Entry<Class, Object>> entries = beanFactoryMap.entrySet();
        for (Map.Entry<Class, Object> entry : entries) {
            //2擷取map集合每個對象(value) ,每個對象屬性擷取到
            Object obj = entry.getValue();
            Class<?> clazz = obj.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            //3 周遊得到每個對象屬性數組,得到每個屬性
            for (Field field : declaredFields) {
                //4判斷屬性上面是否有@DI注解
                DI annotation = field.getAnnotation(DI.class);
                if (annotation != null) {
                    //私有屬性  ,設定可以通路
                    field.setAccessible(true);
                    //5如果有@DI注解,把對象進行設定(注入)
                    try {
                        field.set(obj, beanFactoryMap.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }

        }


    }
           
測試:
package com.coding.test;

import com.coding.bean.AnnotationApplicationContext;
import com.coding.bean.ApplicationContext;
import com.coding.service.UserService;

/**
 * @time 2023-02-22-16:23
 */
public class TestBean {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationApplicationContext("com.coding");
        UserService userServiceClass = (UserService) context.getBean(UserService.class);
        System.out.println(userServiceClass);
        userServiceClass.add();
    }
}
           
結果:
com.coding.service.User[email protected]
自定義bean service........
自定義bean dao.......