Spring系列之手写注解与配置文件的解析

目录

引入

在前面咱们已经完成了IOC,DI,AOP的实现,基本的功能都已经完成了,咱们的手写框架也能勉强使用起来。为了让咱们的框架可以使用起来比较简单,这一节咱们来实现注解和xml的配置。git

tips

本章的xml和注解的功能都是为实现bean的建立,其余如aop等功能可仿造实现。github

为何要加注解和xml配置

若是有同窗测试过咱们写好的框架,可能会感觉到使用起来很是麻烦,在测试的时候咱们须要显示的来定义bean以及运行过程当中须要的其余对象。spring

public void test() throws Exception {
        DefaultBeanDefinition bd = new DefaultBeanDefinition();
        bd.setClazz(User.class);
        bd.setSingleton(true);
        bd.setBeanFactoryName("TestFactory");
        bd.setCreateBeanMethodName("createMethod");
        bd.setStaticCreateBeanMethodName("staticCreateMethod");

        factory.register(bd, "user");
        bd = new DefaultBeanDefinition();
        bd.setClazz(BeforeAdvice.class);
        factory.register(bd, "myBeforeAdvice");

        AopProxyCreator aapc = new AopProxyCreator();
        aapc.setBeanFactory(factory);
        factory.registerBeanPostProcessor(aapc);
        // 向AdvisorAutoProxyCreator注册Advisor
        aapc.register(new RegexMatchAdvisor("myBeforeAdvice", "execution(* bean.User.*())", new RegexExpressionPointCutResolver()));
        User user = (User) factory.doGetBean("user");
        user.sayHello();
    }
复制代码

如上为测试一个AOP的功能,须要定义不少的对象来完成功能,这还只是一个对象的功能加强,在实际使用中确定会有大量的实例。这样在使用起来就变得及其麻烦了。参考Spring中,能够经过xml和annotation的方式来简化类定义或者其余一些处理。api

注解和xml的整个处理过程

在实际实现以前,咱们先来宏观的看一些注解和xml是如何来解析的。 bash

xml

基本上在spring中xml就是这样工做的,同理,注解也差很少是这样一个过程。 网络

注解

上面的这一过程实际上就是咱们须要实现的功能,如今咱们就根据以上过程进行实现吧。框架

XML

定义XML标记

这一小节的内容并不重要,实际就咱们的开发中做用并不大,稍微了解便可。没兴趣的能够直接跳到下一节。dom

定义xml标记的方式有dtd和xsd两种,假设我想定义一个下面这样的xml标记:post

<?xml version="1.0"?>
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body> </note> 复制代码

那么咱们既能够用dtd实现,也能够用xsd实现:学习

dtd

<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
复制代码

xsd

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3schools.com"
xmlns="http://www.w3schools.com"
elementFormDefault="qualified">

<xs:element name="note">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="to" type="xs:string"/>
      <xs:element name="from" type="xs:string"/>
      <xs:element name="heading" type="xs:string"/>
      <xs:element name="body" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

</xs:schema>
复制代码

这节内容只是稍微提下,有兴趣的能够搜一些资料学习。

加载xml文件

要来加载文件那么首先咱们须要知道这个文件的位置,很明显这个位置须要由用户来指定,由用户来告诉咱们须要加载那些配置文件,那么用户该如何来指定呢?

这里咱们须要定义新的类和接口来完成这件事,这个新定义的类用来完成xml文件的加载,解析以及对bean实例的建立和注册等。同时该类是给用户使用的,在bean建立后还须要让用户可以取到容器中的实例,即该类还须要具有beanfactory的部分功能。

ApplicationContext

如何加载配置文件

说到加载,首先咱们须要明白这些配置文件可能会以什么样的形式展示,通常来讲有着如下的类型:

  • FileSystem:从本地文件加载
  • URL:从网络获取
  • ClassPath:在项目中读取配置

可能还有其余不一样的形式,固然这不重要。咱们须要明白的是很明显不一样形式的内容加载的方式确定是不一样的,好比经过文件系统加载的是得到一个文件,而url形式的是从网络获取数据。因此咱们须要为每一种方式都提供个性的加载方式。

加载的方式不同,那么解析呢?

解析xml,咱们都是须要获取到xml的流信息,都是解析xml的InputStream,因此虽然加载方式各不相同,可是解析的方法是能够通用的。

根据上面的分析,咱们能够知道,加载的过程实际上就是获取一个InputStream的过程,很明显每一种方式获取配置文件都须要最终返回一个InputStream,这里咱们定义一个获取InputStream的接口。

InputStream

对于配置文件的加载实际上无论经过哪种方式,实际上都是对文件进行操做,为了方便可以对解析提供一致的接口,咱们须要对不一样方式加载进行抽象,使其能接受任意类型的加载方式对象。这里咱们定义Resource接口来表示xml资源,不一样的实现类表示不一样的加载方式。Resource接口具有一部分文件的特性。

Resource

到了如今咱们还有一个很重要的问题,就是咱们如何来分辨当前须要加载的配置是属于哪种类型呢?只有解决了这一问题才能正确的解析。

这里咱们使用前缀匹配的方式来分配不一样类型的配置文件加载器,好比:

  • FileSystem:使用file:前缀
  • ClassPath:使用classpath:前缀
  • URL:可使用url:前缀,或者直接使用instanceof来校验对象

经过不一样的前缀来来返回不一样的对象,若是看过前面几节的同窗应该立刻就能反应过来这里确定要用工厂模式了[笑]。

那么加载配置文件这一行为在何时进行呢?

考虑到类ApplicationContext是用户使用框架的入口,该类还包含获取bean实例的操做,要保证在该类实例化完成后让ApplicationContext具有获取实例的能力,那么加载配置文件的功能就须要在ApplicationContext的构造方法中完成。

结合以上分析,对ApplicationContext类图作出修改。

ResourceFactory

如何解析配置文件

加载已经完成了,就是说咱们如今已经取到不一样的Resource,那么如今如何对其进行解析呢?

对于xml类型的文件解析咱们这里使用dom4j,这个东西不必深刻研究,要用到的时候查下api就好了。对于xml的解析就是获取节点,而后对获取节点的内容。回顾咱们以前在作IOC的时候,建立一个bean首先是须要获取BeanDefinition。xml这里也同样,根据解析到的内容构造BeanDefinition,经过BeanDefinitionRegistery注册。在对bean实例化时取用。因此很明显咱们须要定义一个新的接口用于解析Resource,将解析后的内容封装为BeanDefinition并注册。

Reader

好了,到这里关于xml的加载和解析基本就完成了,而对于annotation的解析流程基本也是这样的,用户指定须要扫描的包,框架遍历包所在目录及子目录,记录下相应被注解修饰的类,反射生成class对象,获取class对象数据生成BeanDefinition。类图在上面也已经体现出来了。后面就须要咱们去写代码了。

相关的代码已经托管到github