`
leogao_emcom
  • 浏览: 83221 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

Spring框架核心源代码的分析及其感受-1

阅读更多

(事先用Word写好的,再贴上来,格式有点...,有时间再调整,如果看不清楚,请直接下载我写的Word文档,在-6第六篇文章中,有错别字就别怪我了,没有时间检查)

对于 Java 开发者来说,经常会用到一个框架 —Spring, 但是从用的角度来讲,很多人已经认为比较“复杂”了,但是我认为这个和思维方式有关,当人看到一个电视机,然后看完说明书(相当于 Spring 的用户文档),最后在磕磕碰碰的点击各种按钮后电视机开始工作了,但是他某一天想让电视机能干点说明书描述的东西以外的事情的时候,现在的电视机也就无能为力了,比如他想让电视机能够具备播放 N 种格式的视频,并且可以录制电视节目,他买的电视机并不能满足他的要求,必须换新的了,但是恰好他的这个老电视机的电路板上留出了各种插口,可以帮助用户拓展功能,这个说明书没有写,只有维修人员知道,属于内部秘密,因为他们公司所有的电视都用一个电路板的设计做的,只是板子上有不同的插件罢了,这能减少很多设计成本、制作成本、维护成本、人员成本。那么这个用户根本不知道他的电视机还能增强,结果他又买了一个新的,这个新的电视机结果也是用着同样的电路板只是多了几个插件!对于软件这件东西,其实也是一样, spring 也是一个软件,我们就属于那些使用者,如果我们不知道其中的原理,就没有办法有效地在合适的场景下使用 Spring 的一般特性和高级特性,也不知道如何在面对不同的应用环境中如何拓展好它以便更加好的让它为我们的应用服务。以上是一种观点,估计您能理解,我还有一个补充观点,那就是这些出了名的框架包装了很多设计思想,把这些设计思想分解开,你会发现对你自己设计软件时很有帮助,而且不仅仅如此,更加学会了一种阅读代码的方式、学习其设计思想、建立创新型思维的入口的捷径,反倒比看那些什么 Spring 整合 Hibeernate *** 指南等等而学得彻底,但是这些捷径的前提是,我们必须破除一个观念就是“权威的东西一定很复杂或者深奥”的想法,或者会用就行的观念,或者直接去源代码而不去先去了解其理论模型的含义,这些不好的观念是可以把你带到书呆子或者工具狂的恶意帮手。

从这篇文章开始,我将细细的分析 spring 源代码,我大可以去分析什么 Hibernate Struts 等等这些东西,但是我要说我只是拿 Spring 作为特例来做分析,并不是最终目的,最终目的是为了和大家探讨如下几个问题:

1)   如何做设计前的分析

2)   如何利用好现有的知识系统

3)   如何对待各种设计理念

4)   如果学好和用好工具软件

5)   如何学好技术

等等,而且在此中最大可能性的解释共通理念、技术的重要性,如何能做到触类旁通等等。所以这系列的文章绝对不是一个什么指南,或者就是为了分析源代码而分析的文章。

开始之前,我先要照顾一下初学者,什么是 Spring? ( 你可以直接到官方网站上去查 , 不过这里还是给个定义 , 作为 Spring 的整体概念,这个整体概念将被各种子概念解释,各种子概念将被实际的实现解释 )

Spring 提供对象关系解耦的能力,又能使各个松散耦合的对象关联起来实现一个系统的功能,我这里值的系统也是一个整体观念,具体的可能是一个应用层组件系统,也可能就是一个组件,它并且提供对一个对象本身声明周期的管理。好了,这好像是一个很学究的定义,其实上面这个定义还可以翻译成:把一个系统的关注点分离(对象或其他什么东西的关系解耦),然后再让他们协调起来干一个明确目标的事情 . 这样来讲可能是看起来有些矛盾和抽象了,其实我们可以观察一下周围的物件,比如电视机,电视机每个零件都是独立的,比如电子管,这个电子管没有被组装到这个电视机中之前谁也不知道它的作用,还可能装到一个手表里也说不定,所以它的独立的,高内聚低耦合的,然而在设计师的“设计“下,把这些互相独立的零件组装起来,来干电视机应该干的事情,而这些独立的零件,又是可以替换的,坏了或者升级都要替换掉,所以 Spring 这个框架解释了软件设计中最最朴素的也是最最本质的问题那就是对一个概念的关注点的分离或者分层,然后还要使其结合起来实现一个整体的目标!以上看起来很抽象,那么我们先用一个如何使用 Spring 的简单代码示例来说明问题:

 

设计一个功能(略)

1) 实现各个独立的类

2 )写配置文件

3 )部署

再看看客户程序如何使用这个功能:

New FileSystemXmlApplicationContext(“C:\beans.xml).getBean();

 

 

看到了吧,一个功能(整体概念)被分解了,每一个被装配的对象都是可以通过外部配置文件替换的,但是又是一个整体,对立而统一,你会发现每个对象(类的实例)都是可以独立存在的,也可以被装备到其他的应用中,并且可以脱离整体而进行单独测试,这一切带来的好处都是基于上面那么多论述的理由而来的!如此简单,如此优美。接下来我们来看看 Spring 的核心实现 :IOC AOP.( 切忌不可被名词或者什么概念吓倒,很多名词都是在使用隐喻的办法说明一个抽象的概念,是为了离你近一些,明白一些,比如 IOC, 控制反转,控制是隐喻,反转也是隐喻 )

一,               IOC 就是 A 类依赖另外 B 类的时候,不需要 A 管理 B 的生命周期,也就是不用再在 A 中来 new B, 也就是说 A 不必知道 B 是否存在,而只是有个接口引用就可以了,那么谁来管理它们之间的依赖(也就是把 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 容器,那么对于 Spring 的设计目的来说, IOC 容器的实现就是其核心部分之一。下面我们来看看 Spring IOC 容器(容器也是一个隐喻 - 一个容器里面肯定能装东西才叫容器,用来说明作用,其实具体实现很简单,后面有讲)的实现 ( 分析的基础是了解其使用、概念或者具体使用过 ):

//#ToDO 源代码文件夹结构


从上面这个源代码文件夹结构中(幸亏他们的名字起的好),我第一眼就看到了 org.springframework.context 这个子项目( Spring 所有的大的独立部分都是以子项目存在的,这样很好独立维护和升级,这也符合分解和结合的观点) , 因为我们经常使用的容器就是 ApplicationContext, 我们以此为入口点进行分析(最好的习惯就是从你最常用或者大家最常用或者框架、软件的说明书上直接入口使用点开始顺藤摸瓜),我在 Eclipse 中看到了 ApplicationContext 的源代码 ( 注释都被我去掉了,是为了更加清晰 ):


public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,

       MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    String getId();

    String getDisplayName();

    long getStartupDate();

    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

} 





 

由于我们之前总是在使用 Spring, 所以一眼就看出来 getParent 能得到“父“容器, getAutowireCapableBeanFactory 与自动装配有关 . 我们再看看它有哪些实现,我猜测我可以看到一个我们非常熟悉的 ApplicationContext ,然后我就可以按这个最熟悉 ApplicationContext 开始分析其原理了,幸好 Eclipse 提供便捷的工具让你知道一个接口有多少实现!请按 F4!

哈哈,可以看到 ClassPathXmlApplicationContext FileSystemXmlApplicationContext, 这两个都是我们经常用到的 ApplicationContext 实现,我挑 FileSystemXmlApplicationContext 进行分析,切忌在分析一个东西的实现时没有理论基础没有主线或者就直接看源代码,会非常容易进到代码丛林中的,最终迷失在茫茫的代码中(但是实际代码并不多,只是我们自己把自己搞晕了 , 阅读代码时保持一个方向进行,抓清主干然后分析分支是有效的办法, FileSystemXmlApplicationContext 的实现是 :

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

    public FileSystemXmlApplicationContext() {

    }

    public FileSystemXmlApplicationContext(ApplicationContext parent) {

       super (parent);

    }

    public FileSystemXmlApplicationContext(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) 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 parent)

           throws BeansException {

       super (parent);

       setConfigLocations(configLocations);

       if (refresh) {

           refresh();//其他构造函数都是这个构造函数的实现的,而这个构造函数的核心就是这个refresh方法

       }

    }

    @Override

    protected Resource getResourceByPath(String path) {

       if (path != null && path.startsWith( "/" )) {

           path = path.substring(1);

       }

       return new FileSystemResource(path);//解析文件路径为Resouce

    }

} 





 


啊?怎么这么多方法?看清楚了很多都是构造函数和一些重载的方法实现( @Override 注释的), 对于可能在前进路上看到的一大坨代码,不要惊呼,先看看整体这些属性和方法的类别(构造函数,方法等等),先分个类,再找关键的部分,找关键的部分也需要技巧,那就是从调用方找,我们客户程序在使用 ApplicationContext 时,不就是先在构造函数中设置一下 XML 配置文件的位置(这个配置文件就是声明了对象之间依赖关系的地方),然后通过 getBean 方法取的容器中的 Bean( 装配好的 ) 实例吗?所以我主动在上面的代码寻找构造函数的细节和 getBean 方法,等找到这些关键部分之后,我们再看支持它们实现的类变量,属性和其他方法即可,可但是我在上面的代码中没有看到什么 getBean 方法,只看到构造函数中的细节了,而且很多地方用 super 调用父类的构造函数之实现,因为总和 OO 这类的语言打交道,直接会就想到 getBean 可能在父类中!但是稍等一下,从调用方使用它的角度看,构造函数应该是解析 XML 配置的地方,而 XML 配置文件中讲述了如何装配 Bean 的信息,看来 getBean 方法依赖于这个解析过程,这是一个基础!好了!先不要马上进入找 getBean 的过程吧,先把它的基础搞明白,开始找 XML 解析的过程!对!你应该看到了,在


public  FileSystemXmlApplicationContext(String[] configLocations, boolean  refresh, ApplicationContext parent)

           throws BeansException {

       super (parent);

       setConfigLocations(configLocations);

       if (refresh) {

           refresh();

       }

    } 



 

这个构造函数中(其他构造函数都是调用这个构造函数的),貌似看到了一些端倪,第一关联了 parent, 估计 AppliactionContext 父子关系建立是通过这方式的!但是我现在先不关心这个的实现,我是想知道 XML 如何被解析的, setConfigLocation 方法的调用也不是,从名字上猜出八九不离十(你看,写程序的时候,名字越贴切越好,给各位推荐一本书《代码整洁之道》),只能是 refresh 方法了,从上面的代码分析我大概知道 XML 解析就在这里面实现的,但是这个方法干了些其他什么事情我还不知道(分析代码时,必须有点想象力,但是这种想象力必须建立在“证据“上,不要瞎猜),我迫不及待地按 F3 进入 refresh 方法中看个究竟 ( 这时你发现 refresh 方法是在 AbstractApplicationContext 类中实现的 , 为什么在这里实现?除了 OO 继承的观念外,还有一种设计思想在里面,这个思想我在后面的分析中会写到 )

public void refresh () throws BeansException, IllegalStateException {

       synchronized ( this . startupShutdownMonitor ) {

           // Prepare this context for refreshing.

           prepareRefresh();
           ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
           prepareBeanFactory(beanFactory);
         try {

              postProcessBeanFactory(beanFactory);
              invokeBeanFactoryPostProcessors(beanFactory);
              registerBeanPostProcessors(beanFactory);
              initMessageSource();
              initApplicationEventMulticaster();
              onRefresh();
              registerListeners();

...

  




 其实这里是一个初始化ApplicationContext的过程模板(模板设计模式),基类里面的主方法Refresh起到一个流程整合的作用,其调用的方法可能是抽象方法,需要子类去实现的,这样做的好处是,在一固定算法框架下,做到分步实现的解藕,想象一下,每一个分步都是可以被子类替换的,利用模板设计模式把过程也解藕了。

另外,你还可以看到上面有MessageSource,PostProcessor,applicationEvent的内容,熟悉Spring使用的朋友,你应该知道这里是干了什么了,有时间的话,我们就回来分析分析这些方法!不过我们现在先研究一下obtainFreshBeanFactory()


4
1
分享到:
评论
2 楼 magicyang919 2014-01-10  
这个必须顶,我们要知道的就是为什么spring这么设计,而不是只是为了看代码而看代码
1 楼 pastore123 2013-11-30  
分析的很好,思维过程讲的很好,通俗易懂

相关推荐

Global site tag (gtag.js) - Google Analytics