适用读者:有必定经验的,本文不适合初学者,由于可能不能理解我在说什么java
文章思路:不会一开始就像别的博客文章那样,Bean 的生命周期,源码解读(给你贴一大堆的源码)。我的以为应该由问题驱动,为何为出现 BeanFactory ,为何会有生命周期。git
一开始咱们使用 bean 都是简单bean,如 vo ,po,entity,dto,咱们是这么玩的程序员
XXEntity xxEntity = new XXEntity(); xxEntity.setPropA("字符串");
后面可能出现了某个比较复杂的 bean ,它有一个对象作为属性,须要在构造时或构造后设置值(示例而已,不要较真),如redis
// 构建序列化实例,这里 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 实例 ,而不须要关心建立细节,使用哪一个序列化等问题
上面代码有几个问题缓存
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 语法maven
使用 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();
而后把其解析成一颗依赖树,这样就能够先构造树叶,而后逐层构造对象 ,但也有一种棘手的状况 ,那就是循环依赖post
root |-jedis |- serializable
什么是循环依赖呢,最简单的 A 依赖于 B,B 依赖于 A ,或者中间有更多的依赖最后造成了一个圈,A-B-C-A
最原始的解决方式是这样的,咱们能够先使用构造函数把它们都建立出来,不能是有带它们的构造函数,而后经过 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 }
程序员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 添加到容器中了
创做不易,但愿能够支持下个人开源软件,及个人小工具,欢迎来 gitee 点星,fork ,提 bug 。
Excel 通用导入导出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代码 ,从数据库生成代码 ,及一些项目中常常能够用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven