品Spring:bean定义上梁山

Posted 李新杰的博客园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了品Spring:bean定义上梁山相关的知识,希望对你有一定的参考价值。

认真阅读,收获满满,向智慧又迈进一步。。。



技术不枯燥,先来点闲聊


先说点好事高兴一下。前段时间看新闻说,我国正式的空间站建设已在进行当中。下半年,长征五号B运载火箭将在海南文昌航天发射场择机将空间站核心舱发射升空。预计用2到3年将空间站建好。

虽然到时你们不让我上去,不过我也为这件事出不了什么力,算扯平了。哈哈,但是我还是会衷心的祝福你。

长征五号火箭首次采用5米大直径的箭体结构,总加注量达到780吨,起飞时共有10台发动机产生1078吨的推力,具备近地轨道25吨、地球同步转移轨道14吨的运载能力。

不要误会,本文不是讲火箭的,关键我也讲不了呀。但是我们可以分析下火箭的特点。自身体积和重量非常大,内部结构与实现极其复杂。能够运送的物品与自身比起来微乎其微,关键这玩意老贵老贵了。

因此SpaceX推出的猎鹰可回收火箭,从一开始就受到了全世界的关注。回收之后可以被重新利用,不但缩短了火箭发射的周期,平均下来每次发射的成本也少了很多。享受同样的服务,花更少的钱,不是所有人的最爱嘛。

回到程序,其实现在大部分程序员,包括我自己在内都应该属于2.0或3.0版本的程序员,因为我们的职业生涯都是从Spring开始的。在Spring之前Java企业级应用开发标准其实是EJB(企业级JavaBean)。

EJB只是一个标准或规范,由各大服务提供商来实现。EJB非常笨重而且用起来也痛苦,关键还很贵。其实和火箭的性质一样,自身的负担太重。这个问题必须要解决。

因此,Elon Musk认识到了这点,就开始着手研制可回收火箭。在当时,Rod Johnson也认识到了EJB特点并深受其害,于是发明了Spring,轻量级、免费开源,慢慢就流行起来了。

看到了吧,在工作或生活中,凡是遇到苦逼的地方,就说明这也是一个极有可能产生大成功的地方。这个秘密我可是告诉诸位了,能不能成功就看你们的了。哈哈。

其实Spring现在也已经变得足够复杂了。


要想渡人,必先渡己


易中天老师曾说过,能够制造工具标志着人类的诞生。目前人类建造的文明,哪一个是通过双手刨出来的,不都是通过工具建造出来的嘛。

比如我们盖高楼用的建材,不都是通过塔吊一点点吊上去的,随着建材的堆砌,楼越来越高。那么我们来思考一个问题,塔吊本身是如何上去的?

塔吊本身就是一个工具了,所以(一般情况下)不可能再有其它工具来帮它了。所以塔吊只能实现自我爬升,实际也是这样的,相信大多人都见过,速度很慢的。

我们都在用Windows操作系统,点点鼠标,敲敲键盘,用着很爽。但是在开发Windows系统本身时却是很苦逼的一件事。

我主要想表达两方面的意思:

如果有一天,我们能从工具的使用者变成设计者或制造者,那就厉害多了。

还有就是,一个工具在对外提供服务前,一定要解决好自身的搭建或构建问题。

Spring其实就是个工具,开发人员把业务代码写好后,把类作为bean注册到工具上,后续的运行时全部交由Spring这个工具接管,简直不要太爽。

当然,这也是Spring存在的价值和意义。但是不要忘了,建造Spring这个工具的那批人可就不爽了,为了“取悦”这个工具的使用者天天挖空心思。

所以“品Spring”这个系列文章,是想要逐步解密Spring这个工具的建造细节,而不是教怎么使用这个工具的。目前还不能熟练使用这个工具的小伙伴可就要加油了。

所以Spring这个工具应该优先考虑好自我的搭建,才能更好的为开发员人服务。

就像开发人员,必须有一个良好的家庭,吃饱喝足睡够之后,才能写出水平最高的代码。如果刚在家和老婆吵完架,那写出的代码应该都是bug的样子,哈哈。


相同的套路,相似的处理


编译器是编译代码的,但它本身也是代码。饭店的工作人员是服务客人吃饭的,但他们本身也要吃饭。指针是指向地址的,但它们本身也有地址。

描述业务的数据通常称为业务数据,描述业务数据的数据通常称为元数据。业务数据和元数据都是数据,只不过是取了两个更加细化的名字罢了。

上面描述的这些情形里面都包含了两方面的事物,可以理解为是“一前一后”的感觉。它们既不是相同也不是不同,而是相似。即处理方式相同,侧重点不同。

就以饭店为例吧,这个所有人都熟悉。给客人做的饭,要求色香味俱全,即当作艺术品来对待。员工自己吃的饭,要求味美可口简单快捷,即当作食物来对待。

因为客人和员工的要求不同,所以处理的侧重点、工序和用料不同,但是处理方式大致都一样,无非就是煎炒炸蒸煮等等。

相信都已经明白了,如果还不清楚,就想想盖商品房、盖集资房和盖职工宿舍有什么区别?本质上没有区别,而且还可能是同一个施工队干的。

回到程序,经常听到说做中间件开发的人都很厉害,写纯业务代码的好像都是菜鸟。怎么说呢,做中间件和做业务确实目的不同、侧重点不同、用到的技术不同,所以要求也不同。

写出代码的好坏只取决于个人水平,因为中间件代码和业务代码没有本质的区别,都是对数据的操作和传递,都是顺序分支循环的程序结构。

而且都是用同一个编译器编译,关键编译器才不管你是什么代码呢,反正都是Java代码,对吧,哈哈。

回到Spring,既然Spring能把业务开发人员写的业务类作为bean来对待(即注册bean定义),难道就不能把用来处理这些业务类的“系统类”(即Spring自身的类)也当作bean来对待吗?

答案是完全可以,而且实际也是这样做的。本来业务类和系统类就是人为划分的,本质上都是程序,没啥区别。

都作为bean来对待后,带来了一个非常好非常好的好处,就是统一了编程模型,这非常重要。

就是业务开发人员可以使用@Component/@Bean注册bean定义,使用@Autowired装配bean依赖。框架开发人员在开发Spring框架时也可以这样使用。

这样就模式统一、写法统一、思维统一,当然很爽了。

可以这样理解,在建造这个工具时,就已经可以享受这个工具带来的便利了。

如果无法体会到这种好处的,照例通过一个小事情说明下。

比如一群中国人,彼此协作的很好,突然来了一个老外,互相又听不懂对方,但是还要交流,只要让翻译在中间翻来覆去,是不是净瞎耽误工夫,肯定是不爽的。

当然,这个事情有时也是不成立的,比如老外是一个年轻漂亮的小姐姐,是吧,所以说任何事情都不是绝对的。

Spring统一了编程模型后,Spring自身的类和业务类都注册了bean定义,那这些bean定义间真的就完全一样吗?

答案当然是否定的,毕竟作用不同,肯定是有差别的,只是大小罢了。


身先士卒,冲锋陷阵


业务类注册bean定义的目的是为了将来运行这个bean实现业务逻辑的。那么它的bean定义是怎么注册的呢?显然是Spring为它注册的。

因此在Spring内部,有一个专门为其它类注册bean定义的接口,即BeanDefinitionRegistryPostProcessor接口:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}


接口只有一个方法,就是注册bean定义。

这个接口有一个非常重要的实现类,即ConfigurationClassPostProcessor类,它里面包含了所有Spring支持的注册bean定义方式的实现。

因Spring统一了编程模型,所以这个类也被注册了bean定义。又因为这个bean只有运行起来后才可以为其它类注册bean定义,所有这个类无论是注册bean定义还是运行,在时间轴上都是比较靠前的。

事实上,它是Spring注册的第一个bean定义,bean的类就是刚才提到的那个实现类,bean的名称是:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor


说白了这个类就是用来处理那些使用注解标注的类,为它们注册bean定义的。所以这个类它自己的bean定义注册绝对不能再使用注解的方式,而是使用代码直接注册的,可以认为是写死的。

通过高铁乘务员和旅客的这种情况,可能会理解的更明白些。

1、乘务员是人,旅客也是人,他们都是人。框架里的类注册bean定义,业务类注册bean定义,它们都注册bean定义。

2、乘务员必须先到达,然后才能迎接旅客,在时间轴上要靠前些。框架里的类必须先注册bean定义,然后才能为其它类注册bean定义,在时间轴上同样也要靠前些。

3、乘务员是走工作人员通道进入,旅客是正常买票/检票进入,进入方式是不同的。框架里的类是直接通过写代码的方式注册的bean定义,业务类是通过标注解的方式注册的bean定义,注册bean定义的方式也是不同的。

所以慢慢就会发现,任何上层封装的很完美的代码,当一步步到达底层时,都会进行一些“特殊”的处理。这种现象并不只是程序代码里才有,在历史上一直都存在。

易中天品三国中讲过一个小故事,曹操领兵打仗时定了一条军规,凡是谁的马践踏了农田,是要杀头的。有一天不凑巧,曹操自己的马惊了,跳进了农田。

曹操喊来相关负责人,问他,“按规定应该怎么处置啊”?答曰:“应该杀头”。但这可是曹操啊,怎么能杀头呢。于是那个人又说:“身体发肤受之于父母,我们自己没有权力损坏”。

于是曹操就拔出剑,割下了自己的一缕头发,扔到了地上。这就相当于受到了非常严重的处罚,等于死过了。这不就是特殊处理嘛。

这也说明了,跟领导混的,必须得有几把刷子,毕竟古语道,伴君如伴虎啊。

扯得有点远了,再回到正题。

Spring为了使bean定义的处理更加灵活,还提供了一个特殊的接口,即BeanFactoryPostProcessor接口,也就是上面那个接口的父接口:

public interface BeanFactoryPostProcessor {

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}


当执行到这个接口的时候,所有的bean定义其实都已经注册好了,所以这个接口的目的就是再给一次修改(或完善)bean定义的机会。

当这个接口执行完后,所有的bean定义就真的确定下来了,不会再变了。

如果对Spring不是很熟悉的,只要记住下面这个总结就行了:

这两个接口是特殊的,它们就是用来为其它类注册bean定义或修改已注册好的bean定义的。

这两个接口的实现类也会被注册bean定义,只不过在时间轴上会非常的靠前。

这两个接口可以有多个实现类,可以通过实现排序接口进行排序,以保证逻辑上先后顺序的正确性。

其实不懂这些也无所谓,不影响成为一个合格的程序员。


bean定义上梁山


108位梁山好汉上梁山的原因和过程各不相同,但最后都到了梁山。

bean定义的注册方式和方法也有好几种,但最后都能注册成功。

因为历史已经走到了注解和Java配置的时代,所以它们就是主角了。

使用注解注册bean定义

标有@Component注解的类

标有@Configuration注解的类

品Spring:实现bean定义时采用的“先进生产力”

品Spring:SpringBoot发起bean定义注册的“二次攻坚战”

品Spring:对@Resource注解的处理方法

spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段

05Spring源码-手写篇-手写Bean配置

Spring课程 Spring入门篇 3-2 Spring bean装配(上)之bean的生命周期