1、Spring框架核心源代码的分析及其感受对于Java开发者来说,经常会用到一个框架Spring,但是从用的角度来讲,很多人已经认为比较“复杂”了,但是我认为这个和思维方式有关,当人看到一个电视机,然后看完说明书(相当于Spring的用户文档),最后在磕磕碰碰的点击各种按钮后电视机开始工作了,但是他某一天想让电视机能干点说明书描述的东西以外的事情的时候,现在的电视机也就无能为力了,比如他想让电视机能够具备播放N种格式的视频,并且可以录制电视节目,他买的电视机并不能满足他的要求,必须换新的了,但是恰好他的这个老电视机的电路板上留出了各种插口,可以帮助用户拓展功能,这个说明书没有写,只有维修人员知道
2、,属于内部秘密,因为他们公司所有的电视都用一个电路板的设计做的,只是板子上有不同的插件罢了,这能减少很多设计成本、制作成本、维护成本、人员成本。那么这个用户根本不知道他的电视机还能增强,结果他又买了一个新的,这个新的电视机结果也是用着同样的电路板只是多了几个插件!对于软件这件东西,其实也是一样,spring也是一个软件,我们就属于那些使用者,如果我们不知道其中的原理,就没有办法有效地在合适的场景下使用Spring的一般特性和高级特性,也不知道如何在面对不同的应用环境中如何拓展好它以便更加好的让它为我们的应用服务。以上是一种观点,估计您能理解,我还有一个补充观点,那就是这些出了名的框架包装了很多
3、设计思想,把这些设计思想分解开,你会发现对你自己设计软件时很有帮助,而且不仅仅如此,更加学会了一种阅读代码的方式、学习其设计思想、建立创新型思维的入口的捷径,反倒比看那些什么Spring整合Hibeernate、*指南等等而学得彻底,但是这些捷径的前提是,我们必须破除一个观念就是“权威的东西一定很复杂或者深奥”的想法,或者会用就行的观念,或者直接去源代码而不去先去了解其理论模型的含义,这些不好的观念是可以把你带到书呆子或者工具狂的恶意帮手。从这篇文章开始,我将细细的分析spring源代码,我大可以去分析什么Hibernate、Struts等等这些东西,但是我要说我只是拿Spring作为特例来做
4、分析,并不是最终目的,最终目的是为了和大家探讨如下几个问题:1) 如何做设计前的分析2) 如何利用好现有的知识系统3) 如何对待各种设计理念4) 如果学好和用好工具软件5) 如何学好技术等等,而且在此中最大可能性的解释共通理念、技术的重要性,如何能做到触类旁通等等。所以这系列的文章绝对不是一个什么指南,或者就是为了分析源代码而分析的文章。开始之前,我先要照顾一下初学者,什么是Spring? (你可以直接到官方网站上去查,不过这里还是给个定义,作为Spring的整体概念,这个整体概念将被各种子概念解释,各种子概念将被实际的实现解释)Spring提供对象关系解耦的能力,又能使各个松散耦合的对象关联
5、起来实现一个系统的功能,我这里值的系统也是一个整体观念,具体的可能是一个应用层组件系统,也可能就是一个组件,它并且提供对一个对象本身声明周期的管理。好了,这好像是一个很学究的定义,其实上面这个定义还可以翻译成:把一个系统的关注点分离(对象或其他什么东西的关系解耦),然后再让他们协调起来干一个明确目标的事情.这样来讲可能是看起来有些矛盾和抽象了,其实我们可以观察一下周围的物件,比如电视机,电视机每个零件都是独立的,比如电子管,这个电子管没有被组装到这个电视机中之前谁也不知道它的作用,还可能装到一个手表里也说不定,所以它的独立的,高内聚低耦合的,然而在设计师的“设计“下,把这些互相独立的零件组装起
6、来,来干电视机应该干的事情,而这些独立的零件,又是可以替换的,坏了或者升级都要替换掉,所以Spring这个框架解释了软件设计中最最朴素的也是最最本质的问题那就是对一个概念的关注点的分离或者分层,然后还要使其结合起来实现一个整体的目标!以上看起来很抽象,那么我们先用一个如何使用Spring的简单代码示例来说明问题:设计一个功能(略)1) 实现各个独立的类2)写配置文件3)部署再看看客户程序如何使用这个功能:New FileSystemXmlApplicationContext(“C:beans.xml).getBean();看到了吧,一个功能(整体概念)被分解了,每一个被装配的对象都是可以通过外
7、部配置文件替换的,但是又是一个整体,对立而统一,你会发现每个对象(类的实例)都是可以独立存在的,也可以被装备到其他的应用中,并且可以脱离整体而进行单独测试,这一切带来的好处都是基于上面那么多论述的理由而来的!如此简单,如此优美。接下来我们来看看Spring的核心实现:IOC和AOP.(切忌不可被名词或者什么概念吓倒,很多名词都是在使用隐喻的办法说明一个抽象的概念,是为了离你近一些,明白一些,比如IOC,控制反转,控制是隐喻,反转也是隐喻)一, IOC就是A类依赖另外B类的时候,不需要A管理B的生命周期,也就是不用再在A中来new B,也就是说A不必知道B是否存在,而只是有个接口引用就可以了,那
8、么谁来管理它们之间的依赖(也就是把A&B建立一种实际的关系),那就是IOC容器干的事情,你可以把它想象成一个中间者,那么具体实现一个控制反转是靠的什么,一个类能和其他类发生依赖的地方基本上是构造函数参数和属性或者方法参数,所以就有了DI(依赖注入)的具体实现IOC的方式:构造函数注入和属性注入,下面是一个简单的伪代码例子:A a=new A();/IOC容器来创建B b=new B();/IOC容器来创建a.bRef=b;/IOC容器来关联或者B b=new B();/IOC容器来创建A a=new A(b);/IOC容器来创建发现所有的new和关联工作都“可以“交给IOC容器,那么对于Spr
9、ing的设计目的来说,IOC容器的实现就是其核心部分之一。下面我们来看看Spring IOC容器(容器也是一个隐喻-一个容器里面肯定能装东西才叫容器,用来说明作用,其实具体实现很简单,后面有讲)的实现(分析的基础是了解其使用、概念或者具体使用过):/#ToDO 源代码文件夹结构从上面这个源代码文件夹结构中(幸亏他们的名字起的好),我第一眼就看到了org.springframework.context这个子项目(Spring所有的大的独立部分都是以子项目存在的,这样很好独立维护和升级,这也符合分解和结合的观点),因为我们经常使用的容器就是ApplicationContext,我们以此为入口点进行
10、分析(最好的习惯就是从你最常用或者大家最常用或者框架、软件的说明书上直接入口使用点开始顺藤摸瓜),我在Eclipse中看到了ApplicationContext的源代码(注释都被我去掉了,是为了更加清晰):public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver String getId(); Strin
11、g getDisplayName(); long getStartupDate(); ApplicationContext getParent(); AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;由于我们之前总是在使用Spring,所以一眼就看出来getParent能得到“父“容器,getAutowireCapableBeanFactory与自动装配有关.我们再看看它有哪些实现,我猜测我可以看到一个我们非常熟悉的ApplicationContext,然后我就可以按这
12、个最熟悉ApplicationContext开始分析其原理了,幸好Eclipse提供便捷的工具让你知道一个接口有多少实现!请按F4!哈哈,可以看到ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,这两个都是我们经常用到的ApplicationContext实现,我挑FileSystemXmlApplicationContext进行分析,切忌在分析一个东西的实现时没有理论基础没有主线或者就直接看源代码,会非常容易进到代码丛林中的,最终迷失在茫茫的代码中(但是实际代码并不多,只是我们自己把自己搞晕了,阅读代码时保持一个方向
13、进行,抓清主干然后分析分支是有效的办法,FileSystemXmlApplicationContext的实现是:public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext public FileSystemXmlApplicationContext() public FileSystemXmlApplicationContext(ApplicationContext parent) super(parent); public FileSystemXmlApplicationContext(
14、String configLocation) throws BeansException this(new String configLocation, true, null); public FileSystemXmlApplicationContext(String. configLocations) throws BeansException this(configLocations, true, null); public FileSystemXmlApplicationContext(String configLocations, ApplicationContext parent)
15、 throws BeansException this(configLocations, true, parent); public FileSystemXmlApplicationContext(String configLocations, boolean refresh) throws BeansException this(configLocations, refresh, null); public FileSystemXmlApplicationContext(String configLocations, boolean refresh, ApplicationContext p
16、arent) throws BeansException super(parent); setConfigLocations(configLocations); if (refresh) refresh(); Override protected Resource getResourceByPath(String path) if (path != null & path.startsWith(/) path = path.substring(1); return new FileSystemResource(path); 啊?怎么这么多方法?看清楚了很多都是构造函数和一些重载的方法实现(Ov
17、erride注释的),对于可能在前进路上看到的一大坨代码,不要惊呼,先看看整体这些属性和方法的类别(构造函数,方法等等),先分个类,再找关键的部分,找关键的部分也需要技巧,那就是从调用方找,我们客户程序在使用ApplicationContext时,不就是先在构造函数中设置一下XML配置文件的位置(这个配置文件就是声明了对象之间依赖关系的地方),然后通过getBean方法取的容器中的Bean(装配好的)实例吗?所以我主动在上面的代码寻找构造函数的细节和getBean方法,等找到这些关键部分之后,我们再看支持它们实现的类变量,属性和其他方法即可,可但是我在上面的代码中没有看到什么getBean方法
18、,只看到构造函数中的细节了,而且很多地方用super调用父类的构造函数之实现,因为总和OO这类的语言打交道,直接会就想到getBean可能在父类中!但是稍等一下,从调用方使用它的角度看,构造函数应该是解析XML配置的地方,而XML配置文件中讲述了如何装配Bean的信息,看来getBean方法依赖于这个解析过程,这是一个基础!好了!先不要马上进入找getBean的过程吧,先把它的基础搞明白,开始找XML解析的过程!对!你应该看到了,在public FileSystemXmlApplicationContext(String configLocations, boolean refresh, Ap
19、plicationContext parent) throws BeansException super(parent); setConfigLocations(configLocations); if (refresh) refresh(); 这个构造函数中(其他构造函数都是调用这个构造函数的),貌似看到了一些端倪,第一关联了parent,估计AppliactionContext父子关系建立是通过这方式的!但是我现在先不关心这个的实现,我是想知道XML如何被解析的,setConfigLocation方法的调用也不是,从名字上猜出八九不离十(你看,写程序的时候,名字越贴切越好,给各位推荐一本书
20、代码整洁之道),只能是refresh方法了,从上面的代码分析我大概知道XML解析就在这里面实现的,但是这个方法干了些其他什么事情我还不知道(分析代码时,必须有点想象力,但是这种想象力必须建立在“证据“上,不要瞎猜),我迫不及待地按F3进入refresh方法中看个究竟(这时你发现refresh方法是在AbstractApplicationContext类中实现的,为什么在这里实现?除了OO继承的观念外,还有一种设计思想在里面,这个思想我在后面的分析中会写到):public void refresh() throws BeansException, IllegalStateException sy
21、nchronized (this.startupShutdownMonitor) / Prepare this context for refreshing. prepareRefresh(); / Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); / Prepare the bean factory for use in this context. prepareBeanFactory(b
22、eanFactory); try / Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); / Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); / Register bean processors that intercept bean creation. registerBea
23、nPostProcessors(beanFactory); / Initialize message source for this context. initMessageSource(); / Initialize event multicaster for this context. initApplicationEventMulticaster(); / Initialize other special beans in specific context subclasses. onRefresh(); / Check for listener beans and register t
24、hem. registerListeners(); / Instantiate all remaining (non-lazy-init) singletons finishBeanFactoryInitialization(beanFactory); / Last step: publish corresponding event. finishRefresh(); catch (BeansException ex) / Destroy already created singletons to avoid dangling resources. destroyBeans(); / Rese
25、t active flag. cancelRefresh(ex); / Propagate exception to caller. throw ex; 呵呵,无论从调用关系、方法名称和注释都能敏感的感觉到找对地方了,prepareRefresh的注释写“refreshing”,真搞不清这个概念,先进去看看:protected void prepareRefresh() this.startupDate = System.currentTimeMillis(); synchronized (this.activeMonitor) this.active = true; if (logger.i
26、sInfoEnabled() logger.info(Refreshing + this); 发现,就是重置一个标志位(先不要急于对看见的东西着迷,我说的是看到和目标暂时无关的东西),我发现貌似和我找的东西无关,先退到到主程序再说(这只是一个小例子,如果遇到一个大的和查找目的无关的代码,你先大体看看,然后再决定是不是马上退回去,不要在这里浪费时间,迟早还会回来的,这可以帮助你减少陷入代码丛林中的几率),接下来看ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();发现obtainFreshBeanFacto
27、ry()方法的实现是:protected ConfigurableListableBeanFactory obtainFreshBeanFactory() refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled() logger.debug(Bean factory for + getDisplayName() + : + beanFactory); return beanFactory; 对于refreshBeanFactor
28、y方法:protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;唉?一个抽象方法!谁实现它,反正不是AbstractApplicationContext!想退回去?且慢,当你不明确知道一个方法调用在干什么,尤其是抽象方法,你最好先找找它的实现,再想退回去也不迟,因为只要你忽略掉一个部分,就会对后面的代码的理解产生偏差,但是不要太深入了,这样既可以保证不会掉到实现的代码丛林中,也不会失掉什么关键点!幸好Eclipse有查看一个类、方法实现关系的工具,请按F4,看来有个好工具
29、对学习是有好的帮助的,所以千万不要低估工具的力量!另外你看ConfigurableListableBeanFactory beanFactory = getBeanFactory();只是取一个对象工厂!也是抽象方法,你可以也按我上面和下面介绍的办法去分析,结果就是一个创建对象工厂实例的逻辑,没有加载XML的逻辑!这里就不累述了,发现这样一个实现关系,父类中有DefaultResourceLoader,有Resource加载的能力吧?XML配置文件不就是一种资源吗?看来靠近了想要到达的地方了,至少不会太远了,于是你想先进去看看,不过我还是建议你看看这个实现关系中的其他类,做到心里有数,至少知道
30、这个地图,不然你一个劲往里钻,没有记不起来如何出来了,尤其是类多了时候,于是我先看了一下上面这个结构,知道下面还有两个ApplicationContext实现,Ok,先看看这个资源加载器,因为从名字来看更接近我想找的东西!public class DefaultResourceLoader implements ResourceLoader private ClassLoader classLoader; public DefaultResourceLoader() this.classLoader = ClassUtils.getDefaultClassLoader(); public De
31、faultResourceLoader(ClassLoader classLoader) this.classLoader = classLoader; public void setClassLoader(ClassLoader classLoader) this.classLoader = classLoader; public ClassLoader getClassLoader() return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader(); public Resource getResource(String location) Assert.notNull(location
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1