写在前面的话
适用读者: 有一定经验的, 本文不适合初学者, 因为可能不能理解我在说什么
文章思路: 不会一开始就像别的博客文章那样, 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 把对象通过属性设置值. 所以除了构造注入外, 通过属性方式是可以解决循环依赖的.
这时我们的 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 公式
博客地址:
gitee: https://gitee.com/sanri/sanri-excel-poi
使用模板代码 , 从数据库生成代码 , 及一些项目中经常可以用到的小工具
博客地址:
gitee: https://gitee.com/sanri/sanri-tools-maven