Spring - IOC容器基本实现

1、Spring IOC容器实现源码

1、配置文件的封装:Resource接口

Resource资源抽象接口:来封装底层资源
1、抽象了所有Spring内部使用到的底层资源:File、URL、ClassPath等
2、不同来源的资源文件有相应的Resource实现:FileSystemResource、UrlResource、ClassPathResource等
3、Spring配置文件的读取是通过ClassPathResource进行封装的

//封装配置文件:将配置文件封装为Resource类型的实例方法
Resource res = new ClassPathResource("beans.xml");
2、XMLBeanFactory的初始化

XMLBeanFactory的初始化有若干方法,Spring中提供了很多构造函数:

1、XmlBeanFactory类源码:里面提供了两个构造方法,可实现XMLBeanFactory初始化与加载配置文件资源

public class XmlBeanFactory extends DefaultListableBeanFactory {

   private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

   public XmlBeanFactory(Resource resource) throws BeansException {
      //构造方法的重载,内部调用了下面的构造方法
      this(resource, null);
   }

   public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
      super(parentBeanFactory);
      //加载资源的真正实现,但是在加载之前还有一个调用父类构造函数初始化的过程
      this.reader.loadBeanDefinitions(resource);
   }
}

2、跟踪到父类DefaultListableBeanFactory中:一个构造方法,只是实例化BeanFactory,但是并没有加载资源

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    //和子类相同,这个父类仍然需要调用父类构造函数初始化
    public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
       super(parentBeanFactory);
    }
}

3、跟踪父类到父类:AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
      implements AutowireCapableBeanFactory {
   /**
    * Create a new AbstractAutowireCapableBeanFactory.
    */
   public AbstractAutowireCapableBeanFactory() {
      super();
      /**
      * 忽略给定接口的自动装配功能,这样做的目的是什么?
      * 当A中有属性B时,那么Spring在获取A的Bean的时候,如果属性B还没有初始化,那么Spring会自动初始化B
      * 但是在某些特定场景下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。
      * Spring这样介绍的:自动装配是忽略给定的依赖接口,典型应用就是通过其他方式解析Application上下文依赖
      * 类似于BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware注入
      */
      ignoreDependencyInterface(BeanNameAware.class);
      ignoreDependencyInterface(BeanFactoryAware.class);
      ignoreDependencyInterface(BeanClassLoaderAware.class);
   }
}
3、加载(读取)配置文件:XmlBeanDefinitionReader
/**
1、XmlBeanDefinitionReader中loadBeanDefinitions的方法:
  1、封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource类使用EncodedResource进行封装
  2、获取输入流,从Resource中获取对应的InputStream并构造InputSource
  3、通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions
*/
	@Override
  public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		InputStream inputStream = encodedResource.getResource().getInputStream();
		InputSource inputSource = new InputSource(inputStream);
		return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	} 

/**
2、进入doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	1、加载XML文件,并得到对应的Document
	2、根据返回的Document注册Bean信息,解析及注册BeanDefinitions
  这两个步骤就是Spring容器部分的实现基础,就是我们手写的Spring IOC容器的思想
*/
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource){
		Document doc = doLoadDocument(inputSource, resource);
		return registerBeanDefinitions(doc, resource);
	}
/**
3、加载配置文件,获取Document
	同样XmlBeanDefinitionReader对于文档的读取并没有亲力亲为,而是委托了DocumentLoader
	但是DocumentLoader是个接口,真正调用loadDocument()方法的是实现类DefaultDocumentLoader
*/
 protected Document doLoadDocument(InputSource inputSource, Resource resource){
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(),          this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
 }

/**
4、defaultDocumentLoader:
	1、创建DocumentBuilderFactory
	2、通过DocumentBuilderFactory创建DocumentBuilder
	3、解析inputSource来返回Document对象
*/
public class DefaultDocumentLoader implements DocumentLoader {
	@Override
	public Document loadDocument(..){
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

	protected DocumentBuilderFactory createDocumentBuilderFactory(..) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		return factory;
	}

	protected DocumentBuilder createDocumentBuilder(..){
		DocumentBuilder docBuilder = factory.newDocumentBuilder();
		return docBuilder;
	}
}
4、解析及注册BeanDefinitions

上面我们已经把文件转成了Document,接下来就是提取及注册bean就是我们的重头戏了。

当程序已经拥有XML文档文件的Document实例对象时,就会调用下面这个方法了:

/**
1、registerBeanDefinitions:
在这个方法中使用了单一职责,将逻辑处理委托给单一的类进行处理,这个类就是BeanDefinitionDocumentReader
BeanDefinitionDocumentReader是接口,实例化的工作在createBeanDefinitionDocumentReader()中完成。
通过这个方法BeanDefinitionDocumentReader真正的类型是DefaultcreateBeanDefinitionDocumentReader
DefaultcreateBeanDefinitionDocumentReader是其实现类
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //记录加载bean前BeanDefinition的个数
   int countBefore = getRegistry().getBeanDefinitionCount();
   //加载及注册Bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   //记录本次加载的BeanDefinition个数
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

/**
2、进入DefaultcreateBeanDefinitionDocumentReader后:
	发现registerBeanDefinitions方法时为了提取root作为参数继续注册Bean
	doRegisterBeanDefinitions(root);这个方法时真正的xml解析阶段,前面都是准备阶段
*/
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

/**
3、doRegisterBeanDefinitions(Element root)解析及注册bean
*/
protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
	
    if (this.delegate.isDefaultNamespace(root)) {
        //处理profile属性
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        ....
    }
	//解析前留给子类实现,空方法,模板方法模式
    preProcessXml(root);
    //专门处理解析
    parseBeanDefinitions(root, this.delegate);
    //解析后留给子类实现,空方法,模板方法模式
    postProcessXml(root);

    this.delegate = parent;
}   

/**
 4、parseBeanDefinitions(root, this.delegate);
 	因为Spring的xml配置有两大Bean声明:
 	一个是默认的:<bean id="" class=""/>
 	另一个是自定义的:<tx:annotation-driven/>
 	
 	如果采用Spring默认的Spring当然知道怎么做,但是如果是自定义的,就需要实现一些接口及配置了
*/

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //处理自定义的
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        //处理默认的
        delegate.parseCustomElement(root);
    }
}

2、手写一个Spring IOC容器

Spring IOC的底层原理:

1、容器中存放的两个组件:

@Data
public class Car {
    private String name;
    private String length;
    private String width;
    private String height;
    private Wheel wheel;
}

@Data
public class Wheel {
    private String brand;
    private String specification ;
}

2、需要被Spring容器解析的XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="wheel" class="com.hh.bean.Wheel">
        <property name="brand" value="Michelin" />
        <property name="specification" value="265/60 R18" />
    </bean>

    <bean id="car" class="com.hh.bean.Car">
        <property name="name" value="Mercedes Benz G 500"/>
        <property name="length" value="4717mm"/>
        <property name="width" value="1855mm"/>
        <property name="height" value="1949mm"/>
        <property name="wheel" ref="wheel"/>
    </bean>
</beans>

3、自定义的IOC容器:

  1. 加载 xml 配置文件,遍历其中的标签
  2. 获取标签中的 id 和 class 属性,加载 class 属性对应的类,并创建 bean
  3. 遍历标签中的标签,获取属性值,并将属性值填充到 bean 中
  4. 将 bean 注册到 bean 容器中
public class SpringIOC {

    //使用一个Map来做Spring的容器,作用就是放置Spring管理的对象
    private Map<String, Object> beanMap = new HashMap<>();

    public SpringIOC(String location) throws Exception {
        loadBeans(location);
    }

    public Object getBean(String name) {
        Object bean = beanMap.get(name);
        if (bean == null) {
            throw new IllegalArgumentException("there is no com.hh.bean with name " + name);
        }

        return bean;
    }

    private void loadBeans(String location) throws Exception {
        /**
         * 1、读取配置文件
         *      javax.xml.parsers 包中的DocumentBuilderFactory用于创建DOM模式的解析器对象 ,
         *      DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,
         *      但该类提供了一个newInstance方法 ,这个方法会根据本地平台默认安装的解析器,
         *      自动创建一个工厂的对象并返回。
         *   1、调用 DocumentBuilderFactory.newInstance() 方法得到创建 DOM 解析器的工厂
         *   2、调用工厂对象的 newDocumentBuilder方法得到 DOM 解析器对象。
         *   3、把要解析的 XML 文档转化为输入流,以便 DOM 解析器解析它
         *   4、调用 DOM 解析器对象的 parse() 方法解析 XML 文档,得到代表整个文档的 Document 对象,
         *      利用DOM特性对整个XML文档进行操作。
         *   5、得到 XML 文档的根节点:doc.getDocumentElement();---<beans></beans>
         *   6、得到节点的子节点:root.getChildNodes();---<com.hh.bean></com.hh.bean>
         */
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        InputStream inputStream = new FileInputStream(location);
        Document doc = docBuilder.parse(inputStream);
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getChildNodes();


        /**
         * 2、遍历Bean标签,创建Bean,并将其放入map容器中
         *      1、<com.hh.bean id="wheel" class="com.hh.com.hh.bean.Wheel">
         *           1、获取Bean的id
         *           2、获取Bean的className:这是个全类名
         *
         *      2、根据反射创建Bean对象:beanClass = Class.forName(className);
         *             如果bean的全类名不正确,抛出异常
         *
         *      3、获得beanClass后,将其对应的对象创建出来:bean = beanClass.newInstance();
         *           1、设计的容器默认调用无参构造函数创建对象,如果没有会抛出异常
         *           2、暂时没有使用带参构造函数逻辑
         */
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                String id = ele.getAttribute("id");
                String className = ele.getAttribute("class");

                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    throw new RuntimeException("请检查Bean的class配置是否正确"+className);
                }

                // 创建bean对象
                Object bean = null;
                try {
                    bean = beanClass.newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("你的Bean没有空参数构造函数");
                }

                /**
                 * 遍历 <property> 标签:将Bean的属性注入
                 *     注入分为两种情况:
                 *     1、简单--->value属性注入:if(value != null)
                 *     2、麻烦--->其他Bean注入:else if(ref!=null)
                 *
                 *  1、<property name="brand" value="Michelin"/>
                 *      1、获取属性name ,2、获取属性value
                 *  2、<property name="wheel" ref="wheel"/>
                 *      1、获取属性name ,2、获取属性ref      
                 */
                NodeList propertyNodes = ele.getElementsByTagName("property");
                for (int j = 0; j < propertyNodes.getLength(); j++) {
                    Node propertyNode = propertyNodes.item(j);
                    if (propertyNode instanceof Element) {
                        Element propertyElement = (Element) propertyNode;
                        String name = propertyElement.getAttribute("name");
                        String value = propertyElement.getAttribute("value");

                        // 利用反射将 com.hh.bean 相关字段访问权限设为可访问
                        Field declaredField = bean.getClass().getDeclaredField(name);
                        declaredField.setAccessible(true);

                        if (value != null && value.length()>0) {
                            // 将属性值填充到相关字段中
                            declaredField.set(bean, value);
                        } else {
                            String ref = propertyElement.getAttribute("ref");
                            if (ref == null || ref.length()==0) {
                                throw new IllegalArgumentException("ref config error");
                            }
                            // 将引用填充到相关字段中
                            declaredField.set(bean, getBean(ref));
                        }

                        /**
                         * 3、将 com.hh.bean 注册到 com.hh.bean 容器中
                         */
                        registerBean(id, bean);
                    }
                }
            }
        }
    }

    private void registerBean(String id, Object bean) {
        beanMap.put(id, bean);
    }
}

4、测试SpringIOCTest:

public class SpringIOCTest {
    @Test
    public void getBean() throws Exception {
        String location = SpringIOC.class.getClassLoader().getResource("beans.xml").getFile();
        SpringIOC bf = new SpringIOC(location);
        Wheel wheel = (Wheel) bf.getBean("wheel");
        System.out.println(wheel);
        Car car = (Car) bf.getBean("car");
        System.out.println(car);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我一直在流浪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值