Post

Spring应用架构(一)设计理念

设计初衷

对于一个应用程序,如果达到一定的复杂程度,都会需要一个基础骨架,在这个骨架的基础上来维护引用程序的秩序,管理提供业务服务功能的各个程序组成要素。对于开发者而言,这个骨架就是开发框架,一个好的开发框架甚至可以带动一门编程语言的发展,典型的就是Ruby On Rails。对于Java体系而言,SpringFramework也起到了类似的作用。在现在的Java企业级程序中,Spring几乎都是必选的组件。

Spring的主要功能就是管理各个程序对象,以一个轻量级的容器作为基础,对程序对象提供一致性的管理。这里的管理是一个广泛的概念,主要可以分成以下几个部分。

  1. 生命周期管理:控制应用对象运行的生命周期。首先,轻量级的容器必须将“创建新对象”的逻辑从使用者那里剥离出来,这样的好处在于减少对象之间的耦合,和针对对象复用(singleton模式)的大量硬编码;更精细的生命周期管理,可以提供包括各种回调机制,在需要初始化对象、激活对象或者容器本身即将构造的时候,可以通过这些回调机制通知被管理的对象,或者使用者,这样以一致的方式提供对应用程序的拓展。
  2. 查找服务:容器提供某种途径,用于获取受管对象的引用。容器的查找功能将“对业务对象实现细节的了解”从使用者那里抽象出来,将其隐藏在容器内部。查找服务是容器的核心服务,也就是说,容器的核心就是一个工厂,使用者只需要关心最终的产品,而不需要知晓生产的过程。
  3. 配置管理:容器需要提供统一的方法来配置运行在其中的对象,并且允许对象的参数化。简单的配置应该从Java代码中抽取出来。这样改变配置就不需要重新编译代码。
  4. 依赖决议:容器不仅可以管理基础对象类型(String、Integer)等的配置,还需要管理各个对象之间的依赖关系。

上面的5点是站在基础容器的视角来定义“管理程序对象”的概念。除此之外,我们希望容器还可以提供

  1. 企业级服务:为容器内运行的对象提供事务管理和声明式的服务

    企业级服务的概念,在EJB中非常流行,Spring的初衷是解决EJB对于开发,过于笨重和繁琐的问题而提出的解决方案。同时Spring也吸收了EJB中大量优秀的设计。企业级服务,在我理解,就是一套成熟的企业应用开发范式和最佳实践。

  2. 线程管理:对于访问受管对象的操作,容器提供一个线程模型,这里针对的是多线程环境下的服务对象提供,对于无状态的服务对象而言,可以简单共享;反之则需要额外处理。
  3. 对象池:提供一个实例吃来管理对象
  4. 集群管理:容器可以自己提供集群的支持,客户端可以完全透明地和不同容器进行通信。这一点,如果将所有的应用组件部署在每一台机器上,也就是实现物理层面的单机业务闭环,并且在之前提供了负载均衡等全局性的支持,那么容器提供的集群服务就不需要了,同样如果应用组件是无状态的,那么容积级别的集群也不是很重要。
  5. 管理:容器为其中运行的对象提供管理服务,例如通过控制台或者JMX进行管理。
  6. 远程服务:两个容器之间的对象调用,可以分成两个部分:“暴露远程服务”+“消费远程服务”。
  7. 可定制化和拓展新:允许为受管对象提供定制服务,例如:安全检查等声明式的服务。

最后我们还希望容器具备下面的特点

  1. 可以管理应用代码,又不给应用代码强加对容器的依赖,例如可以无须修改地将遗留代码引入容器,这种特性叫做“非侵入性”,除非绝对必要,否则基础设施不应该把对容器的依赖强加给应用代码。
  2. 可以快速启动
  3. 不需要特殊的部署步骤,与之对应的是依赖于引用容器提供服务的EJB等重量级框架。
  4. 最小程度依赖的API,以保证可以运行在不同的环境中,例如Web容器,独立的客户端,甚至是Applet,轻量级容器不应该依赖J2EE,应该是纯Java的。
  5. 将对象交给轻量级容器管理的时候,工作量和性能开销都应该很小。既可以在其中管理粗粒度的对象,又可以管理细粒度的对象。
  6. 可插拔性,容器可以为各种组件提供可插拔性,例如,对于同一个组件接口,可以为它提供不同的实现类,容器将调用方和具体的实现策略隔离。尽管Java接口提供了很好的契约-实现分离,但是还必须有某种途径帮助我们找到一个接口的实现。如果在代码中进行硬编码,就不能充分获得接口带给我们的好处。
  7. 一致性,如果没有容器作为基础,服务定位将变得无据可寻,不同的服务对象可能被放在不同的地方。配置和管理也变得混乱,给维护带来很大的困难。

控制反转

通常只有非常简单的业务对象才能独立运行,而大多数的业务对象都有依赖关系。比如与其他对象,与数据访问对象,与各种资源之间存在依赖管理。为了让业务对象,不依赖容器,我们可以使用控制反转和依赖注入来处理这种依赖关系。依赖注入(DI)是最流行的IoC类型,利用IoC去消除所有的查找(lookup)代码,让容器自动决定对象间的依赖关系。

IoC的实现策略

  1. 依赖查找:容器提供回调接口和上下文环境给组件,组件必须自己使用容器提供的API来查找资源和协作对象,仅有的控制反转,值体现在回调方法上,容器将回调这些回调方法,从而让应用代码获得相关的资源。使用以来查找的方式,应用代码会以来容器提供的注册表或者上下文。
  2. 依赖注入:组件不做定位查询,只是提供普通的Java方法让容器去决定依赖关系,容器全权负责组件的装配。它会把符合依赖关系的对象通过JavaBean属性或者构造参数传递给所需要的对象。这种方式不依赖于特定的容器API和接口,称为“基于语言的IoC”。

    依赖注入的根本原则就是:应用对象不应该负责查找资源或者其他依赖的协作组件。配置对象的工作应该交给IoC容器来完成,“查找资源”的逻辑应该从应用代码中抽取出来,交给容器负责。

依赖注入的局限性

IoC容器通常可以在配置阶段判断所有对象之间的依赖关系,并通过JavaBean的属性或者构造函数来组装对象。这种方式存在下面一些缺点。

  1. 如果不能吧锁协作对象或者资源保存在实例变量中,必须每次使用不同的对象。比如协作对象不是线程安全的,就会出现这种情况,Spring提供了一个基于AOP的解决方案:受管对象引用的是一个AOP代理(FactoryBean)“每一次调用创建一个新对象”的逻辑被隐藏在框架内部,显然这会导致对容器的依赖,也就是离开了Spring容器,应用程序就不具备这样的功能了。
  2. 组件需要根据上下文环境进行不同的配置,例如如果可以获取SuperFoobar就获取,否则获取Foobar,这类有条件的配置逻辑可能需要再生命周期中编写特定的代码,也会导致对容器的依赖。
  3. 直接使用容器,而不只是在容器内运行业务对象,也会导致对容器的依赖,这时候问题应该是,这种依赖合理吗?
  4. 预先确定所有潜在的协作对象的做法,在一些情况下,并非好主意,如果有很多的协作对象,并且这种协作对象虽则逻辑的深入不断变化,那么最好是不要预设,而是在实际需要的时候去取用,lazy-init可以解决定义的懒加载,却无法处理依赖的懒加载

依赖和API

大多数情况下,我们都不需要依赖容器,当需要依赖容器的时候,Spring提供的BeanFactoryApplicationConext接口也很容易配置到测试用例中,这也非常轻量级。正常使用下Sprign的IoC是通过元数据来配置的,这些元数据可以使XML或者property或者Annotation(存在API上的依赖,不过也非常轻量级,你可以使用Java的Injection标准API)。

如果想要组件在容器之间是可移植的,应该采用下面的原则

  1. 尽量避免代码对容器的依赖,应该尽量选择依赖注入
  2. 如果使用某项企业级服务必须以来容器的话,应该尽量使用声明式而非编程式。
  3. 尽量保证代码是无入侵性的,紧密依赖于任何框架都是危险的,就算是你自己的框架也是如此。例如框架的升级就很难独立完成,会干扰到应用代码。并且高度依赖性的代码难以测试和复用,一个合理的设计应该始终尽量避免入侵性。
  4. 在进行代码构建的时候,将程序的可测试性作为开发和设计过程的一个要点。

Spring的定位

Spring的设计背景是在重量级的EJB开发模式,统治J2EE开发的时代。同时由于Java体系的开放,同时代也有很多轻量级的框架,解决企业级Java开发各方面的问题。Spring的定位可以简单总结如下:

  1. 解决被其他框架忽略的部分,将各种专用的框架整合在一起形成连贯的整体架构。同时提供其他框架没有涉及的地方,比如通用性的事务管理,应该说Spring是一个应用框架,而不是Web框架,IOC框架,AOP框架或者中间层框架。
  2. 易于选择,一个框架应该有清楚的层次划分,允许用户使用其中的单项功能,而不是需要接受整个世界观。
  3. 易于使用,相比J2EE而言,首先应该解决问题,让容易容易完成,而不是现在就要为将来的复杂需求买单(比如分布式事务)。
  4. 鼓励最佳实践,Spring不强制使用任何架构风格。
  5. 统一配置,一个号的基础架构可以让应用配置灵活并且统一,不需要自制Singleton和工厂,从中间层到web控制器,所有的配置需求都可以用统一的方式满足
  6. 可拓展,因为Spring本身就是基于接口的,而非基于类,容易很容易拓展和定制,Spring的组件允许各种形式的Strategy模式。

因此,Spring是一个完整的应用框架,它可以在很多应用层面发挥作用,Spring由多个子框架组成,而且这些框架都相对独立。开发者可以选择使用其中的一部分,而不会引入过多无用的依赖。

springRuntime

  • Beans工厂:Spring轻量级IoC容器能够配置、装配JavaBean和大多数普通Java对象,使得开发者不必定制Singleton和自己的配置机制。Spring提供了多个即拿即用的功bean工厂实现。
  • 应用上下文:Application Context 是对bean 工厂的拓展,它在bean工厂的基础上增加了对信息源、资源加载以及事件机制的支持,并提供了接入现有系统环境的能力,Sprign同样提供了一些现成的实现。
  • AOP框架:Spring的AOP框架提供了对AOP的支持,可以对轻量级容器中的任何对象进行拦截。可以轻松地为JavaBean提供代理,无缝地将拦截器和其他的Advice整合起来,关于AOP的详细概念和具体细节,后面的章节会再做分析。
  • 事务管理:提供通用的事务管理基础设施
  • 集成ORM工具:集成ORM工具,简化资源的配置、获取和释放、将ORM和事务、DAO抽象集成起来,让应用无需定制ThreadLocal会话和自己的事务操作。可以在不同的ORM实现中提供一致性的体验。
  • Web MVC:提供一个web MVC框架实现
  • 远程调用支持:Sprign为远程服务的访问和创建提供了一个抽象层,避免在应用对象中硬编码对服务的查找。

Sprign的主要目标就是为了让J2EE更易用,同时促进好的变成习惯,Spring不重复发明轮子。它的目标是让现有的技术更加容易使用

This post is licensed under CC BY 4.0 by the author.