设计模式终章----手写IOC容器

2021-11-15 15:32:56 浏览数 (1)

手写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,来看一下它的源码

代码语言:javascript复制
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来描述的,如下面的配置文件

代码语言:javascript复制
<bean id="userDao" class="com.dao.impl.UserDaoImpl"></bean>

bean标签还有很多属性: scope,init-method,destory-method等


BeanDefinitionReader接口

BeanDefinitionReader接口定义的功能:

代码语言:javascript复制
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

代码语言:javascript复制
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注册中心

代码语言:javascript复制
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的

代码语言:javascript复制
    private final Map<String, BeanDefinition> beanDefinitionMap;

创建容器

ClassPathXmlApplicationContext对Bean配置资源的载入是从refresh()方法开始的。

refresh()方法是一个模板方法,规定了IOC容器的启动流程,有些逻辑要交给器其子类实现。

他对Bean配置资源进行载入,ClassPathXmlApplicationContext通过调用父类AbstractApplicationContext的refresh()方法启动整个IOC容器对Bean定义的载入过程.


手写SpringIOC容器

现在要对下面的配置文件进行解析,并自定义Spring框架的IOC对涉及到的对象进行管理

代码语言:javascript复制
<?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标签里面的数据

代码语言:javascript复制
//用来封装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对象

代码语言:javascript复制
//用来存储和管理多个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数据

代码语言:javascript复制
//用来封装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对象的名称
代码语言:javascript复制
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–注册表接口的子实现类

代码语言:javascript复制
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数据
代码语言:javascript复制
//用来解析配置文件的,该接口只是定义了规范
public interface BeanDefinitionReader
{
    //获取注册表对象
  BeanDefinitionRegistry getRegistry();
    //加载配置文件,并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;
}

XmlBeanDefinitionReader子实现类

XmlBeanDefinitionReader类专门用来解析xml配置文件的,该类实现BeanDefinitionReader接口,并实现接口中的两个功能

dom4j的依赖

代码语言:javascript复制
<dependency>

<groupId>dom4j</groupId>

<artifactId>dom4j</artifactId>

<version>1.6.1</version>

</dependency>

需要进行解析的xml格式,参考下面:

具体代码实现:

代码语言:javascript复制
//针对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对象

代码语言:javascript复制
//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对象的创建
代码语言:javascript复制
//定义非延时加载功能---继承BeanFactory接口
public interface ApplicationContext extends BeanFactory
{
    void refresh()throws Exception;
}

AbstractApplicationContext类
  • 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中顶一个Map集合,作为bean对象存储的容器
  • 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则
  • BeanDefinitionReader类型的对象的创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader的那个子实现类对象
代码语言:javascript复制
//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()方法,并实现依赖注入的操作
代码语言:javascript复制
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方法
代码语言:javascript复制
//工具类,负责拼接字符串
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类型的字段完成依赖注入

代码语言:javascript复制
<?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>

测试

代码语言:javascript复制
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源码,通过该案例的实现,可以降低学习成本

0 人点赞