主頁 >  其他 > 設計模式終章----手寫IOC容器

設計模式終章----手寫IOC容器

2021-09-18 17:16:49 其他

手寫IOC容器

  • Spring核心架構
  • Bean概述
  • Spring IOC相關介面分析
    • BeanFactory介面
    • BeanDefinition介面
    • BeanDefinitionReader介面
    • BeanDefinitionRegistry 介面
      • SimpleBeanDefinitionRegistry---簡單的bean注冊中心
    • DefaultListableBeanFactory探究
  • 創建容器
  • 手寫SpringIOC容器
    • 定義Bean相關的Pojo類
      • PropertyValue類
      • MultablePropertyValues類
      • BeanDenfinition類
    • 定義注冊表相關類
      • BeanDefinitionRegistry介面
      • SimpleBeanDefinitionRegistry--注冊表介面的子實作類
  • 定義決議器相關類
    • BeanDefinitionReader介面
      • XmlBeanDefinitionReader子實作類
  • IOC容器相關類
    • BeanFactory介面---延時加載
      • 子介面ApplicationContext---非延時加載
        • AbstractApplicationContext類
          • ClassPathXmlApplicationContext類
            • StringUtils--負責拼接字串,找到對應需要執行的set方法
  • 測驗IOC
    • 將上面寫的spring模塊,安裝到maven的本地倉庫中
    • 新建專案,引入上面手寫的spring模塊
  • 流程圖
  • 原始碼地址
  • 手寫IOC總結
    • 手寫IOC使用的設計模式
    • 符合大部分設計原則
      • 整個設計和Spring的設計還是有一定的出入


Spring核心架構

Spring大約有20個模塊,由1300多個不同的檔案構成

這些模塊可以分為:

核心容器,AOP和設備支持,資料訪問和集成,Web組件,通信報文和集成測驗,下面是Spring框架的總體架構圖:
在這里插入圖片描述
核心容器由beans,core,context和expression(Spring Expression Language,SPEL)4個模塊組成

要點一:

  • spring-beans和spring-core模塊是Spring框架的核心模塊,包含了控制反轉(IOC)和依賴注入(DI).
  • BeanFactory使用控制反轉對應用程式的配置和依賴性規范與實際的應用程式代碼進行了分離,
  • BeanFactory屬于延時加載,也就是說在實體化容器物件后并不會自動實體化Bean,只有當Bean被使用時,BeanFatory才會對該Bean進行實體化與依賴關系的裝配.

要點二:

  • spring-context模塊架構與核心模塊之上,擴展了BeanFactory,為它添加了Bean生命周期控制,框架事件體系及資源加載透明化等功能,
  • 此外,此模塊還提供了許多企業支持,如郵件訪問,遠程訪問,任務調度,
  • ApplicationContext是該模塊的核心介面,它的超類是BeanFactory.

要點三;

  • spring-context-support模塊是對Spring IOC容器及IOC子容器的擴展支持

要點四:

  • spring-context-indexer模塊是Spring的類管理組件和Classpath掃描組件

要點五:

  • spring-expression模塊是統一運算式語言EL的擴展模塊,可以查詢,管理運行中的物件,同時也可以方便地呼叫物件方法,以及操作陣列,集合等,
  • 它的語法類似于傳統EL,但提供了額外的功能,最出色的要數函式呼叫和簡單的字串模板函式,
  • EL的特性是基于Spring產品的需求而設計的,可以非常方便地同Spring IOC進行互動

Bean概述

在這里插入圖片描述


Spring IOC相關介面分析

BeanFactory介面

在這里插入圖片描述

在這里插入圖片描述
這三個介面共同定義了Bean的集合,Bean之間的關系及Bean行為,

最基本的IOC容器介面是BeanFactory,來看一下它的原始碼

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
     //根據Bean的名稱,獲取IOC容器中的Bean物件 
    Object getBean(String var1) throws BeansException;
   //根據Bean的名稱,獲取IOC容器中的Bean物件,并指定獲取到的Bean物件的型別,這樣我們使用時,就不需要進行強制型別轉換
    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
   //判斷容器中是否包含指定名稱的Bean
    boolean containsBean(String var1);
   //根據Bean的名稱判斷是否是單例
    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
    //是否是多實體Bean 
    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
  
    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

在BeanFactory里只對IOC容器的基本行為做了定義,根本不關心你的Bean是如何定義及加載的,

正如我們只關心能從工廠里得到什么產品,不關心工廠是怎么生產這些產品的,

BeanFactory有一個很重要的子介面,就是ApplicationContext介面,該介面主要來規范容器中的bean物件是非延時加載的,即在創建容器物件的時候就對Bean進行初始化,并存盤到一個容器中

在這里插入圖片描述
要知道工廠是如何產生物件的,我們需要看具體的IOC容器實作,Spring提供了許多IOC容器實作,比如:

  • ClasspathXmlApplicationContext :根據類路徑加載xml組態檔,并創建IOC容器物件
  • FileSystemXmlApplicationContext:根據系統路徑加載xml組態檔,并創建IOC容器物件
  • AnnotationConfigApplicationContext:加載注解類配置,并創建IOC容器

BeanDefinition介面

Spring IOC容器管理我們定義的各種Bean物件及其相互關系,而Bean物件在Spring實作中是以BeanDefinition來描述的,如下面的組態檔

<bean id="userDao" class="com.dao.impl.UserDaoImpl"></bean>

bean標簽還有很多屬性: scope,init-method,destory-method等

在這里插入圖片描述


BeanDefinitionReader介面

在這里插入圖片描述
在這里插入圖片描述
BeanDefinitionReader介面定義的功能:

public interface BeanDefinitionReader {
//獲取BeanDefinitionRegistry 注冊器物件
    BeanDefinitionRegistry getRegistry();

    @Nullable
    ResourceLoader getResourceLoader();

    @Nullable
    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

//下面的loadBeanDefinitions都是從指定的資源中加載bean定義資訊
    int loadBeanDefinitions(Resource var1) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(Resource... var1) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String var1) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String... var1) throws BeanDefinitionStoreException;
}

BeanDefinitionRegistry 介面

BeanDefinitionReader用來決議bean定義,并封裝BeanDefinition物件,而我們定義的組態檔中定義了很多Bean標簽,所以就有一個問題,決議的BeanDefinition物件存盤到哪兒?

答案就是BeanDefinition的注冊中心,而該注冊中心頂層介面就是BeanDefinitionRegistry

public interface BeanDefinitionRegistry extends AliasRegistry {
//往注冊表中注冊bean,即bean定義加載后被封裝成的BeanDefinition物件
    void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;
//從注冊表洗掉指定名稱的bean
    void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
//獲取注冊表中指定名稱的Bean
    BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
//判斷注冊表中是否已經注冊了指定名稱的bean,即BeanDefinition物件
    boolean containsBeanDefinition(String var1);
//獲取注冊表中所有bean的名稱
    String[] getBeanDefinitionNames();
//獲取注冊表中注冊的bean的個數
    int getBeanDefinitionCount();
//判斷當前的bean名稱在注冊表中是否已經在使用了
    boolean isBeanNameInUse(String var1);
}

在這里插入圖片描述


SimpleBeanDefinitionRegistry—簡單的bean注冊中心

public class SimpleBeanDefinitionRegistry extends SimpleAliasRegistry implements BeanDefinitionRegistry {
//重點是這個beanDefinitionMap ---作為容器存放beanDefinition物件
//將beanDefinition物件放入map集合的程序就稱為注冊bean
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(64);


    public SimpleBeanDefinitionRegistry() {
    }

//下面都是重寫父類的方法
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "'beanName' must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
....
}

DefaultListableBeanFactory探究

該類中也有一個屬性是用來注冊bean的

    private final Map<String, BeanDefinition> beanDefinitionMap;

創建容器

ClassPathXmlApplicationContext對Bean配置資源的載入是從refresh()方法開始的,

refresh()方法是一個模板方法,規定了IOC容器的啟動流程,有些邏輯要交給器其子類實作,

他對Bean配置資源進行載入,ClassPathXmlApplicationContext通過呼叫父類AbstractApplicationContext的refresh()方法啟動整個IOC容器對Bean定義的載入程序.


手寫SpringIOC容器

現在要對下面的組態檔進行決議,并自定義Spring框架的IOC對涉及到的物件進行管理

<?xml version="1.0" encoding="UTF-8"?>

<beans>

<bean id="userService" class="com.pojo.UserService">

<property name="userDao" ref="userDao"></property>

<bean id="userDao" class="com.pojo.UserDao"></bean>

</beans>

定義Bean相關的Pojo類

PropertyValue類

用于封裝bean的屬性,體現到上面就是封裝bean標簽的子標簽的property標簽資料

每個PropertyValue實體物件,封裝一條property標簽里面的資料

//用來封裝bean標簽下的property標簽下的屬性
//name屬性,ref屬性:給參考型別賦值,value屬性:給基本資料型別及String型別屬性賦值
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PropertyValue
{
    private  String name;
    private String ref;
    private String value;
}

MultablePropertyValues類

一個bean標簽可以有多個property子標簽,所以再定義一個MultablePropertyValues類,用來存盤并管理多個PropertyValue物件

//用來存盤和管理多個PropertyValue物件
public class MultablePropertyValues implements
        Iterable<PropertyValue> {
//定義list集合物件,用來存盤propertyvlaue物件
    private  final List<PropertyValue> propertyValueList;
    //用final修飾的變數只能賦值一次,并且必須在構造方法結束前進行賦值
    public MultablePropertyValues()
       {
           propertyValueList=new ArrayList<>();
       }
    public MultablePropertyValues(List<PropertyValue> valueList)
    {
        if(valueList==null)
        {
         propertyValueList=new ArrayList<>();
        }
        else
        {
            propertyValueList=new ArrayList<>();
        }
    }
    //獲取所有PropertyValue物件,以陣列形式回傳
    public PropertyValue[] getPropertyValues()
    {
        //將集合轉換為陣列并回傳即可
        //指定回傳陣列的型別
        return propertyValueList.toArray(new PropertyValue[0]);
    }
    //根據name屬性值獲取PropertyValue物件
    public PropertyValue getPropertyValue(String propertyName)
    {
        //遍歷集合
        for (PropertyValue propertyValue : propertyValueList) {
            if(propertyValue.getName().equals(propertyName))
            {
             return propertyValue;
            }
        }
        return null;
    }
    //判斷集合是否為空
    public boolean isEmpty()
    {
        return propertyValueList.isEmpty();
    }
    //添加PropertyValue物件
    public MultablePropertyValues addPropertyValue(PropertyValue pv)
    {
        //判斷傳遞進來的PropertyValue物件,是否和集合中已有的重復了,如果重復了,就記性覆寫操作
        for(int i=0;i<propertyValueList.size();i++)
        {
            PropertyValue propertyValue = propertyValueList.get(i);
             if(propertyValue.getName().equals(pv.getName()))
             {
                 //進行覆寫操作
                 propertyValueList.set(i,propertyValue);
                 return this;//實作鏈式編程
             }
        }
        //沒有就直接添加
        propertyValueList.add(pv);
        return this;
    }
    //判斷是否有指定name屬性值的物件
    public boolean contains(String propertyName)
    {
       return getPropertyValue(propertyName)!=null;
    }
    //獲取迭代器物件
    @Override
    public Iterator<PropertyValue> iterator()
    {
        //呼叫list集合里面獲取迭代器的方法
        return propertyValueList.iterator();
    }
}

BeanDenfinition類

BeanDenfinition用來封裝bean資訊的,主要包含id(即物件的名稱),class(需要交由spring管理類的全類名)及子標簽property資料

//用來封裝bean標簽資料
//id屬性,class屬性,property子標簽資料
@Data
public class BeanDefinition
{
       private String id;
       private String className;
       private  MultablePropertyValues propertyValues;
       //利用無參構造對PropertyValues里面的list集合進行初始化
   public BeanDefinition()
    {
        propertyValues=new MultablePropertyValues();
    }
}

定義注冊表相關類

BeanDefinitionRegistry介面

BeanDefinitionRegistry介面定義了注冊表相關操作,定義了如下功能:

  • 注冊BeanDefinition到注冊表中
  • 從注冊表中洗掉指定名稱的BeanDefinition物件
  • 根據名稱從注冊表中獲取BeanDefinition物件
  • 判斷注冊表中是否含有指定名稱的BeanDefinition物件
  • 獲取注冊表中BeanDefinition物件的個數
  • 獲取注冊表中所有BeanDefinition物件的名稱
public interface BeanDefinitionRegistry
{
    //往注冊表中注冊bean,即bean定義加載后被封裝成的BeanDefinition物件
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
    //從注冊表洗掉指定名稱的bean
    void removeBeanDefinition(String beanName);
    //獲取注冊表中指定名稱的Bean
    BeanDefinition getBeanDefinition(String beanName);
    //判斷注冊表中是否已經注冊了指定名稱的bean,即BeanDefinition物件
    boolean containsBeanDefinition(String beanName);
    //獲取注冊表中所有bean的名稱
    String[] getBeanDefinitionNames();
    //獲取注冊表中注冊的bean的個數
    int getBeanDefinitionCount();
    //判斷當前的bean名稱在注冊表中是否已經在使用了
    boolean isBeanNameInUse(String beanName);
}

SimpleBeanDefinitionRegistry–注冊表介面的子實作類

public class SimpleBeanDefinitionRegistry implements  BeanDefinitionRegistry{

    //定義一個容器,用來存盤BeanDefinition物件
    private Map<String,BeanDefinition> beanDefinitionMap=new HashMap<>();

    //往注冊表中注冊bean,即bean定義加載后被封裝成的BeanDefinition物件
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    //從注冊表洗掉指定名稱的bean
    @Override
    public void removeBeanDefinition(String beanName) {
beanDefinitionMap.remove(beanName);
    }

    //獲取注冊表中指定名稱的Bean
    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    //判斷注冊表中是否已經注冊了指定名稱的bean,即BeanDefinition物件
    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    //獲取注冊表中所有bean的名稱
    @Override
    public String[] getBeanDefinitionNames()
    {
        //獲取key的set集合,再將set集合,轉換為陣列,陣列的型別為string
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }

    //獲取注冊表中注冊的bean的個數
    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    //判斷當前的bean名稱在注冊表中是否已經在使用了
    @Override
    public boolean isBeanNameInUse(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }
}

定義決議器相關類

BeanDefinitionReader介面

BeanDefinitionReader是用來決議組態檔并在注冊表中注冊bean資訊,定義了兩個規范:

  • 獲取注冊表的功能,讓外界可以通過該物件獲取注冊表物件
  • 加載組態檔,并注冊bean資料
//用來決議組態檔的,該介面只是定義了規范
public interface BeanDefinitionReader
{
    //獲取注冊表物件
  BeanDefinitionRegistry getRegistry();
    //加載組態檔,并在注冊表中進行注冊
    void loadBeanDefinitions(String configLocation) throws Exception;
}

XmlBeanDefinitionReader子實作類

XmlBeanDefinitionReader類專門用來決議xml組態檔的,該類實作BeanDefinitionReader介面,并實作介面中的兩個功能

dom4j的依賴

<dependency>

<groupId>dom4j</groupId>

<artifactId>dom4j</artifactId>

<version>1.6.1</version>

</dependency>

需要進行決議的xml格式,參考下面:
在這里插入圖片描述

具體代碼實作:

//針對xml組態檔進行決議的類
public class XmlBeanDefinitionReader implements BeanDefinitionReader{
    //宣告注冊表物件
    private BeanDefinitionRegistry registry;

    //建構式里面進行賦值
    public XmlBeanDefinitionReader()
    {
      registry=new SimpleBeanDefinitionRegistry();
    }

    @Override
    public BeanDefinitionRegistry getRegistry()
    {
        return registry;
    }

    //只實作類路徑下的組態檔加載
    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
         //使用dom4j進行xml組態檔的決議
        SAXReader reader=new SAXReader();
        //獲取類路徑下的組態檔
        InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
        //傳入位元組輸入流,獲取檔案物件
        Document document = reader.read(is);
        //根據Document物件獲取根標簽物件(beans)
        Element rootElement=document.getRootElement();
        //獲取根標簽下所有的bean標簽物件
        List<Element> beanElements = rootElement.elements("bean");
        //遍歷集合--獲取每個bean標簽上的屬性值
        for (Element beanElement : beanElements) {
            //獲取id屬性
            String id = beanElement.attributeValue("id");
            //獲取class屬性
            String className = beanElement.attributeValue("class");
            //將id屬性和class屬性封裝到BeanDefinition物件中
            //1.創建BeanDefinition物件
            BeanDefinition beanDefinition=new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);

            //創建MultablePropertyValues物件
            MultablePropertyValues multablePropertyValues=new MultablePropertyValues();

            //獲取bean標簽下所有的property標簽物件
            List<Element> propertyElements = beanElement.elements("property");
            for (Element propertyElement : propertyElements) {
                //獲取name屬性
                String name = propertyElement.attributeValue("name");
                //獲取ref屬性
                String ref = propertyElement.attributeValue("ref");
                //獲取value屬性
                String value = propertyElement.attributeValue("value");
                //使用PropertyValue物件封裝上述屬性
                PropertyValue propertyValue=new PropertyValue(name,ref,value);
                //添加到管理PropertyValue物件的容器中
                multablePropertyValues.addPropertyValue(propertyValue);
            }
         //將multablePropertyValues物件封裝到BeanDefinition物件中
            beanDefinition.setPropertyValues(multablePropertyValues);
         
         //將beanDefinition物件注冊到注冊表中
         //xml檔案中配置的id做為管理BeanDefinition物件的容器中的名字 
            registry.registerBeanDefinition(id,beanDefinition);
        }
    }
}

IOC容器相關類

BeanFactory介面—延時加載

在該介面中定義IOC容器統一規范獲取bean物件

//IOC容器父介面
public interface BeanFactory
{
    //根據bean物件的名稱獲取bean物件
       Object getBean(String name)throws Exception;
     //根據bean物件的名稱獲取bean物件,并進行型別轉換
    //方法設定為泛型方法,并且第二個引數為泛型引數,型別必須是T型別的或者其子類
    <T> T getBean(String name,Class<? extends T> clazz)throws Exception;
}

子介面ApplicationContext—非延時加載

該介面的所有子實作類的bean物件的創建都是非延時的,所以在該介面中定義refresh()方法,該方法主要完成以下兩個功能:

  • 加載組態檔
  • 根據注冊表中的BeanDefinition物件封裝的資料進行bean物件的創建
//定義非延時加載功能---繼承BeanFactory介面
public interface ApplicationContext extends BeanFactory
{
    void refresh()throws Exception;
}


AbstractApplicationContext類

  • 作為ApplicationContext介面的子類,所以該類也是非延時加載,所以需要在該類中頂一個Map集合,作為bean物件存盤的容器
  • 宣告BeanDefinitionReader型別的變數,用來進行xml組態檔的決議,符合單一職責原則
  • BeanDefinitionReader型別的物件的創建交由子類實作,因為只有子類明確到底創建BeanDefinitionReader的那個子實作類物件
//ApplicationContext介面的子實作類,用于立即加載
public abstract class AbstractApplicationContext implements ApplicationContext {
     //宣告決議器變數,具體是xml決議還是什么決議,由子類決定
     protected BeanDefinitionReader beanDefinitionReader;
     //定義用于存盤bean物件的map容器
    protected Map<String,Object> signletonObjects=new HashMap<>();
    //宣告組態檔路徑的變數
    protected String configLocation;

    //重寫父類refresh方法
    @Override
    public void refresh() throws Exception {
       //加載BeanDefinition物件
        beanDefinitionReader.loadBeanDefinitions(configLocation);
      //初始化bean
        finishBeanInialLization();
    }
    //bean的初始化
    private  void finishBeanInialLization()throws Exception{
        //獲取注冊表物件
        BeanDefinitionRegistry registry=beanDefinitionReader.getRegistry();
        //獲取BeanDefinition物件
        String[] beanNames = registry.getBeanDefinitionNames();
        //進行bean的初始化方法
         //將注冊表中已有的BeanDefinition物件,全部通過反射創建物件后,放入IOC容器中
        for (String beanName : beanNames) {
            //呼叫父類的getBean方法,獲取對應的bean物件
            //getBean方法的具體實作,有對應的子類完成
           //getBean對應的子類實作方式,會有區別,例如ClassPathXmlApplicationContext重寫getBean方法
           //里面邏輯主要是讀取xml,而如果是其他方式創建子類,那么getBean重寫的邏輯也會有區別
          getBean(beanName);
        }
    }
}

ClassPathXmlApplicationContext類

該類主要是加載類路徑下的組態檔,并進行bean物件的創建,主要完成以下功能:

  • 在構造方法中,創建BeanDefinition物件
  • 在構造方法中,呼叫refresh()方法,用于進行組態檔的加載,創建bean物件并存盤到容器中
  • 重新父介面中的getBean()方法,并實作依賴注入的操作
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{

  public ClassPathXmlApplicationContext(String configLocation)
   {
       //組態檔的路徑
       this.configLocation=configLocation;
       //創建決議器物件
       beanDefinitionReader=new XmlBeanDefinitionReader();
       //呼叫父類中的refresh方法
       try {
           refresh();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
   //根據bean物件的名稱獲取bean物件
    @Override
    public Object getBean(String name) throws Exception {
        //判斷物件容器中是否包含指定名稱的bean物件,如果包含,直接回傳即可
        //如果不包含,需要自行創建

        //嘗試從容器中獲取指定bean物件
        Object o = signletonObjects.get(name);
        //容器中存在該物件
        if(o!=null)
        {
            return o;
        }
        //獲取BeanDefinition物件
        //1.先獲取注冊表物件
        BeanDefinitionRegistry beanDefinitionRegistry=beanDefinitionReader.getRegistry();
        //2.從注冊表中獲取對應的BeanDefinition物件
        BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(name);
        //如果注冊表中也沒有對應的BeanDefinition物件,那么拋出例外
        if(beanDefinition==null)
            return null;
        //3.獲取bean資訊中的className:物件的全類名
        String className = beanDefinition.getClassName();
        //4.通過反射創建物件
        Class<?> clazz = Class.forName(className);
        //獲得了實體化完后的物件
        Object beanObj=clazz.newInstance();

        //進行依賴注入操作---創建完物件后的屬性賦值
        //先獲取所有property物件--每一個里面property物件封裝了物件的一個屬性的名字和對應的值
        MultablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        //獲取迭代器,遍歷存放property物件的list集合
        Iterator<PropertyValue> iterator = propertyValues.iterator();
        //使用迭代器遍歷
        while(iterator.hasNext())
        {
            //獲取name屬性值
            PropertyValue next = iterator.next();
            String propertyName = next.getName();
            //獲取value屬性
            String propertyValue = next.getValue();
            //獲取ref屬性
            String propertyRef = next.getRef();
            //ref和value只能出現一個
            if(propertyRef!=null&&!"".equals(propertyRef))
            {
                //ref屬性存在
                //獲取依賴的bean物件---使用遞回
                Object bean = getBean(propertyRef);
                //拼接方法名---傳入屬性名,獲取其對應的set方法的名字
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                //獲取所有的方法物件
                Method[] methods=clazz.getMethods();
                //找到我們需要執行的物件屬性的set方法
                for (Method method : methods) {
                    if(methodName.equals(method.getName()))
                    {
                        //執行該setter方法
                        //這里是對依賴物件進行屬性注入
                        //例如:
                        // class Person
//                        {
//                            Stu stu
//                        }
                        method.invoke(beanObj,bean);
                    }
                }
            }
            //value屬性存在
            if(propertyValue!=null&&!"".equals(propertyValue))
            {
                //拼接方法名--傳入屬性名,獲取對應的set方法
                String methodName1= StringUtils.getSetterMethodByFieldName(propertyName);
                //獲取method物件,方法名稱,和引數型別,這里引數我們針對的是String型別進行操作
                Method method = clazz.getMethod(methodName1, String.class);
                //執行哪個物件的方法,實參的值
                method.invoke(beanObj,propertyValue);
            }

        }

        //回傳beanObj物件之前,將該物件存盤到map容器中
        signletonObjects.put(name,beanObj);
        return beanObj;
  }

    //根據bean物件的名稱獲取bean物件,并進行強制型別轉換
    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
       //首先呼叫上面的getBean方法得到一個bean物件
        Object bean = getBean(name);
        //如果為null,說明沒有這個物件
        if(bean==null)
            return null;
        //進行型別強轉,并回傳強轉后的物件
        return clazz.cast(bean);
    }
}


StringUtils–負責拼接字串,找到對應需要執行的set方法
//工具類,負責拼接字串
public class StringUtils
{
    //構造器私有,不能創建實體化物件
    private StringUtils(){}
    //靜態方法---欄位名前面拼接set
    //userDao ---> setUserDao
    //屬性名第一個字母大寫,前面拼上set
    public static String getSetterMethodByFieldName(String fieldName)
    {
        String methodName="set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
        return methodName;
    }
}


測驗IOC

將上面寫的spring模塊,安裝到maven的本地倉庫中

在這里插入圖片描述


新建專案,引入上面手寫的spring模塊

目前寫的IOC只能做到對String型別的欄位完成依賴注入

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <bean id="animal" class="com.dao.Animal">
        <property name="dog" ref="dog">
        </property>
        <property name="type" value="">
        </property>
    </bean>
    <bean id="dog" class="com.dao.Dog">
        <property name="name" value="湯姆">
        </property>
        <property name="age" value="3">
        </property>
    </bean>
</beans>

測驗

public class main
{
    public static void main(String[] args) throws Exception {
        //加載組態檔,啟動IOC容器
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
        //根據名字獲取指定bean物件
        Dog dog = (Dog)applicationContext.getBean("dog");
        System.out.println(dog);
        //根據名字和型別獲取
        Animal animal = applicationContext.getBean("animal", Animal.class);
        System.out.println(animal);
        //如果傳入的名字,沒有找到對應的bean物件
        Object hhhh = applicationContext.getBean("hhhh");
        System.out.println(hhhh);
    }
}

在這里插入圖片描述


流程圖

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述


原始碼地址

碼云倉庫:

碼云地址

后續可能也會繼續補充


手寫IOC總結

手寫IOC使用的設計模式

  • 工廠模式: 這個使用工廠模式+組態檔的方式
  • 單例模式: spring ioc 管理的bean物件都是單例的,此處的單例不是通過構造器進行單例的控制的,而是spring框架對每一個bean只創建一個物件
  • 模板方法模式: AbstractApplicationContext類中的finishBeanInialLization()方法呼叫了子類的getBean()方法,因為getBean的實作和環境息息相關
  • 迭代器模式: 對于MultablePropertyValues類定義使用到了迭代器模式,因為此類中存盤并管理PropertyValue物件,也屬于一個容器,所以給該容器提供一個遍歷方式

Spring框架其實使用到了很多設計模式,入AOP使用到了代理模式,選擇JDK代理或者CGLIB代理使用到了策略模式,還有配接器模式,裝飾者模式,觀察者模式等


符合大部分設計原則

整個設計和Spring的設計還是有一定的出入

Spring框架底層是很復雜的,進行了很深入的封裝,并對外提供了很好的擴展性,而我們自定義SpringIOC有一下幾個目的:

  • 了解Spring底層對物件的大體管理機制
  • 了解設計模式在具體開發中的使用
  • 以后學習Spring原始碼,通過該案例的實作,可以降低學習成本

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301140.html

標籤:其他

上一篇:STM32MP157 Linux系統移植開發篇18:Linux內核藍牙驅動移植

下一篇:生產級K8S基礎環境部署-20210917

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more