spring最核心思想--ioc控制反轉和依賴注入


  前言

  鄙人剛開始寫博客,定有很多不足的地方,盡量以多種方式進行講解,如果有那種方式更容易你理解或者有任何意見可以給我留言或者私信,歡迎大家評論。

一IOC  

一核心概念    

控制反轉:將bean的生成交給容器,程序可以從容器中獲取指定的bean。

個人理解:此優勢也是spring能夠流行並成為java主流框架的主要原因,java是幫助java程序員以對象的方式管理 內存,而spring則是一個管理對象的框架。如果使用spring,在開發中基本上不需要考慮內存相關的問題。

接下來從一個設計者的思路去交流下spring的設計思路

 

二需求

基於以上的需求,我們需要做的核心的需求的是:生成,管理bean,向使用者提供。

三設計

看到這,是不是第一反應就是可以用工廠模式,沒錯,spring框架中針對此設計也是工廠模式。核心類為Beanactory,核心方法為getBean()。

1 public interface BeanFactory {
2     /**
3      * 獲取bean
4      * @param name bean的名字
5      * @return bean 實例
6      * @throws Exception
7      */
8     Object getBean(String name) throws Exception;
9 }

現在有個工廠,同時我們面對2個問題:

1BeanFactory如何知道生成什么樣的bean(bean是由用戶指定的)?

2用戶如何定義一個bean,便於用戶使用也便於beanFactory?

 

接下來先考慮第二個問題,現在誰也不知道用戶將定義怎樣的bean,那就來個萬能大法,先定義的一個接口專門做這個事,接口為BeanDefinition。

回到第一個問題,我們如何將BeanDefinition告訴給BeanFactory?解決這個問題同時我們還要考慮可擴展性。這個解決方案是注冊模式。

public interface BeanDefinitionRegistry {
  /**
   * 注冊bean定義
  * @param beanName bean名稱(bean的唯一標識)
  * @param beanDefinition bean定義
  * @throws BeanDefinitionRegistException 注冊異常
  */
  void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegistException;
}

 

每個bean通過BeanDefinitionRegistry接口通知給BeanFactory。

目前只剩下一個問題:BeanDefinition如何定義,通俗點就是BeanDefinition里面都有什么。

Bean生成都有哪些情況,

1是否單例

2bean類名

3生成方式:

  3.1指定初始化方法

    必須的信息:bean的類名

  3.2通過工廠模式

    3.2.1靜態的

    

public class PersonFactory{  

  public static Person getPerson(){
   return new Person();
  }
}

 

    3.2.2成員方法

public class PersonFactory{  
    public Person getPerson(){
       return new Person();  
    } 
} 

   工廠模式下,必須獲取的信息如下:工廠bean的命,工廠方法名

  3.3new的方式

4銷毀的方法

具體接口如下:

public interface BeanDefinition {

    String SCOPE_SINGLETION = "singleton";

    String SCOPE_PROTOTYPE = "prototype";

    /**
     * 類
     */
    Class<?> getBeanClass();

    /**
     * Scope
     */
    String getScope();

    /**
     * 是否單例
     */
    boolean isSingleton();

    /**
     * 是否原型
     */
    boolean isPrototype();

    /**
     * 工廠bean名
     */
    String getFactoryBeanName();

    /**
     * 工廠方法名
     */
    String getFactoryMethodName();

    /**
     * 初始化方法
     */
    String getInitMethodName();

    /**
     * 銷毀方法
     */
    String getDestroyMethodName();

    /**
     * 校驗bean定義的合法性
     */
    default boolean validate() {
        // 沒定義class,工廠bean或工廠方法沒指定,則不合法。
        if (this.getBeanClass() == null) {
            if (StringUtils.isBlank(getFactoryBeanName()) || StringUtils.isBlank(getFactoryMethodName())) {
                return false;
            }
        }

        // 定義了類,又定義工廠bean,不合法
        if (this.getBeanClass() != null && StringUtils.isNotBlank(getFactoryBeanName())) {
            return false;
        }

        return true;
    }

}

 

 

ioc容器的主要設計均已設計完成。

簡單的實現源代碼如下:

package core.ioc.impl;


import core.exception.BeanDefinitionRegistException;
import core.ioc.BeanDefinition;
import core.ioc.BeanDefinitionRegistry;
import core.ioc.BeanFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


import javax.xml.ws.WebServiceException;
import java.io.Closeable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

    private final Log logger = LogFactory.getLog(DefaultBeanFactory.class);

    //存在beanDefinition的緩存
    private Map<String,BeanDefinition> beanDefinitionMap= new ConcurrentHashMap<>(256);
    //存放單例bean實例的緩存
    private Map<String,Object> beanMap = new ConcurrentHashMap<>(256);

    //獲取bean實例
    public Object getBean(String name) throws Exception {
        return this.doGetBean(name);
    }

    protected Object doGetBean(String beanName) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //驗證bean不能為空
        Objects.requireNonNull(beanName,"beanName不能為空");

        //查看是否已經創建,如果已經創建則直接從緩存中取得返回
        Object instance = beanMap.get(beanName);
        if(null!=instance){
            return instance;
        }

        //如果沒有創建,則需要創建,
        BeanDefinition bd = this.getBeanDefinition(beanName);
        Objects.requireNonNull(bd,"beanDefinition 不能為空");

        Class<?> type=bd.getBeanClass();
        if(type!=null){
            if(StringUtils.isBlank(bd.getFactoryMethodName())){
                //構造方法來構造對象
                instance =this.createInstanceByConstructor(bd);
            }else{
                //通過靜態工廠方法創建對象
                instance=this.createInstanceByStaticFactoryMethod(bd);

            }
        }else{
            //通過工廠bean方式來構造對象
            instance=this.createInstanceByFactoryBean(bd);
        }


        //執行初始化方法,比如說給屬性賦值等
        this.doInit(bd,instance);

        //如果是單例,則將bean實例放入緩存中
        if(bd.isSingleton()){
            beanMap.put(beanName,instance);
        }

        return instance;
    }


    /**
     * 通過構造方法來構造對象
     * @param bd dean定義
     * @return bean實例
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private Object createInstanceByConstructor(BeanDefinition bd) throws IllegalAccessException, InstantiationException {
        return bd.getBeanClass().newInstance();
    }

    /**
     * 通過靜態工廠方法創建bean
     * @param bd bean定義
     * @return bean 實例
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> type=bd.getBeanClass();
        Method m=type.getMethod(bd.getFactoryMethodName(),null);
        return m.invoke(type,null);
    }

    /**
     * 通過工廠bean 方式來構造對象
     * @param bd
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     */
    private Object createInstanceByFactoryBean(BeanDefinition bd) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        Object factoryBean =this.doGetBean(bd.getFactoryBeanName());
        Method m=factoryBean.getClass().getMethod(bd.getFactoryMethodName(),null);

        return m.invoke(factoryBean,null);

    }




    /**
     * 初始化
     * @param bd
     * @param instance
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void doInit(BeanDefinition bd,Object instance) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if(StringUtils.isNotBlank(bd.getInitMehtodName())){
            Method m=instance.getClass().getMethod(bd.getInitMehtodName(),null);
            m.invoke(instance,null);
        }
    }




    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegistException {
        Objects.requireNonNull(beanName,"注冊bean需要給入beanName");
        Objects.requireNonNull(beanDefinition,"注冊bean需要給入beanDefinition");

        //檢驗給如的bean是否合法
        if(!beanDefinition.validata()){
            throw new BeanDefinitionRegistException(String.format("名字為[%s]的bean的定義不合法:%s",beanName,beanDefinition));
        }

        //驗證beanDefinition已經存在
        if(this.containBeanDefinition(beanName)){
            throw new BeanDefinitionRegistException(String.format("名字為[%s]的bean定義已經存在:%s",
                    beanName,this.getBeanDefinition(beanName)));
        }

        this.beanDefinitionMap.put(beanName,beanDefinition);
    }

    /**
     * 獲取beanDefinition
     * @param beanName bean的名稱 唯一標識
     * @return beanDefinition
     */
    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return this.beanDefinitionMap.get(beanName);
    }


    /**
     * 驗證beanDefinition是否已經存在
     * @param beanName bean的名稱 唯一標識
     * @return true:已存在 false:不存在
     */
    @Override
    public boolean containBeanDefinition(String beanName) {
        return this.beanDefinitionMap.containsKey(beanName);
    }

    /**
     * 執行指定的銷毀方法
     * @throws WebServiceException
     */
    @Override
    public void close() throws WebServiceException {
        //執行單例實例的銷毀方法
        for(Map.Entry<String,BeanDefinition> e:this.beanDefinitionMap.entrySet()){
            String beanName=e.getKey();
            BeanDefinition bd=e.getValue();

            if(bd.isSingleton() && StringUtils.isNotBlank(bd.getDestroyMethodName())){
                Object instance = this.beanMap.get(beanName);
                try {
                    Method m = instance.getClass().getMethod(bd.getDestroyMethodName());
                    m.invoke(instance,null);
                } catch (NoSuchMethodException e1) {
                    logger.error(String.format("執行bean[%s] %s 的 銷毀方法異常!",beanName,bd), e1);
                    e1.printStackTrace();
                } catch (IllegalAccessException e1) {
                    logger.error(String.format("執行bean[%s] %s 的 銷毀方法異常!",beanName,bd), e1);
                    e1.printStackTrace();
                } catch (InvocationTargetException e1) {
                    logger.error(String.format("執行bean[%s] %s 的 銷毀方法異常!",beanName,bd), e1);
                    e1.printStackTrace();
                }
            }
        }

    }
}
DefaultBeanFactory
package core.ioc.impl;

import core.ioc.BeanDefinition;
import org.apache.commons.lang3.StringUtils;

public class GenericBeanDefinition implements BeanDefinition {


    private Class<?> beanClass;

    //是否為單例
    private String scope = BeanDefinition.SCOPE_SINGLETION;

    //bean工廠的名稱
    private String factoryBeanName;
    //bean工廠方法名
    private String factoryMethodName;

    //初始化方法
    private String initMethodName;
    //銷毀方法
    private String destroyMethodName;




    /**
     * 自動生成設置的方法 start
     */
    public void setBeanClass(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

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

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

    public void setFactoryMethodName(String factoryMethodName) {
        this.factoryMethodName = factoryMethodName;
    }

    public void setInitMethodName(String initMethodName) {
        this.initMethodName = initMethodName;
    }

    public void setDestroyMethodName(String destroyMethodName) {
        this.destroyMethodName = destroyMethodName;
    }
    /**
     * 自動生成設置的方法 end
     */


    @Override
    public Class<?> getBeanClass() {
        return this.beanClass;
    }

    @Override
    public String getScope() {

        return this.scope;
    }

    @Override
    public boolean isSingleton() {
        return BeanDefinition.SCOPE_SINGLETION.equals(this.scope);
    }

    @Override
    public boolean isPrototype() {
        return BeanDefinition.SCOPE_PROTOTYPE.equals(this.scope);
    }

    @Override
    public String getFactoryBeanName() {
        return this.factoryBeanName;
    }

    @Override
    public String getFactoryMethodName() {
        return this.factoryMethodName;
    }

    @Override
    public String getInitMehtodName() {
        return this.initMethodName;
    }

    @Override
    public String getDestroyMethodName() {
        return this.destroyMethodName;
    }



    @Override
    public String toString() {
        return String.format("GenericBeanDefinition [beanClass=%s, scope=%s, factoryBeanName=%s, " +
                "factoryMethodName=%s, initMethodName=%s, destroyMethodName=%s]",
                beanClass,scope,factoryBeanName,factoryMethodName,initMethodName,destroyMethodName);
    }

    /**
     * 疑問: 為什么要重寫equals 方法
     *
     * 重寫equals方法需要注意以下幾點:
     * 1自反性:對於任何非空引用x,x.equals(x)應該返回true
     * 2對稱:對於任何引用x,y,如果x.equals(y) 返回true,那么 y.equals(x)也應該返回true。
     * 3傳遞性:對於任何引用x,y和z,如果x=y 為true,那么y=z也一定為true,x=z也一定為true。
     * 4一致性:如果x和y引用的對象沒有發生變化,那么返回調用x.equals(y),應該返回同樣的結果。
     * 5非空性:對於任意非空引用x,x.equals(null)應該返回false。
     *
     * 重寫equals方法,就必須重寫hashCode
     * 原因是HashMap的需要
     */

    @Override
    public boolean equals(Object obj) {
        if(this==obj){
            return true;
        }

        if(null==obj){
            return false;
        }

        if(getClass() !=obj.getClass()){
            return false;
        }

        GenericBeanDefinition other=(GenericBeanDefinition) obj;

        //驗證每個屬性是否相當,只有當每個屬性均相等時,才是一個對象
        if(beanClass ==null){
            if(other.beanClass!=null){
                return false;
            }
        }else if(!beanClass.equals(other.beanClass)){
            return false;
        }

        if(destroyMethodName== null){
            if(other.destroyMethodName!=null){
                return false;
            }
        }else if(!destroyMethodName.equals(other.destroyMethodName) ){
            return false;
        }

        if(factoryBeanName== null){
            if(other.factoryBeanName!=null){
                return false;
            }
        }else if(!factoryBeanName.equals(other.factoryBeanName) ){
            return false;
        }

        if(factoryMethodName== null){
            if(other.factoryMethodName!=null){
                return false;
            }
        }else if(!factoryMethodName.equals(other.factoryMethodName) ){
            return false;
        }

        if(initMethodName== null){
            if(other.initMethodName!=null){
                return false;
            }
        }else if(!initMethodName.equals(other.initMethodName) ){
            return false;
        }

        if(scope== null){
            if(other.scope!=null){
                return false;
            }
        }else if(!scope.equals(other.scope) ){
            return false;
        }


        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((beanClass == null) ? 0 : beanClass.hashCode());
        result = prime * result + ((destroyMethodName == null) ? 0 : destroyMethodName.hashCode());
        result = prime * result + ((factoryBeanName == null) ? 0 : factoryBeanName.hashCode());
        result = prime * result + ((factoryMethodName == null) ? 0 : factoryMethodName.hashCode());
        result = prime * result + ((initMethodName == null) ? 0 : initMethodName.hashCode());
        result = prime * result + ((scope == null) ? 0 : scope.hashCode());
        return result;
    }
}

 

二DI 依賴注入

 一、需求分析

  依賴注入的本質:賦值,給構造參數賦值,給屬性賦值

  背景:在ioc容器負責生成bean的實例,bean不僅有方法還有屬性,我們需要將bean的是屬性進行初始化,在這樣的背景下,我們將面對第一個問題

  問題1:我們需要在什么時候或者在哪里對bean的屬性進行初始化?

    分析:我們平時寫代碼時如何創建bean,例如 GirlFriend,存在以下倆種情況:

      1GirlFriend gf=new GirlFriend();

      2GirlFriend gf=new GirlFriend(“小紅”,18);

      3GirlFriend gf=GirlFriendFactory.getGirlFriend(“小紅”,18);

      4 class boy{

        GirlFriend gf=new GirlFriend();       

      }

    

  答:根據舉例分析得出分為倆種,1:有參構造參數/工廠方法;2:屬性依賴。

  解決了第一個問題,我們將面臨第二個問題,問題2:構造參數的入參有哪幾種情況?·

  答:基本類型、String、集合、依賴另一個ioc容器中的bean 等

  面對以上的情況,我們將如何設計,詳情請看設計

 二DI設計

   1構造參數依賴

   如果我們什么設計,首先要考慮的是先把信息存貯起來,以便后續的使用,那么第一個問題則是:

   問題1:參數可以是一個也可以是多個,用什么來存貯呢?

   答:答案很明顯,是用list,一定要保證存貯多個並且有序,其中有序很重要,因為存在構造函數的重載。

   問題2:參數可以是直接只,也可以是bean依賴,如何表示?

   答:object

   如果用object表示,那我們如何區分是bean依賴?

   答:無法判斷

   問題3:盡然無法判斷,我們有沒有其他解決方案,不是用object還能使用其他的嗎?

   答:只需要區分是否為bean依賴即可,使用object是沒有問題的。

   問題4:如何進行bean依賴的區分

   答:基於面向接口的編程的思想,定義個專門標識bean依賴的接口BeanReference,這樣就可以解決。

   問題5:如果某個參數是數組或者集合等,他們元素中有的是bean依賴,該如何處理

   答:元素值還是用BeanReference,同樣bean工廠在使用時需要遍歷替換

   問題6:BeanReference如何定義

    

public interface BeanReference{
     //依賴的bean的名稱
     private String beanName;        
     //獲取依賴bean的名稱
    String getBeanName();
}   

  問題7:ioc容器如何獲取構造函數

  答:在BeanDefinition中添加個方法,即List getConstructorArgumentValues(),通過此方法來獲取構造函數

  問題8:有參數,如何判斷是個構造方法或者工廠方法

  分析:存在以下情況

    1方法可以重載的

    2形參定義時可能是接口或者父類,實參則是具體的子實現

    3反射提供的獲取的構造方法、方法的API如下:

    

 

     答:

    1先根據參數的類型進行精確匹配查找,如果未找到,則進行第2步

    2獲取所有的構造方法,扁你了,通過參數數量過濾,再比較形參類型與實參類型。

    問題9:對於原型bean在第二次時否可以省略判斷,即對於原型bean,我們可以緩存下這個構造方法或者工廠方法?

    答:可以,提升性能,我們需要在BeanDefinition接口中添加4個方法。

    

    /* 下面的四個方法是供beanFactory中使用的 */

    public Constructor<?> getConstructor();

    public void setConstructor(Constructor<?> constructor); public Method getFactoryMethod(); public void setFactoryMethod(Method factoryMethod);

    問題10:循環依賴如何處理?(注意:面試題來嘍

    分析:循環依賴會導致什么問題? 答:會導致死循環,所以我們時是不允許循環依賴的,尤其是構造函數的循環依賴

    答:將正在構造的bean進行緩存,構造完成后移除,當遇到bean的依賴時,首先查看依賴的bean是否在構造中,如果在,則拋出異常。

    

    2屬性依賴

    問題1:屬性依賴時什么?、

    答:某個屬性依賴某個值

    問題2:該如何描述一個屬性依賴?

    答:屬性名,只,定義一個類來表示這個倆值

    問題3:會有多個屬性依賴,怎么存放

    答:list

    問題4:屬性只的情況和構造參數值時一樣的嗎?

    答:是

    

 2 
 3 /**
 4  * 屬性值依賴定義
 5  * 
 6  */
 7 public class PropertyValue {
 8 
 9     private String name; 10 11 private Object value; 12 13 public PropertyValue(String name, Object value) { 14 super(); 15 this.name = name; 16 this.value = value; 17  } 18 19 public String getName() { 20 return name; 21  } 22 23 public void setName(String name) { 24 this.name = name; 25  } 26 27 public Object getValue() { 28 return value; 29  } 30 31 public void setValue(Object value) { 32 this.value = value; 33  } 34 35 }

    注意:BeanDefinition中添加List<PropertyValue > getPropertyValues(); 以便BeanFactory可以獲取屬性依賴的信息

  最后總結下循環依賴

    1構造實例對象時(即構造參數循環依賴),會陷入死循環,不允許構造對象時循環依賴的

    2屬性可以循環依賴, 因為bean已經構建完畢。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM