天天看點

動手實作Spring的IOC功能(基于注解)

動手實作Spring的IOC功能(基于注解)

檔案結構
動手實作Spring的IOC功能(基于注解)
設計技術

主要設計兩大技能點:反射,單例模式

實作過程

首先從啟動spring開始,啟動spring需要一個ApplicationContext的類,這個類中傳入一個配置類,這個配置類主要是說明了包掃描的路徑

建立一個配備了包掃描路徑的類,掃描com/ssm/service下面的檔案

AppConfig檔案

@IComponentScan("com.ssm.service")
public class AppConfig {
}
           

還需要實作@IComponentScan的注解

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IComponentScan {
    String value() default "";
}
           

另外再寫兩個Service,Service中需要三類注解,所有需要自己先實作這三類注解:@IComponent注解,@IAutowired注解和@Scope注解

實作@IComponent注解,主要作用是标注一個類為spring容器的bean,等下我需要講這個類執行個體化

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IComponent {
    String value() default "";
}
           

實作@IAutowired注解,主要是在類中引入xxxservice執行個體

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.METHOD})
//可用在字段和構造方法和普通方法上面
public @interface IAutowired {
}
           

實作@Scope注解,我主要實作它的兩種方式

singleton單例模式:全局有且僅有一個執行個體,預設為單例模式

prototype原型模式:每次擷取bean的時候都會有一個新執行個體

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "singleton";
}
           

UserService檔案如下

@IComponent("userService")
public class UserService {
}
           

OrderService檔案如下

先不用關注實作的接口InitializingBean和BeanNameAware,主要是OrderService中使用到@IAutowired注解拿到UserService對象

import com.spring.*;

/**
 * @author ssm
 */
@IComponent("orderService")
@Scope("prototype")
public class OrderService implements InitializingBean, BeanNameAware {

    @IAutowired
    private UserService userService;

    //這個beanName主要想要實作spring能夠自動将OrderService這個Bean的名字(即Component裡面的名字)指派給這個beanName
    private String beanName;

    public void test(){
        //這裡測試是看輸出的這個userservice是否為空,如果不為空就說明我們寫的依賴注入功能實作了
        System.out.println(userService);
    }

    @Override
    public void afterPropertiesSet() {
        //bean在生成的過程當中會調用這個方法
        System.out.println("初始化");
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
        //測試是否在生成bean時候自動指派了beanName
        System.out.println("beanName:  "+beanName);
    }
}
           

最重要的部分是ApplicationContext的内容

我在代碼中做了詳細的注釋,主要就是:

掃描配置類,通過配置類上面的IComponent注解得到掃描的路徑,掃描這個路徑下的所有檔案并且得到每個檔案的Class對象,講Class對象存入List,從List中一個一個解析類,這裡的解析類主要是看類上面是否有IComponent注解,解析得到每個類的相關資訊(BeanName,BeanClass,Scope),将每個類的相關資訊存入beanDefinitionMap,beanDefinitionMap是map結構,key是BeanName,value是BeanDefinition,BeanDefinition是每個Bean相關的資訊

生成bean執行個體化,主要使用了反射調用構造方法,即beanClass.getDeclaredConstructor()生成執行個體,用beanClass.getDeclaredFields()來判斷類中的成員變量上是否有@IAutowired注解,如果有該注解則去調用getBean(beanName)方法,該方法從beanDefinitionMap中取bean的資訊來生成bean對象,getBean方法中拿到Bean執行個體後将其指派給字段上加了@IAutowired注解的對象

import com.ssm.service.UserService;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ssm
 */
public class SSMApplicationContext {

    //主要儲存了bean的資訊,比如儲存了class,scope,beanName
    private ConcurrentHashMap<String ,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //單例池,主要存着執行個體化出來的單例對象
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
    public SSMApplicationContext(Class configClass){
        //構造方法主要是思考spring啟動的時候需要做什麼?
        //1:掃描類
        //2:建立bean
        //       思考:spring啟動是所有bean都要建立嗎?主要建立哪種類型的bean呢?
        //             是非懶加載的單例!啟動spring的時候就建立bean
        //2:第二個步驟概括就是要生成單例bean,并且要把生成的bean放入單例池中

        //掃描到類之後要幹什麼?解析這個類,具體解析些什麼資訊,比如有component注解

        //掃描配置檔案下的類,得到所有類對象
        List<Class> classList = scan(configClass);
        for (Class clazz : classList) {
            //一個一個解析類,将所有類對象的基本資訊存入beanDefinition
            //ConcurrentHashMap<String ,BeanDefinition> beanDefinitionMap,key是beanName,value是BeanDefinition(主要是scope和beanClass)
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setBeanClass(clazz);

            IComponent component = (IComponent) clazz.getAnnotation(IComponent.class);
            String beanName = component.value();

            if (clazz.isAnnotationPresent(Scope.class)){
                Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                beanDefinition.setScope(scope.value());
            }else {
                beanDefinition.setScope("singleton");
            }

            //判斷掃描IComponent的類是不是實作了beanPostProcessor
            if (BeanPostProcessor.class.isAssignableFrom(clazz)){
                //判斷clazz是否是BeanPostProcessor的實作類/子類
                try {
                    BeanPostProcessor bpp = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                    beanPostProcessorList.add(bpp);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
            beanDefinitionMap.put(beanName,beanDefinition);
        }

        for (String beanName: beanDefinitionMap.keySet()){
            //beanDefinitionMap裡面存着bean執行個體化的基本資訊
             BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
             if (beanDefinition.getScope().equals("singleton")){
                 //生成這個bean(和bean的生命周期有關,如下)
                 Object bean = createBean(beanName,beanDefinition);
                 singletonObjects.put(beanName,bean);//把建立出來的bean放入單例池中儲存起來
             }
        }
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        //生成這個bean(和bean的生命周期有關,如下)
        Class beanClass = beanDefinition.getBeanClass();
        try {
            //執行個體化(用class調用構造方法來進行執行個體化)
            Object bean = beanClass.getDeclaredConstructor().newInstance();
            //填充屬性
            Field[] fields = beanClass.getDeclaredFields();//思考:類中的什麼屬性是需要填充的
            for (Field field : fields) {
                if (field.isAnnotationPresent(IAutowired.class)){
                    //在字段上判斷是否加了Autowired注解
                    //思考:存在加了Autowired注解的字段,那我填充屬性,拿什麼東西填充呢?填充對象是?
                    //OrderService類中userService加了注解,那我應該拿一個UserService的對象去給這個加了注解的userservice指派
//                    UserService userService = (UserService) getBean(field.getName());
                    Object userService = getBean(field.getName());

                    field.setAccessible(true);//反射中必須設定true才可以通過反射通路字段,才能給執行個體化指派
                    field.set(bean,userService);//用userService給這個執行個體化的bean指派
                }
            }
            //Aware
            if (bean instanceof BeanNameAware){
                //bean是否實作了BeanNameAwaren接口
                //實作了則将bean進行強制類型轉化
                ((BeanNameAware)bean).setBeanName(beanName);
            }

            //...可實作程式員定義的邏輯
            //可以實作多個BeanPostProcessor,是以循環
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                bean = beanPostProcessor.postProcessBeforeInitialization(bean,beanName);
            }

            //初始化(instanceof隻能針對執行個體來寫,不能用于針對類來寫)
            if (bean instanceof InitializingBean){
                //bean是否實作了InitializingBean接口
                //實作了則将bean進行強制類型轉化
                ((InitializingBean)bean).afterPropertiesSet();
            }

            //...可實作程式員定義的邏輯
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                bean = beanPostProcessor.postProcessAfterInitialization(bean,beanName);
            }
            return bean;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }


    public Object getBean(String beanName){
        //還需要提供一個getBean方法,傳回值是Object
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("prototype")){
            return createBean(beanName,beanDefinition);
        }else {
            //單例生成,從單例池中拿對象
            Object bean = singletonObjects.get(beanName);
            if (bean == null){
                Object o = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,o);
                return o;
            }
            return bean;
        }
    }


    private List<Class> scan(Class configClass) {
        List<Class> classList = new ArrayList<>();

        //掃描類,主要是為了得到掃描的路徑
        //如何得到掃描路徑,先拿到Annocation注解,因為我們知道是IComponentScan注解,
        // 是以這裡進行了強轉,然後調用value得到了掃描的路徑
        IComponentScan iComponentScan = (IComponentScan) configClass.getAnnotation(IComponentScan.class);
        String scanPath = iComponentScan.value();
//        System.out.println(scanPath);  com.ssm.service
        //其實掃描是為了掃描目錄,但scanPath隻是得到了路徑,真的目錄的格式應該是com/ssm/service,是以需要轉換scanPath
        scanPath = scanPath.replace(".","/");//粗暴轉為目錄格式,替換.為/

        //思考:如何掃描類(使用類加載器,調用類加載器上的getResource來獲得)
        ClassLoader classLoader =  SSMApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(scanPath);
//        System.out.println(resource);
        //resource在沒有轉為檔案目錄的時候輸出是null,轉為檔案目錄以後輸出是 file:/C:/Users/SSM/Desktop/exercise/my_spring/target/classes/com/ssm/service

        File file = new File(resource.getFile());
        File[] files = file.listFiles();//掃描目錄下所有的檔案,什麼格式都會掃描進來

        for (File f : files) {
            //System.out.println(f);
            //C:\Users\SSM\Desktop\exercise\my_spring\target\classes\com\ssm\service\OrderService.class
            //C:\Users\SSM\Desktop\exercise\my_spring\target\classes\com\ssm\service\UserService.class
            String absolutePath = f.getAbsolutePath();
            absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));

            //System.out.println(absolutePath);
            //再次把斜線變成點
            absolutePath = absolutePath.replace("\\",".");//com.ssm.service.OrderService
            try {
                Class<?> clazz = classLoader.loadClass(absolutePath);//加載類
                classList.add(clazz);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return classList;
    }
}
           

BeanDefinition檔案如下:主要用來存儲bean資訊的

public class BeanDefinition {
    private String scope;
    private Class beanClass;

    public String getScope() {
        return scope;
    }

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

    public Class getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(Class beanClass) {
        this.beanClass = beanClass;
    }
}
           

在上面的ApplicationContext中有以下代碼單獨摘出來如下:

//Aware
if (bean instanceof BeanNameAware){
    //bean是否實作了BeanNameAwaren接口
    //實作了則将bean進行強制類型轉化
    ((BeanNameAware)bean).setBeanName(beanName);
}

//...可實作程式員定義的邏輯
//可以實作多個BeanPostProcessor,是以循環
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
    bean = beanPostProcessor.postProcessBeforeInitialization(bean,beanName);
    System.out.println(bean+"------------------------beanPostProcessor");
}

//初始化(instanceof隻能針對執行個體來寫,不能用于針對類來寫)
if (bean instanceof InitializingBean){
    //bean是否實作了InitializingBean接口
    //實作了則将bean進行強制類型轉化
    ((InitializingBean)bean).afterPropertiesSet();
}

//...可實作程式員定義的邏輯
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
    bean = beanPostProcessor.postProcessAfterInitialization(bean,beanName);
    System.out.println(bean+"------------------------beanPostProcessor");
}
           

這裡參考bean的生命周期,這裡主要實作了Aware接口,bean初始化,BeanPostProcessor接口,這部分代碼省略

最後附上代碼:https://gitee.com/vampire-boom/spring-ioc