序言 软件开发者还必须具有好奇心。软件业中的开发总是不断更换目标,不断进化。新框架,新技术,新语言和新方法学总是不断地出现。每一样都是新工具,需要我们掌握和加入到我们的工具箱中,使我们能够更好更快地完成我们的工作。 最后一个特征,也是最受珍爱的特征就是“惰性”。这种惰性刺激开发者努力工作来寻找需要最少工作量的解决方案。正是有了这种好奇心,惰性和我们拥有的所有的分析能力,四年前我们俩开始行动起来,寻找开发软件的新方法。 当今时代,Java社区的开源软件已经达到了一个关键的数量。无数的开源框架在Java世界遍地开花。为决定选用一种框架,它必需能极大满足我们的需要—它必需能满足我们80%的需求。对于任何不能满足我们的功能,框架必需容易地扩展来包含这些功能。扩展并不意味着随便组装一下,这样以后会感到恶心,而是意味着以优雅的方式来扩展。这样的要求有点过分,哈? 我们立即采用的这样的框架之一就是ant。从一开始我们就可以看出,ant是由一个能理解我们构建Java应用的痛楚的开发者创建的。从从选用ant的那一刻起,不再有javac,不再有CLASSPATH。所需的一切只是直观的(即使有时候比较繁琐)XML配置文件。生活(和构建)变得简单。 随着我们的进展,我们开始采用越来越多的工具。Eclipse成为我们的IDE选择。Log4J成为我们的(也是所有其他人的)缺省的日志工具。Lucene替代了我们的商业搜索解决方案。所有这些工具都满足我们的准则:满足需求的同时,易用,易理解,易扩展。 但仍然缺少一些东西,这些伟大的工具如ant和Eclipse,被设计用来辅助开发软件,或者满足某一特定应用需要,如使用Lucene进行搜索和使用log4J来进行记录日志。他们都没有解决企业应用的核心:持久化,事务和与其他企业资源集成。 直到去年我们发现了令人惊奇的两大企业武器Spring和Hibernate。这两个框架满足了几乎所有的中间层及数据层的需求。 我们首先采用了Hibernate。它是最直观的特性最多的ORM工具。但直到采用了Spring我们才使代码看上去更加优雅。使用Spring的控制反转(Inversion of Control)我们可以抛弃所有的自定义工厂及配置器。事实上这也是我们将Spring集成到我们应用中的最初原因。它的wiring使我们的应用程序更加合理化,移除了我们自制的解决方案。(每一个开发者都会编写自己的框架,但是有时候必需得放弃)。 我们很快发现了一个额外的好处,Spring提供了对Hibernate的很容易地继承。这使我们不必自制对Hibernate的继承类,可以直接使用Spring的支持。这引领我们走进Spring对透明持久化的支持。 如果你稍加注意的话你应该发现了一个模式。越多地使用Spring,你就能发现越多的Spring的特性。而且我们发现的每一个特性都让我们感受到乐趣。它的Web MVC框架在好几个应用中很好地工作。它的AOP支持在好几个地方,特别是安全方面对我们很有帮助。它的JDBC支持对于小型程序很有帮助。对了,我们还用它来做日程。还有JNDI访问。还有email集成。谈及能不能满足开发的需要,Spring可谓是雪中送炭。 我们非常喜欢Spring,并且决定应该写一本关于它的书。幸运地是,我们之中已经有人为manning写过一本书,知道写书是怎么回事。很快“应该写这样一本书的人”成为了我们。我们写这本书的目的是传递Spring的精神。Spring框架是我们工作的乐趣—我们预测也会成为您的乐趣。最后,我们希望这本书能成为你到达终点的快乐的交通工具。 第一部分 Spring基础 第一章 快速进入Spring世界 1.1 为什么选择Spring? 1.1.1 J2EE开发者的一天 1.1.2 Spring的承诺 <!--[if !supportLists]-->n <!--[endif]-->好的设计比起底层的实现技术更重要。 <!--[if !supportLists]-->n <!--[endif]-->通过接口松散耦合的JavaBean是很好的模型。 <!--[if !supportLists]-->n <!--[endif]-->代码应该易于测试。 好的设计比起底层的实现技术更重要。 不必为了使用EJB技术而使用它,真正需要它提供的服务才去使用它。 通过接口松散耦合的JavaBean是很好的模型 使用Spring,JavaBean通过接口依赖于协作。你所需要做的就是创建彼此通过接口通信的类,其他的都交给Spring来做。 代码应该易于测试 测试不需启动J2EE容器,因为你测试的是POJO。 1.2 Spring是什么? 简单地讲,Spring是一个轻量级的控制反转和面向方面编程的框架。以下是这个描述的详细解释: <!--[if !supportLists]-->n <!--[endif]-->轻量级。从两个方面:大小和预处理。Spring发布版本的就是一个JAR包,大小就是1M多一点。经Spring预处理的时间几乎可以忽略。 <!--[if !supportLists]-->n <!--[endif]-->控制反转。Spring通过控制反转来达到松散耦合。通过IOC,对象被动地接受他们的依赖,而不是主动地查找和创建他们的依赖。 <!--[if !supportLists]-->n <!--[endif]-->面向方面。Spring对AOP的支持使业务逻辑从系统服务中分离出来。 <!--[if !supportLists]-->n <!--[endif]-->容器。从包容和管理应用程序对象生命周期的意义上来讲,Spring是一个容器。你可以配置你的每一个JavaBean如何被创建,以及他们之间的关联。不应当把Spring同重量级的EJB容器混淆,因为EJB容器是重量级的而且工作起来很笨重。 <!--[if !supportLists]-->n <!--[endif]-->框架。Spring组合简单的JavaBean成复杂的的应用程序。在Spring中应用程序对象通常通过XML文件声明式地组合。Spring提供很多基础的功能,如事务管理,持久化框架集成等,你要做的就是开发业务逻辑。 1.2.1 Spring模块 核心容器 Spring的核心容器提供Spring框架的基础功能。在这个容器中有一个BeanFactory,它是任何基于Spring的应用程序的核心。BeanFactory是工厂模式的一个实现,他应用IOC将应用程序配置和依赖说明从实际的应用程序代码中分离出来。 应用程序上下文模块 核心模块的BeanFactory使Spring成为一个容器,而应用程序上下文模块使Spring成为一个框架。这个模块扩展了BeanFactory,增加了对I18N消息,应用程序生命周期事件和验证的支持。 此外,这个模块提供了一些企业级服务,如email,JNDI访问,EJB集成,远程访问和计划任务。还包括对模板框架如Velocity和FreeMaker集成的支持。 Spring的AOP模块 Spring在AOP模块对AOP提供了丰富的支持。这个模块提供了开发自己的基于Spring的应用程序的方面的基础。 为确保Spring和其他AOP框架之间的互操作性,Spring的很多对AOP的支持都是基于AOP Alliance定义的API。AOP Alliance是一个开源的项目,它的目标是通过定义组通用的接口和组件,提高对AOP的采用率和不同AOP实现之间的互操作性。 Spring模块还为Spring引入了元数据编程。通过Spring的元数据支持,你可以向源代码中加入标注(Annotation)来指示在何处通过何种方式来应用方面(Aspects)。 JDBC抽象和DAO模块 Spring的JDBC和DAO模块使数据库代码更加整洁和简单,并且防止一些由于关闭数据库资源引起失败带来的问题。这个模块还在由多个数据库服务器给出的错误消息之上建立了一层有意义的异常。 另外,这个模块使用了Spring的AOP模块为Spring应用程序中的对象提供了事务管理服务。 ORM集成模块 Spring为那些喜欢使用ORM功能的人提供了ORM模块。Spring没有试图实现自己的ORM解决方案,只是为几个流行的ORM框架,如Hibernate,JDO和iBATIS SQL Maps提供了外挂功能。Spring的事务管理支持这些ORM框架和JDBC。 Spring的Web模块 Web上下文模块建立于应用程序上下文模块,提供了一个适合于基于Web的应用的上下文。此外,这个模块提供了对一些基于Web的任务的支持,如对于文件上传的multipart请求的透明处理和将请求参数程序化绑定到业务对象。还包含对Struts的集成的支持。 SpringMVC框架 Spring为构建Web应用程序带来了一个完整特性的MVC框架。尽管Spring可以容易地与其他MVC框架如Struts集成,但是Spring的MVC框架使用IOC提供控制逻辑与业务逻辑的清晰分离。它允许你声明式地将请求参数绑定到业务逻辑。还有就是Spring的MVC框架可以利用Spring的其他服务,如I18N消息机制和验证机制。 1.3 快速上手 GreetingServiceImpl类的greeting字段有两个方法可以设置,通过构造函数或属性的setter方法。不明显的是谁来调用构造函数或setter方法,答案是通过Spring配置文件让Spring容器来调用。 Spring配置文件的根元素是<beans>元素。<bean>元素告诉Spring容易一个类的信息和如何配置这个类。<bean>元素的子元素<property>元素用来设定类的属性。当然也可以通过构造函数的入参来设定属性。 1.4 理解控制反转IOC 1.4.1 注入依赖 任何稍大一点的应用程序都由多于两个类组成,他们相互协作完成一些业务逻辑。通常每一个对象都负责取得与它协作的那些对象的引用。你会发现,这会导致高耦合,难测试的代码。 通过应用IoC,对象在创建的时候,通过调度系统中每一个对象的外部实体给予他们的依赖。即依赖被注入对象。即IoC的意思是关于对象如何取得他们协作的对象的依赖责任的反转。 1.4.2 IoC实践 问题的所在是由于耦合。降低耦合的一个通用技术是将实现细节隐藏在接口后面,这样可以交换具体实现而不影响客户类。但重要的是对象如何取得它引用的对象,如果是给予的方式,那么在测试用例中只要给予Mock对象,而在产品系统中给予真实的对象,就解决了不能单独测试的问题。 这就是IoC要做的事情。 创建应用程序组件之间关联的动作被成为wiring,通常的方法是通过XML文件。 1.4.3 企业级应用程序中的IoC 1.5 应用面向方面编程 1.5.1 AOP简介 将这些关注方面分散于系统的多个组件,会对代码引入两个级别的复杂性: <!--[if !supportLists]-->n <!--[endif]-->实现系统范围关注方面的代码,在多个组件间存在重复。这意味着如果你需要修改这些关注方面的话,你需要修改多个组件。即使你将这些关注方面抽象到单独的模块,对组件的影响只是一个方法调用,这个方法调用也会在多个组件中重复。 <!--[if !supportLists]-->n <!--[endif]-->组件中会散布一些与核心功能无关的代码。 AOP能够让这些服务模块化,然后以声明的形式应用于它们应该影响的组件。这样能够使组件能够更具有聚合性,专注于他们的关注方面,可以完全忽略需要涉及的系统服务。 通过AOP,可以用多层功能覆盖核心的应用。这些层可以灵活的声明式地应用于应用程序,核心应用程序甚至不知道他们的存在。 1.5.2 AOP实践 织补Aspect 在Spring中,Aspect通过Spring的XML文件织入对象,同Bean被绕在一起的方式差不多。 1.5.3 企业级的AOP 尽管Spring的AOP可以用来分布于应用程序核心逻辑各处的关注方面,但它的主要工作是作为Spring对声明式事务支持的基础。Spring具有几个方面,使对JavaBeans声明事务策略成为可能。而Acegi安全系统提供对JavaBeans的声明式安全。同所有Spring配置一样,事务和安全策略都在Spring配置文件中描述。 使用Spring的TransactionProxyFactoryBean,使我们能够对既存的class监听函数调用和应用事务上下文。 1.6 Spring的代替品 EJB是一个标准 EJB是有JCP定义的规范,作为标准具有一些重要的启示: <!--[if !supportLists]-->n <!--[endif]-->广泛的业界支持—有很多重要的厂商都支持这项技术,如Sun,IBM,Oracle和BEA。这意味着很多年之内EJB都会被支持和积极开发,这使很多公司感觉到选择EJB作为J2EE框架比较安全。 <!--[if !supportLists]-->n <!--[endif]-->广泛的采用—EJB技术已经被成千上万的公司部署。因此EJB是大部分J2EE开发者的工具,拥有EJB技术更容易找到工作,公司使用EJB技术也更容易招聘到技术人员。 <!--[if !supportLists]-->n <!--[endif]-->有工具支持—EJB规范是一个固定的东西,这更容易地使得厂商更快地生产工具来帮助开发者创建EJB应用程序。EJB工具具有更广泛的选择。 Spring和EJB的共同点 作为J2EE容器,EJB和Spring都为开发者开发应用程序提供了强大的特性。下表列出了两个框架的共同特性和它们的实现比较。 特性 <!--[if !supportLists]-->n <!--[endif]-->支持跨越远程方法调用的事务。 <!--[if !supportLists]-->n <!--[endif]-->自身不支持分布式事务—必需与JTA事务管理器一起使用。 <!--[if !supportLists]-->n <!--[endif]-->可以使用通配符*来针对每一个方法或每一个类定义事务行为。 <!--[if !supportLists]-->n <!--[endif]-->不能声明式地定义回滚行为,必需程序实现。 <!--[if !supportLists]-->n <!--[endif]-->可以显示地或通过正则表达式定义对哪些方法应用事务行为。 <!--[if !supportLists]-->n <!--[endif]-->可以针对每一个方法或每一个异常类型声明式地定义回滚行为。 <!--[if !supportLists]-->n <!--[endif]-->一个建立于Spring之上的开源的安全框架Acegi,通过Spring配置文件或类元数据提供声明式安全。 对于大部分的J2EE项目,EJB和Spring都能满足其技术需求。但是也有例外—你的应用程序也许需要远程事务调用,如果是这样的话,选择EJB更合适。但Spring提供了对JTA事务的集成,也能满足。但如果你要找的是提供声明式事务管理和灵活的持久化引擎的J2EE框架,Spring是最好的选择。 EJB的复杂性 EJB以下的复杂性使大家倾向于选择轻量级的容器: <!--[if !supportLists]-->n <!--[endif]-->编写一个EJB太过于复杂—写一个EJB必需接触至少四个文件:业务接口,home接口,bean实现和部署描述子。可能还牵扯到其他类,如utility类和Value Object。相反Spring使你通过POJO定义你的实现,和通过注入或AOP来缠绕任何额外的服务。 <!--[if !supportLists]-->n <!--[endif]-->EJB具有侵入性—为使用EJB容器提供的服务,则必需使用javax.ejb接口。这使组件代码与EJB技术绑定,使组件很难用于EJB容器之外。Spring通常不要求组件实现、扩展或使用任何特定于Spring的类或接口,是组件更具有可复用性,即使没有Spring的存在也能使用。 <!--[if !supportLists]-->n <!--[endif]-->实体EJBs功能较弱—实体EJBs不如其他ORM工具特性多,也不够灵活。Spring可以集成很多其他ORM框架。Value Object会导致重复的代码,使用Spring和其他ORM工具,实体对象不与持久化机制耦合,可以传递于应用程序的不同层。 1.6.2 考虑其他轻量级容器 类型 下面看一下其他轻量级容器。 PicoContainer PicoContainer是一个小型的轻量级的容器,通过构造函数和setter函数的形式提供IoC。有一个子项目NanoContainer通过XML和各种脚本语言提供对配置PicoContainer的支持。 PicoContainer的一个局限是,对于任何一个类型,在注册表中只允许存在一个实例。 PicoContainer只是一个容器,不提供其他Spring提供的强大功能,如AOP和第三方框架集成。 HiveMind HiveMind是一个相对较新的IoC容器。它也是通过构造函数和setter函数来缠绕和配置服务。HiveMind允许用XML文件或它的简单的数据语言定义你的配置。 HiveMind还通过Interceptors提供类似AOP的特性。但是没有Spring的AOP框架强大。 它提供管理组件的框架,但不提供对其他技术的集成。 Avalon Avalon是第一批开发出来的IoC容器之一。但设计中存在很多错误。Avalon主要提供接口依赖的IoC。这使Avalon成为侵入式的框架。 1.6.3 Web框架 Struts Struts可以看作是Web MVC框架的事实上的标准。 最常使用的Struts类是Action类。Action是一个抽象类,因此你的所有处理输入的类都必需继承这个类,相比之下Spring提供了Controller接口。 另一个重要的区别是二者处理表单输入的方式。通常情况下,当用户提交一个Web表单时,进来的数据对应于你的应用程序中的一个对象。为处理表单提交,Struts要求使用ActionForm类处理传入的参数。这意味这你必需创建一个类来将表单提交映射到你的领域对象。Spring允许你直接将表单提交映射到领域对象。 Struts提供内置的声明式表单验证支持。这意味着你可以通过XML文件来定义验证传入的表单数据的规则。这使验证与业务逻辑分离,也会导致一些笨拙和混乱。Spring不提供声明式验证,不过你可以自己集成一个验证框架,如Jakarta Commons Validator。 Spring可以集成Struts。 WebWork WebWork提供多视图技术。WebWork与其他框架的最大区别是它为处理Web提交增加了一个抽象层。 WebWork提供了一个Spring没有提供的功能:Action链。它允许你将一个逻辑请求映射到一系列的Actions。 Tapestry Tapestry与之前提到的Web框架有很大区别。Taperstry不提供基于请求-响应的Servlet机制的框架。它是一个从可复用组件创建Web应用的框架。 Tapestry背后的思想是,减轻开发者思考Sessions属性和URLs,而是以组件和方法的形式考虑Web应用。 Tapestry不是一个使用JSPs的框架,而是代替JSPs。 1.6.4 持久化框架 第二章 缠绕Beans 创建对象关联的通常办法会导致难以复用和进行单元测试的代码。 在Spring中,组件本身不负责管理与其他组件的关联。相反,对写作组件的引用是由容器给他们的。创建应用程序组件之间关联的动作成为缠绕(Wiring)。 2.1 包含Beans 2.1.1 介绍BeanFactory Bean工厂知道应用程序中的很多对象,在实例化对象时,它能够创建协作对象之间的关联关系。这移除了Bean本身和它的客户端的配置负担。因此,当Bean工厂分发对象时,这些对象是已经完全配置好的,已经知道了他们协作的对象,已经可以供使用。 Spring中有很多BeanFactory的实现,但最有用的是org.springframework.beans.factory.xmlBeanFactory,它基于包含在XML文件中的定义来加载Beans。 为创建XmlBeanFactory,向构造函数传递一个java.io.InputStream。这个InputStream向工厂提供XML文件。Beans以懒加载的方式加载到Beans工厂中,Bean工厂会立即加载Bean的定义,但只要当这些Beans被使用的时候才会被实例化。 为从BeanFactory获取Bean,只要调用getBean()方法,向它传递你要获取的bean的名称。当调用getBean()时,工厂会实例化Bean并开始使用依赖注入来设置Bean的属性。 2.1.2 使用应用程序上下文 <!--[if !supportLists]-->n <!--[endif]-->提供解析文本消息的方法,包括对这些消息提供I18N支持。 <!--[if !supportLists]-->n <!--[endif]-->提供加载文件资源,如图片,的通用方法。 <!--[if !supportLists]-->n <!--[endif]-->可以向注册为监听者的Beans发布事件。 因为ApplicationContext的额外功能,几乎在所有的应用程序中都优先使用ApplicationContext,而非BeanFactory。 ApplicationContext的实现有很多,而常用的有三个: <!--[if !supportLists]-->n <!--[endif]-->ClassPathXmlApplicationContext—从在classpath中定位的XML文件加载一个上下文定义,将上下文定义文件作为classpath资源处理。 <!--[if !supportLists]-->n <!--[endif]-->FileSystemXmlApplicationContext—从文件系统中的XML文件加载上下文定义。 <!--[if !supportLists]-->n <!--[endif]-->XmlWebApplicationContext—从包含于Web应用中的XML文件加载上下文定义。 获取Bean的方式同BeanFactory,可以使用getBean()方法,因为ApplicationContext继承了BeanFactory接口。 应用程序上下文与Bean工厂的另一个比较大的区别是他们加载单例Beans的方式。BeanFactory懒加载所有Beans,而ApplicationContext预先加载所有的单例Beans。 2.1.3 Bean的生命周期 <!--[if !supportLists]-->1. <!--[endif]-->容器找到Bean的定义并实例化Bean。 <!--[if !supportLists]-->2. <!--[endif]-->Spring使用依赖注入产生Bean定义中指定的所有的属性。 <!--[if !supportLists]-->3. <!--[endif]-->如果Bean实现了BeanNameAware接口,工厂调用setBeanName()方法,传递Bean的ID。 <!--[if !supportLists]-->4. <!--[endif]-->如果Bean实现了BeanFactoryAware接口,工厂调用setBeanFactory方法,并传递工厂本身的实例。 <!--[if !supportLists]-->5. <!--[endif]-->如果存在与Bean关联的BeanPostProcessors,则调用他们的postProcessBeforeInitialization方法。 <!--[if !supportLists]-->6. <!--[endif]-->如果指定了Bean的init方法,则调用这个方法。 <!--[if !supportLists]-->7. <!--[endif]-->最后,如果存在与Bean关联的BeanPostProcessors,则调用他们的postProcessAfterInitialization方法。 从这时候起,Bean可以被应用程序使用,并一直存在与Bean工厂中,直到不需要它。有两种方式将Bean从Bean工厂中移除。 <!--[if !supportLists]-->1. <!--[endif]-->如果Bean实现了DisposableBean接口,调用destroy()方法。 <!--[if !supportLists]-->2. <!--[endif]-->如果指定了自定义的destroy方法,则调用这个方法。 处在Spring应用程序上下文中的Bean与BeanFactory中的唯一区别是,如果Bean实现了ApplicationContextAware接口,则调用它的setApplicationContext方法。 2.2基本的缠绕(Basic Wiring) 2.2.1使用XML缠绕 上下文定义文件的根节点是<beans>元素,这个元素具有一个或多个<bean>元素作为子元素。每一个<bean>元素都定义一个配置到Spring容器中的JavaBean。 2.2.2 增加一个Bean 原型vs单例 缺省情况下,所有Spring中的Beans都是单例的。如果每一次都要请求一个特定的实例的话,可以定义一个原型Bean。将<bean>元素的singleton属性设置为false就可以将Bean定义为原型Bean。 初始化与销毁 使用init-method属性指定的方法在Bean实例化之后立即执行。destroy-method属性指定的方法在Bean从容器移除之前调用。Spring提供了两个接口,可以执行相同的功能:InitializingBean和DisposableBean。前者提供了一个方法afterPropertiesSet(),在Bean的所有属性设置后执行,后者提供的方法destroy()在Bean从容器中移除时调用。使用接口的好处是不需要配置,但是也会将Bean与Spring的API绑定起来。 2.2.3 通过setter方法注入依赖 简单的Bean的配置 使用<property>元素的子元素<value>元素可以来给Bean设置原始类型的属性。原始类型的属性的设定无须指定类型,Spring会自动进行类型转换。 引用其他Beans 使用<property>元素的子元素<ref>元素来引用其他Beans。 内部的Beans 可以将<bean>元素直接嵌入到<property>元素。这种缠绕方式的缺点是被嵌入的元素不能在别处使用。而且影响XML文件的可读性。 缠绕集合 Spring支持很多种集合类型作为Bean属性,如下表所示。 XML 缠绕集合是使用上表中提到的XML元素,而不是使用<value>或<ref>。 缠绕lists和arrays List的元素可以是<value>,<ref>,<bean>或其他<list>。 缠绕集合 集合可以保证元素的唯一性。 缠绕maps Map中的每一个记录都用一个<entry>元素定义。<entry>的key属性限定为只能是String类型。 缠绕Properties 使用<props>元素缠绕,每一个<props>元素的子元素都是用一个<prop>元素定义。 设定null值 使用<null/>元素。 Setter注入的一个代替方式 通过构造函数注入。 2.2.4 通过构造函数注入依赖 处理模糊的构造函数参数 如果出现模糊的情况,Spring会抛出org.springframework.beans.factory.UnsatisfiedDependencyException。有两种方法来解决这个问题,使用下标或类型。 使用<constructor-arg>的index属性指定参数的下标。还可以使用type属性指定参数的类型。 如何选择:使用构造函数还是使用setter方法? 这是一个有争议的问题。 支持使用构造函数注入的理由: <!--[if !supportLists]-->n <!--[endif]-->强制执行一个强依赖协议。简单的说,Bean如果没得到所有的依赖就不会被实例化。 <!--[if !supportLists]-->n <!--[endif]-->既然所有的依赖都可以通过构造函数设定,使用setter方法比较多余。 <!--[if !supportLists]-->n <!--[endif]-->只允许通过构造函数设定函数,可以让这些属性不可更改(immutable)。 支持setter方法注入依赖的理由: <!--[if !supportLists]-->n <!--[endif]-->如果依赖比较多,构造函数的参数会比较长。 <!--[if !supportLists]-->n <!--[endif]-->如果构造可用的对象的方法有多个的话,就会有多个构造函数。 <!--[if !supportLists]-->n <!--[endif]-->如果构造函数的多个参数的类型一样的话,很难确定参数的目的是什么。 <!--[if !supportLists]-->n <!--[endif]-->构造函数注入使自身不具有好的集成性。 2.3 自动缠绕(autowiring) 有四种类型的自动缠绕: <!--[if !supportLists]-->n <!--[endif]-->byName—在容器中查找与指定的名称相同的Bean。 <!--[if !supportLists]-->n <!--[endif]-->byType—在容器中查找一个唯一的与指定类型相同的Bean。如果没有找到则不缠绕,如果找到多个,抛出org.springframework.beans.factory.UnsatisfiedDependencyException。 <!--[if !supportLists]-->n <!--[endif]-->constructor—在容器中查找与被缠绕的Bean的构造函数匹配的Bean,使用指定的构造函数的参数。 <!--[if !supportLists]-->n <!--[endif]-->autodetect—试图先使用constructor,然后使用byType方法。 2.3.1 处理自动缠绕的模糊性 2.3.2 混合使用自动缠绕和显式缠绕 2.3.3 缺省自动缠绕 2.3.4 自动缠绕还是不自动缠绕 2.4 使用Spring的特殊Beans <!--[if !supportLists]-->n <!--[endif]-->可以通过后置处理配置文件来参与Bean和Bean的工厂的生命周期。 <!--[if !supportLists]-->n <!--[endif]-->从外部属性文件加载配置信息。 <!--[if !supportLists]-->n <!--[endif]-->改变Spring的依赖注入,在配置Bean属性的时候,自动将String类型转换成其他类型。 <!--[if !supportLists]-->n <!--[endif]-->加载文本消息,包括国际化的消息。 <!--[if !supportLists]-->n <!--[endif]-->监听或响应由其他Beans或容器发布的应用程序事件。 <!--[if !supportLists]-->n <!--[endif]-->使其意识到自己在Spring容器中的身份。 2.4.1 后置处理Beans 书写Bean后置处理器 定义一个类实现BeanPostProcessor接口。 注册Bean后置处理器 如果应用程序运行于Bean工厂,则需要使用程序来进行注册,调用工厂的addBeanPostProcessor方法,将上面定义的类作为参数传入。如果应用程序运行于应用程序上下文,只需要将定义的类注册为一个Bean。容器会将其识别为后置处理器并应用于所有的Beans。 Spring自带的Bean后置处理器 例如,ApplicationContextAwareProcessor对所有实现ApplicationContextAware接口的Beans设置应用程序上下文。 DefaultAdvisorAutoProxyCreator基于容器中所有的候选advisors创建AOP代理。 2.4.2 后置处理Bean工厂 有两个比较有用的实现。PropertyPlaceHolderConfigurer从一个或多个外部属性文件中加载属性,并用这些属性填充Bean缠绕XML文件中的占位符变量。CustomEditorConfigurer让你注册java.beans.PropertyEditor来将属性缠绕值翻译成其他属性值。 2.4.3 使配置外部化 如果使用ApplicationContext作为Spring容器的话,外部化属性是很容易的。可以使用PropertyPlaceHolderConfigurer来告诉Spring从外部属性文件来加载特定的配置。如果你的配置分解为多个外部属性文件的话,使用PropertyPlaceHolderConfigurer的locations属性来设置<list>元素。 2.4.4 定制属性编辑器 Spring自带了几个自定义编辑器: <!--[if !supportLists]-->n <!--[endif]-->URLEditor—将String对象缠绕到URL对象。 <!--[if !supportLists]-->n <!--[endif]-->ClassEditor <!--[if !supportLists]-->n <!--[endif]-->CustomDateEditor <!--[if !supportLists]-->n <!--[endif]-->FileEditor <!--[if !supportLists]-->n <!--[endif]-->LocaleEditor <!--[if !supportLists]-->n <!--[endif]-->StringArrayPropertyEditor <!--[if !supportLists]-->n <!--[endif]-->StringTrimmerEditor 2.4.5 解析文本消息 一般情况下,通过ApplicationContext的getMessage方法来访问消息,也可以在JSP中使用<spring:message>标签来访问。 2.4.6 监听事件 <!--[if !supportLists]-->n <!--[endif]-->ContextClosedEvent—当应用程序上下文关闭的时候发布。 <!--[if !supportLists]-->n <!--[endif]-->ContextRefreshedEvent—当应用程序上下文初始化或刷新的时候发布。 <!--[if !supportLists]-->n <!--[endif]-->RequestHandledEvent—当请求已经被处理的时候,在Web应用上下文环境内发布。 如果你想要Bean对应用程序事件作出响应,只需实现org.springframework.context.ApplicationListener接口。 2.4.7 发布事件 2.4.8 让Beans有意识 知道你是谁 BeanNameAware的setBeanName()方法。 知道你在哪生存 Spring的BeanFactoryAware和ApplicationContextAware接口让Beans意识到容器的存在。 第三章 创建方面(Aspects) 一个方面(Aspect)是你实现的一个横切的功能。它是你模块化的应用程序的一个方面或领域。最常见的例子是写日志(logging)。 连接点(joinpoint) 一个连接点是应用程序执行过程中的一个点,在这个点可以插入一个方面(Aspect)。这个点可以是调用方法,可以是抛出异常,也可以是修改一个字段。在这些点,方面的代码可以插入到应用程序的正常流程来增加新的行为。 修订(Advice) Advice是方面的实际实现。以logging为例,logging advice包含包含实际实现loggin的代码。Advice在jointpoints插入到应用程序中。 切入点(Pointcut) 切入点定义应该应用何种连接点Advice。Advice可以应用于AOP框架支持的任何连接点。 引入(Introduction) Introduction允许你向既存的类增加方法或属性。 目标(Target) Target是被修订的类。可以是你写的类,也可以是第三方的类。 代理(Proxy) 代理是向目标对象应用修订后创建的对象。就客户端对象而言,目标对象和代理对象是相同的。 编织(Weaving) 编织是向目标对象应用方面来创建一个新的代理的对象的过程。方面在指定的连接点被织入目标对象。编织可以发生在目标类的生命周期的多个点: <!--[if !supportLists]-->n <!--[endif]-->编译期—方面在目标类被编译时织入。这需要一个特殊的编译器。 <!--[if !supportLists]-->n <!--[endif]-->类加载期—方面在目标类被加载进JVM时织入。这需要一个特殊的ClassLoader来增强目标类的字节码。 <!--[if !supportLists]-->n <!--[endif]-->运行时—方面在应用程序执行的某个时刻被织入。 Advice包含需要应用的横切行为。 3.1.2 Spring的AOP实现 切入点通常以XML形式写在Spring配置文件中。其他框架,如AspectJ,要求必需使用一种特殊的语法来定义方面和切入点。 Spring在运行时修订对象 Spring只有在代理Bean在应用程序中被需要时才创建代理对象。Spring有两种方式可以产生代理类。如果你的目标对象实现了暴露了必要方法的接口,则Spring使用JDK的java.lang.reflect.Proxy接口。这个类允许Spring动态地创建新类来实现必要的接口,织入任何修订,和代理任何从这些接口的对目标类的方法调用。如果你的目标类不实现接口,Spring使用CGLIB来产生目标类的一个子类。创建子类时,Spring织入修订,并使用子类代理所有到目标类的调用。 Spring实现AOP Alliance接口 AOP Alliance是一个都对用Java实现AOP感兴趣的多方的联合项目。他们的目标是标准化Java AOP接口来提供不同Java的AOP实现的互操作性。这意味着实现他们接口的AOP修订会在任何与AOP Alliance兼容的框架中复用。 Spring只提供方法连接点 这会有碍于创建细粒度的修订,如监听对对象字段的更新。 3.2 创建修订(Advice) 修订类型 3.2.1 Before修订 3.2.2 After修订 3.2.3 Around修订 3.2.4 Throws修订 3.2.5 引入(Introduction)修订 3.3 定义切入点 3.3.1 在Spring中定义切入点 ClassFilter是根据类来过滤方面,而MathodFilter是根据方法来过滤。 3.3.2 理解修订(advisors) 3.3.3 使用Spring的静态切入点 NameMatchMethodPointcut 根据提供的名称来匹配方法名,提供的名称可以带通配符。 正则表达式切入点 RegexpMethodPointcut使你可以使用正则表达式来定义切入点。 3.3.4 使用动态切入点 3.3.5 切入点操作 3.4 创建介绍(Introductions) 3.4.1 实现IntroductionInterceptor 3.4.2 创建IntroductionAdvisor 3.4.3 谨慎使用Introduction Advice 3.5 使用ProxyFactoryBean 属性 最经常使用的属性是target,proxyInterfaces和interceptorNames。 3.6 自动代理 3.6.1 BeanNameAutoProxyCreator 3.6.2 DefaultAdvisorAutoProxyCreator 3.6.3 元数据自动代理 第二部分 Spring的业务层 第四章 数据库相关问题 Spring提供在它的所有DAO框架中一直的异常层次结构。 4.1.1 理解Spring的DataAccessException 你不必一定要处理DataAccessException DataAccessException是一个RuntimeException,因此它是unchecked的异常。这意味着,当持久化层抛出这些异常时,你的代码不一定要处理。这遵循了Spring的通用理念:checked异常会导致很多不必要的catch或throws语句,使代码比较凌乱。 DataAccessException是从Spring的NestedRuntimeException继承,根Exception可以从NestedRuntimeException的getClause方法获得。 Spring对异常进行分类 如下表所示,Spring有丰富的异常层次: 异常 4.1.2 使用DataSources 从JNDI取得DataSource Spring应用程序通常运行于J2EE应用服务器或Tomcat之类的Web服务器。这些服务器可以通过JNDI提供DataSource。在Spring中我们处理这件事情也是通过Spring bean。在这种情况下,我们使用JndiObjectFactoryBean。 创建DataSource连接池 如果我们的Spring容器运行的环境不存在DataSource,而我们又希望使用连接池的好处,我们只需实现一个连接池bean,实现DataSource接口即可。一个很好的例子是Jakarta Commons DBCP项目的BasicDataSource类。只需在Spring配置文件中配置一个bean即可。 测试时使用DataSource Spring自带了一个轻量级的DataSource实现,用于单元测试或单元测试套件。 4.1.3 一致性DAO Support 模板回调设计的顶部有一个DAO Support类,你的DAO类可以从这些类继承。 4.2 在Spring中使用JDBC 代码比较冗赘。 4.2.2 使用JdbcTemplate 写数据 PreparedStatementCreator接口的实现负责创建一个PreparedStatement,这个接口只提供了一个方法createPreparedStatement。 这个接口的实现通常也实现另一个接口:SqlProvider。通过实现这个类的方法—getSql()—使你的类向JdbcTemplate类提供SQL字符串。 对PreparedStatementCreator的补充是PreparedStatementSetter。实现这个接口的类接受一个PreparedStatement参数,负责设定所有的参数。 如果要更新多行,可以使用BatchPrepareStatementSetter。如果你的JDBC驱动支持批量操作,更新会批量进行,创建高效的数据库访问,否则Spring会模拟批处理,但是语句会一个一个执行。 读数据 我们要告诉Spring如何处理ResultSet中的每一行,通过实现RowCallbackHandler的方法processRow来处理。 如果通过查询获取多个对象,可以实现一个子接口。例如,如果要获取某个类的所有对象,可以实现ResultReader接口。Spring提供了这个接口的一个实现,RowMapperResultReader。RowMapper接口负责将ResultSet的一行映射到一个对象。 JdbcTemplate也提供了很多返回结果为int或String等简单类型的方法。 调用存储过程 Spring提供对存储过程的支持是通过实现接口CallableStatementCallBack。 4.2.3 将操作创建为对象 创建SqlUpdate对象 为创建执行插入或更新的可复用对象,继承SqlUpdate类。 使用MappingSqlQuery查询数据库 通过继承MappingSqlQuery来将一个查询建模为对象。 4.2.4 自动递增键值 4.3 Spring的ORM框架支持简介 4.4 在Spring中集成Hibernate 4.4.2 管理Hibernate资源 4.4.3 通过HibernateTemplate访问Hibernate 4.4.4 继承HibernateDaoSupport 4.5.1 配置JDO 在Spring中,使用LocalePersistenceManagerFactoryBean配置PersistenceManagerFactoryBean。有了JDO PersistenceManagerFactory,下一步就是将此bean缠绕到JdoTemplate。 4.5.2 使用JdoTemplate访问数据 4.6 Spring与iBATIS 4.6.2 使用SqlMapClientTemplate 4.7 Spring与OJB 4.7.1 设置OJB的PersistenceBroker 第五章 管理事务 <!--[if !supportLists]-->n <!--[endif]-->Atomic—事务由一个或多个活动绑定起来作为一个工作单元。 <!--[if !supportLists]-->n <!--[endif]-->Consistent—一旦事务结束,系统的状态与业务状态一致。 <!--[if !supportLists]-->n <!--[endif]-->Isolated—事务应当允许多个用户同是工作而不会相互添乱。 <!--[if !supportLists]-->n <!--[endif]-->Durable—事务结束后,事务的结果应当持久化。 5.1.2 理解Spring的事务管理支持 5.1.3 Spring事务管理器简介 事务管理器实现 为使用事务管理器,必需在你的应用程序上下文中声明这个事务管理器。 JDBC事务 如果使用JDBC做应用程序的持久化,DataSourceTransactionManager可以为你处理事务边界。使用XML缠绕到应用程序上下文中。 Hibernate事务 如果你的应用程序使用Hibernate进行持久化,可以使用HibernateTransactionManager可以为你处理事务。HibernateTransactionManager将事务管理的职责代理到net.sf.hibernate.Transaction对象。 JDO(Java Data Object)事务 JdoTransactionManager。 OJB事务 PersistenceBrokerTransactionManager。 JTA事务 JtaTransactionManager。如果使用多个数据源,需要使用JtaTransactionManager。 5.2 Spring中的程序式事务 如果你想完全控制事务的边界,使用程序式事务比较好。通常你不需要如此精确的边界,这就是为什么通常在应用程序代码之外声明事务。 5.3 声明事务 5.3.1 理解事务属性 <!--[if !supportLists]-->n <!--[endif]-->传播行为 <!--[if !supportLists]-->n <!--[endif]-->隔离级别 <!--[if !supportLists]-->n <!--[endif]-->只读提示 <!--[if !supportLists]-->n <!--[endif]-->事务有效时间 传播行为 传播行为定义了相对于客户端和被调用方法的事务的边界。Spring定义了七种传播行为,如下表所示: 传播行为 隔离级别 事务的隔离级别如下表所示: 隔离级别 并不是所有的资源管理器都支持所有的这些隔离级别。 只读 声明事务是只读的,给予底层数据存储机会对事务进行优化。 事务超时 设定事务过一定时间后自动回滚。 5.3.2 声明简单的事务策略 MatchAlwaysTransactionAttributeSource是最简单的TransactionAttributeSource的实现,当调用其getTransactionAttribute()方法时,不管事务中缠绕的是哪一个方法,每次都返回相同的TransactionAttribute(PROPAGATION_REQUIRED和ISOLATION_DEFAULT)。 改变缺省的TransactionAttribute 缺省策略是PROPAGATION_REQUIRED和ISOLATION_DEFAULT,如果要改变的话,只需对transactionAttribute缠绕另一个TransactionAttribute。 5.4 根据方法名声明事务 5.4.1 使用NameMatchTransactionAttributeSource 事务属性描述子的格式: PROPAGATION,ISOLATION,readOnly,-Exceptions,+Exceptions 5.4.2 简化名称匹配的事务 5.5 使用元数据声明事务 <bean id="transactionAttributeSource" class="org.springframework.transaction.interceptor. AttributesTransactionAttributeSource"> <constructor-arg> <ref bean="attributesImpl"/> </constructor-arg> </bean> 注意这里的构造函数参数attributesImpl,事务属性源使用它与底层的元数据实现进行交互。 5.5.2 使用Commons Attribute声明事务 <bean id="attributesImpl" class="org.springframework. metadata.commons.CommonsAttributes"> ... </bean> 标记事务性方法 Jakarta Commons Attributes通过替换方法/类前面的注释中doclet标签来实现。 设置Commons Attributes的构建 Jakarta Commons Attributes的机制是预编译你的代码中的doclet标签,重写你的类,在代码中嵌入元数据。需要在构建文件中加入与编译器。 5.6 裁剪事务声明 5.6.2 自动代理事务 第六章 远程访问 客户端调用代理,好像是代理在提供服务功能,代理代表客户端同远程服务通信。在服务器端,可以使用以上六种模型将任何Spring管理的Bean的功能暴露为远程服务。 6.2 使用RMI 6.2.2 导出RMI服务 首先书写服务接口,无须从java.rmi.Remote继承。然后定义这个接口的实现类。下面就可以将这个实现类配置成Spring配置文件中的<bean>。然后使用RmiServiceExporter将此实现导出成服务。 RMI有很多局限性。首先,跨防火墙工作比较困难。因为RMI的通信使用任意端口,典型的防火墙都不允许这样做。另外,RMI是基于Java的。这意味着服务器端和客户端都必须使用Java编写。 6.3 使用Hessian和Burlap进行远程访问 Hessian和Burlap的唯一区别就是基于二进制消息还是基于XML。 6.3.1 访问Hessian和Burlap服务 6.3.2 使用Hessian/Burlap暴露Bean功能 使用HessianServiceExporter。 配置Hessian控制器 RmiServiceExporter与HessianServiceExporter之间的另一个主要区别是,因为Hessian是基于HTTP的,HessianServiceExporter实现为一个Spring MVC控制器。这意味着如果要导出Hessian服务,需要执行两个额外的配置步骤: <!--[if !supportLists]-->1. <!--[endif]-->在Spring配置文件中配置一个URL处理器,来对合适的Hessian服务bean分派Hessian服务URLs。 <!--[if !supportLists]-->2. <!--[endif]-->在web.xml中配置一个DispatcherServlet,将应用程序部署为一个Web应用程序。 将URL模式映射到Hessian服务。 导出Burlap服务 使用BurlapServiceExporter。 6.4 使用HTTP Invoker 6.4.1 通过HTTP访问服务 6.4.2 将beans暴露为HTTP服务 HTTP Invoker的局限性在于,它的服务端和客户端都必须是基于Spring的。 6.5 使用EJB Spring提供了两个代理工厂beans,来对EJBs进行代理:LocalStatelessSessionProxyFactoryBean和SimpleRemoteStatelessSessionProxyFactoryBean。 <bean id="paymentService" class="org.springframework.ejb. access.LocalStatelessSessionProxyFactoryBean" lazy-init="true"> <property name="jndiName"> <value>payService</value> </property> <property name="businessInterface"> <value>com.springinaction.payment.PaymentService</value> </property> </bean> 缠绕EJBs 与缠绕其他Spring beans相同。 工作机制 首先,在启动的时候,LocalStatelessSessionProxyFactoryBean使用由jndiName属性指定的JNDI名称来通过JNDI查找EJB的本地home接口。然后,每次调用接口方法的时候,代理都调用本地home接口的create()方法获取EJB的引用。最后,代理调用EJB相应的方法。 访问远程EJB 使用SimpleRemoteStatelessSessionProxyFactoryBean。 6.5.2 开发支持Spring的EJBs <!--[if !supportLists]-->n <!--[endif]-->AbstractMessageDrivenBean—对于开发消息驱动的beans比较有用。不接受来自JMS的消息。 <!--[if !supportLists]-->n <!--[endif]-->AbstractJmsMessageDrivenBean—接受来自JMS的消息。 <!--[if !supportLists]-->n <!--[endif]-->AbstractStatefulSessionBean <!--[if !supportLists]-->n <!--[endif]-->AbstractStatelessSessionBean 6.6 使用基于JAX-RPC的Web services 6.6.1 引用JAX Web service 6.6.2 在Spring中缠绕web服务 第七章 访问企业服务 Spring的JNDI抽象,使你能够在你的应用程序配置文件中声明JNDI查询,从而可以将这些对象缠绕到其他beans的属性中,好像JNDI对象也是POJO。 7.1.1 使用通常的JNDI 7.1.2 代理JNDI对象 7.2 发送E-mail <!--[if !supportLists]-->n <!--[endif]-->CosMailSenderImpl—SMTP邮件发送器的简单实现,基于Jason Hunter的COS实现。 <!--[if !supportLists]-->n <!--[endif]-->JavaMailSenderImpl—基于JavaMail API的实现。允许发送MIME消息。 <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host"> <value>mail.springtraining.com</value> </property> </bean> 还可以定义邮件模板。 7.3 定时任务 7.3.1 使用Java的Timer进行定时 通过继承java.util.TimerTask类。 对定时任务作出计划 ScheduledTimerTask定义定时任务执行的频率。 启动计时器 TimerFactoryBean。 缺陷是,只能设定运行的频率,而不能精确设定每天何时运行定时任务。 7.3.2 使用quartz定时器 创建一个Job 继承Spring的QuartzJobBean。 计划job Quartz的Trigger类用来决定何时以何种方式运行Quartz Job。Spring自带了两个触发器:SimpleTriggerBean和CronTriggerBean。 启动job SchedulerFactoryBean。 7.3.3 按计划调用方法 7.4 使用JMS发送消息 7.4.1 使用JMS模板发送消息 使用模板 使用JmsTemplate来发送消息。 缠绕模板 使用JMS1.0.2 区别是JmsTemplate102需要知道是在使用点对点还是发布订阅模式。缺省情况下,JmsTemplate102假定你使用点对点模式。 处理JMS异常 不必处理JMSException。 7.4.2 处理消息 7.4.3 转化消息 第三部分 Spring的web层 过程开始于客户端发送一个请求,接受这个请求的第一个组件是Spring的DispatcherServlet。同大部分基于Java的MVC框架一样,Spring MVC使用一个唯一的前端控制器来处理request。在Spring MVC中DispatcherServlet就是前端控制器。负责处理请求的Spring MVC组件是controller,为了知道哪一个controller应该处理某个请求,DispatcherServlet查询一个或多个HandlerMapping。HandlerMapping的工作就是将URL模式映射到Controller对象。一旦DispatcherServlet得到Controller对象,它将request分派给这个控制器来执行业务逻辑。执行完业务逻辑后,Controller返回一个ModelAndView对象给DispatcherServlet。ModelAndView对象可以包含一个View对象或者View对象的名称。如果包含的是View对象的名称,DispatcherServlet通过ViewResolver来查找对应的View对象。最后,DispatcherServlet将request分派给View对象,View对象负责产生一个响应到客户端。 8.1.2 配置DispatcherServlet 分解应用程序上下文 可以将各层的xml文件分开。便于维护。为保证所有这些配置文件都会被加载,必需在web.xml文件中配置上下文加载器。有两个可供选择的加载器:ContextLoaderListener和ContextLoaderServlet。 8.1.3 Spring MVC核心 实现Controller接口,handleRequest方法。 配置Controller Bean DispatcherServlet使用的缺省处理器映射是BeanNameUrlHandlerMapping。 声明view resolver View resolver的工作是将返回的ModelAndView中的view名称映射为view对象。对于产生JSP的views,最简单的莫过于使用InternalResourceViewResolver。 <bean id="viewResolver" class="org.springframework.web. servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> 将view名称映射为/WEB-INF/jsp/[view名称].jsp 创建JSP 所有的组件放在一起工作 8.2 将requests映射到controllers <!--[if !supportLists]-->n <!--[endif]-->BeanNameUrlHandlerMapping—基于控制器的名称将控制器映射到URLs。 <!--[if !supportLists]-->n <!--[endif]-->SimpleUrlHandlerMapping—使用在上下文配置文件中定义的属性集合将控制器映射到URLs。 <!--[if !supportLists]-->n <!--[endif]-->CommonsPathMapHandlerMapping—使用controller代码中的元数据将控制器映射到URLs。 8.2.1 将URLs映射到bean名称 8.2.2 使用SimpleUrlHandlerMapping 8.2.3 使用元数据映射controllers 8.2.4 使用多个HandlerMapping 8.3 使用Controllers处理请求 类型 AbstractController AbstractCommandController SimpleFormController 8.3.1 编写简单的控制器 ModelAndView简介 ModelAndView对象完全封装view和model数据。 缠绕Controller 8.3.2 处理命令 Handler方法还接受一个Command参数。 8.3.3 处理form提交 验证输入 Org.springframework.validation.Validator接口为Spring MVC提供验证。 8.3.4 使用wizard处理复杂表单 扩展AbstractWizardFormController类。 页面跳转 getTargetPage() 结束wizard 使用特殊的请求参数“_finish”。 取消wizard 增加取消按钮,使用“_cancel”参数。 每次验证一个表单 控制器的validatePage()方法,可以针对每个页面进行验证。 8.3.5 使用一个控制器处理多个动作 根据URL模式确定方法名。 解决方法名称 MultiActionController基于方法名称resolver来解决方法名称。缺省的方法名称解决器是InternalPathMethodNameResolver。Spring提供了另外两种:ParameterMethodNameResolver和PropertiesMethodNameResolver。 8.3.6 使用Throwaway控制器 8.4 解决views Spring提供了ViewResolver的四个实现: <!--[if !supportLists]-->n <!--[endif]-->InternalResourceViewResolver—将逻辑view名称解决为View对象,这些对象是使用模板文件资源生成。(如JSPs和Velocity模板) <!--[if !supportLists]-->n <!--[endif]-->BeanNameViewResolver—将逻辑view名称解决为DispatcherServlet的应用程序上下文中的View Beans。 <!--[if !supportLists]-->n <!--[endif]-->ResourceBundlerViewResolver—将逻辑View名称解决为包含在ResourceBundle中的View对象。 <!--[if !supportLists]-->n <!--[endif]-->XmlViewResolver—从XML文件解决View beans,这个XML文件是与DispatcherServlet的应用程序上下文分开的。 8.4.1 使用模板views 8.4.2 解决view beans 在分离的XML文件中声明view beans。 XmlFileViewResolver。 从Resouce bundle解决view ResourceBundleViewResolver。 8.4.3 选择View Resolver 使用多个View Resolver 可以使用多个,使用order属性设定优先级。 8.5 使用Spring的绑定标签 8.6 处理异常 第九章 View层的其他选择 9.1.2 配置Velocity引擎 9.1.3 解决Velocity view 9.1.4 格式化日期和数字 9.1.5 暴露request和session属性 9.1.6 在Velocity中绑定表单字段 9.2 使用FreeMaker 9.2.1 构造FreeMaker view 9.2.2 配置FreeMaker引擎 9.2.3 解决FreeMaker view 9.2.4 在FreeMaker中绑定表单字段 9.3 使用Tiles设计页面布局 配置Tiles TilesConfigurer。 解决Tiles view 使用InternalResourceViewResolver即可,只要将viewClass设定为TilesView。它将逻辑view名称解决为Tiles定义。 9.3.2 Tiles控制器 9.4 产生非HTML输出 9.4.2 产生PDF文档 9.4.3 产生其他非HTML文件 第十章 使用其他Web框架 10.1.2 实现可访问Spring的Struts Action 10.1.3 代理Actions 将Actions缠绕为Spring beans 将Actions注册为Spring beans。让代理来找Actions。 使用request代理 DelegatingRequestProcessor。 10.2 使用Tapestry 10.3 与JSF集成 10.4 与WebWork集成 第十一章 Spring应用程序的安全性 11.1 Acegi安全系统简介 Acegi使用监听request的servlet过滤器来执行认证和实施安全性。 Acegi可以在较低级别上通过保证方法调用的安全实施安全性。通过使用Spring AOP,Acegi代理对象,应用方面来保证用户具有适当的授权来调用安全方法。 11.1.1 安全监听器 11.1.2 认证管理器 11.1.3 访问决策管理器 11.1.4 运行管理器 11.2 管理认证 public interface AuthenticationManager { public Authentication authenticate(Authentication authentication) throws AuthenticationException; } Authenticate方法接受一个Authentication对象,并试图认证用户。如果认证成功,则返回一个完整的Authentication对象,包含用户的授权信息。如果认证失败,则抛出AuthenticationException异常。Acegi提供了AuthenticationManager的一个实现:ProviderManager。 11.2.1 配置ProviderManager <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref bean="daoAuthenticationProvider"/> <ref bean="passwordDaoProvider"/> </list> </property> </bean> 通过providers属性给予ProviderManager一组认证提供者。认证提供者由net.sf.acegisecurity.provider.AuthenticationProvider接口定义。Spring提供了这个接口的多个实现,如下表所示: 认证提供者 AuthByAdapterProvider CasAuthenticationProvider DaoAuthenticationProvider JaasAuthenticationProvider PasswordDaoAuthenticationProvider RemoteAuthenticationProvider RunAsImplAuthenticationProvider TestingAuthenticationProvider 11.2.2 使用数据库认证 声明DAO认证提供者 <bean id="authenticationProvider" class="net.sf.acegisecurity. providers.dao.DaoAuthenticationProvider"> <property name="authenticationDao"> <ref bean="authenticationDao"/> </property> </bean> authenticationDao属性标识用来从数据库获取用户信息的bean。这个属性应当是net.sf.acegisecurity.providers.dao.AuthenticationDao。Acegi提供了AuthenticationDao的两个实现:InMemoryDaoImpl和JdbcDaoImpl。 使用内存Dao <bean id="authenticationDao" class="net.sf.acegisecurity. providers.dao.memory.InMemoryDaoImpl"> <property name="userMap"> <value> palmerd=4moreyears,ROLE_PRESIDENT bauerj=ineedsleep,ROLE_FIELD_OPS,ROLE_DIRECTOR myersn=traitor,disabled,ROLE_FIELD_OPS </value> </property> </bean> 局限性在于必需编辑Spring配置文件并重新部署应用来管理安全。 声明JDBC DAO <bean id="authenticationDao" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean> JdbcDaoImpl假定你在数据库中使用特定的表来存储用户信息,特别地,使用“Users”表和“Authorities”表。但可以使用usersByUserNameQuery来设定如何获取用户信息。JdbcDaoImpl还提供了usersByUserNameMapping属性来引用MappingSqlQuery接口。这个接口将ResultSet转换成UserDetails对象。 同样可以通过设定authoritiesByUserNameQuery和authoritiesByUserNameMapping属性来获取授权信息。 使用加密密码 Acegi提供了三个密码解码器: <!--[if !supportLists]-->n <!--[endif]-->PlainTextPasswordEncoder(缺省)--不执行任何解码。 <!--[if !supportLists]-->n <!--[endif]-->Md5PasswordEncoder—执行MD5解码。 <!--[if !supportLists]-->n <!--[endif]-->ShaPasswordEncoder—执行安全哈希算法解码。 可以通过更改DaoAuthenticationProvider的passwordEncoder属性来设定解码器。还可以对解码器使用salt source,即密钥(encrytion key)。Acegi提供了两个salt sources:ReflectionSaltSource和SystemWideSaltSource。 缓存用户信息 DaoAuthenticationProvider通过UserCache接口的实现来支持用户信息的缓存。 public interface UserCache { public UserDetails getUserFromCache(String username); public void putUserInCache(UserDetails user); public void removeUserFromCache(String username); } Acegi提供了两种实现:NullUserCache和EhCacheBasedUserCache。前者不执行任何缓存。后者基于开源的ehcache项目。 <bean id="userCache" class="net.sf.acegisecurity. providers.dao.cache.EhCacheBasedUserCache"> <property name="minutesToIdle">15</property> </bean> minutesToIdle设定在没有读取缓存的情况下,用户信息应当驻留多长时间。 11.2.3 使用LDAP中心库进行认证 <bean id="authenticationProvider" class="net.sf.acegisecurity. providers.dao.PasswordDaoAuthenticationProvider"> <property name="passwordAuthenticationDao"> <ref bean="passwordAuthenticationDao"/> </property> </bean> 这个Dao属性应当实现PasswordAuthenticationDao接口。Acegi不提供这个接口的实现,但是在它的sandbox中有LdapPasswordAuthenticationDao实现。 11.2.4 使用Acegi和Yale CAS支持单点登录 11.3 控制访问 public interface AccessDecisionManager { public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config) throws AccessDeniedException; public boolean supports(ConfigAttribute attribute); public boolean supports(Class clazz); } Supports方法考量资源的类型和配置属性(安全资源的访问必需条件),据此决定访问决策管理器是否可以对资源做出访问决策。Decide方法是作出最终决策的地方,如果它正常返回不抛出异常的话,则授权对安全资源的访问。否则,拒绝访问。 11.3.1 投票访问决策 <!--[if !supportLists]-->n <!--[endif]-->net.sf.acegisecurity.vote.AffirmativeBased <!--[if !supportLists]-->n <!--[endif]-->net.sf.acegisecurity.vote.ConsensusBased <!--[if !supportLists]-->n <!--[endif]-->net.sf.acegisecurity.vote.UnanimousBased 这三者的区别之处在于访问决策管理器如何作出最后的决策,如下表所示: 访问决策管理器 例如,以下对UnanimousBased的配置: <bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.UnanimousBased"> <property name="decisionVoters"> <list> <ref bean="roleVoter"/> </list> </property> </bean> 11.3.2 决定如何投票 public interface AccessDecisionVoter { public static final int ACCESS_GRANTED = 1; public static final int ACCESS_ABSTAIN = 0; public static final int ACCESS_DENIED = -1; public boolean supports(ConfigAttribute attribute); public boolean supports(Class clazz); public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config); } Acegi提供了RoleVoter实现。处理基于角色的安全资源配置。 11.3.3 处理角色禁止 11.4 保护Web应用 过滤器 当请求提交到Acegi保护的Web应用时,请求被传递给所有Acegi的过滤器。 11.4.1 代理Acegi过滤器 在web.xml中只包含对FilterToBeanProxy的声明。最后,还要将过滤器与URL模式关联。 11.4.2 执行Web安全性 使用过滤器安全截获器 11.4.3 处理login 11.5 方法调用安全性 |
|