Spring系列之手写注解与配置文件的解析
目录
引入
在前面我们已经完成了IOC,DI,AOP的实现,基本的功能都已经完成了,我们的手写框架也能勉强使用起来。为了让我们的框架能够使用起来比较简单,这一节我们来实现注解和xml的配置。
tips
本章的xml和注解的功能都是为实现bean的创建,其他如aop等功能可仿造实现。
为什么要加注解和xml配置
如果有同学测试过我们写好的框架,可能会感受到使用起来非常麻烦,在测试的时候我们需要显示的来定义bean以及运行过程中需要的其他对象。
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的方式来简化类定义或者其他一些处理。
注解和xml的整个处理过程
在实际实现之前,我们先来宏观的看一些注解和xml是如何来解析的。
基本上在spring中xml就是这样工作的,同理,注解也差不多是这样一个过程。
上面的这一过程实际上就是我们需要实现的功能,现在我们就根据以上过程进行实现吧。
XML
定义XML标记
这一小节的内容并不重要,实际就我们的开发中作用并不大,稍微了解即可。没兴趣的可以直接跳到下一节。
定义xml标记的方式有dtd和xsd两种,假设我想定义一个下面这样的xml标记:
<?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的部分功能。
如何加载配置文件
说到加载,首先我们需要明白这些配置文件可能会以什么样的形式展现,一般来说有着以下的类型:
- FileSystem:从本地文件加载
- URL:从网络获取
- ClassPath:在项目中读取配置
可能还有其他不同的形式,当然这不重要。我们需要明白的是很明显不同形式的内容加载的方式肯定是不同的,比如通过文件系统加载的是获得一个文件,而url形式的是从网络获取数据。所以我们需要为每一种方式都提供个性的加载方式。
加载的方式不一样,那么解析呢?
解析xml,我们都是需要获取到xml的流信息,都是解析xml的InputStream,所以虽然加载方式各不相同,但是解析的方法是可以通用的。
根据上面的分析,我们可以知道,加载的过程实际上就是获取一个InputStream的过程,很明显每一种方式获取配置文件都需要最终返回一个InputStream,这里我们定义一个获取InputStream的接口。
对于配置文件的加载实际上不管通过哪一种方式,实际上都是对文件进行操作,为了方便能够对解析提供一致的接口,我们需要对不同方式加载进行抽象,使其能接受任意类型的加载方式对象。这里我们定义Resource接口来表示xml资源,不同的实现类表示不同的加载方式。Resource接口具备一部分文件的特性。
到了现在我们还有一个很重要的问题,就是我们如何来分辨当前需要加载的配置是属于哪一种类型呢?只有解决了这一问题才能正确的解析。
这里我们使用前缀匹配的方式来分配不同类型的配置文件加载器,比如:
- FileSystem:使用file:前缀
- ClassPath:使用classpath:前缀
- URL:可以使用url:前缀,或者直接使用instanceof来校验对象
通过不同的前缀来来返回不同的对象,如果看过前面几节的同学应该马上就能反应过来这里肯定要用工厂模式了[笑]。
那么加载配置文件这一行为在什么时候进行呢?
考虑到类ApplicationContext是用户使用框架的入口,该类还包含获取bean实例的操作,要保证在该类实例化完成后让ApplicationContext具备获取实例的能力,那么加载配置文件的功能就需要在ApplicationContext的构造方法中完成。
结合以上分析,对ApplicationContext类图做出修改。
如何解析配置文件
加载已经完成了,就是说我们现在已经取到不同的Resource,那么现在如何对其进行解析呢?
对于xml类型的文件解析我们这里使用dom4j,这个东西没必要深入研究,要用到的时候查下api就行了。对于xml的解析就是获取节点,然后对获取节点的内容。回顾我们之前在做IOC的时候,创建一个bean首先是需要获取BeanDefinition。xml这里也一样,根据解析到的内容构造BeanDefinition,通过BeanDefinitionRegistery注册。在对bean实例化时取用。所以很明显我们需要定义一个新的接口用于解析Resource,将解析后的内容封装为BeanDefinition并注册。
好了,到这里关于xml的加载和解析基本就完成了,而对于annotation的解析流程基本也是这样的,用户指定需要扫描的包,框架遍历包所在目录及子目录,记录下相应被注解修饰的类,反射生成class对象,获取class对象数据生成BeanDefinition。类图在上面也已经体现出来了。后面就需要我们去写代码了。
相关的代码已经托管到github