beanFactory 设计模式 Bean 生命周期的胡言乱语,哈哈
写在前面的话
适用读者:有一定经验的,本文不适合初学者,因为可能不能理解我在说什么
文章思路:不会一开始就像别的博客文章那样,Bean 的生命周期,源码解读(给你贴一大堆的源码)。个人觉得应该由问题驱动,为什么为出现 BeanFactory ,为什么会有生命周期。
正文
一开始我们使用 bean 都是简单bean,如 vo ,po,entity,dto,我们是这么玩的
XXEntity xxEntity = new XXEntity();
xxEntity.setPropA("字符串");
后面可能出现了某个比较复杂的 bean ,它有一个对象做为属性,需要在构造时或构造后设置值(示例而已,不要较真),如
// 构建序列化实例,这里 Serializable 是接口,使用接口的好处是在使用别的序列化时,不需要修改 jedis 类
Serializable fastJsonSerizlizable = new FastJsonSerizlizable();
// 构建目标 jedis 实例 ,需要先构建序列化对象
Jedis jedis = new Jedis();
jedis.setSerializable(fastJsonSerizlizable);
这时来了 serviceA 类和 serviceB 类,它们都需要使用 redis,我不可能在每个类里面都去把 jedis 实例化的过程写一遍,这时有经验的同学会写一个工具类来创建 jedis ,像这样
public BeanUtil {
// 可以把创建序列化单拿出来,因为除了 redis 需要序列化之外,kafka 也需要序列化
public static Serializable createSerializable(){
return new FastJsonSerizlizable();
}
public static Jedis createJedis(){
Jedis jedis = new Jedis();
jedis.setSerializable(createSerializable());
return jedis;
}
}
// 这里我 serviceA,serviceB 都可以使用 createJedis 来直接获取 jedis 实例 ,而不需要关心创建细节,使用哪个序列化等问题
上面代码有几个问题
- 每次使用时都会创建 jedis 对象 ,而每一个 jedis 对象又会单独对一个 Serializable 对象 ,但是 fastJson 的序列化和 jedis 都只是工具类型的东西,一个实例足已。
- 无法对 Jedis 进行配置
- 不能让使用者去创建 BeanUtil 实例 ,改进的代码 如下
public BeanUtil {
// 禁用 BeanUtil 构建
private BeanUtil(){}
// 这里我们可以使用 bean 的全路径 => bean 实例来缓存 bean
static Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
static{
// 初始化时,在内容缓存这些 bean 的实例,因为 jedis 依赖于 serializable ,需要需要先创建 serializable
Serializable serializable = createSerializable();
beansCache.put(Serializable.class.getSimpleName(),serializable);
Jedis jedis = createJedis();
beansCache.put(jedis.class.getSimpleName(),jedis);
}
static Serializable createSerializable(String type){
Serializable serializable = beansCache.get("serializable");
if(serializable != null)return serializable;
switch(type){
case "kryo": // kryo 不能用单例,请忽略本问题,示例而已
return new KryoSerializable();
case "protostuff":
return new protostuffSerializable();
default:
return new FastJsonSerizlizable();
}
}
static Jedis createJedis(String serializableType){
Jedis jedis = new Jedis();
Serializable serializable = beansCache.get("serializable");
jedis.setSerializable(serializable);
return jedis;
}
//然后对外提供获取 Bean 的方法
public static Object getBean(String beanName){
return beansCache.get(beanName);
}
public static T getBean(Class<T> type){
return beansCache.get(type.getSimpleName());
}
}
但如果写这个类的是小明,经过一段时间后这个类里会出现大量的 createXx 和 XX 的初始化操作,而且依赖程度也非常复杂,这时小明想,是时候优化一波了,于是小明想了一种解决方案,定义了一种 xml 语法
使用 bean 标签来定义一个 bean,每个 bean 都有唯一的一个 id 信息 ,使用 property 来定义它的属性 ,如果是复杂属性使用 ref ,解析这个xml 得到一个完整的 bean 依赖图
<beans>
<bean id="serializable" class="com.xx.FastJsonSerizlizable" />
<bean id="jedis" class="com.xx.Jedis">
<property name="host" value="localhost" />
<property name="serializable" ref="serializable"/>
</bean>
</beans>
这时会有一个依赖问题,我创建 jedis 要先创建 serializable ,但是 serializable 的 xml bean 定义是写在文件前面 的,小明想了一个办法,先把 ref 使用字符串先存着,全部放到一个 bean 定义中,像这样
Map<String,BeanDefinition> beanDefinitions = new HashMap();
然后把其解析成一颗依赖树,这样就可以先构造树叶,然后逐层构造对象 ,但也有一种棘手的情况 ,那就是循环依赖
root
|-jedis
|- serializable
什么是循环依赖呢,最简单的 A 依赖于 B,B 依赖于 A ,或者中间有更多的依赖最后形成了一个圈,A-B-C-A
最原始的解决方式是这样的,我们可以先使用构造函数把它们都创建出来,不能是有带它们依赖对象的构造函数,然后通过 set 把对象通过属性设置值。
spring 其实也是这么干的,所以对于循环依赖,spring 是不支持构造注入,支持 set 注入
这时我们的 BeanUtil 变成了这样,想想不能叫工具类了,改为实体类 Factory
public BeanFactory {
Map<String,BeanDefinition> beanDefinitions = new HashMap();
// 这里我们可以使用 bean 的全路径 => bean 实例来缓存 bean
Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
{
// 加载 xml bean 配置文件
beanDefinitions = loadXml(contextConfigurations:String []);
//实例化所有 bean
beansCache = instanceBeans(beanDefinitions);
}
//然后对外提供获取 Bean 的方法
public Object getBean(String beanName){
return beansCache.get(beanName);
}
public T getBean(Class<T> type){
return beansCache.get(type.getSimpleName());
}
}
这看起来已经足够完美了,但这时程序员A提问了,我需要对我的某个类的初始化时,我要获取一些比如连接资源,文件资源,然后在类销毁时想要回收资源,但根据上面没任何办法可以做到。
小明说,这好办,我提供几个接口给你,你实现一下,我会在实例化 Bean 的时候 ,如果发现你有实现接口,在相应的过程里我就帮你调用一下,于是小明就添加了两个接口
public interface InitializingBean{
void afterPropertiesSet() throws Exception;
}
public interface DisposableBean{
void destroy() throws Exception;
}
程序员A 的问题解决了,这时程序员B说,有没有一种办法,可以对所有 Bean 的初始化过程进行拦截,而不是我当前这个类,我想把每一个 service 改成代理类,我想要给 service 中的方法添加事务。
小明说,那好吧,我把 bean 的属性都注入完了,然后给这个 bean 交给你,你装饰一下这个 bean 然后再还给我,于是小明提供出了这样一个接口 ,在 bean 初始化前和初始化后,你都可以来修改 bean ,不要要注意,这个是针对全局的,不是你个人的 bean ,要做好过滤操作
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException ;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}
刚开始一两个 postProcessor 还没啥问题,后面这种处理类多了,顺序问题就开始显露出来了,比如程序员A 写了个 postProcessor ,然后打包给了程序员B用,可能在 BeanFactory 先加载的程序A 的,但这时候程序员 B 不乐意了,我要先加载我的,我的要在 A 之前执行
小明说,你们所有的 Process 实现个接口,返回一个数字用来表示顺序,数字越小越先执行,优先级越高,我会在取到所有的 postProcess 后做一个排序
public interface Order{
int order();
}
程序员C 这时又发问了,我创建了一个 BeanA 但我怎么样可以拿到 BeanC 啊,我想看看 c 的一些属性。
小说说,真烦,我干脆把 map 都给你好,不,我把 BeanFactory 都给你好了,于是有了这个接口
public interface BeanFactoryAware{
void setBeanFactory(BeanFactory beanUtil);
}
这时程序D 又问了,我在 setBeanFactory 的时候 ,我创建的全局 processor 执行了吗,还是在之后执行,头大。
小明说,我整理下执行顺序,取个名吧,叫 bean 的生命周期,顺便再提供几个实用的接口,bean 的名字我还没告诉你呢,于是整理的生命周期如下
反射创建 Bean
填充对象属性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多个
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多个
DisposableBean.destory()
程序员E 又说了,xml 配置太麻烦了,jdk1.5 不是有注解吗,我在类上加个标识,你扫描我的类,帮我创建实例呗
然后我需要用的时候,我在属性上加个标识,你同样可以根据类型找到依赖的类,然后把对应的实例创建好,帮我把值放进去就好了,如果这个类的创建过程比较复杂,我自己来创建,然后我把它返回给你,我定义一个方法,加个 Bean 的标识,你来读进容器。
于是小明又加了 @Component
来表示组件,@Bean
来表示自定义实例创建,@Autowired
来注入对象 @PostConstruct
来执行类的初始化工作 @PreDestroy
来做类的销毁工作,类的生命周期变成这样
反射创建 Bean
填充对象属性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多个
PostConstruct
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多个
PreDestroy
DisposableBean.destory()
但是为了兼容以前的 xml 形式,小明这时把 BeanFactory 抽象成接口,提供 getBean 方法,根据职责单一原则,BeanFactory 不应该再做解析 Bean 的工作;
再创建一个接口用于加载 Bean 定义,有两个实现 XmlBeanRegistry ,AnnotationBeanRegistry ,加载 Bean 定义后再合并,考虑到以后还有可能添加别的注册 bean 的方式 ,一次性提供一个对外的接口
public interface BeanFactoryPostProcessor{
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
你可以把你规则写成的 bean 定义,实例化为我要求的 BeanDefinition 然后发给我就可以自定义实现把你自定义的 bean 添加到容器中了。
到此,这个 BeanFactory 已经相当完善了,它定义了一整套 bean 的生命周期,并且可以让使用者介入 bean 的创建过程,但 spring 有一套更加完善的 bean 生命周期,下篇将写 spring 的 beanFactory
虽然不主张重复造轮子,但前提是你得了解轮子的内部构造。
beanFactory 设计模式 Bean 生命周期的胡言乱语,哈哈的更多相关文章
- beanFactory 设计模式 Bean 生命周期
写在前面的话 适用读者:有一定经验的,本文不适合初学者,因为可能不能理解我在说什么 文章思路:不会一开始就像别的博客文章那样,Bean 的生命周期,源码解读(给你贴一大堆的源码).个人觉得应该由问题驱 ...
- Spring之BeanFactory及Bean生命周期
1.spring通过BeanFactory灵活配置.管理bean,Spring对管理的bean没有任何特别的要求,完全支持对POJO的管理: 2.BeanFactory有个ApplicationCon ...
- 好记性不如烂笔头85-spring3学习(6)-BeanFactory 于bean生命周期
假设BeanFactory为了产生.管理Bean, 一个Bean从成立到毁灭.它会经过几个阶段运行. 据我所知,一般bean包括在生命周期:设定,初始化,使用阶段,四个核心阶段销毁. 1.@Bean的 ...
- spring(二、bean生命周期、用到的设计模式、常用注解)
spring(二.bean生命周期.用到的设计模式.常用注解) Spring作为当前Java最流行.最强大的轻量级框架,受到了程序员的热烈欢迎.准确的了解Spring Bean的生命周期是非常必要的. ...
- Bean生命周期及BeanFactory
1.spring通过BeanFactory灵活配置.管理bean,Spring对管理的bean没有任何特别的要求,完全支持对POJO的管理: 2.BeanFactory有个ApplicationCon ...
- Spring源码系列 — Bean生命周期
前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...
- Spring 高级特性之二:Processor——Bean生命周期关键触发时机
任何对象都有生命周期,那么Spring Bean对象创建.管理.销毁的整个生命周期个关键触发时机如何体现呢?先说结论,后续案例验证结论. 根据上图可知,实际bean对象涉及生命周期的主要是一个构造器和 ...
- Spring Bean 生命周期2
在spring中,从BeanFactory或ApplicationContext取得的实例为Singleton,也就是预设为每一个Bean的别名只能维持一个实例,而不是每次都产生一个新的对象使用Sin ...
- Spring bean 生命周期验证
一.从源码注释看bean生命周期 从JDK源码上看,BeanFactory实现类需要支持Bean的完整生命周期,完整的初始化方法及其标准顺序(格式:接口 方法)为: 1.BeanNameAware s ...
随机推荐
- jquery图片放大插件鼠标悬停图片放大效果
都知道jquery都插件是非常强大的,最近分享点jquery插件效果,方便效果开发使用. 一.HTML代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHT ...
- 尝鲜 vue3.x 新特性 - CompositionAPI
0. 基础要求 了解常见的 ES6 新特性 ES6 的导入导出语法 解构赋值 箭头函数 etc... 了解 vue 2.x 的基本使用 组件 常用的指令 生命周期函数 computed.watch.r ...
- JAVA-JDBC-c3p0连接池配置
配置c3p0数据源的方式有三种,分别是手动配置,xml文件配置和properties文件配置,这三种配置方式存在一种即可. 通常来讲,用文件配置更方便书写和阅读 配置如下: 注:*号部分写自己的配置 ...
- win10下VSCode+CMake+Clang+GCC环境搭建
win10下VSCode+CMake+Clang+GCC环境搭建 win10下VSCode+CMake+Clang+GCC环境搭建 安装软件 VSCode插件安装 新建文件夹, 开始撸代码 main. ...
- 04、JDBC范例
范例:JDBC查询 package com.hsp; import java.sql.Connection; import java.sql.DriverManager; import java.sq ...
- 从干将莫邪的故事说起--java比较操作注意要点
故事背景 <搜神记>: 楚干将.莫邪为楚王作剑,三年乃成.王怒,欲杀之.剑有雌雄.其妻重身当产.夫语妻曰:“吾为王作剑,三年乃成.王怒,往必杀我.汝若生子是男,大,告之曰:‘出户望南山,松 ...
- docker镜像制作必备技能
正文 使用过docker的都知道dockerfile,其用于定义制作镜像的流程,由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像.可参考往期文章学习:docker基础知识整理 ...
- python编程基础之二十九
栈和队列: 栈:先进后出,其他没多少特别之处了,一般可以用列表模拟栈,也可以用双端队列,封死一端. 队列:先进先出,也可以用列表模拟,但是一般用库函数,需要导collections 包:主要是为了解决 ...
- Proving Equivalences UVA - 12167
题文:https://vjudge.net/problem/UVA-12167 题解: 很明显,先要缩点.然后画一下图就会发现是入度为0的点和出度为0的点取max. 代码: #include < ...
- 性能测试:Jmeter压测过程中的短信验证码读取
问题背景 现如今国内的大部分软件或者网站应用,普遍流行使用短信业务,比如登录.注册以及特定的业务通知等. 对于这些业务,在使用Jmeter进行性能测试的过程中,就会需要自动获取和填入短信验证码,否则性 ...