1、Spring 技术内幕深入解析 Spring 架构与设计原理(一)引子缘起 已经很久没有写帖子了,现在总算是有点时间写些东西,也算是对自己的一个记录吧。刚刚完成了一个软件产品,从概念到运营都弄了一下,正在推广当中,虽然还没有能够达到盈亏平衡,但是这个过程,对自己也算是一种历练。先不管结果如何,好呆走过这么一遭了。 我打算用这个帖子,把自己在这个过程中的一些心得,特别是对 Spring 新的理解,记录下来。使用这个帖子的标题,持续下来。 简单来说,自己的软件产品是一个基于互联网的 SaaS 协同软件平台,操作简单,支持流程定义,管理和多种客户端 -像短信,MSN,智能手机什么的(我这里就不多做什
2、么广告了),也有一个企业版的版本,使用的技术框架是 Hibernate + Spring + Wicket,下面是 Linux 和 MySQL,还有云计算的平台的使用,以支持其扩展性,虽然现在还没有可扩展性的需求,但似乎不难从 SaaS 上,就会想 到云计算, 其实,它们真的是天生的一对! 关于云计算,自己对这个技术很感兴趣,觉得和开源软件的结合,是很有意思的,因为它们都有基于服务的基因,在云计算平台的使用上,也有一些初步的 实践。云计算是一个很有意思的话题,但在这里主要是想谈 Spring,所以对云计算,这里就先不多说了,但非常欢迎有兴趣的朋友和一起另外找地方讨论! 回到正题,在我自己的产品
3、中,其中除了 Wicket 和云计算外,其他都是大家非常熟知的了,像 Hibernate, Spring, MySQL 什么的。在这个过程中,发现自己对一些技术点也有了新的认识,最有体会的是 Spring。当然,在这个过程中,更大的收获是对产品开发整个过程 的认识,在这点上,真是一言难尽. 回到自己还算了解的 Spring, 这次我使用的是 3.0 的代码,所以,有机会也把这些代码读了几遍,比原来的理解要加深了许多,也发现了不少和 2.0 代码不同的地方,以及自己一些对 Spring 的新的理解,这些,就让我就用这个帖子系列,给自己总结一下,也算是对自己以前的那个代码分析的帖子做一个新的交代吧
4、。 自己对 Spring 一点小小的见解 简化 Java 企业应用的开发,是 Spring 框架的目标.就是我们熟知的当年的那个interface21,也亦非吴下阿蒙了,由它演进出来的 Spring,以及由它带来的崭新开发理念,也早已伴随着这个开源框架的广泛应用,而飞入寻常百姓家。与此同时,伴随着 Spring 的成熟,开源社区的成 长,在 Rod.Johnson 的领导下,以 Spring 为核心的一系列开源软件的产品组合,其脉络也逐渐的清晰和丰富起来;现在,已经发展成为一个包括软 件运行,构建,部署运营,从而涵盖整个软件服务生命周期的产品族群;同时也成为,在当今主流的软件业态中,一个不可或
5、缺的重要组成。 在最近完成的 VMware 公司对 Spring 的运营者 SpringSource 公司的收购中,也让我们又看到了一个,在开源软件中,蕴含着的巨大 商业价值,以及又一次基于开源模式的商业成功;也让我们看到,Spring 为自己设计的未来定位,它与云计算的融合趋势,以及,努力成为在云计算业态 中,PaaS(Platform As a Service)服务有力竞争者的战略设想;由此,可以想象,在云计算这个全新的计算时代中,如何秉承 Spring 的一贯风格,为云计算应用的开发,提供 高可靠,高可用,高可扩展,高性能的应用平台,对 Spring 团队来说,是一个面临的全新挑战;在这
6、个领域中的雄心和今后的作为,那就让我们一起拭目以待 吧。这里也有点凑巧了,正好 Spring 和云计算都是自己喜欢的东西,说不定以后,我还能够在这两者的结合上再写些东西呢。 作为一个庞大的体系,Spring 在 Java 企业应用中, 和我们熟悉的企业应用服务器一样,比如我们熟知的其他产品,像 Weblogic,Websphere,JBoss,.NET 这些等等,其定位和目的,都在 于希望能够起到一个企业应用资源的集成管理,以及为应用开发提供平台支持的作用,这和我们熟知的,像 UNIX 和 Windows 这样传统意义上的操作系统, 在传统的计算系统中,起到的作用非常的类似。只不过,按照个人的
7、理解,它们不同在于,我们熟知的传统操作系统关心的是存储,计算,通信,外围设备这些物理 资源的管理,并在管理这些资源的基础上,为应用程序提供一个统一平台和服务接口;而像 Spring 这样的应用平台,它们关心的是在 Java 企业应用中,对 包括那些像 Web 应用,数据持久化,事务处理,消息中间件,分布式计算等等这些,为企业应用服务的抽象资源的统一管理,并在此基础上,为应用提供一个基于 POJO 的开发环境。尽管各自面向的资源,管理的对象,支持的应用以及使用的场景不同,但这两者在整个系统中的定位,却依然有着可以类比和相互参考的地 方,从某种意义上看,它们都起到一个资源协调,平台支持,以及服务集
8、成的作用。 所以我觉得可以使用,我们看待传统操作系统的方法和一些基本观念,来对Spring 进行系统分析,以及对 Spring 进行层次划分,这样可能更加 容易理解,同时,所以,个人感觉,仿照传统操作系统的眼光,把对 Spring 框架的实现,划分为核心,组件和应用这三个基本的层次,来理解 Spring 框 架是不错的一个方法,就算是众所周知的“三段论”的应用吧。不知道这种分析方法,是不是太庸俗,但我自己还是觉得挺受用的,呵呵,谁叫我是个俗人呢! 今天先写一些,就算是起个头吧,明天继续! 写写 IOC/AOP 的一些具体东西。 深入解析 Spring 架构与设计原理(一)IOC 实现原理IOC
9、 的基础 下面我们从 IOC/AOP 开始,它们是 Spring 平台实现的核心部分;虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工 作,但对这两个核心模块工作原理和运作机制的理解,对深入理解 Spring 平台,却是至关重要的;因为,它们同时也是 Spring 其他模块实现的基础。从 Spring 要做到的目标,也就是从简化 Java EE 开发的出发点来看,简单的来说,它是通过对 POJO开发的支持,来具体实现的;具体的说,Spring 通过为应用开发提供基于 POJO的开发模式,把 应用开发和复杂的 Java EE 服务,实现解耦,并通过提高单元测试的覆盖率,从而有效
10、的提高整个应用的开发质量。这样一来,实际上,就需要把为 POJO 提供支持的,各种 Java EE 服务支持抽象到应用平台中去,去封装起来;而这种封装功能的实现,在 Spring 中,就是由 IOC 容器以及 AOP 来具体提供的,这两个模块,在很大程 度上,体现了 Spring 作为应用开发平台的核心价值。它们的实现,是 Rod.Johnson 在他的另一本著作Expert One-on-One J2EE Development without EJB 中,所提到 Without EJB 设计思想的体现;同时也深刻的体现了 Spring 背后的设计理念。 从更深一点的技术层面上来看,因为 S
11、pring 是一个基于 Java 语言的应用平台,如果我们能够对 Java 计算模型,比如像 JVM 虚拟机实现技术 的基本原理有一些了解,会让我们对 Spring 实现的理解,更加的深入,这些 JVM 虚拟机的特性使用,包括像反射机制,代理类,字节码技术等等。它们都是 在 Spring 实现中,涉及到的一些 Java 计算环境的底层技术;尽管对应用开发人员来说,可能不会直接去涉及这些 JVM 虚拟机底层实现的工作,但是了解 这些背景知识,或多或少,对我们了解整个 Spring 平台的应用背景有很大的帮助;打个比方来说,就像我们在大学中,学习的那些关于计算机组织和系统方面 的基本知识,比如像数
12、字电路,计算机组成原理,汇编语言,操作系统等等这些基本课程的学习。虽然,坦率的来说,对我们这些大多数课程的学习者,在以后的工 作中,可能并没有太多的机会,直接从事这么如此底层的技术开发工作;但具备这些知识背景,为我们深入理解基于这些基础技术构架起来的应用系统,毫无疑问, 是不可缺少的。随着 JVM 虚拟机技术的发展,可以设想到的是,更多虚拟机级别的基本特性,将会持续的被应用平台开发者所关注和采用,这也是我们在学习平台 实现的过程中,非常值得注意的一点,因为这些底层技术实现,毫无疑问,会对 Spring 应用平台的开发路线,产品策略产生重大的影响。同时,在使用 Spring 作为应用平台的时候,
13、如果需要更深层次的开发和性能调优,这些底层的知识,也是我们知识库中不可缺少的部分。有了这些底层知识,理解整个系 统,想来就应该障碍不大了。 IOC 的一点认识 对 Spring IOC 的理解离不开对依赖反转模式的理解,我们知道,关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是解决面向对象系统设 计复杂性和提高面向对象系统可测试性的一个有效的解决方案。这个问题触发了 IoC 设计模式的发展,是 IoC 容器要解决的核心问题。同时,也是产品化的 IoC 容器出现的推动力。而我觉得 Spring 的 IoC 容器,就是一个开源的实现依赖反转模式的产品。 那具体什么是 I
14、oC 容器呢?它在 Spring 框架中到底长什么样?说了这么多,其实对 IoC 容器的使用者来说,我们常常接触到的 BeanFactory 和ApplicationContext 都可以看成是容器的具体表现形式。这些就是 IoC 容器,或者说在 Spring 中提 IoC 容 器,从实现来说,指的是一个容器系列。这也就是说,我们通常所说的 IoC 容器,如果深入到 Spring 的实现去看,会发现 IoC容器实际上代表着一系列功 能各异的容器产品。只是容器的功能有大有小,有各自的特点。打个比方来说,就像是百货商店里出售的商品,我们举水桶为例子,在商店中出售的水桶有大有小; 制作材料也各不相同
15、,有金属的,有塑料的等等,总之是各式各样,但只要能装水,具备水桶的基本特性,那就可以作为水桶来出售来让用户使用。这在 Spring 中也是一样,它有各式各样的 IoC容器的实现供用户选择和使用;使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本 情况,那会对容器的使用是非常有帮助的;就像我们在购买商品时进行的对商品的考察和挑选那样。 我们从最基本的 XmlBeanFactory 看起,它是容器系列的最底层实现,这个容器的实现与我们在 Spring 应用中用到的那些上下文相比, 有一个非常明显的特点,它只提供了最基本的 IoC 容器的功能。从它的名字中可以看出,这个 IoC
16、容器可以读取以 XML 形式定义的 BeanDefinition。理解这一点有助于我们理解ApplicationContext 与基本的 BeanFactory 之间的区别和联系。我们可 以认为直接的 BeanFactory 实现是 IoC 容器的基本形式,而各种ApplicationContext 的实现是 IoC 容器的高级表现形式。 仔细阅读 XmlBeanFactory 的源码,在一开始的注释里面已经对 XmlBeanFactory 的功能做了简要的说明,从代码的注释还可以看到,这是 Rod Johnson 在 2001 年就写下的代码,可见这个类应该是 Spring 的元老类了。它是
17、继承 DefaultListableBeanFactory 这个 类的,这个DefaultListableBeanFactory 就是一个很值得注意的容器! Java 代码 1. public class XmlBeanFactory extends DefaultListableBeanFactory 2. private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 3. public XmlBeanFactory(Resource resource) throws BeansExcept
18、ion 4. this(resource, null); 5. 6. public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException 7. super(parentBeanFactory); 8. this.reader.loadBeanDefinitions(resource); 9. 10. XmlBeanFactory 的功能是建立在 DefaultListableBeanFactory 这个基本容器的基础上的,在这个基本容器的基 础上实现了其他诸如 XML 读取的
19、附加功能。对于这些功能的实现原理,看一看 XmlBeanFactory 的代码实现就能很容易地理解。在如下的代码中可以 看到,在 XmlBeanFactory 构造方法中需要得到Resource 对象。对 XmlBeanDefinitionReader 对象的初始化,以及使 用这个这个对象来完成 loadBeanDefinitions 的调用,就是这个调用启动了从Resource 中载入 BeanDefinitions 的过 程,这个 loadBeanDefinitions 同时也是 IoC 容器初始化的重要组成部分。 简单来说,IoC 容器的初始化包括 BeanDefinition 的 Re
20、souce 定位、载入和注册这三个基本的过程。我觉得重点是在载入和对 BeanDefinition 做解析的这个过程。可以从 DefaultListableBeanFactory 来入手看看 IoC 容器是怎样完成 BeanDefinition 载入的。在 refresh 调用完成以后,可以看到 loadDefinition的调用: Java 代码 1. public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext 2. public Abstrac
21、tXmlApplicationContext() 3. 4. public AbstractXmlApplicationContext(ApplicationContext parent) 5. super(parent); 6. 7. /这里是实现 loadBeanDefinitions 的地方 8. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException 9. / Create a new XmlBeanDefinitionReader for the giv
22、en BeanFactory. 10. / 创建 XmlBeanDefinitionReader,并通过回调设置到BeanFactory 中去,创建 BeanFactory 的使用的也是DefaultListableBeanFactory。 11. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); 12. 13. / Configure the bean definition reader with this contexts 14. / resource loadi
23、ng environment. 15. / 这里设置 XmlBeanDefinitionReader, 为XmlBeanDefinitionReader 配置 ResourceLoader,因为DefaultResourceLoader 是父类,所以 this 可以直接被使用 16. beanDefinitionReader.setResourceLoader(this); 17. beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this); 18. 19. / Allow a subclass to prov
24、ide custom initialization ofthe reader, 20. / then proceed with actually loading the bean definitions. 21. / 这是启动 Bean 定义信息载入的过程 22. initBeanDefinitionReader(beanDefinitionReader); 23. loadBeanDefinitions(beanDefinitionReader); 24. 25. 26. protected void initBeanDefinitionReader(XmlBeanDefinitionRea
25、der beanDefinitionReader) 27. 这里使用 XmlBeanDefinitionReader 来载入 BeanDefinition 到容器中,如以下代码清单所示: Java 代码 1. /这里是调用的入口。 2. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException 3. return loadBeanDefinitions(new EncodedResource(resource); 4. 5. /这里是载入 XML 形式的 BeanDefinitio
26、n 的地方。 6. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException 7. Assert.notNull(encodedResource, “EncodedResource must not be null“); 8. if (logger.isInfoEnabled() 9. logger.info(“Loading XML bean definitions from “ + encodedResource.getResource(); 10.
27、 11. 12. Set currentResources = this.resourcesCurrentlyBeingLoaded.get(); 13. if (currentResources = null) 14. currentResources = new HashSet(4);15. this.resourcesCurrentlyBeingLoaded.set(currentResources); 16. 17. if (!currentResources.add(encodedResource) 18. throw new BeanDefinitionStoreException
28、( 19. “Detected recursive loading of “ + encodedResource + “ - check your import definitions!“); 20. 21. /这里得到 XML 文件,并得到 IO 的 InputSource 准备进行读取。22. try 23. InputStream inputStream = encodedResource.getResource().getInputStream(); 24. try 25. InputSource inputSource = new InputSource(inputStream);
29、26. if (encodedResource.getEncoding() != null) 27. inputSource.setEncoding(encodedResource.getEncoding(); 28. 29. return doLoadBeanDefinitions(inputSource, encodedResource.getResource(); 30. 31. finally 32. inputStream.close(); 33. 34. 35. catch (IOException ex) 36. throw new BeanDefinitionStoreExce
30、ption( 37. “IOException parsing XML document from “ + encodedResource.getResource(), ex); 38. 39. finally 40. currentResources.remove(encodedResource); 41. if (currentResources.isEmpty() 42. this.resourcesCurrentlyBeingLoaded.set(null); 43. 44. 45. 46./具体的读取过程可以在 doLoadBeanDefinitions 方法中找到: 47. /这是
31、从特定的 XML 文件中实际载入 BeanDefinition 的地方 48. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 49. throws BeanDefinitionStoreException 50. try 51. int validationMode = getValidationModeForResource(resource); 52. /这里取得 XML 文件的 Document 对象,这个解析过程是由documentLoader 完成的,这个 documen
32、tLoader 是DefaultDocumentLoader,在定义 documentLoader 的地方创建 53. Document doc = this.documentLoader.loadDocument( 54. inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware(); 55. /这里启动的是对 BeanDefinition 解析的详细过程,这个解析会使用到 Spring 的 Bean 配置规则,是我们下面需要详细关注的地方。 56. return registe
33、rBeanDefinitions(doc, resource); 57. 58. catch (BeanDefinitionStoreException ex) 59. throw ex; 60. 61. catch (SAXParseException ex) 62. throw new XmlBeanDefinitionStoreException(resource.getDescription(), 63. “Line “ + ex.getLineNumber() + “ in XML document from “ + resource + “ is invalid“, ex); 64
34、. 65. catch (SAXException ex) 66. throw new XmlBeanDefinitionStoreException(resource.getDescription(), 67. “XML document from “ + resource + “ is invalid“, ex); 68. 69. catch (ParserConfigurationException ex) 70. throw new BeanDefinitionStoreException(resource.getDescription(), 71. “Parser configura
35、tion exception parsing XMLfrom “ + resource, ex); 72. 73. catch (IOException ex) 74. throw new BeanDefinitionStoreException(resource.getDescription(), 75. “IOException parsing XML document from “ + resource, ex); 76. 77. catch (Throwable ex) 78. throw new BeanDefinitionStoreException(resource.getDes
36、cription(), 79. “Unexpected exception parsing XML document from “ + resource, ex); 80. 81. 关于具体的 Spring BeanDefinition 的解析,是在BeanDefinitionParserDelegate 中完成的。这个类里包含了各种 Spring Bean定义规则的处理,感兴趣的同学可以仔细研究。我们举一个例子来分析这个处理过程,比如我们最熟悉的对 Bean 元素的处理是怎样完成的,也就是我们 在XML 定义文件中出现的这个最常见的元素信息是怎样被处理的。在这里,我们会看到那些熟悉的 Bea
37、nDefinition 定义的处理,比如id、name、aliase 等属性元素。把这些元素的值从 XML 文件相应的元素的属性中读取出来以后,会 被设置到生成的 BeanDefinitionHolder 中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种 Bean 的属性配置,通 过一个较为复杂的解析过程,这个过程是由 parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到 BeanDefinition 对象中并设置到 BeanDefinitionHolder 中去,如以下清单所示: Java 代码 1. public BeanDefi
38、nitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) 2. /这里取得在元素中定义的 id、name 和 aliase 属性的值 3. String id = ele.getAttribute(ID_ATTRIBUTE); 4. String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); 5. 6. List aliases = new ArrayList(); 7. if (StringUtils.hasLength(nameAttr)
39、 8. String nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS); 9. aliases.addAll(Arrays.asList(nameArr); 10. 11. 12. String beanName = id; 13. if (!StringUtils.hasText(beanName) 15. if (logger.isDebugEnabled() 16. logger.debug(“No XML id specified -using “ + beanName + 17. “
40、 as bean name and “ + aliases + “ as aliases“); 18. 19. 20. 21. if (containingBean = null) 22. checkNameUniqueness(beanName, aliases, ele); 23. 24. 25. /这个方法会引发对 bean 元素的详细解析 26.AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); 27. if (beanDefinition
41、!= null) 28. if (!StringUtils.hasText(beanName) 29. try 30. if (containingBean != null) 31. beanName = BeanDefinitionReaderUtils.generateBeanName( 32. beanDefinition, this.readerContext.getRegistry(), true); 33. 34. else 35. beanName = this.readerContext.generateBeanName(beanDefinition); 36. / Regis
42、ter an alias for the plain beanclass name, if still possible, 37. / if the generator returned the class name plus a suffix. 38. / This is expected for Spring 1.2/2.0 backwards compatibility. 39. String beanClassName = beanDefinition.getBeanClassName(); 40. if (beanClassName != null 44. 45. 46. if (logger.isDebugEnabled() 47. logger.debug(“Neither XML id nor name specified - “ + 48. “using generated bean name “ +beanName + “); 49. 50.