目录
Spring 系列之 IoC 的原理及手动实现
Spring 系列之 DI 的原理及手动实现
Spring 系列之 AOP 的原理及手动实现
引入
在前面我们已经完成了 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. 类图在上面也已经体现出来了. 后面就需要我们去写代码了.
相关的代码已经托管到 https://github.com/lliyu/myspring
来源: https://juejin.im/post/5c2451c66fb9a049f819573c