前方高能预警: 本文将会有大量代码出没.
1. 背景
在看一些框架源码的时候, 可以看见他们很多都会和 Spring 去做结合. 举个例子 dubbo 的配置:
很多人其实配置了也就配置了, 没有去过多的思考: 为什么这么配置 spring 就能识别, dubbo 就能启动?
如果你也需要做一个框架和 Spring 结合, 或者你想知道 Spring 其他框架是如何和 Spring 做结合的, 那么你应该了解一下 Spring 的扩展机制.
2. 如何扩展
本篇文章想从 Spring 的两个流程去介绍如何扩展, 一个是容器初始化流程, 一个是 Bean 的创建流程进行将.
2.1 容器的初始化
要想使用 Spring, 第一步肯定是需要先让容器初始化. 在 AbstractApplicationContext 中有一个 refresh 方法定义了容器如何进行刷新:
在 refresh 中的具体流程如下图:
其中比较常见的扩展在加载 BeanDefinition 中和执行 BeanPostProcessor. 下面讲述一下如何进行这两个的扩展.
2.1.1 加载 BeanDefinition
在介绍加载 BeanDefinition 之前, 先让我们了解一下什么是 BeanDefinition, 顾名思义 BeanDefinition 描述 Bean 的信息的, 比如他的 class 信息, 属性信息, 是否是单例, 是否延迟加载等.
如何加载呢? 一般有两种手段, 一个是通过我们的 xml, 一个是通过一些扩展手段.
xml 加载如下:
我们在 spring 的 xml 中配置这样一个 bean 的定义, 他会进行解析然后转换成我们的 BeanDefinition.
还有种方式是通过 xml schema 扩展的方式, 关于 xsd 的一些详细介绍可以参考这篇文章: Spring 中的 xml schema 扩展机制 . 有些同学会问不是还有个注解的方式吗? 我们在学的时候一般书上都写 xml 和注解两种方式, 注解其实也是使用了 xml schema 的扩展机制, 等会我会细讲.
2.1.1.1 xml schema 扩展
什么是 xml schema 的扩展呢?
Spring 允许你自己定义 xml 的的结构并且可以用自己的 bean 解析器进行解析. 这里参考一下 Spring 中的 xml schema 扩展机制 进行自定义扩展的 4 个步骤:
编写一个 xml schema 文件描述的你节点元素.
在 resources/META-INF / 目录下定义 demo.xsd 文件. 这里定义了一个 demo 的节点元素, 其中定义了一个 name 字段.
编写一个 NamespaceHandler 的实现类
编写一个或者多个 BeanDefinitionParser 的实现 (关键步骤).
注册上述的 schema 和 handler. 在 resources/META-INF/ 目录下面创建 spring.handler 文件输入:
, 这一步将我们之前的标签的 url 映射到我们 NamespaceHandler. 再创建一个 spring.schemas 文件, 输入:
这一步将 xsd 的 url 进行了映射.
回到注解, 大家配置注解的时候一般都是使用下图进行配置:
但是可以看见其依然是使用 xml schema 扩展进行处理, 在 Spring 中有个叫 ContextNamespaceHandler, 注册很多解析器:
其中有一个解析器是 compnent-scan, 在他的 parse 方法中定义了如何进行注解扫描, 获取注解:
利用这个扩展机制的还有 AOP,MVC,Spring-Cache 以及我们的一些开源框架比如 Dubbo 等.
2.1.1.2 BeanFactoryPostProcessor 扩展
这个机制可以让我们在真正的实例化 Bean 之前对 BeanDefinition 进行修改.
这里我举例一个实战的例子, 想必大家很多都配置过数据库连接池吧, 这里拿 Druid 来举例:
然后我们创建一个 druid.properties 输入:
对于这种配置自己玩玩已经满足, 但是在公司有个问题, 密码放在项目中明码存储, 这样是不行的, 别人只要获得了你项目的查看权限那么密码就会被泄漏, 所以一般的公司会有一个统一的密码存储服务, 只有足够的权限才能够使用, 那么我们可以把密码放在统一存储服务中, 通过对服务的调用才能进行密码的使用, 那么我们怎么把从远程服务中获取到的密码注入到我们 Bean 中呢? 那么就要使用我们的 BeanFactoryPostpRrocessor, 下面的代码继承 PropertyPlaceholderConfigurer(BeanFactoryPostpRrocessor 的实现类):
在 xml 中有:
通过这种方式我们可以有几个好处:
设置统一配置中心, 那么我们不需要修改我们项目中的文件, 只需要在配置中心页面中修改即可.
设置统一密码中心, 那么我们不需要暴露明文在项目中, 密码如何保护那么就直接丢给密码中心即可.
2.2 Bean 的创建
一般我们在 API 中获取一个 Bean 都会如下操作:
通过 GetBean 操作进行获取, 前面我们讲到过如果是非延迟加载的单例 Bean 那么会在容器刷新的时候进行加载, 如果是延迟加载的 Bean 那么会在我们获取 Bean 的时候根据 BeanDefinition 进行加载. 首先在 AbstractBeanFactory 有两个方法一个是 doCreate, 一个是 create 用来描述如何创建一个 Bean. 这里说一下单例 Bean 是如何创建的:
doCreateBean 操作流程如下图:
可以看见真正的创建 bean 的操作在 CreateBean 中, 对于真正的创建 Bean 有如下流程:
.
2.2.1 Aware 接口
Spring 提供了很多 Aware 接口用于进行扩展, 通过 Aware 我们可以设置很多想设置的东西:
invokeAwareMethod 提供了三种最基本的 Aware, 如果是 ApplicationContext 的话那么在 ApplicationContextAwareProcessor 又进行了一轮 Aware 注入.
BeanNameAware: 如果 Spring 检测到当前对象实现了该接口, 会将该对象实例的 beanName 设置到对钱对象实例中.
BeanClassLoaderAware: 会将加载当前 Bean 的 ClassLoader 注入进去.
BeanFactoryAware: 将当前 BeanFactory 容器注入进去.
如果使用 ApplicaitonContext 类型的容器的话又会有下面几种:
EnvironmentAware: 将上下文中 Enviroment 注入进去, 一般获取配置属性时可以使用.
EmbeddedValueResolverAware: 将上下文中 EmbeddedValueResolver 注入进去, 一般用于参数解析. ResourceLoaderAware: 将上下文设置进去.
ApplicationEventPublisherAware: 在 ApplicationContext 中实现了 ApplicationEventPublisher 接口, 所以可以将自己注入进去.
MessageSourceAware: 将自身注入.
ApplicationContextAware: 这个是我们见的比较多的, 会将自身容器注入进去.
2.2.2 BeanPostProcessor
在前面我们说过 BeanFactoryPostProcessor, 这两个名字很像, BeanFactoryPostProcessor 是用来对我们 BeanFactory 中的 BeanDefinition 进行处理, 此时 Bean 还未生成. 而 BeanPostProcessor 用来对我们生成的 Bean 进行处理.
在 BeanPostProcessor 分为两个方法, 一个是用于初始化前置处理, 一个是初始化用于后置处理.
有一种特殊的 BeanPostProcessor,InstantiationAwareBeanPostProcessor, 其会在我们实例化流程之前, 如果实现了这个接口, 那么就会使用其返回的对象实例, 不会进入后续流程.
实战: BeanPostProcessor 有什么用呢?
如果你有一个需求, 打点项目中方法每个方法的运行时常, 你很容易想到用 AOP 去做, 如果不用 AOP 的话那么你可以使用 BeanPostProcessor 的后置处理方法, 将对应的每个 Bean 都进行动态代理.
2.2.3 InitializingBean/init-method
Spring 提供了我们对 Bean 进行初始化逻辑的扩展:
实现 InitalizingBean 接口:
在 afterPropertiesSet() 方法中我们可以写入我们的初始化逻辑.
通过 xml 方式:
在 init-method 中定义了我们初始化方法.
2.2.4 DisposableBean/destory-method
俗话说, 生与死轮回不止. 那么我们有了生的扩展, 自然 Spring 提供了死的扩展. 我们也可以通过下面两个扩展来实现我们销毁的逻辑:
DisposableBean: 实现 DisposableBean 接口
实现 destroy 方法即可.
实现 xml:
在 destroy-method 中定义销毁方法.
PS: 在我们 Spring 容器中如果要在 JVM 关闭时自动调用关闭的方法那么我们可以 ((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook(); 注册关闭钩子, 这样在关闭 JVM 的时候我们的 Bean 也能安全销毁.
3. 总结
本篇文章从 Spring 容器启动原理, 以及 Bean 的初始化原理介绍, 引出了多个基本的扩展点. 当然这部分扩展点还仅仅是 Spring 中的一部分, 感兴趣的可以阅读 Spring 的文档, 或者阅读 Spring 源码. 如果能掌握这些扩展, 以后自己造轮子的时候和 Spring 结合这些扩展是不能少的.
最后打个广告, 如果你觉得这篇文章对你有文章, 可以关注我的技术公众号, 最近作者收集了很多最新的学习资料视频以及面试资料, 关注之后即可领取, 你的关注和转发是对我最大的支持, O(∩_∩)O
来源: http://www.tuicool.com/articles/uMZRv2E