IOC及DI功能分析与设计

前言

本篇文章会从IOC分析,IOC设计实现 ,包括 bean工厂 bean定义 以及bean定义注册接口;以及di进行分析,BeanReference 如何找到对应的类进行依赖注入,构造参数依赖定义,并且怎么判断出bean工厂中参数依赖的问题。

IOC分析

IOC:Inversion of Control 控制反转, 也称依赖倒置(反转 )
反转:依赖对象的获得被反转了。 由自己创建,反转为从IOC容器中获 取(和自动注入)
ioc 带来的好处:
  • 代码更简洁,不需要去new要使用的对象了。
  • 面向接口编程,使用者与具体类解耦, 易扩展、替换实现者。
  • 可以方便进行AOP增强。
IOC容器的工作:负责创建、管理类实例,向使用者提供实例。
IOC容器负责来创建类实例对象,需要就从IOC容器中get。也称IOC容器为Bean工厂。
所以说在一些面试上问bean工厂是什么,其实也是在问ioc容器。

IOC容器设计&实现

实现一个ioc容器需要下面三个元素 

  • bean工厂接口,ioc容器是实现抽象接口,向用户提供获取bean基本入口。
/**
 * Bean工厂,IOC容器最顶层的抽象
 */
public interface BeanFactory {
    /**
     * 用户端获取Bean的方法
     * @param beanName
     * @return
     */
    Object getBean(String beanName) throws Exception;
}
  • bean定义接口,向ioc容器规定bean创建成什么样。
  • bean定义的注册接口,如何将bean定义注册进入bean工厂,这就是bean定义注册接口的含义,也是为什么需要这个接口。

就是一个定义注册,我们可以给它定义一个定义注册接口

/**
 * Bean定义注册接口,用来完成Bean定义和Bean工厂之间沟通
 */
public interface BeanDefinitionRegistry {
    /**
     * 想Bean工厂,注册Bean定义信息
     * @param beanName
     * @param beanDefinition
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException;

    /**
     * 获取已经注册的BeanDefinition
     * @param beanName
     * @return
     */
    BeanDefinition getBeanDefinition(String beanName);

    /**
     * 是否包含了已经定义的beanName
     * @param beanName
     * @return
     */
    boolean containsBeanDefinition(String beanName);
}

为什么要用beanDefinitionReistery也是为了解决降低耦合,不至于强一致性。如果直接往里面注册,一旦bean定义修改了什么,则会修改具体的beanfactory.这里很像门面模式。

设计ioc时,一定是多用接口去隔绝开,从而进行扩展。这是spring的经典思想。

每个bean定义有一个唯一的名称  因此有了beanName.

BeanDefinition

BeanDefinition的用途就是告诉bean工厂该如何创建某类bean,

获得获得类的实例的方式  

  • new 构造方法  
Person p = new Person();
  • 工厂 静态方法 
public class PersonFactory{
        public static Person getPerson(){
            return new Person();
        } 
}
  • 工厂 方法 
public class PersonFactory{
    public Person getPerson(){
    return new Person();
    } 
}

以及通过反射也好,还是  直接采用new  ,这里 在工厂中。一般需要知道。

Bean工厂帮我们创建bean时,它需要获知哪些信息,这都是在ioc中需要获取到的。
  • new 构造方法  需要知道 类名
  •  静态工厂方法  需要知道 工厂类名、工厂方法名
  • 成员工厂方法 需要知道 工厂bean名  工厂方法名
并且需要考虑 单例 等信息。
bean定义是给bean工厂创建bean用的,那bean定义接口应向bean工厂提供哪些方法?
  • 获取bean的类名:getBeanClass() :Class
  • 获取工厂方法名:getFactoryMethodName():String
  • 获取工厂bean名:getFactoryBeanName():String
  • 是否是单例等方法:getScope():String isSingleton() isPrototype()
类对象交给IOC容器来管理, 类对象的生命周期中还可能有什么生命阶段事情要做 。这都是需要考虑的
  • 比如创建对象后可能需要进行一些初始化
  • 还有有些对象在销毁时可能要进行一些特定的销毁逻辑(如释放资源)
  • 那就在Bean定义中提供让用户可指定初始化、销毁方法。
  • 对Bean工厂就需提供 getInitMethodName() getDestroyMethodName()
这肯定需要增强这些。

 这里在bean定义的接口上可以知道的。

/**
 * Bean定义,用来指定Bean构建信息接口
 */
public interface BeanDefinition {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    /**
     * 获取Bean的类名
     * @return
     */
    Class<?> getBeanClass();

    /**
     * 
     * @param beanClass
     */
    void setBeanClass(Class<?> beanClass);
    
    String getScope();
    void setScope(String scope);
    boolean isSingleton();
    boolean isPrototype();
    String getFactoryMethodName();
    void setFactoryMethodName(String factoryMethodName);
    String getFactoryBeanName();
    void setFactoryBeanName(String factoryBeanName);
    void setInitMethod(String initMethod);
    String getInitMethod();
    void setDestroyMethod(String destroyMethod);
    String getDestroyMethod();
     
    
    default boolean validate() {
        // 没有BeanCalss信息,只能通过成员工厂来构建对象
        if(getBeanClass() == null) {
            if(StringUtils.isBlank(getFactoryBeanName()) || StringUtils.isBlank(getFactoryMethodName())){
                return false;
            }
        }
        
        // Class存在的情况,还指定FactoryBeanName,构建对象的方式冲突
        if(getBeanClass() != null && StringUtils.isNotBlank(getFactoryBeanName())) {
            return false;
        }
        return true;
    };
}

定义的beandefinition bean定义。

 这是最常见的bean定义实现主要作的也是 定义的最多的 方法实现。获取到 这个在spring中  会解析并产生生成 bean定义。

在  默认的 DefaultBeanFactory  中  需要实现BeanFactory BeanDefinitionRegistry 接口来实现  的

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

这里实现 beanfactory工厂时,首先需要存储 bean定义的容器

 private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

其次在实现时注册bean定义时, 判断 bean定义 是否可以添加

 @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {
        Objects.requireNonNull(beanName, "注册bean需要指定beanName");
        Objects.requireNonNull(beanDefinition, "注册bean需要指定beanDefinition");
        
        if(!beanDefinition.validate()) {
            throw new BeanDefinitionRegisterException("名字为["+beanName+"]的bean定义不合法:"+beanDefinition);
        }
        
        if(containsBeanDefinition(beanName)) {
            throw new BeanDefinitionRegisterException("名字为["+beanName+"]已存在:"+getBeanDefinition(beanName));
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

然后以及获取 bean定义的方法  这些都比较简单。

 @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

在 获取 getBean时, 并创建  对应object对象。

先去缓存里面判断一下beanName对应的对象已经创建好了    这里需要创建一个全局的 bean

 private Map<String, Object> beanMap = new ConcurrentHashMap<>();

构建的方式有三种:构造函数、静态工厂、成员工厂  在进行判断是否  创建 bean等功能

  @Override
    public Object getBean(String beanName) throws Exception {
        return doGetBean(beanName);
    }

    private Object doGetBean(String beanName) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能为空");

        // 先去缓存里面判断一下beanName对应的对象已经创建好了
        Object bean = beanMap.get(beanName);
        if(bean != null) {return bean;}
        
        // 构建的方式有三种:构造函数、静态工厂、成员工厂
        BeanDefinition bd = beanDefinitionMap.get(beanName);
        Objects.requireNonNull(bd, "找不到["+beanName+"]的bean定义信息");
        Class<?> type = bd.getBeanClass();
        if(type != null){
            // 通过构造函数
            if(StringUtils.isBlank(bd.getFactoryMethodName())) {
                bean = createBeanByConstructor(bd);
            }else{ // 通过静态工厂方式构建对象
                bean = createBeanByStaticFactory(bd);
            }
        }else{
            // 成员工厂方式构建对象
            bean = createBeanByFactoryBean(bd);
        }
        
        // 开始Bean生命周期
        if(StringUtils.isNotBlank(bd.getInitMethod())) {
            doInitMethod(bean, bd);
        }
        
        // 对单例bean的处理
        if(bd.isSingleton()){
            beanMap.put(beanName, bean);
        }
        
        return bean;
    }

通过判断beanclass去判断  使用构造方法去创建对应的 类。

    private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
        Object instance = bd.getBeanClass().newInstance();
        return instance;
    }

通过静态工厂方式构建对象 因为是静态方法  则可以采用直接采用 class.getmethod获取到  invoke方法。

  private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
        Class<?> type = bd.getBeanClass();
        Method method = type.getMethod(bd.getFactoryMethodName(), null);
        return method.invoke(type, null);
    }

成员工厂方式构建对象  要获取到 factorybean  通过getbean的方式去获取。

   private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
        String factoryBeanName = bd.getFactoryBeanName();
        Object factoryBean = getBean(factoryBeanName);
        Method method = factoryBean.getClass()
                .getMethod(bd.getFactoryMethodName(), null);
        return method.invoke(factoryBean, null);
    }

最终去创建出一个正常的bean出来。

以及  最后的  开始Bean生命周期

  // 开始Bean生命周期
        if(StringUtils.isNotBlank(bd.getInitMethod())) {
            doInitMethod(bean, bd);
        }
        
  private void doInitMethod(Object bean, BeanDefinition bd) throws Exception {
        Method method = bean.getClass().getMethod(bd.getInitMethod(), null);
        method.invoke(bean, null);
    }

对单例bean的处理

 // 对单例bean的处理
        if(bd.isSingleton()){
            beanMap.put(beanName, bean);
        }

这里都需要作的操作。

最后添加针对单例Bean执行销毁方法 

@Override
    public void close() throws IOException {
        // 针对单例Bean执行销毁方法
        for(Map.Entry<String, BeanDefinition> e : beanDefinitionMap.entrySet()) {
            String beanName = e.getKey();
            BeanDefinition definition = e.getValue();
            
            if(definition.isSingleton() && StringUtils.isNotBlank(definition.getDestroyMethod())) {
                Object instance = beanMap.get(beanName);
                if(instance == null) {continue;}
                
                Method m = null;
                try {
                    m = instance.getClass().getMethod(definition.getDestroyMethod(), null);
                    m.invoke(instance, null);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

设计一个简单的 ioc容器 。

DI分析

这里就涉及到依赖的分析,包括构造参数依赖 属性依赖  。

依赖注入的本质 是给入构造参数值,给属性赋值

因为赋值,才要考虑怎么 赋值给某个值。

对于参数值、属性值  有可能是 直接值、bean依赖

对于这种构造参数 的注入  需要找到依赖关系。

直接值会有哪几种情形   这里对于 mybatis等比较热的框架都会考虑的事情。
  • 基本数据类型值、String
  • 数组、集合
  • Properties
  • map
本质:参数值、属性值,都是值。bean工厂在进行依赖注入时,就是给入值。

在DI设计时,如何告诉bean工厂该给入什么构造参数值?即如何来定义参数依赖?  

如何来定义属性依赖?  
对于构造参数的值,如何去定义。
这都需要如何怎么去解决这个问题。
如何考虑 这些构造参数 ,怎么进行依赖设计。
参数可以多个,用什么存储
  • 集合:List
按参数顺序放入List  顺序来表示  参数得顺序。
参数值可以是直接值,也可以是bean依赖,如何表示:Object?
  • 可以,也只能用Object了。
  • List<Object> constructorArgumentValues
如果用Object来表示值,如何区分是Bean依赖?
为Bean依赖定义一种数据类型 (BeanReference),bean工厂在构造Bean实
例时,遍历判断参数是否是BeanReference, 如是则替换为依赖的bean实例。
/**
 * 表示Bean依赖的类
 */
public class BeanReference {
    private String beanName;
    public BeanReference(String beanName) {
        this.beanName = beanName;
    }

    public String getBeanName() {
        return beanName;
    }
}
如果直接值是数组、集合等,它们的元素中有的是bean依赖,怎么处理?
  元素值还是用BeanReference,同样bean工厂 在使用时需遍历替换。
在BeanDefinition中增加获得构造参数值的接口

DI实现

首先需要把bean定义中的构造参数引用转为真实的值,在DefaultBeanFactory中增加一个方法来干这事。
使用 getConstructorArgumentValues  方法去转换成真实得事情。
    private Object[] getConstructorArgumentValues(BeanDefinition bd) throws Exception {
        List<?> args = bd.getConstructorArgumentValues();
        return getRealVaues(args);
    }

 整个值的解析转换。

这里对于ioc进行创建出来的对象, 如果带有参数的情况。 则需要 创建类进行修改。

    private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
        Object instance = null;
        if(CollectionUtils.isEmpty(bd.getConstructorArgumentValues())) {
            instance = bd.getBeanClass().newInstance();
        }else{
            Object[] args = getConstructorArgumentValues(bd);
            if(args == null) {
                instance = bd.getBeanClass().newInstance();
            }else {
                return determineConstructor(bd, args).newInstance(args);
            }
        }
        return instance;
    }

找对应的构造函数,有参数了,如何断定是哪个构造方法、哪个工厂方法

  • 方法是可以重载的
  • 形参定义时可能是接口或父类,实参则是具体的子实现
  • 反射提供的获取的构造方法、方法的API
判断逻辑:
  •  1 先根据参数的类型进行精确匹配查找,如未找到,则进行第2步查找;
  •  2获得所有的构造方法,遍历,通过参数数量过滤,再比对形参类型与实参类型。
determineConstructor  用来判断  选择使用那个构造参数。
包括 创建 一个方法
  private Constructor determineConstructor(BeanDefinition bd, Object[] args) throws Exception {

找到无参构造方法  进行判断返回

 Constructor ct = null;
        if(args == null) {return bd.getBeanClass().getConstructor(null);}
        Class<?>[] paramType = new Class[args.length];

对于原型Bean,从第二次开始获取Bean实例时,可以直接从第一次缓存中获取构造方法

        // 对于原型Bean,从第二次开始获取Bean实例时,可以直接从第一次缓存中获取构造方法
        ct = bd.getConstructor();
        if(ct != null) {return ct;}

根据参数类型获取构造方法

  int j = 0;
        for(Object p : args) {
            paramType[j++] = p.getClass();
        }

找到对应的class.

 根据 类型 找到 对应的构造方法

ct = bd.getBeanClass().getConstructor(paramType);

当没找到 时,遍历所有的 构造参数,判断形参跟实参进行类型匹配 

 if(ct == null) {
            Constructor<?>[]  cts = bd.getBeanClass().getConstructors();
            // 判断逻辑:先判断参数数量,依次判断形参跟实参进行类型匹配
            outer: for(Constructor<?> c : cts) {
                Class<?>[] paramterTypes = c.getParameterTypes();
                if(paramterTypes.length == args.length) {
                    for(int i = 0; i < paramterTypes.length; i++) {
                        if(!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
                            continue outer;
                        }
                    }
                    ct = c;
                    break outer;
                }
            }
        }

最后进行调用构造方法

 if(ct != null) {
            if(bd.isPrototype()) {
                bd.setConstructor(ct);
            }
        }else {
            throw new Exception("找不到对应的构造方法:"+bd);
        }

这里主要两点 主要是要怎么确认 对于bean参数的类型怎么用来区分,以及构造实例时,对参数怎么判断,选择使用那个构造参数 ,判断的逻辑就是 先根据参数的类型进行精确匹配查找,找到了最好,如果找不到 则对比形参类型与实参类型,进行判断。

当我们判断出构造方法或工厂方法后,对于原型Bean,下次获取Bean是否就可以省去
判断了?

也就是说,对于原型bean,我们可以缓存下这个构造方法或工厂方法。

这都需要做的。

  private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
        Class<?> type = bd.getBeanClass();
        Object[] realArgs = getRealVaues(bd.getConstructorArgumentValues());
        Method method = determineFactoryMethod(bd, realArgs, type);
        return method.invoke(type, realArgs);
    }

determineFactoryMethod 也需要判断具体的方法

 循环依赖如何处理 

构造实例对象时的循环依赖,会陷入僵死局面,是不允许构造实例时的循环依赖的。如何发
现循环依赖?
加入一个正在构造的bean的记录,每个bean开始构造时加入到该记录中,构造完成后
从记录中移除。如果有依赖,先看依赖的bean是否在构造中,如是就构成了循环依赖,抛出异常

属性依赖

某个属性依赖某个值

描述属性依赖

定义属性依赖描述实体类PropertyValue   ,对于多个依赖,则 采用list来存储,
/**
 * 属性值类型,用作属性依赖注入
 */
public class PropertyValue {
    private String name;
    private Object value;

    public PropertyValue(String name, Object value) {
        super();
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

创建bean成功过后,需要 执行      setPropertyDIVAlues(bd, bean); 完成属性注入。

 private void setPropertyDIVAlues(BeanDefinition bd, Object instance) throws Exception {
        if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
            return;
        }
        for (PropertyValue pv : bd.getPropertyValues()) {
            if (StringUtils.isBlank(pv.getName())) {
                continue;
            }
            Class<?> clazz = instance.getClass();
            Field p = clazz.getDeclaredField(pv.getName());

            p.setAccessible(true);

            Object rv = pv.getValue();
            Object v = null;
            if (rv == null) {
                v = null;
            } else if (rv instanceof BeanReference) {
                v = this.doGetBean(((BeanReference) rv).getBeanName());
            } else if (rv instanceof Object[]) {
                // TODO 处理集合中的bean引用
            } else if (rv instanceof Collection) {
                // TODO 处理集合中的bean引用
            } else if (rv instanceof Properties) {
                // TODO 处理properties中的bean引用
            } else if (rv instanceof Map) {
                // TODO 处理Map中的bean引用
            } else {
                v = rv;
            }

            p.set(instance, v);

        }
    }

针对属性注入是相对来说简单的。只需要判断需要引入的类型就可以。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踩踩踩从踩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值