Spring 3.1 中文手册


参考文档 3.1 Ver 1.3(build on 20110819) 南磊 译 本文档的拷贝适用于您自己使用或是分发给他人,您不能从中收取任何费用并且任何拷贝必 须含有这个版权声明,无论是分发打印版还是电子版。 版权说明: Spring 中文版参考文档由南磊翻译,您可以自由使用或分发他人,但是您不能从中收取 任何费用,任何拷贝必须有版权声明。如有商业用途倾向,必须联系原作者和译者。 翻译文档支持网站为:http://code.google.com/p/translation (翻译期间,每周五在此更新) 译者联系方式如下,有对翻译的任何意见和建议,不要犹豫,请点击: 电子邮件:nanlei1987@gmail.com 微博:http://weibo.com/nanlei1987 第一部分 Spring framework 概述.......................................................................................................................5 第 1 章 Spring Framework 介绍.......................................................................................................................6 1.1 依赖注入和控制反转 ............................................................................................................................6 1.2 模块............................................................................................................................................................6 1.2.1 核心容器...........................................................................................................................................7 1.2.2 数据访问/整合 ................................................................................................................................7 1.2.3 Web .....................................................................................................................................................8 1.2.4 AOP 和设备组件...............................................................................................................................8 1.2.5 测试....................................................................................................................................................8 1.3 使用方案...................................................................................................................................................8 1.3.1 依赖管理和命名规约 ................................................................................................................. 12 1.3.1.1 Spring 依赖和基于 Spring ................................................................................................... 13 1.3.1.2 Maven 依赖管理 ................................................................................................................... 14 1.3.1.3 Ivy 依赖管理........................................................................................................................... 15 1.3.2 日志................................................................................................................................................. 16 1.3.2.1 不使用 Commons Logging.................................................................................................. 17 1.3.2.2 使用 SLF4J.............................................................................................................................. 17 1.3.2.3 使用 Log4J ............................................................................................................................. 19 第二部分 Spring 3 的新特性............................................................................................................................. 21 第 2 章 Spring 3.0 的新特性和增强 ............................................................................................................ 21 2.1 Java 5 ........................................................................................................................................................ 21 2.2 改进的文档 ........................................................................................................................................... 21 2.3 新的文章和教程 .................................................................................................................................. 21 2.4 新的模块组织方式和构建系统........................................................................................................ 22 2.5 新特性概述 ........................................................................................................................................... 22 2.5.1 为 Java 5 更新的核心 API........................................................................................................... 23 2.5.2 Spring 表达式语言 ........................................................................................................................ 23 2.5.3 控制反转(IoC)容器 ................................................................................................................ 24 2.5.3.1 基于 Java 的 bean 元数据.................................................................................................. 24 2.5.3.2 使用组件定义 bean 的元数据.......................................................................................... 25 2.5.4 通用的类型转换系统和字段格式化系统 .............................................................................. 25 2.5.5 数据层 ............................................................................................................................................ 25 2.5.6 Web 层............................................................................................................................................. 25 2.5.6.1 全面的 REST 支持 ................................................................................................................ 26 2.5.6.2 @MVC 的增加........................................................................................................................ 26 2.5.7 声明式的模型验证 ...................................................................................................................... 26 2.5.8 先期对 Java EE 6 的支持 ............................................................................................................. 26 2.5.9 嵌入式数据库的支持 ................................................................................................................. 26 第 3 章 Spring 3.1 的新特性和增强 ............................................................................................................ 27 3.1 新特性概述 ........................................................................................................................................... 27 第三部分 核心技术............................................................................................................................................. 28 第 4 章 IoC 容器............................................................................................................................................... 29 4.1 Spring IoC 容器和 bean 的介绍 ......................................................................................................... 29 4.2 容器概述................................................................................................................................................ 29 4.2.1 配置元数据 ................................................................................................................................... 30 4.2.2 实例化容器 ................................................................................................................................... 31 4.2.2.1 处理基于 XML 的配置元数据........................................................................................... 32 4.2.3 使用容器........................................................................................................................................ 33 4.3 Bean 概述................................................................................................................................................ 34 4.3.1 命名 bean ...................................................................................................................................... 34 4.3.1.1 在 bean 定义外面起别名................................................................................................... 35 4.3.2 实例化 bean .................................................................................................................................. 36 4.3.2.1 使用构造方法实例化 ......................................................................................................... 36 4.3.2.2 使用静态工厂方法来实例化 ............................................................................................ 36 4.3.2.3 使用实例工厂方法来实例化 ............................................................................................ 37 4.4 依赖......................................................................................................................................................... 38 4.4.1 依赖注入........................................................................................................................................ 38 4.4.1.1 基于构造方法的依赖注入 ................................................................................................ 39 4.4.1.2 基于 setter 方法的依赖注入............................................................................................. 41 4.4.1.3 解决依赖过程....................................................................................................................... 42 4.4.1.4 依赖注入示例....................................................................................................................... 43 4.4.2 深入依赖和配置 .......................................................................................................................... 45 4.4.2.1 直接值(原生类型,String,等) ............................................................................ 45 4.4.2.2 引用其它 bean(协作者) ............................................................................................... 47 4.4.2.3 内部 bean .............................................................................................................................. 48 4.4.2.4 集合......................................................................................................................................... 48 4.4.2.5 null 和空字符串..................................................................................................................... 51 4.4.2.6 使用 p-命名空间的 XML 快捷方式 ................................................................................. 52 4.4.2.7 使用 c-命名空间的 XML 快捷方式 .................................................................................. 53 4.4.2.8 复合属性名称....................................................................................................................... 54 4.4.3 使用 depends-on..................................................................................................................... 54 4.4.4 延迟初始化 bean ......................................................................................................................... 55 4.4.5 自动装配协作者 .......................................................................................................................... 55 4.4.5.1 自动装配的限制和缺点 ..................................................................................................... 56 4.4.5.2 从自动装配中排除 bean.................................................................................................... 57 4.4.6 方法注入........................................................................................................................................ 57 4.4.6.1 查找方法注入....................................................................................................................... 58 4.4.6.2 任意方法的替代 .................................................................................................................. 59 4.5 Bean 的范围 ........................................................................................................................................... 60 4.5.1 单例范围........................................................................................................................................ 61 4.5.2 原型范围........................................................................................................................................ 62 4.5.3 单例 bean 和原型 bean 依赖 .................................................................................................... 63 4.5.4 请求,会话和全局会话范围 .................................................................................................... 63 4.5.4.1 初始化 Web 配置................................................................................................................. 63 4.5.4.2 请求范围................................................................................................................................ 64 4.5.4.3 会话范围................................................................................................................................ 64 4.5.4.4 全局会话范围....................................................................................................................... 65 4.5.4.5 各种范围的 bean 作为依赖 .............................................................................................. 65 4.5.5 自定义范围 ................................................................................................................................... 67 4.5.5.1 创建自定义范围 .................................................................................................................. 67 4.5.5.2 使用自定义范围 .................................................................................................................. 68 4.6 自定义 bean 的性质............................................................................................................................ 69 4.6.1 生命周期回调............................................................................................................................... 69 4.6.1.1 初始化回调 ........................................................................................................................... 70 4.6.1.2 销毁回调................................................................................................................................ 70 4.6.1.3 默认的初始化和销毁方法 ................................................................................................ 71 4.6.1.4 组合生命周期机制.............................................................................................................. 72 4.6.1.5 启动和关闭回调 .................................................................................................................. 73 4.6.1.6 在非 Web 应用中,优雅地关闭 Spring IoC 容器......................................................... 74 4.6.2 ApplicationContextAware 和 BeanNameAware .................................................... 75 4.6.3 其它 Aware 接口 ........................................................................................................................ 75 4.7 Bean 定义的继承 .................................................................................................................................. 77 4.8 容器扩展点 ........................................................................................................................................... 78 4.8.1 使用 BeanPostProcessor 来自定义 bean ..................................................................... 78 4.8.1.1 示例:BeanPostProcessor 风格的 Hello World.................................................. 79 4.8.1.2 示例:RequiredAnnotationBeanPostProcessor....................................... 81 4.8.2 使用 BeanFactoryPostProcessor 自定义配置元数据........................................... 81 4.8.2.1 示例:PropertyPlaceholderConfigurer....................................................... 82 4.8.2.2 示例:PropertyOverrideConfigurer............................................................... 83 第一部分 Spring framework 概述 Spring framework 是一个轻量级的解决方案,在构建一站式企业级应用程序上有很大的 潜能。Spring 是模块化的,允许你使用仅需要的部分,而不需要引入其余部分。你可以使用 IoC 容器,和 Struts 一起使用,而且你也可以仅仅使用 Hibernate 整合代码或者是 JDBC 抽象 层。Spring framework 支持声明式的事务管理,通过 RMI 或 Web Service 远程访问业务逻辑 代码,并且提供多种持久化数据的选择。它提供饿了一个全功能的 MVC 框架,允许你显式 地整合 AOP 到软件中。 Spring 被设计成非侵入式的,也就是说你的业务逻辑代码通常是不会对 Spring 框架本身 产生依赖的。在你的整合层面(比如数据访问层),一些依赖于数据访问技术和 Spring 的类 库是会存在的。但是,也很容易将这些依赖从你剩余的代码中分离出来。 本文档是 Spring 框架特性的参考指南。如果你有任何想法,建议或是对本文档的疑问, 请发送到用户邮件列表中或者是在线论坛中,论坛地址是 http://forum.springsource.org。 第 1 章 Spring Framework 介绍 Spring Framework 是一个 Java 平台,它提供了对开发 Java 应用程序的一种广泛的基础支 持。Spring 控制这个基础,那么你就可以集中精力于应用程序了。 Spring 允许你从“普通 Java 对象(POJO)”来构建应用程序,并且将应用企业级服务非 侵入地应用于 POJO 中。这个能力适用于 Java SE 编程模型,全部或部分的 Java EE。 作为应用程序的开发人员,下面就是你可以使用 Spring 平台优点的例子:  编写 Java 方法来执行数据库事务而不需要处理事务 API。  编写本地 Java 方法来访问远程程序而不需要处理远程访问 API。  编写本地 Java 方法来执行管理操作而不需要处理 JMX 的 API。  编写本地 Java 方法来处理消息而不需要处理 JMS 的 API。 1.1 依赖注入和控制反转 Java 应用程序 -- 一个宽松的术语,囊括了从被限制的 applet 到 n 层服务器端企业级应 用程序的全部 – 典型地是,包含了组成独特应用程序的合作对象。那么这些在应用程序中 的对象就会相互依赖。 尽管 Java 平台提供了丰富的应用程序开发功能,但是它也缺乏组织基本模块到一个整 体的方式,把这个任务留给了架构师和开发人员。也就是说,你可以设计如工厂,抽象工厂, 构建者,装饰者和服务定位器等模式来组合各个类和构成应用程序的对象实例。然而,这些 模式是最简单的:最佳的做法是给定一个名称,并且描述这个模式做了什么,在哪里可以应 用它,它所强调的问题是什么等等。模式使你必须自己实现的最佳实践形式化。 Spring Framework 的控制反转(Inversion of Control,IoC)组件提供组合不同的组件到完 整可用的应用程序的形式化方法来强调这个问题。Spring Framework 编写了形式化的设计模 式作为顶级对象,你可以用来整合到你的应用程序中。很多组织和研究机构使用 Spring Framework 的这个方式来设计强壮的,可维护的应用程序。 1.2 模块 Spring Framework 包含了很多特性并且组织成 20 个模块。这些模块分为核心容器,数 据访问/整合,Web,AOP(Aspect Oriented Programming,面向切面编程),设备组件和测试, 在下图中来展示。 背景 “问题是,[它们]反向控制哪一方面?”,2004 年,Martin Fowler 在他个人站点提出 了这个关于控制反转(IoC)问题。Fowler 建议重命名这个原则,使得它更好地自我解释, 同事提出了依赖注入。 要 深 入 了 解 IoC 和 DI , 可 以 参 考 Fowler 的 文 章 , 地 址 是 : http://martinfowler.com/articles/injection.html Spring 框架概要 1.2.1 核心容器 核心容器(4.1 节)包含了核心(Core), Bean 组件(Beans),上下文(Context)和表 达式语言(Expression Language)模块。 核心和 Bean(4.1 节)模块提供了框架部分内容的基础,包含 IoC 和依赖注入特性。 BeanFactory 是工厂模式的精密实现。它去掉了编程化单例的需要,并允许你解除配置和 实际程序逻辑特定依赖之间的耦合。 上下文(4.14 节)模块构建了由核心和 Bean(4.1 节)模块提供的坚实基础:它可以让 你以框架样式的风格来访问对象,这和 JNDI 注册是相似的。上下文模块集成了来自 Bean 模 块的特性并且加入了对国际化(比如使用资源束)的支持,事件传播,资源加载和 Servlet 容器显式地创建上下文。上下文模块也支持 Java EE 特性,比如 EJB,JMX 和基本的远程调用。 ApplicationContext 接口是上下文模块的焦点。 表达式语言(第 7 章)模块提供了强大的表达式语言,在运行时查询和操作对象图。这 是 JSP 2.1 规范中的统一表达式语言(unified EL)的一个扩展。该语言支持设置和获取属性 值,属性定义,方法调用,访问数组,集合和索引的上下文,逻辑和数字运算,命名变量和 从Spring 的IoC容器中以名称来获取对象。它也支持列表投影和选择,还有普通的列表聚集。 1.2.2 数据访问/整合 数据访问/整合层由 JDBC,ORM,OXM,JMS 和事务模块组成。 JDBC(13.1 节)模块提供了 JDBC 抽象层,它移除了冗长的 JDBC 编码,但解析了数据库 提供商特定的错误代码。 ORM(14.1 节)模块提供了对流行的对象-实体映射 API 的整合层,包含 JPA(14.5 节), JDO(14.4 节),Hibernate(14.3 节)和 iBatis(14.6 节)。使用 ORM 包你就可以使用全部的 O/R-映射框架并联合其它 Spring 提供的特性,比如前面提到的简单声明式的事务管理特性。 OXM(第 15 章)模块提供了支持 JAXB,Castor,XMLBeans,JiBX 和 Xstream 对对象/XML 映射实现的抽象层。 Java 消息服务(JMS(第 22 章))模块包含生成和处理消息的特性。 事务(第 11 章)模块支持对实现特定接口的类和所有 POJO(普通 Java 对象)的编程 式和声明式的事务管理。 1.2.3 Web Web 层由 Web,Web-Servlet,Web-Struts 和 Web-Portlet 模块组成。 Spring 的 Web 模块提供了基本的面向 Web 整合的特性,比如文件上传功能,IoC 容器 的初始化使用了Servlet 的监听器和变相Web的应用上下文。它还包含了和 Web相关的 Spring 的远程调用支持部分。 Web-Servlet 模块包含了 Spring 对 Web 应用的模型-视图-控制器(MVC(16.1 节))实现。 Spring 的 MVC 框架提供了一个在领域模型代码和 Web 表单之间的整洁分离,并且整合了其 它所有 Spring 框架的特性。 Web-Struts 模块包含了使用 Spring 应用程序整合经典 Strtus Web 层的支持类。要注意这 个支持从 Spring 3.0 开始就废弃了。可以考虑迁移应用程序到 Struts 2.0 和 Spring 的整合或者 到 SpringMVC 方案。 Web-Portlet 模块提供用于 portlet 环境和 Web-Servlet 模块功能镜像的 MVC 实现。 1.2.4 AOP 和设备组件 Spring 的 AOP(8.1 节)模块提供了 AOP 联盟-允许的的面向切面编程实现,允许你定义 如方法-拦截器和横切点来整洁地解耦应该被分离的功能实现代码。使用源码级的元数据功 能,也可以混合行文信息到代码中,这个方式和.NET 属性很相似。 分离的 Aspects 模块提供对 AspectJ 的整合。 设备组件模块提供了设备对类的支持还有类加载器的实现来用于特定的应用服务器。 1.2.5 测试 测试模块支持 Spring 组件和 Junit 或 TestNG 的测试。它提供了 Spring 应用上下文的一致 加载并缓存这些上下文内容。它也提供 mock 对象,你可以用来孤立地测试代码。 1.3 使用方案 前面描述的构建块使得 Spring 可以在很多方案中作为业务逻辑实现的选择,从 applet 到使用了 Spring 的事务管理功能和 Web 框架整合的功能完善的企业级应用。 典型地功能完善的 Spring Web 应用 Spring 的声明式事务管理特性(11.5 节)使得 Web 应用程序可以全部事务化,就好像 使用了 EJB 容器管理的事务。全部的自定义业务逻辑可以使用简单的 POJO 来实现并且被 Spring 的 IoC 容器来管理。额外的服务包含对发送邮件的支持,验证对 Web 层独立,这可以 让你选择在哪里执行验证规则。Spring 的 ORM 支持对 JPA,Hibernate,JDO 和 iBatis 进行了整 合;比如,当使用 Hibernate 时,你可以继续使用已有的映射文件和标准的 Hibernate 的 SessionFactory 配置。表单控制器无缝地整合了 Web 层 和 领 域 模 型 , 移 除 了 ActionForm 或其它为领域模型转换 HTTP 参数的类需要。 使用了第三方 Web 框架的 Spring 中间层 有时,环境并不允许你完全转换到一个不同的框架。Spring Framework 不强制你使用它 其中部分;它并不是一个所有或没有的方案。已有的使用 WebWork,Struts,Tapestry 或其 它 UI 框架构建的前端可以使用基于 Spring 的中间层来整合,这就允许你使用 Spring 的事务 特性。你仅仅需要给你的业务逻辑装上 ApplicationContext,对整合 Web 层使用 WebApplicationContext。 远程调用使用方案 当你需要通过 Web Service 访问已有的代码,你可以使用 Spring 的 Hessian-,Burlap-, Rmi-或 JaxPpcProxyFactory 类。开启远程访问已有的应用并不困难。 EJB – 包装已有的 POJO Spring Framework 也提供了对企业级 Java Bean 的访问和抽象层(第 21 章),这就可以 重用已有的 POJO,可扩展包装它们到无状态会话 bean,不安全的 Web 应用可能需要声明式 安全。 1.3.1 依赖管理和命名规约 依赖管理和依赖注入是不同的概念。要在应用程序中添加 Spring 优美的特性(比如依 赖注入),你需要组合所需的类库(jar 文件)并添加到在运行时环境的类路径中,而编译时 也是需要的。这些依赖不是注入的虚拟组件,而是文件系统(通常是这样)的物理资源。依 赖管理的过程包括定位那些资源,存储它们并将它们添加到类路径中。依赖可以是直接的(比 如,应用程序在运行时需要 Spring),或者是间接的(比如,应用程序需要的 commons-dbcp 还依赖 commons-pool)。间接的依赖也被认为是“过度的”,而且那些依赖本身就难以识 别和管理。 如果你决定使用 Spring,那么你需要获得 jar 包的拷贝,包括你所需要的 Spring 的模块。 为了使用上的便捷,Spring 被打包成模块集合,尽可能地分离了其中的依赖,那么比如如果 你不想编写 Web 应用程序,就不需要 spring-web 模块。要参考 Spring 模块的库,本指南使 用了一个速记命名规约 spring-*或者 spring-*.jar,这里的“*”代表了模块的短名 称(比如,spring-core,spring-webmvc,spring-jms 等)。真实的 jar 文件命名或 许就是这种形式的(看下面的示例),但也有可能不是,通常它的文件名中还有一个版本号 (比如,spring-core-3.0.0.RELEASE.jar)。 通常,Spring 在四个不同的地方发布组件:  在社区下载点 http://www.springsource.org/downloads/community。这里你可以找到所有 Spring 的 jar 包,它们被压缩到一个 zip 文件中,可以自由下载。这里 jar 包的命名从 3.0 版本开始以 org.springframework.*-.jar 格式。  Maven 的中央库,也是 Maven 检索的默认资源库,并不会检索特殊的配置来使用。很 多 Spring 依赖的常用类库也可以从 Maven 的中央库中获得,同事 Spring 社区绝大多数 用户使用 Maven 作为依赖管理工具,这对于他们来说是很方便的。这里 jar 包的命名是 spring-*-.jar 格 式 的 , 并 且 Maven 的 groupId 是 org.springframework。  企业级资源库(Enterprise Bundle Repository,EBR),这是由 SpringSource 组织运营的, 同时也提供和 Spring 整合的所有类库。对于所有 Spring 的 jar 包及其依赖,这里也有 Maven 和 Ivy 的资源库,同时还有很多开发人员在使用 Spring 编写应用程序时能用到的 大量常用类库。而且发布版本,里程碑版本和开发版本也都在这里部署着。这里 jar 文 件的命名和社区下载的(org.springframework.*-.jar)一致,并且 依赖的外部类库(不是来自 SpringSource 的)也是使用的这种“长的”形式,并以 com.springsource 作为前缀。可以参考 FAQ 部分获取更多信息。  在 Amazon S3 为开发和里程碑发布(最终发布的拷贝这里也会有)设置的公共 Maven 资源库中。Jar 文件名称和 Maven 中央库是一样的,那么这里是获取 Spring 开发版本来 使用的地方,其它的类库是部署于 Maven 中央库的。 那么首先你要决定的事情是如何管理你的依赖:很多人使用自动化的系统,比如 Maven 和 Ivy,但是你也可以手动下载所有的 jar 文件。当使用 Maven 或 Ivy 获取 Spring 时,之后你 需要决定你要从哪里来获取。通常来说,如果你关注 OSGi,那就使用 EBR,因为它对所有 的 Spring 依赖兼容 OSGi,比如 Hibernate 和 Freemarker。如果对 OSGi 不感兴趣,那么使用 哪个都可以,他们也各有利弊。通常讲,为你的项目选择一个或者另外一个;但是不要混用。 因为相对于 Maven 中央库而言,EBR 组件非常需要一个不同的命名规则,那么这就特别重 要了。 表 1.1 Maven 中央库和 SpringSource EBR 资源库的比较 特性 Maven 中央库 EBR OSGi 的兼容 不明确 是 组件数量 成千上万;所有种类 几百个;是 Spring 整合用到的 一致的命名规约 没有 有 命名规约:GroupId 相异。新组件通常使用域 名,比如 org.slf4j。老组件 通常仅仅使用组件名,比 如 log4j。 原始的域名或主包根路径,比如 org.springframework 命名规约:ArtifactId 相异。通常是项目或模块 名,使用连字符“-”分隔, 比如 spring-core,log4j。 捆绑符号名称,从主包路径分离,比如 org.springframework.beans。如果 jar 需要 修补以保证兼容 OSGi,那么就附加 com.springsource ,比如 com.springsource.org.apache.log4j 命名规约:Version 相异。很多新的组件使用 m.m.m 或 m.m.m.X(m 是 数字,X 是文本)。老的使 用 m.m。有一些也不是。 顺序是确定的但通常并不 可靠,所以不是严格的可 靠。 OSGi 版本数字 m.m.m.X,比如 3.0.0.RC3。 文本标识符使用相同的数字值规定版本 的字母顺序。 发布 通常是自动通过 rsync 或 源码控制更新。项目作者 可以上传独立的 jar 文件 到 JIRA。 手动(由 SpringSource 控制的 JIRA) 质量保证 根据政策。精确度是作者 的责任。 宽泛的 OSGi 清单,Maven POM 和 Ivy 元 数据。QA 由 Spring 团队执行。 主办 Contegix 提供。由 Sonatype 和一些镜像构成。 由 SpringSource 的 S3 构成 搜索工具 很多 http://www.springsource.com/repository 和 SpringSource 工具 整合 通过和 Maven 依赖管理的 STS 来整合 通过 Maven,Roo,CloudFoundry 的 STS 进行广泛整合 1.3.1.1 Spring 依赖和基于 Spring 尽管 Spring 提供整合和对大量企业级及其外部工具的支持,那么它也有心保持它的强 制依赖到一个绝对小的数目:你不需要为了简单的使用去定位并下载(甚至是自动地)大量 的 jar 文件来使用 Spring。对于基本的依赖注入那只需要一个强制的外部依赖,就是日志(可 以参考下面关于日志的深入介绍)。 下面,我们来概述一下配置一个基于 Spring 的应用程序所需的配置,首先使用 Maven 和 Ivy。在所有的示例中,如果有那一点不清楚,可以参考你所使用的依赖管理系统的文档, 或者参考一些示例代码 – Spring 本身在构建时使用 Ivy 来管理依赖,而我们大多数的示例使 用 Maven。 1.3.1.2 Maven 依赖管理 如果你正在使用 Maven 来进行依赖管理,那么你就不需要明确地提供日志依赖。比如, 要为应用程序配置创建应用上下文并使用依赖注入,你的 Maven 依赖可以是这样的: 就是这么简单。要注意如果你在编译代码时不需要 Spring 的 API,那么 scope 可以声明 为 runtime,这就可以用于典型的基本依赖注入用例。 在上面的示例中,我们使用了 Maven 中央库的命名规约,那么就可以在 Maven 中央库 或 SpringSource S3 的 Maven 资源库起作用。要使用 S3 的 Maven 资源库(比如里程碑版本或 开发快照),你需要在 Maven 的配置文件中指定资源库的位置。对于完整发布版如下: 对于里程碑版本如下: 对于开发快照版本如下: org.springframework spring-context 3.0.0.RELEASE runtime com.springsource.repository.maven.release http://maven.springframework.org/release/ false com.springsource.repository.maven.milestone http://maven.springframework.org/milestone/ false com.springsource.repository.maven.snapshot http://maven.springframework.org/snapshot/ true 要使用 SrpingSource 的 EBR,那么你还需要一个对依赖不同的命名规约。名称通常很容 易去猜测,比如这个示例中,就是: 你也可能需要明确地声明资源库的位置(仅仅 URL 是重要的): 如果你手动去管理依赖,这个在上面声明的资源库 URL 是不可以浏览的,但是也有一 个用户界面,地址是 http://www.springsource.com/repository,这可以用来搜索和下载依赖。 它也有Maven和Ivy配置的代码片段,你可以复制下来并粘贴到你所使用的工具中,很方便。 1.3.1.3 Ivy 依赖管理 如果你使用 Ivy 来管理依赖,那么有一些简单的命名和配置选项。 要配置 Ivy 定位到 SpringSource EBR,需要添加如下 resolvers 到你的 ivysettings.xml 中: org.springframework org.springframework.context 3.0.0.RELEASE runtime com.springsource.repository.bundles.release http://repository.springsource.com/maven/bundles/releas e/ 上面的 XML 并不是合法的,因为行太长了 – 如果你要复制粘贴,那么要移除中部 url 模式部分结尾额外的行。(中文文档中已经去除) 一旦 Ivy 配置好去查看 EBR,那么添加依赖就很简单了。只要拉起在资源库浏览器中的 捆绑详细信息页面,你就会发现为你准备好的 Ivy 代码片段,你就可以将它包含到你的依赖 部分中了。比如(在 ivy.xml 中): 1.3.2 日志 对于 Spring 来说,日志是一个非常重要的依赖,因为 a)这是唯一的强制外部依赖,b) 开发人员都会想看到他们实用工具的一些输出内容,而且 c)Spring 整合了多种工具,它们都 会选择一种日志依赖。应用程序开发人员的目标之一就是在核心位置对整个应用程序有一个 统一的日志配置,包含对全部的外部组件。因为日志框架有很多种选择,那么这就可能有些 难以选择了。 Spring 中强制的日志依赖是 Jakarta 的 Commons Logging API(JCL)。我们的编译是基于 JCL 的,而且我们使扩展 Spring Framework 的类对 JCL 的 Log 对象都是可见的。对于用户来 说,所有 Spring 的版本都使用相同的日志包是很重要的:因为向后兼容特性的保留,迁移 是很容易的,扩展 Spring 的应用程序也是这样。我们这样做就是使 Spring 中的模块明确地 基于 commons-logging(JCL 的典型实现),在编译时也使得其它模块都基于它。如果你 在使用 Maven,并想知道在哪儿获取到的 commons-logging 依赖,那就是从 Spring 中被 称作是 spring-core 的核心模块中获取的。 关于 commons-logging 比较好的是你不需要做其它的步骤就可以使应用程序工作。 它有一个运行时的发现算法,在我们都知道的类路径下寻找其它日志框架,并且使用它认为 是比较合适的(或者告诉它你需要使用的是哪一个)一个。如果没有可用的,那么你会得到 一个来自 JDK(java.util.logging 或者简称为 JUL)看起来还不错的日志。那么你会发现很多时 候,Spring 应用程序工作中会有日志打印到控制台上,这是很重要的。 1.3.2.1 不使用 Commons Logging 不幸的是,commons-logging 的运行时发现算法对最终用户方便是有问题的。如果 我们可以让时光倒流并让 Spring 从现在开始作为一个新的项目,那么我们会使用不同的日 志依赖。那么首选的可能是 Java 简单的日志门面(SLF4J),它也被 Spring 的开发人员在它们 的应用程序中很多其它的工具所使用。 关闭 commons-logging 也简单:仅仅要确认在运行时不在类路径下就可以了。在 Maven 中去掉这个依赖,因为 Spring 依赖声明的时候会带进来,那么就需要这么来做一下。 目前这个应用程序可能就不能用了,因为在类路径中没有 JCL API 的实现了,所以要修 复这个问题就要提供另外一种实现了。在下一节中,我们来展示如何提供一个可选的 JCL 的 实现,我们使用 SLF4J 作为示例。 1.3.2.2 使用 SLF4J SLF4J 是一个干净的依赖,在运行时也比 commons-logging 效率更高,因为它使用了 编译时构建而不是运行时去发现其它整合的日志框架。这也就意味着你可以更明确地在运行 时去做什么,并且去声明或配置。SLF4J 为很多通用的日志框架提供的绑定,所以通常你可 以选择已有的一个,并绑定配置或管理。 SLF4J 为很多通用日志框架提供绑定,包括 JCL,并且也可以反向:桥接其它日志框架和 它本身。所以在 Spring 中使用 SLF4J 那么就需要使用 SLF4J-JCL 桥来代替 commons-logging 依赖。只要配置好了,那么来自 Spring 的日志调用就会被翻译成调用 SLF4J 的 API,如果在 你应用程序的其它类库使用那个 API,那么你会有一个单独的地方来配置和管理日志。 一个常用的选择是桥接 Spring 到 SLF4J,之后提供从 SLF4J 到 Log4J 的明确绑定。你需要 提供 4 种依赖(并排除已经存在的 commons-logging):桥接工具,SLF4J API,绑定到 Log4J 的工具,还有 Log4J 本身的实现。那么在 Maven 中,你就可以这样来做: org.springframework spring-context 3.0.0.RELEASE runtime commons-logging commons-logging 这看起来就好像很多依赖都需要日志。确实是这样,但它是可选的,而且它的性能要比 由于类加载器问题的 commons-logging 好很多,尤其是如果你使用了一个严格限制的容 器,比如 OSGi 平台。据称那也有性能优势,因为绑定是编译时的而不是运行时的。 另外,在 SLF4J 用户中一个较为常见的选择是使用很少步骤并产生更少的依赖,就是直 接绑定到 Logback。这会移除额外的绑定步骤,因为 Logback 直接实现了 SLF4J,所以你仅仅 org.springframework spring-context 3.0.0.RELEASE runtime commons-logging commons-logging org.slf4j jcl-over-slf4j 1.5.8 runtime org.slf4j slf4j-api 1.5.8 runtime org.slf4j slf4j-log4j12 1.5.8 runtime log4j log4j 1.2.14 runtime 需要依赖两个类库而不是四个(jcl-over-slf4j 和 logback)。 如果你那么做了,你可 能还需要从其它外部依赖(而不是 Spring)中去除 slf4j-api 的依赖,因为你仅仅想在类路径 中有 API 的一个版本。 1.3.2.3 使用 Log4J 很多人出于配置和管理目的而使用 Log4j 作为日志框架。这也很有效率并且易于创建, 而且事实上它也是我们构建和测试 Spring 时,在运行时环境中使用的。Spring 也提供一些工 具来配置和初始化 Log4j,所以在某些模块中它也提供了可选的编译时对 Log4j 的依赖。 要让 Log4j 和默认的 JCL 依赖(commons-logging)起作用,你所要做的就是将 Log4j 放置到类路径下,并且提供配置文件(在类路径的根路径下放置 log4j.properties 或 log4j.xml)。而对于 Maven 用户来说,下面可以是依赖的声明: 下面是 log4j.properties 打印到控制台的日志的配置示例: 运行时容器和本地的 JCL 很多用户在容器中运行Spring 应用程序,而容器本身提供了 JCL 的实现。IBM Webshphere 应用服务器(WAS)就是这种类型。这通常会引起问题,而不幸的是没有解决的银弹;很多 情况下仅仅从应用程序中去除 commons-logging 是不够的。 要清楚地认识这一点:这个问题通常和 JCL 本身一同报告,或者是和 commons-logging:而不是绑定 commons-logging 到另外的框架(通常是 Log4J)。这 org.springframework spring-context 3.0.0.RELEASE runtime log4j log4j 1.2.14 runtime log4j.rootCategory=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n log4j.category.org.springframework.beans.factory=DEBUG 可能会引发问题,因为 commons-logging 改变了它们在运行时环境里,在一些容器中发 现老版本(1.0)而很多人现在使用新的版本(1.1)。 Spring 不会使用任何不通用的 JCL API 部分,所以这里不会有问题,但是 Spring 或你的应用程序尝试去进行日志记录,你可以发 现绑定到 Log4J 是不起作用的。 这种在 WAS 上的情况,最简单的做法就是颠倒类加载器的层次(IBM 称之为“parent last”),那么就是应用程序控制而不是容器来控制 JCL 依赖。这种选择并不总是开放的,但 是在公共领域的代替方法也有其它的建议,根据确切的版本和容器的特性集,您所要求的效 果可能会不同。 第二部分 Spring 3 的新特性 第 2 章 Spring 3.0 的新特性和增强 如果你使用 Spring Framework,你会看到 Spring 现在有两个主要的修订版本:在 2006 年 12 月发布的 Spring 2.0,在 2007 年 11 月发布的 Spring 2.5。现在就是第三个版本 Spring 3.0 了。 2.1 Java 5 框架的整体代码都已经修订来支持 Java 5 的新特性,比如泛型,可变参数和其它的语言 改进。我们也尽最大努力来保持代码的向后兼容。现在我们也有一致的泛型集合和 Map 使 用,泛型 FactoryBean 的一致使用,还有在 Spring AOP API 中对桥接方法的一致解决方案。 泛型上下文监听器仅仅自动接收特定的事件类型。所有的如 TransactionCallback 和 HibernateCallback 回调接口现在也都声明为泛型返回值。总之,Spring 核心代码库都已经为 Java 5 而修订和优化。 Spring 的TaskExecutor 抽象也已经为和Java 5的java.util.concurrent 的紧密整合而更新了。 我们现在为可调用和特性提供顶级的类的支持,还有 ExecutorService 适配器,ThreadFactory 整合等。这和 JSR-236(Java EE6 的并发工具)尽可能是一样的。此外,我们提供对使用新 的@Async 注解(或者 EJB 3.1 的@Asynchronous 注解)异步方法调用的支持。 2.2 改进的文档 Spring 参考文档也有很大的更新,并反射出 Spring 3.0 的改进和新特性。而每一项努力 都是来保证在文档中没有错误,一些错误还会多多少少的存在。如果你确实发现了任何的错 字或者是严重的错误,你可以在午餐期间将它们画上圈,然后请将这些错误报告给 Spring 团队,你可以打开一个问题。 2.3 新的文章和教程 有很多优秀的文章和教程来展示如何开始使用 Spring 3 新特性。请在 Spring 文档页面来 阅读它们。 示例也已经更新采用了 Spring 3 的新特性。此外,示例也已经从源码树中移入到专用的 SVN 资 源 库 中 了 。 访问的地 址 是 : https://anonsvn.springframework.org/svn/spring-samples/。 因此,这些示例不会跟在 Spring 3 的发布包中了,需要从上面提到的资源库中分别来下 Java SE 和 Java EE 支持 Spring Framework 现在完全基于 Java 5 和 Java 6。 此外,Spring 还兼容 J2EE 1.4 和 Java EE5,同时也会介绍一些对 Java EE6 的先期支持。 载。而本文档会继续引用一些示例(特别是 Petclinic)来陈述各种特性。 注意 要获取关于 Subversion(或简称 SVN)的更多信息,可以参考项目主页,地址是: http://subversion.apache.org/。 2.4 新的模块组织方式和构建系统 框架的模块已经被修订了,现在分别组织成一个源码分支是一个 jar 文件:  org.springframework.aop  org.springframework.beans  org.springframework.context  org.springframework.context.support  org.springframework.expression  org.springframework.instrument  org.springframework.jdbc  org.springframework.jms  org.springframework.orm  org.springframework.oxm  org.springframework.test  org.springframework.transaction  org.springframework.web  org.springframework.web.portlet  org.springframework.web.servlet  org.springframework.web.struts 现在我们使用新的 Spring 构建系统,从熟知的 Spring Web Flow 2.0 中而来,它给力我们 如下特性:  基于 Ivy 的“Spring 构建”系统  一致的开发过程  一致的依赖管理  一致的 OSGIi 清单的生成 2.5 新特性概述 下面的列表是 Spring 3.0 的新特性,我们在后面章节的详细讲述会覆盖这些特性。  Spring 表达式语言  IoC 增加/基于 Java 的 bean 元数据  通用的类型转换系统和字段格式化系统  对象转 XML 映射功能(OXM)从 Spring Web Service 项目中移出  全面的 REST 支持  @MVC 的增加 注意: Spring.jar 文件包含了几乎所有框架内容,现在不再提供了。  声明式的模型验证  先期对 Java EE 6 的支持  嵌入式数据库的支持 2.5.1 为 Java 5 更新的核心 API BeanFactory 接口尽可能返回该类型 bean 的实例:  T getBean(Class requiredType)  T getBean(String name, Class requiredType)  Map getBeansOfType(Class type) Spring 的 TaskExecutor 接口现在扩展了 java.util.concurrent.Executor:  扩展的 AsyncTaskExecutor 支持标准的可调用特性 新的基于 Java 5 的转换 API 和 SPI:  无状态的 ConversionService 和转换器  取代标准 JDK 的 PropertyEditors 泛型化的 ApplicationListener 2.5.2 Spring 表达式语言 Spring 引入了一种表达式语言,这和统一 EL 在语法上很相似,但是提供了很多特性。 这种表达式语言可以用于定义基于 XML 和注解的 bean,也可以作为表达式语言的基础,支 持贯穿整个Spring 框架的组合。这些新功能的详细内容可以在第 7章 Spring 表达式语言(SpEL) 中查看。 Spring 表达式语言被用来为 Spring 社区提供一个单一的,支持良好的表达式语言,可以 被用于 Spring 系列的所有产品。它的语言特性由 Spring 系列产品的所有项目的需求来驱动, 包括基于 Eclipse 的 SpringSource Tool Suite(SpringSource 组织开发的工具套件,译者注)代 码完成支持工具的需求。 下面是表达式媛媛如何被用于配置数据库属性设置的示例: 如果你使用注解来配置组件,那么功能也是可用的: @Repository public class RewardsTestDatabase { @Value("#{systemProperties.databaseName}") public void setDatabaseName(String dbName) { … } @Value("#{strategyBean.databaseKeyGenerator}") public void setKeyGenerator(KeyGenerator kg) { … } } 2.5.3 控制反转(IoC)容器 2.5.3.1 基于 Java 的 bean 元数据 从 Java Config 项目中来的一些核心特性现在也被加入到 Spring Framework 中了。这就是 说下面的注解是直接支持的。  @Configuration  @Bean  @DependsOn  @Primary  @Lazy  @Import  @ImportResource  @Value 下面是一个 Java 类提供基本配置信息的示例,使用了新的 Java Config 特性: package org.example.config; @Configuration public class AppConfig { private @Value("#{jdbcProperties.url}") String jdbcUrl; private @Value("#{jdbcProperties.username}") String username; private @Value("#{jdbcProperties.password}") String password; @Bean public FooService fooService() { return new FooServiceImpl(fooRepository()); } @Bean public FooRepository fooRepository() { return new HibernateFooRepository(sessionFactory()); } @Bean public SessionFactory sessionFactory() { // 装配一个session factory AnnotationSessionFactoryBean asFactoryBean = new AnnotationSessionFactoryBean(); asFactoryBean.setDataSource(dataSource()); // 其它配置 return asFactoryBean.getObject(); } @Bean public DataSource dataSource() { return new DriverManagerDataSource(jdbcUrl, username, password); } } 要让它发挥作用,你需要在应用上下文 XML 文件中添加如下的组件扫描项。 或 者 你 可 以 直 接 使 用 AnnotationConfigApplicationContext 来 启 动 被 @Configuration 注解的类: 参考 4.12.2 节“使用 AnnotationConfigApplicationContext 实例化 Spring 容器”来获取关 于 AnnotationConfigApplicationContext 的全部信息。 2.5.3.2 使用组件定义 bean 的元数据 @Bean 注解的方法也可以支持内部的 Spring 组件。它们贡献工厂 bean 的定义到容器中。 参考 4.10.4 节“使用组件定义 bean 的元数据”来获取更多信息。 2.5.4 通用的类型转换系统和字段格式化系统 通用的类型转换系统(参考 6.5 节)已经引入了。系统现在被 SpEL 使用来进行类型转 换,当绑定 bean 的属性值时也会被 Spring 容器和数据绑定器使用。 此外,格式化(参考 6.6 节)SPI 也被引入了,来格式化字段值。这个 SPI 对 JavaBean 的 PropertyEditors 提供了简单的,更强壮替代,在如 Spring MVC 的客户端环境中来使用。 2.5.5 数据层 现在,对象到 XML 映射功能(OXM)被从 Spring Web Service 项目中移到 Spring Framework 的核心中。这个功能可以在 org.springframework.oxm 包下找到。关于使用 OXM 模块 的更多信息可以在第 15 章使用 O/X 映射器编组 XML 找到。 2.5.6 Web 层 对于 Web 层来说,最令人兴奋的新特性是对构建 RESTful Web Service 和 Web 应用程序 的支持。也有一些新的注解可以用于任意的 Web 应用程序。 public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); FooService fooService = ctx.getBean(FooService.class); fooService.doStuff(); } 2.5.6.1 全面的 REST 支持 对构建RESTful应用程序服务器端的支持已经作为已有的注解驱动的MVC web框架而提 供了。客户端的支持由 RestTemplate 类来提供,和其它的模板类是相似的,比如 JdbcTemplate 和 JmsTemplate。服务器和客户端两者的 REST 功能都使用 HttpConverter 来在对象和它们在 HTTP 请求和响应代表之间方便的转换。 MarshallingHttpMessageConverter 使用之前提到的对象到 XML 映射功能。 可以参考 MVC(第 16 章)和 RestTemplate(20.9.1 节)部分获取更多信息。 2.5.6.2 @MVC 的增加 mvc 命名空间被引入来大大简化 Spring MVC 的配置。 其它如@CookieValue 和 @RequestHeader 的注解也被加入了。参考使用 @CookieVlue 注解映射 cookie 值(16.3.3.9 节)和使用@RequestHeader 注解映射请求头部属 性(16.3.3.10 节)来获取更多信息。 2.5.7 声明式的模型验证 一些验证增强(6.7 节),包括 JSR303 支持,使用 Hibernate 校验器作为默认提供者。 2.5.8 先期对 Java EE 6 的支持 通过使用新的@Async 注解(或 EJB 3.1 的@Asynchronous 注解)我们提供异步方法调用。 JSR 303,JSF 2.0,JPA 2.0 等 2.5.9 嵌入式数据库的支持 现在也提供了对嵌入式 Java 数据库引擎(13.8 节)的方便支持,包括 HSQL,H2 和 Derby。 第 3 章 Spring 3.1 的新特性和增强 在 Spring 3.0 引入的支持之上构建,Spring 3.1 现在还在开发中,目前 Spring 3.1 M2 才刚 刚发布。 3.1 新特性概述 (该部分内容待 Spring 3.1 GA 发布后翻译) 第三部分 核心技术 参考文档的这一部分涵盖了 Spring Framework 中不可或缺的技术。 这些内容最主要的是 Spring Framework 的控制反转(IoC)容器。Spring Framework 的 IoC 容器的完全使用是紧跟其后的 Spring 的面向切面编程(AOP)技术的完全覆盖。Spring Framework 有它自己的 AOP 框架,在概念上很容易去理解,在 Java 企业级编程中,它成功 地解决了 80%的 AOP 需求的功能点。 也提供了涵盖的 Spring 和 AspectJ(目前最丰富的 - 在功能方面 – 当然是在 Java 企业 级空间中最成熟的 AOP 实现)的整合。 最终,通过测试驱动开发(test-driven-development,TDD)的软件开发方法,也是 Spring 团队所主张的,所以 Spring 对整合测试的支持也涵盖到了(沿袭单元测试的最佳实践)。Spring 团队也发现了 IoC 的正确使用,当然,这会让单元和集成测试更容易(setter 方法的存在和 类的适当的构造方法可以使得它们很容易的在测试时连接在一起,而不需要设立服务定位器 注册和诸如此类的方法)。这章专门的测试又往说服你。  第 4 章,IoC 容器  第 5 章,资源  第 6 章,验证,数据绑定和类型转换  第 7 章,Spring 表达式语言(SpEL)  第 8 章,使用 Spring 进行面向切面编程  第 9 章,Spring 的 AOP API  第 10 章,测试 第 4 章 IoC 容器 4.1 Spring IoC 容器和 bean 的介绍 本章涵盖了 Spring Framework 的控制反转容器(IoC)[参考 1.1 节的背景]原则的实现。 IoC 也被称为是依赖注入(DI)。这是一个对象定义它们依赖的过程,也就是说,它们使用的 其它对象,仅仅通过构造方法参数,工厂方法参数或在对象被创建后的实例上设置的属性, 亦或者是从工厂方法返回的参数。之后容器在它创建 bean 的时候注入那些依赖。这个过程 是根本上的反向,因此名称是控制反转(IoC), bean 本身控制实例化或直接地使用类的构 造来定位它的依赖,或者是如服务定位器模式的机制。 org.springframework.beans 和 org.springframework.context 包是 Spring Framework 的 IoC 容器的基础。BeanFactory 接口提供高级的配置机制,可以管理任意类 型的对象。ApplicationContext 是 BeanFactory 的子接口。它添加了和 Spring 的 AOP 特性很简便的整合;消息资源处理(用于国际化 i18n),事件发布;应用层特定的上下文, 比如用于 Web 应用程序的 WebApplicationContext。 总之,BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加 了更多企业级开发特定的功能。ApplicationContext 是 BeanFactory 完整的超集, 专门用于本章,来描述 Spring 的 IoC 容器。对于使用 BeanFactory 而不是 ApplicationContext 的更多信息,可以参考 4.15 节“BeanFactory”。 在 Spring 中,对象构成应用程序的骨感,它们是由 Spring 的 IoC 容器管理的,并被称为 bean。一个 bean 就是一个实例化并组装的对象,由 Spring 的 IoC 容器来管理。否则,bean 就是应用程序中众多对象之一。Bean 和它们之间的依赖,反射出由容器使用的配置元数据。 4.2 容器概述 org.springframework.context.ApplicationContext 接口代表了 Spring 的 IoC 容器,负责实例化,配置和装配上述的 bean。容器获得指示来实例化某对象,配置并装 配,这都是通过读取配置元数据实现的。配置元数据在 XML 中,Java 注解或 Java 代码中来 表示。它允许你表达编写应用程序的对象,还有对象间丰富的相互依存的关系。 ApplicationContext 接口的一些实现使用 Spring 开箱的支持。在独立的应用程序 中,通常是来创建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的实例。XML 是定义配置元数据的传统格式,你 可以指示容器使用 Java 的注解或是代码作为元数据的格式,提供少量的 XML 配置声明来开 启对这些额外的元数据格式的支持。 在很多应用场景中,明确的用户代码不需要实例化一个或者多个 Spring IoC 容器的实例。 比如,在 Web 应用场景中,在应用程序的 web.xml 中简单的八(左右)行样板 J2EE 描述 符 XML 文件通常就够了(参考 4.14.4 节“对 Web 应用程序方便的应用上下文实例化”)。如 果你正使用 SpringSource 的工具套件,Eclipse 支持的开发环境或者是 Spring ROO 这样的样板 配置,就可以容易地被创建,点几下鼠标或按键就可以了。 下图是 Spring 如何工作的高级别视图。你的应用程序类联合配置元数据,所以在 ApplicationContext 被创建和实例化后,就得到了一个完全配置的可执行系统或程序。 Spring 的 IoC 容器 4.2.1 配置元数据 正如上图所示,Spring 的 IoC 容器处理配置元数据的一种形式;这个配置元数据代表了 你作为应用开发人员是如何告诉 Spring 容器在你的应用程序中来实例化,配置并装配对象 的。 配置元数据传统上是以直观的 XML 格式提供的,这是本章的大部分内容使用它来传达 Spring IoC 容器关键概念和功能。 注意 基于 XML 的元数据并不是唯一的配置元数据格式。这种配置元数据真正写入时,Spring 的 IoC 容器本身和这种格式完全脱钩。 关于 Spring 容器使用元数据格式的信息,可以参考:  基于注解的配置(4.9 节):Spring 2.5 引入了对基于注解元数据的支持。  基于 Java 的配置(4.12 节):从 Spring 3.0 开始,很多由 Spring JavaConfig 项目提供的特 性称为 Spring Framework 的核心。因此你可以在应用程序外部来定义 bean,使用 Java 代码而不是 XML 文件。要使用这些新的特性,请参考@Configuration,@Bean, @Import 和@DependsOn 注解 Spring 配置典型的是最少由一个容器必须管理的 bean 定义构成。基于 XML 的配置元数 据展示了这些 bean 的配置是用在顶级元素中的元素完成的。 这些 bean 的定义对应构成应用程序中真实的对象。比如你定义的服务层的对象,数据 访问对象(Data Access Object,DAO),表示对象比如 Struts 的 Action 实例,基础设置对象 比如 Hibernate 的 SesstionFactories,JMS 的 Queues 等这些典型的例子。而不用在容 器中定义细粒度的领域模型对象,因为这通常是由 DAO 和业务逻辑负责创建并加载的领域 对象。但是你也可以使用 Spring 和 AspectJ 整合来配置创建在 IoC 容器控制之外的对象。参 考使用在 Spring 中使用 AspectJ 来对领域对象进行依赖注入(8.8.1 节)。 下面的示例展示了基本的基于 XML 的配置元数据的结构: id 属性是一个字符串,用来标识定义的独立的 bean。class 属性定义了 bean 的类型, 需要使用类的完全限定名。id 属性的值指的就是协作对象。指写作对象的 XML 在这个示例 中没有展示;参考依赖(4.4 节)来获取更多信息。 4.2.2 实例化容器 实例化 Spring 的 IoC 容器是很简单的。定位路径或所有路径提供给 ApplicationContext 的构造方法,实际上是表示资源的字符串,它就允许容器从各种外 部资源比如本地文件系统,Java 的 CLASSPATH 等来加载配置元数据 注意 在学习过 Spring 的 IoC 容器之后,你可能想更多了解 Spring 的 Resource 抽象,这在 第 5 章,资源中会描述,它提供了一个从定义 URI 语法的位置读取输入流的简便机制。特别 是,Resource 路径用来构建应用程序上下文,这会在 5.7 节“应用上下文和资源路径”中 来描述。 下面的示例展示了服务层代码(services.xml)的配置文件: ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); 下面的示例展示了数据访问对象的 daos.xml 文件: 在上面的示例中,服务层代码由类 PetStoreServiceImpl 和两个基于 iBatis(iBatis 现已更名为 MyBatis,译者注)对象/实体映射框架的数据访问对象 SqlMapAccountDao 和 SqlMapItemDao 构成。property 的 name 元素指的是 JavaBean 的属性名,而 ref 元 素指的是其它 bean 定义的名称。id 和 ref 元素之间的这种联系表示了两个协作对象的依赖 关系。要了解更多配置对象依赖的信息,可以参考依赖(4.4 节)。 4.2.2.1 处理基于 XML 的配置元数据 跨越多个 XML 文件中定义 bean 是很有用的。通常每个独立的 XML 配置文件代表了一 个逻辑层或架构中的一个模块。 你可以使用 ApplicationContext 的构造方法从所有的 XML 文件片段中来加载 bean。这个 构造方法可以接收多个 Resource 位置,这在之前的部分都已经看到了。另外,可以使用一 个或多个元素来从另外的一个或多个文件中加载 bean。比如: 在 这 个示 例 中, 外部 的 bean 通过三个文件来加载,分别是 services.xml, messageSource.xml 和themeSource.xml。所有的位置路径都是相对于该引用文件的, 所以 service.xml 必须在和引用文件相同路径中或 者 是 类 路 径 下 , 而 messageSource.xml 和 themeSource.xml 必须是在位于引用文件下一级的 resources 路径下。正如你看到的,前部的斜杠被忽略了,这是由于路径都是相对的,最 好就不用斜线。文件的内容会被引入,包括顶级的元素,根据 Spring 的 Schema 或 DTD,它必须是有效 bean 定义的 XML 文件。 注意 在父目录中使用相对路径“../”来引用文件,这是可能的,但是不推荐这么做。这么做 了会创建一个文件,它是当前应用程序之外的一个依赖。特别是,这种引用对于 “classpath:”的 URL(比如,“classpath:../service.xml”)是不推荐的,运行时的解析过 程会选择“最近的”类路径根目录并且会查看它的父目录。类路径配置的修改可能会导 致去选择一个不同的,不正确的目录。 你也可以使用资源位置的完全限定名来代替相对路径:比如,“file:C:/config/services.xml” 或“classpath:/config/services.xml”。这样的话,要注意你会耦合应用程序的配置到指定 的绝对路径。对于绝对路径,一般最好是保持一个间接的使用,比如通过占位符“${...}”, 这会基于运行时环境的 JVM 系统属性来解决。 4.2.3 使用容器 ApplicationContext 是能维护不同的 bean 和它们依赖注册的高级工厂接口。使用 T getBean(Stringname, Class requiredType)方法你可以获取 bean 的实例。 ApplicationContext 允许你读取 bean 并且访问它们,如下所示: 使用 getBean()方法来获取 bean 的实例。ApplicationContext 接口有一些其它 // 创建并配置bean ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); // 获取配置的实例 PetStoreServiceImpl service = context.getBean("petStore", PetStoreServiceImpl.class); // 使用配置的实例 List userList service.getUsernameList(); 方法来获取 bean,但最好应用程序代码不使用它们。事实上,应用程序代码应该没有 getBean()方法的调用,那么就没有对 Spring API 的依赖了。比如,Spring 和 Web 框架整 合时,提供了对各种 Web 框架类库的依赖注入,不如控制器和 JSF 管理的 bean。 4.3 Bean 概述 Spring 的 IoC 容器管理一个或多个 bean。这些 bean 通过提供给容器的配置元数据被创 建出来,比如,在 XML 中的定义的形式。 在容器本身,这些 bean 代表了 BeanDefinition 对象,它们包含(在其它信息中) 下列元数据:  打包的类限定名:就是这些 bean 的真正实现类。  bean 的行为配置元素,这表示了 bean 在容器中(范围,生命周期回调等等)应该是怎 样的行为。  对其它 bean 的引用,这是该 bean 工作所需要的;这些引用通常被称为合作者或依赖。  在新被创建的对象中的其它配置设置,比如,管理连接池的 bean 中使用的连接数或连 接池限制的大小。 元数据翻译成一组属性集合来构成每一个 bean 的定义。 表 4.1 bean 定义 属性 解释章节 class 4.3.2 节,“实例化 bean” name 4.3.1 节,“命名 bean” scope 4.5 节,“bean 的范围” constructor arguments 4.4.1 节,“依赖注入” properties 4.4.1 节,“依赖注入” autowiring mode 4.4.5 节,“装配合作者” lazy-initialization mode 4.4.4 节,“延迟初始化 bean” initialization method 4.6.1.1 节,“初始化回调” descruction method 4.6.1.2 节,“销毁回调” 此外,bean 的定义还包含如何创建特定 bean 的信息,ApplicationContext 的实现 类允许用户将容器外部创建的已有对象的注册。这可以通过 getBeanFactory()方法访问 ApplicationContext 的 BeanFactory 来完成, 它 会 返 回 BeanFactory 的实现类 DefaultListableBeanFactory 。 DefaultListableBeanFactory 通过 registerSingleton(..)和 registerBeanDefinition(..)方法来支持这种注册。 而典型的应用程序只和通过元数据定义的 bean 来工作。 4.3.1 命名 bean 每个 bean 有一个或者多个标识符。这些标识符必须在托管 bean 的容器内唯一。通常 一个 bean 只有一个标识符,但如果它需要多个的话,其它的可以被认为是别名。 在基于 XML 的配置元数据中,你可以使用 id 和/或 name 属性来指定 bean 的标识符。 id 属性允许你指定一个准确的 id。按照管理这些名称是字母和数字(‘myBean’,‘fooService’ 等),但是可能是特殊字符。如果你想为 bean 引入别名,你也可以在 name 属性中来指定, 通过逗号(,),分号(;)或空格来分隔。作为对历史的说明,在 Spring 3.1 版之前,id 属性 是 xsd:ID 类型,只限定为字符。在 3.1 版本中,现在是 xsd:string 类型了。要注意 bean 的 id 的唯一性还是被容器所强制的,但是对于 XML 处理器却不是。 当然你也可以不给 bean 提供 name 或 id。如果没有提供明确的 name 或 id,那么容器 会为这个 bean 生成一个唯一的名称。然而,如果你想通过名称来参考这个 bean,那么可以 使用 ref 元素或者是服务定位器(4.15.2 节)风格的查找,你必须提供一个名称。不提供 名称的动机是和使用内部 bean(4.4.2.3 节)或自动装备合作者(4.4.5 节)相关的。 4.3.1.1 在 bean 定义外面起别名 在 bean 定义的本身,你可以为 bean 提供多于一个的名称,使用一个由 id 属性或 name 属性中的任意名称数指定的名称组合。这些名称对于同一个 bean 来说都是相等的别名,在 一些情况下,这是很有用的,比如在应用程序中允许每个组件参考一个共同的依赖,可以使 用为组件本身指定的 bean 的名称。 然而,在 bean 定义时指定所有的别名是不够的。有事可能想在任意位置,为一个 bean 引入一个别名。这在大型系统中是很常见的例子,其中的配置信息是分在每个子系统中的, 每个子系统都有它自己的对象定义集合。在基于 XML 的配置元数据中,你可以使用 元素来完成这项工作。 在这个示例中,在相同容器中的名称为 fromName 的 bean,在使用过这个别名定义后, 也可以使用 toName 来指引。 比如,在子系统的配置元数据中,A 可能要通过名称‘subsystemA-dataSource’来指向 数据源。为子系统 B 的配置元数据可能要通过名称‘subsystemB-dataSource’来指向数据源。 当处理使用了这两个子系统的主程序时,主程序要通过名称‘myApp-dataSource’来指向数 据源。那么就需要三个名称指向同一个对象,那么就要按照下面的别名定义来进行添加 MyApp 的配置元数据: 现在每个组件和主程序都可以通过一个唯一的保证不冲突的名称来还有其它任意定义 (更有效地是创建命名空间)来参照数据源了,当然它们参照的是同一个 bean。 Bean 的命名约定 该约定是用于当命名 bean 时,对实例字段名称的标准 Java 约定。也就是说,bean 的名称以小写字母开始,后面是驼峰形式的。这样的命名可以是(没有引号) ‘accountManager’,‘ accountService’,‘ userDao’,‘ loginController’ 等。 一致的命名 bean 可以使你的配置信息易于阅读和理解,如果你使用 Spring 的 AOP, 当应用通知到一组相关名称的 bean 时,它会给你很大的帮助。 4.3.2 实例化 bean bean 的定义信息本质上是创建一个或多个对象的方子。当需要一个 bean 时,容器查找 这些方子来查找命名的bean,并且使用由 bean定义信息封装的配置元数据来创建(或获得) 一个真实的对象。 如果你使用基于 XML 的配置元数据,你为要被实例化的对象所指定的类型(或类)是 元素中 class 属性。这个 class 属性,是 BeanDefinition 实例内部的 class 属性,通常是强制要有的。(对于特例,可以参考 4.3.2.3 节,“使用实例的工厂方法来实例 化”和 4.7 节,“Bean 定义的继承”)你可以以两种方法之一来使用 class 属性:  典型的是,指定要被构造的 bean 类,容器本身直接通过反射调用它的构造方法来创建 bean,也就是和 Java 代码中使用 new 操作符是相同的。 指定包含 static 工厂方法的真实的类,会被调用来创建对象,在一些不太常见的情况下, 容器会调用类的 static 工厂方法来创建 bean。从调用 static 工厂方法返回的对象类型可能 是和另外一个类完全相同的类。 4.3.2.1 使用构造方法实例化 当你使用构造方法来创建 bean 时,所有普通的类的使用都和 Spring 兼容。也就是说, 开发中的 bean 不需要实现任何特定的接口或以特定的方式来编码。仅简单指定 bean 的类 就足够了。但基于你使用的是什么类型的 IoC,就可能需要一个默认(空的)构造方法。 Spring 的 IoC 容器可以虚拟地管理任意的你想让它管理的类;而不仅仅限于管理真正的 JavaBean。很多Spring 用户喜欢在容器中使用有默认(无参数)构造方法和在其后有适当setter 和 getter 方法的真正 JavaBean。你也可以在容器中使用很多异样的,非 bean 样式的类。比 如,需要使用遗留的连接池,但是它没有符合 JavaBean 的规范,Spring 也能照样管理它。 基于 XML 的配置元数据,你可以如下来定义 bean: 关于提供构造方法参数(如果需要)和在对象被构造后,设置对象实例属性机制的详情, 请参考 4.4.1 节依赖注入。 4.3.2.2 使用静态工厂方法来实例化 当使用静态工厂方法来定义 bean 的时候,可以使用 class 属性来指定包含 static 工厂 方法的类,而名为 factory-method 的属性来指定静态方法。你应该调用这个方法(还可 以有可选的参数)并返回一个实际的对象,随后将它视为是通过构造方法创建的一样。在遗 内部类名称 如果你想为 static 嵌入的类配置 bean,你需要使用内部类的二进制名称。 比如,如果在 com.example 包下有一个 Foo 类,而这个 Foo 类有一个 static 的内 部类 Bar,那么在定义 bean 时’class’属性的值就会是... com.example.Foo$Bar 请注意名称中$符号的使用,用来从外部类名中分隔内部类名。 留代码中,这样定义 bean 的使用方式之一是调用 static 工厂。 下面 bean 的定义指定了要通过调用工厂方法生成的 bean。这个定义没有指定返回对象 的类型(类),仅仅是包含工厂方法的类。在本例中,createInstance()方法必须是静 态方法。 关于提供(可选的)参数到工厂方法并在对象由工厂返回后设置对象实例属性的机制详 情,请参考 4.4.2 节深入依赖和配置。 4.3.2.3 使用实例工厂方法来实例化 和使用静态工厂方法(4.3.2.2 节)实例化相似,使用实例工厂方法实例化是要调用容器 中已有 bean 的一个非静态的方法来创建新的 bean。要使用这个机制,请把 class 属性留 空,但在 factory-bean 属性中指定当前(或父/祖先)容器中 bean 的名字,该 bean 要 包含被调用来创建对象的实例方法。使用 factory-method 方法来设置工厂方法的名称。 public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } } public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } } 一个工厂类也可以有多于一个工厂方法,比如下面这个: 这个方法展示了工厂 bean 本身可以通过依赖注入(DI)被管理和配置。请参考 4.4.2 节 深入依赖和配置。 注意 在 Spring 文档中,工厂 bean 指的是在 Spring 容器中配置的 bean,可以通过实例(4.3.2.3 节)或静态(4.3.2.2 节)工厂方法来创建对象。与此相反的是,FactoryBean(注意大小 写)指的是 Spring 特定的 FactoryBean(4.8.3 节) 4.4 依赖 典型的企业级应用程序不是由单独对象(或 Spring 中的 bean)构成的。尽管最简单的 应用程序有一些对象协同工作来表示终端用户所看到的连贯的应用。下一节会解释如何去为 完全实现的应用程序定义一组独立的 bean,其中对象间相互协作来达到目标。 4.4.1 依赖注入 依赖注入(DI)是对象定义它们依赖的过程,也就是说,要和它们协同工作其它对象, 仅仅可以通过构造方法参数,工厂方法参数,或者是在工厂方法返回的对象或被构造好后, public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } } 为对象实例设置的属性。容器当创建好 bean,随后就会注入那些依赖。这个过程从根本上 来说是反向的,因此命名为控制反转(IoC), bean 本身直接使用构造好的类或服务定位器 模式来控制实例或它的依赖的所在位置。 应用了 DI 原则,代码就干净多了,当为对象提供它们依赖的时候,解耦是很有效率的。 对象不再去检查它的依赖,也不需要知道位置或依赖的类。因此,类就很容易被测试,特别 是当依赖是接口或抽象基类的时候,这允许在单元测试中使用 stub 或 mock 的实现类。 DI 存在两种主要的方式,基于构造方法的依赖注入(4.4.1.1 节)和基于 setter 方法的依 赖注入(4.4.1.2 节)。 4.4.1.1 基于构造方法的依赖注入 基于构造方法的依赖注入是容器调用构造方法和一组参数完成的,每个都表示着一个依 赖。使用特定的参数来调用 static 工厂方法构造 bean 基本也是相同的,这种说法把给构 造方法的参数和给 static 工厂方法的参数相类似。下面的示例展示了一个仅使用构造方 法进行依赖注入的饿类。注意这个类没有什么特殊之处,就是一个 POJO 而且没有对容器特 定接口,基类或注解的依赖。 构造方法参数解析 构造方法参数解析匹配使用参数的类型。如果在 bean 的构造方法参数不存在潜在的歧 义,那么当 bean 被实例化的时候,定义的构造方法参数的顺序就是被提供到适当构造方法 参数的顺序。请看下面的类: 没有潜在的歧义存在,假设 Bar 和 Baz 类没有继承关系。因此下面的配置就可以使用 了,而且并不需要明确地在元素中去指定构造方法参数的索引和/ 或类型。 public class SimpleMovieLister { // SimpleMovieLister对MovieFinder有依赖 private MovieFinder movieFinder; // 这个构造方法使得Spring容器可以’注入’MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // 业务逻辑就可以’使用’注入的MovieFinder了,代码就省略了... } package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } } 当另外一个 bean 被引用时,类型是明确的,那么匹配就能成功(前面示例中也是这样 的)。当使用简单类型时,比如true,Spring 不能决定值的类型,所以 没有帮助是不能匹配的。看下面的示例: 构造方法参数类型匹配 在上面的情形下,如果你使用 type 属性明确地为构造方法参数指定类型的话,容器可 以进行匹配简单的类型。比如: 构造方法参数索引 使用 index 属性来指定明确的构造方法参数索引。比如: 此外,为了解决多个简单值的歧义,如果构造方法有两个相同类型的参数时,指定所以 可以解决歧义。注意索引是基于 0 开始的。 构造方法参数名称 在 Spring 3.0 中,你也可以使用构造方法参数名称来消除歧义: package examples; public class ExampleBean { // 计算最佳答案的年数 private int years; // 生命,宇宙,所有问题的答案 private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } } 要记住要使得这种方式可用,代码必须和内嵌的调试标识一起编译,那样 Spring 才可 以从构造方法中来查找参数。如果没有和调试标识(或不想)一起编译,那么可以使用 JDK 的注解@ConstructorProperties 来明确构造方法参数。那么示例代码就如下所示: 4.4.1.2 基于 setter 方法的依赖注入 基于setter 方法的依赖注入由容器在调用过无参数的构造方法或无参数的static工厂 方法来实例化 bean 之后,再调用 bean 的 setter 方法来完成的。 下面的示例展示了一个仅仅能使用纯 setter 方法进行依赖注入的类。这是一个常见的 Java 类,是一个没有依赖容器指定的接口,基类或注解的 POJO。 ApplicationContext对它管理的bean支持基于构造方法和 setter 方法的依赖注入。 也支持在一些依赖已经通过构造方法注入之后再进行 setter 方 法 注 入 。 以 BeanDefinition 形式来配置依赖,使用 PropertyEditor 实例将属性从一种形式格式 化到另一种。但很多 Spring 的用户不直接(编程时)使用这些类,而是使用 XML 文件来定 义,之后会在内部转换这些类的实例,需要加载整个 Spring IoC 容器的实例。 package examples; public class ExampleBean { // 忽略属性 @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } } public class SimpleMovieLister { // SimpleMovieLister对MovieFinder有依赖 private MovieFinder movieFinder; // setter方法可以让Spring容器来'注入'MovieFinder public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // 真正’使用’注入的MovieFinder的业务逻辑代码被省略了... } 4.4.1.3 解决依赖过程 容器按如下步骤来解决 bean 的依赖: 1. ApplicationContext 和描述了所有 bean 的配置元数据一起被创建并初始化。配 置元数据可以通过 XML,Java 代码或注解来指定。 2. 对于每一个 bean 来说,它的依赖被表述为属性,构造方法参数的形式,如果你使用 了静态工厂方法来代替构造方法,那么还会是静态工厂方法参数的形式。当 bean 被实际创 建时,这些依赖被提供给 bean。 3. 每个属性或构造方法参数就是一个要被设置的值或者是容器中其它 bean 的引用。 4. 每个属性或构造方法参数值会被转换特定格式的形式,去匹配属性或构造方法参数 的类型。默认情况下,Spring 可以转换给定的字符串格式的值到内建的类型,比如 int,long, String,boolean 等。 当容器被创建时,Spring 容器会来验证每个 bean 的配置,包括验证 bean 的引用属性是 否是一个合法的 bean。然而,bean 属性本身直到 bean 真正被创建出来后才被设置进去。 当容器被创建时,bean 的范围是单例时,会被设置成预实例(默认情况)来创建。范围在 4.5 节,“bean 的范围”部分来解释。否则,bean 就会当被请求的时候被创建。一个 bean 的创建潜在地会引起一系列 bean 被创建,因为 bean 的依赖和它依赖的依赖(等等)要不 创建和定义出来。 基于构造方法还是 setter 方法进行依赖注入? 因为你可以将二者混淆,那么对于基于构造方法或是 setter 方法的依赖注入,有一 个很好的规则就是为强制依赖使用构造方法参数,而对于可选参数使用 setter 方法。要 注意在 setter 方法上使用注解@Required(4.9.1 节),可用于 setter 方法所需的依赖。 Spring 团队通常主张使用 setter 方法注入,因为大量的构造方法参数会使程序变得非 常笨拙,特别是当属性为可选的时候。Setter 方法会让该类的对象今后适合于重新配置或 重新注入。通过 JMX Mbean(第 23 章)来管理就是一个令人关注的用例。 一些人纯粹赞成构造方法注入。提供所有对象的依赖意味着对象在完全初始化状态 时,通常要返回客户端(调用)代码。这个缺点会使得对象变得不适合去重新配置或重 新注入。 使用依赖注入的时候要特别注意一些类。当要选择处理没有源代码时的第三方类库 的时候。遗留的类可能没有暴露任何 setter 方法,而构造方法注入则是唯一可用的依赖 注入方式。 循环依赖 如果你使用主要的构造方法注入,就可能会引起不可解决的循环依赖情形。 比如,类 A 需要通过构造方法注入获得类 B 的实例,而类 B 也需要通过构造方法注 入获得类 A 的实例。如果把类 A 和类 B 进行相互注入,Spring 的 IoC 容器会在运行时检 测到这是循环引用的情况,并且抛出 BeanCurrentlyInCreationException 异常。 一个可行的方案是编辑一些要配置的类的源码,通过 setter 方法而不是构造方法进 行注入。而且,避免构造方法注入并仅仅使用 setter 方法注入。换句话说,尽管这不是 推荐做法,你可以通过 setter 方法注入来配置循环依赖。 不像典型的用例(没有循环依赖),在 bean A 和 bean B 之间的循环依赖强制一个 bean 被注入到另一个中会先于被完全初始化自己(就是经典的鸡和蛋的问题)。 通常情况下你可以信任 Spring 做出正确的事情。它会在容器加载时来检测配置问题, 比如引用了一个不存在的 bean 和循环依赖问题。当 bean 被实际创建出来后,Spring 设置属 性和解决依赖会尽量地晚些。这就意味着 Spring 容器已经加载正确了但晚些时候可能会生 成异常,比如当你请求一个创建时发生问题的对象或者是它的依赖发送问题。例如,bean 因为丢失或非法属性而抛出异常。这种潜在的一些配置问题的可见度延迟也就是为什么 ApplicationContext 的实现类默认情况都是预实例的单例 bean。在前期的时间和内存 消耗中,在 bean 真正需要前来创建这些 bean,当 ApplicationContext 被创建的时候, 你会发现这些配置问题,这还不晚。你也可以覆盖这个默认的行为,那么单例 bean 就会延 迟加载,而不是预实例的了。 如果没有循环依赖的存在,当一个或多个协作的 bean 被注入到一个独立的 bean 时, 每个协作的 bean 就会在被注入之前完全被配置好。这就意味着如果 bean A 对 bean B 有依 赖,那么 Spring 的 IoC 容器会完全配置 bean B 而优先于调用 bean A 中的 setter 方法。换句 话说,bean 被实例化了(而不是预实例的单例 bean),它的依赖才被注入,相关的生命周 期方法(比如配置初始化方法(4.6.1.1 节)或 InitializingBean 的回调方法(4.6.1.1 节))才 被调用。 4.4.1.4 依赖注入示例 下面的示例使用了基于 XML 的配置元数据来进行基于 setter 方法的依赖注入。Spring XML 配置文件的一小部分来指定几个 bean 的定义: public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } } 上面的示例中,setter 方法被声明为匹配 XML 文件中指定的属性。下面的示例使用基于 构造方法的依赖注入: 在 bean 中指定的构造方法参数会被用于 ExampleBean 的构造方法参数。 现在考虑一下这个示例的变化情况,要代替使用构造方法,Spring 被告知调用 static 工厂方法并返回一个对象的实例: public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } } static 工厂方法的参数通过元素来提供,这和构造方法已经 被实际调用是完全一致的。由工厂方法返回的类的类型不需要和包含 static 工厂方法类 的类型相同,尽管在本例中是相同的。实例(非静态)工厂方法可以被用于本质上相同的方 式(除了 factory-bean 属性的使用,来代替 class 属性),所以这里不讨论它们的细节。 4.4.2 深入依赖和配置 正如之前章节中所提到的,你可以定义 bean 的属性和构造方法参数作为其它被管理 bean(协作者)的引用,或者作为内联值的定义。出于这个目的,Spring 的基于 XML 的配 置元数据支持使用元素的子元素类型。 4.4.2.1 直接值(原生类型,String,等) 元素的 value 属性指定了属性或构造方法参数,这是人们可以阅读的 字符串的表达。正如之前提到过的(6.4.2 节),JavaBean 的 PropertyEditor 可以用来转 换这些字符串值从 String 到属性或参数的真实类型。 对于更简洁的 XML 配置,下面的示例使用了 p-命名空间(4.4.2.6 节)。 public class ExampleBean { // 私有的构造方法 private ExampleBean(...) { ... } // 静态工厂方法; 这个方法的参数可以被认为是要返回bean的依赖, // 而不管那些参数是如何被使用的。 public static ExampleBean createInstance (AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // 其它的操作... return eb; } } 上面的 XML 非常简洁;然而,错误之处会在运行时被发现而不是设计的时候,除非在 你创建 bean 的时候,使用如 IntelliJ IDEA 或 SpringSource Tool Suite(STS,SpringSource 组织 开发的工具套件)这样的 IDE 来支持自动地属性补全。这样的 IDE 帮助是强烈建议使用的。 你也可以这样来配置 java.util.Properties 实例: Spring 容器会使用 JavaBean 的 PropertyEditor 机制来转换元素中的文本 到 java.util.Properties 实例。这是一个很好的捷径,也是 Spring 团队少有喜欢使用 嵌套的元素而不是 value 属性方式的地方之一。 idref 元素 idref 元素是一种简单防错的形式来传递容器中另外一个 bean 的 id(字符串值而不是 引用)到元素中。 jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb 上面定义 bean 的代码片段是和下面的片段完全等价(在运行时): 第一种形式比第二种形式可取,因为使用 idref 标签允许容器在部署时来验证被引用 的,命名的 bean 是否真的存在。第二种形式下,没有对传递给 client bean 的 targetName 属性执行验证。错误仅仅是在 client bean 被真正实例化的时候才会被发现(和很多可能 致命的结果)。如果 client bean 是 prototype(4.5 节)类型的 bean,这个错误和结果异常 可能仅仅在容器被部署很长一段时间后才会被发现。 此外,如果被引用的 bean 在同一个 XML 单元中,bean 的名称就是 bean 的 id,那么你 还可以使用 local 属性,这允许 XML 解析器本身在 XML 文档解析的时候,及早地来验证 bean 的 id。 元素带来的一个相同之处(最少是 Spring 2.0 版 本 以 前 的 ) 是 值 在 ProxyFactoryBean 定义的 AOP 拦截器的配置中。当你指定拦截器名称来防止拼错拦截 器的 id 时,可以使用元素。 4.4.2.2 引用其它 bean(协作者) ref 元素是定义元素中最后的一个。在这里 你可以为 bean 设置指定属性的值,来引用被容器管理的另外一个 bean(协作者)。被引用 的 bean 就是要设置属性的这个 bean 的一个依赖,并且是初始化的作为属性设置之前的点 播。(如果协作者是一个单例的 bean,它可能已经被容器初始化过了。)所有引用最终都会 被引用到一个对象中。范围和验证基于通过 bean,local 或 parent 属性指定 id/name 的其它对象。 通过标签的 bean 属性来指定目标 bean 是最常用的方式,并允许创建引用到相 同容器或父容器的任意 bean 中,而不管它们是不是在同一个 XML 文件中。bean 属性的值 可能和目标 bean 的 id 属性相同,或者是目标 bean 的 name 属性值之一。 通过 local 属性来指定目标 bean 是利用了 XML 解析器的能力,来验证 XML 相同文件 内的 id 引用。local 属性的值必须和目标 bean 的 id 属性一致。如果没有在相同的文件内 发现匹配的元素,那么 XML 解析器会报告问题。因此,如果目标 bean 在同一个 XML 文件 中的话,使用 local 这种形式是最佳选择(为了更早地知道错误)。 通过 parent 属性来指定目标 bean 会为 bean 创建引用,它是在当前容器的父容器中 的。parent 属性的值可能和目标 bean 的 id 属性相同,或者是目标 bean 的 name 属性值 之一,而且目标 bean 必须在当前容器的父容器中。当有一个容器继承关系,可以使用这个 bean 的引用,或在你想使用和父 bean 有相同名称的代理包装一个父容器中已有 bean 时。 4.4.2.3 内部 bean 在元素内部的元素的定义被称为 内部 bean。 内部 bean 的定义不需要定义 id 或 name;容器忽略这些值。也会忽略 scope 标识。内 部 bean 通常是匿名的,而且范围通常是 prototype(4.5.2 节)的。注入内部 bean 到协作 bean 是不可能的,只能到包围它的 bean 中。 4.4.2.4 集合 在元素中,你可以分别设置 Java 的 Collection 类型 List,Set,Map 和 Properties 的属性和参数。 class="org.springframework.aop.framework.ProxyFactoryBean"> Map 类型的 key 或 value 的值,或 set 的值,也可以是下列元素之一: 集合合并 在 Spring 2.0 中,容器支持集合的合并。应用开发人员可以定义父样式的元素,子样式的 元素继承或重写来自父集合的值。也就是说,子集合的值是合并元素父与子集合中的元素, 和子集合元素覆盖父集合中指定值的结果。 本节关于导论父子 bean 合并的机制。如果读者不熟悉父和子 bean 的定义,可能要先去 阅读一下相关章节(4.7 节)。 下面的示例说明了集合合并: administrator@example.org support@example.org development@example.org a list element followed by a reference just some string bean | ref | idref | list | set | map | props | value | null 注意在 child bean 中的 adminEmails 属性下的元素中的 merge=true 属性的使用。当 child bean 被容器处理并实例化时,结果实例中有一个 adminEmails Properties 的 集 合 , 包 含 了 合 并 子 bean 的 adminEmails 集合和父 bean 的 adminEmails 集合。 子 bean 的 Properties 集合的值设置继承了从父元素而来的所有属性,子 bean 中 support 的值覆盖了父 bean 中的值。 这种合并行为的应用和集合类型很相似。在元素特 定的情形下,和 List 集合类型相关的语义,也就是说,ordered 集合值的概念,是要维 护的;父值优先于所有子 list 的值。在 Map,Set 和 Properties 集合类型的情况下,没 有顺序的存在。因此对于容器内部使用的,和 Map,Set 和 Properties 实现类型相关的 集合类型没有排序语义的作用。 集合合并的限制 不能合并不同类型的集合(比如 Map 和 List),如果你要尝试这么去做,那么就会抛 出 Exception。merge 属性必须在低级的,继承的,子 bean 中来指定;在父集合中指定 merge 属性是冗余的,也不会看到想要的合并结果。合并特性仅在 Spring 2.0 和更高版本中 可用。 强类型集合(Java 5 以上版本) 在 Java 5 或更高版本中,你可以使用强类型集合(使用泛型)。也就是说,可以声明一 个 Collection 类型,它可以仅仅包含 String 元素(作为示例)。如果你使用 Spring 来 administrator@example.com support@example.com sales@example.com support@example.co.uk administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk 对强类型的 Collection 依赖注入到 bean 中,你可以利用 Spring 的类型转换来支持这样 强类型 Collection 实例的元素,在被加到 Collection 之前,可以转换成合适的类型。 当foo bean的accounts属性准备好注入时,关于强类型元素Map 类型的泛型信息就通过反射机制准备好了。因此 Spring 的类型转换工具识别到各种元素的 值作为 Float 类型,字符串值 9.99,2.75 和 3.99 被转换成实际的 Float 类型。 4.4.2.5 null 和空字符串 Spring 将属性的空参数当作空 String。下面基于 XML 的配置元数据片段设置了电子邮 件属性为空 String 值("")。 上面的例子和下面的 Java 代码是一样的:exampleBean.setEmail("")。 元素控制 null 值。比如: 上面的配置和下面的 Java 代码一致:exampleBean.setEmail(null)。 public class Foo { private Map accounts; public void setAccounts(Map accounts) { this.accounts = accounts; } } 4.4.2.6 使用 p-命名空间的 XML 快捷方式 p-命名空间可以使你使用 bean 的元素属性,而不是内嵌的元素,来描述属 性的值和/或协作的 bean。 Spring 2.0 和后续版本支持使用命名空间(附录 C)的可扩展的配置格式,这是基于 XML 的 Schema 定义的。本章中讨论的 bean 的配置格式是定义在 XML 的 Schema 下的。然而, p-命名空间则不是定义在 XSD 文件下的,仅仅存在于 Spring 的核心中。 下面的示例展示了两个 XML 片段,解析得到相同的结果:第一个使用了标准的 XML 格 式,第二个使用了 p-命名空间。 示例展示了 p-命名空间下的属性,在 bean 的定义中称为 email。这就告诉 Spring 来包 含属性声明。正如前面提到的,p-命名空间没有 schema 定义,所以你可以设置属性的名称 和 bean 中属性的名称一样。 下面的示例包含了两个 bean 的定义,两者都有对其它 bean 的引用: 正如你所看到的一样,这个例子包含着不仅仅使用了 p-命名空间的属性值,也使用了 特殊格式来声明属性的引用。在第一个 bean 中 使用了来创建 john bean 对 jane bean 的引用关系,第二个 bean 中,使用了 p:spouse-ref="jane"作为属性来做相同的事情。本例中,spouse 是属性名,而-ref 部分表明了这不是一个直接值而是一个对其它 bean 的引用。 注意 P-命名空间没有标准 XML 格式那么灵活。比如,声明属性引用的格式和以 Ref 结尾的 属性相冲突,而标准的 XML 格式则不会。我们建议谨慎选择所用的方法并和开发团队成员 充分地交流,避免产生同时使用这三种方法的 XML 文档。 4.4.2.7 使用 c-命名空间的 XML 快捷方式 和 4.4.2.6 节,“使用 p-命名空间的 XML 快捷方式”相似,c-命名空间是在 Spring 3.1 中 被引入的,允许使用内联属性来配置构造方法参数而不是使用 constructor-arg 元素。 我们来看 4.4.1.1 节,“基于构造方法的依赖注入”中的示例,现在使用 c 命名空间: c:命名空间使用了和 p:(以-ref 结尾的 bean 引用)相同的转换机制通过它们的名称 来设置构造方法参数。这样一来,即便没有在 XSD schema(但是存在于 Spring 核心的内部) 中定义,它还是需要声明出来。 在极少数的情况下,构造方法参数名称是不可用的(通常如果字节码在编译时没有调试 信息),我们可以使用参数索引: <-- '传统的'声明方式 --> <-- 'c-命名空间'声明方式 --> <-- 'c-命名空间'索引声明 --> 注意 因为 XML 的语法,索引符号需要在其头部使用_作为 XML 属性名称,因为 XML 中属性 是不能以数字开头的(尽管一些 IDE 允许这样做)。 在实际运用中,构造方法解析机制(4.4.1.1 节)在匹配参数时是非常有效率的,所以除 非真有需要,我们建议在配置中使用名称符号。 4.4.2.8 复合属性名称 在设置 bean 的属性时,只要路径中的所有组件,除了最后一个属性名称是非 null 的, 可以使用复合或者嵌套的属性名称。参考下面的 bean 定义。 foo bean 有一个 fred 属性,它还有一个 bob 属性,而它仍有一个 sammy 属性,而最 终的 sammy 属性被设置成数值 123。为了让这样的配置可用,foo 的属性 fred,fred 的 属性 bob 在 bean 被构造好后必须不能为 null,否则会抛出 NullPointerException 异常。 4.4.3 使用 depends-on 如果一个 bean 是另外一个 bean 的依赖,通常表明了它会被设置成另外一个的属性。 典型的情况是在基于 XML 的配置元数据中使用(4.4.2.2 节)元素来完成。然而,有 时在两个 bean 之间的依赖并不是直接的;比如,类中的静态初始化器需要触发,就像数据 库驱动程序的注册。depends-on 属性可以明确地强制一个或多个 bean 在使用这个元素的 bean 被初始化之前被初始化。下面的示例使用了 depends-on 属性来表示一个独立 bean 的依赖: 为了表示对多个 bean 的依赖,提供一个 bean 名称的列表作为 depends-on 属性的值 即可,要用逗号,空白或分号分隔开,它们都是有效的分隔符: 注意 在 bean 定义中的 depends-on 属性可以指定初始化时的依赖,也可以是仅仅是单例 (4.5.1 节)bean,对应销毁时的依赖。和给定 bean 定义了 depends-on 关系的依赖 bean 首先被销毁,先于给定的 bean 本身。因此 depends-on 也能控制关闭顺序。 4.4.4 延迟初始化 bean 默认情况下,ApplicationContext 的实现类积极地创建并配置所有单例的 bean (4.5.1 节),作为初始化过程的一部分。通常来说,这种预实例化是非常可取的,因为在配 置或周边环境中的错误可以直接被发现,而不是几小时或几天后去发现。当这种行为不可用 时,你可以阻止单例 bean 的预实例化,在 bean 定义中使用延迟初始化来标记一下就可以 了。延迟初始化 bean 告诉 IoC 容器在该 bean 第一次被请求时再来实例化,而不是在启动时 实例化。 在 XML 中,这个行为可以通过元素的 lazy-init 属性来控制;比如: 当前者配置被 ApplicationContext 处理时,命名为 lazy 的 bean 并不会在 ApplicationContext 启动时被预实例化,而 not.lazy bean 会被先预实例化。 而到延迟初始化bean是一个单例 bean的依赖时,且这个单例 bean不是延迟初始化的, 那么 ApplicationContext 也会在启动时来创建延迟初始化的 bean,因为它必须满足单 例bean的依赖。延迟初始化的 bean被注入到单例 bean中的时候它就不是延迟初始化的了。 你也可以在容器级别来控制延迟初始化,在 元素上使用 default-lazy-init 属性;比如: 4.4.5 自动装配协作者 Spring 容器可以自动装配协作 bean 之间的关系。你可以允许 Spring 自动为你的 bean 来 解析协作者(其它 bean),通过检查 ApplicationContext 的内容即可。自动装配有下 列优势:  自动装配可以显著减少指定属性或构造方法参数的需要。(如 bean 模板的机制,这将在 本章的 4.7 节来讨论,在这方面是有价值的。)  自动装配可以更新配置对象的演变。比如,如果你需要给一个类添加依赖,那个依赖可 以自动被填入而不需要修改配置。因此,自动装配在开发期间是非常有用的,当代码库 变得更稳定时切换到明确的装配也没有否定的选择。 当使用基于 XML 的配置元数据(4.4.1 节)时,你可以为 bean 的定义指定自动装配模 式,在元素上设置 autowire 属性即可。自动装配功能有五种模式。你可以为每 个 bean 来指定自动装配,因此可以选择为哪一个来指定自动装配。 表 4.2 自动装配模式 模式 解释 no (默认情况)没有自动装配。Bean 的引用必须通过 ref 元素来定义。对于 大型的部署,修改默认设置是不推荐的,因为明确地指定协作者会给予更多 的控制和清晰。某种程度上来说,它勾勒出了系统的结构。 byName 通过属性名称来自动装配。Spring 以相同名称来查找需要被自动装配的 bean。 比如,如果 bean 被设置成由名称来自动装配,并含有一个 master 属性(也 就说,有 setMaster(..)方法),Spring 会查找名为 master 的 bean 定义,并且 用它来设置属性。 byType 如果 bean 的属性类型在容器中存在的话,就允许属性被自动装配。如果存 在多于一个,就会抛出致命的异常,这就说明了对那个 bean 不能使用 byType 进行自动装配。如果没有匹配的 bean 存在,就不会有任何效果;属性不会 被设置。 constructor 和 byType 类似,但是是应用于构造方法参数的。如果在容器中没有确定的构 造方法参数类型的 bean 的存在,就会发生致命的错误。 使用 byType 或 constructor 自动装配模式,你可以装配数组和集合类型。这种情况下所 有在容器内匹配期望类型的自动装配候选者会被用来满足依赖。如果期望的键类型是 String,你可以自动装配强类型的 Map。自动装配的 Map 值会包括匹配期望类型的实例, 而且 Map 的键会包含对应 bean 的名称。 你可以联合自动装配行为和依赖检查,这会在自动装配完成之后执行。 4.4.5.1 自动装配的限制和缺点 当在项目中一直使用时,自动装配是非常不错的。如果自动装配通常是不使用的,它就 可能会迷惑开发人员来使用它去装配仅仅一两个 bean。 考虑一下自动装配的限制和缺点:  在 property 和 constructor-arg 中设置的明确依赖通常覆盖自动装配。不能自动 装配所谓的简单属性,比如原生类型,String 和 Class(还有这样简单属性的数字)。这 是由于设计的限制。  自动装配没有明确装配那么精确,尽管,在上面的表格中已经说明了,Spring 很小心地 避免在有歧义的情况下去猜测,但也可能会有意想不到的结果,在 Spring 管理的对象 中间的关系可能就不会再清晰地说明了。  装配信息可能对从 Spring 容器中来生成文档的工具来说不可用。  在容器中的多个 bean 定义可能通过 setter 方法或构造方法参数匹配指定的类型进行自 动装配。对于数组,集合或 Map,这不一定是一个问题。而对期望简单值的依赖,这 种歧义不能随意解决。如果没有唯一的 bean 可用,就会抛出异常。 在后面一种情况中,你有几种选择:  放弃自动装配而使用明确的装配。  避免设置它的 autowire-candidate 属性为 false 来自动装配 bean,这会在下一 节中来解释。  设置元素的 primary 属性为 true 来指定单独的 bean 作为主要的候选者。  如果你使用 Java 5 或更高版本,使用基于注解的配置实现更细粒度的控制,这会在 4.9 节,“基于注解的容器配置”中来解释。 4.4.5.2 从自动装配中排除 bean 在每个 bean 的基础上,你可以从自动装配中来排除 bean。在 Spring 的 XML 格式配置 中,设置元素的 autowire-candidate 属性为 false;容器会把指定的 bean 对自动装配不可用(包含注解风格的配置,比如@Autowired(4.9.2 节))。 你也可以基于 bean 名称的模式匹配来限制自动装配候选者。顶级的元素中 的 default-autowire-candidates 属性接受一个或多个模式。比如,为了限制自动装 配候选者到任意名称以 Repository 结尾 bean 的状态,提供*Repository 值。要提供多个模式, 把它们定义在以逗号分隔的列表中。Bean 定义中 autowire-candidate 属性的 true 或 false 明确的值通常是优先的,而且对于这些 bean 来说,模式匹配规则是不适用的。 这些技术对那些永远不想通过自动装配被注入到其它 bean 中的 bean 来说是很有用的。 这并不意味着未包含的 bean 不能使用自动装配来配置。相反,bean 本身不是自动装配其它 bean 的候选者。 4.4.6 方法注入 在很多应用场景中,很多容器中的 bean 是单例(4.5.1 节)的。当一个单例的 bean 需 要和其它单例的 bean 协作时,或者一个非单例的 bean 需要和其它非单例的 bean 协作时, 典型地做法是通过定义一个 bean 作为另外一个的属性来控制依赖。当 bean 的生命周期不 同时问题就产生了。假设单例 bean A 需要使用非单例(prototype,原型)bean B,或许在 A 的每个方法调用上。容器仅仅创建单例 bean A 一次,因此仅仅有一次机会去设置属性。容 器不能每次为 bean A 提供所需的 bean B 新的实例。 解决方法就是放弃一些控制反转。你可通过实现 ApplicationContextAware 接口 以让 bean A 去感知容器(4.6.2 节),而且在每次 bean A 需要 bean B 的实例时,可以让容器 调用 getBean(“B”)(4.2.3 节)去获取(一个新的)bean B。下面的代码就是这种方式的例子: // 使用了有状态的命令风格的类来执行一些处理 package fiona.apple; // 导入Spring的API import org.springframework.beans.BeansException; import org.springframework.context.Applicationcontext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process(Map commandState) { // 抓取相应命令的新实例 Command command = createCommand(); // 在命令实例上(希望标记新的)设置状态 command.setState(commandState); return command.execute(); } protected Command createCommand() { // 注意Spring API的依赖! return this.applicationContext.getBean("command", Command.class); } 前面的做法是不可取的,因为这些业务代码感知并耦合到了 Spring 框架中。方法注入, 这种 Spring IoC 容器中的有点先进的功能,允许以一种干净的方式来处理这个用例。 4.4.6.1 查找方法注入 查找方法注入是容器在其管理的 bean 上来覆盖方法的能力,来返回容器中其它命名 bean 的查找结果。查找,典型的情况是涉及原型 bean,这是在之前章节中描述的情景。Spring Framework 从 CGLIB 类库中,通过使用字节码生成机制实现了这种方法注入,来动态地生成 覆盖方法的子类。 注意 要让这些动态子类起作用,在类路径下必须有 CGLIB 的 jar 文件。这些 Spring 容器中的 子类不能是 final 类型的,要被覆盖的方法也不能是 final 类型的。而且,测试有 abstract 方法的类要求你自己去编写类的子类并提供 abstract 方法的实现。最后,是 方法注入目标的对象还不能被序列化。 看一下之前代码片段中的 CommandManager 类,你会看到 Spring 容器会动态地覆盖 createCommand()方法的实现。CommandManager 类不会有任何 Spring 的依赖,在重新设计 的例子中你会看到: 在客户端类中包含要被注入(本例中的 CommandManager)的方法,要被注入的方法 需要从下面的示例中编写一个签名: public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } 你可以在博客文章中来阅读更多关于方法注入的动机。 package fiona.apple; // 没有Spring API的导入! public abstract class CommandManager { public Object process(Object commandState) { // 抓取一个心得合适的Command接口的实例 Command command = createCommand(); //在命令实例上(希望标记新的)设置状态 command.setState(commandState); return command.execute(); } // 好的... 但是这个方法的实现在哪儿? protected abstract Command createCommand(); } [abstract] theMethodName(no-arguments); 如果方法是 abstract 的,动态生成的子类就实现这个方法。否则,动态生成的子类覆盖 定义在源类中的确定方法。比如: 当需要 command bean 的新的实例时,标识为 commandManager 的 bean 调用它自己的 方法 createCommand()。部署 command bean 为原型的可必须要小心,那确实就是需要 的才行。如果被部署为单例的(4.5.1 节),那么每次会返回相同的 command bean 的实例。 提示 感兴趣的读者可能会发现要使用 ServiceLocatorFactoryBean (在 org.springframework.beans.factory.config 包下)。在 ServiceLocatorFactoryBean 中的使用的方法和其它工具类是相似的,ObjectFactoryCreatingFactoryBean,但 是它允许你去指定你自己的查找接口而不是 Spring 特定的查找接口。对这些类查询一下 JavaDoc 文档,还有博客文章来获取 ServiceLocatorFactoryBean 的额外的信息。 4.4.6.2 任意方法的替代 在方法注入中,用处不如查找方法注入大的一种形式,就是使用另外一个方法实现来替 换被容器管理的 bean 中的任意方法的能力。用户可以安全地跳过本节的其它部分,直到你 真正需要这些功能的时候来回过头来看。 使用基于 XML 的配置元数据,对于部署的 bean,你可以使用 replaced-method 元 素来使用另外一个方法替换已有的方法实现。看看下面这个类,有一个我们想要去覆盖的 computeValue 方法: 实现了 org.springframework.beans.factory.support.MethodReplacer 接口的类提供新的方法定义。 public class MyValueCalculator { public String computeValue(String input) { // 真正的代码... } // 其它方法... } 部署源类的 bean 定义和指定的方法覆盖可以是这样的: 你可以在元素中使用一个或多个包含的元素来 指示被覆盖方法的方法签名。如果方法是被重载的并在类中有很多种形式的存在,那么参数 的签名是必须的。为了简便,字符串类型的参数可能是类型全名的一个子串。比如,下面所 有的都匹配 java.lang.String: 因为用参数的数量用来区分每一个可能的选择通常是足够的,这种快捷方式可以节省大 量的输入,允许你去输入匹配参数类型的最短的字符串。 4.5 Bean 的范围 当创建 bean 时,也就创建了对通过 bean 定义创建的类真正实例的配方。bean 定义的 配方这个概念是很重要的,因为它就意味着,你可以通过配方来创建一个类的多个对象实例。 你不仅可以控制从特定 bean 定义创建出的对象的各个依赖和要配置到对象中的值,也 可以控制对象的范围。这个方法非常强大并且很灵活,你可以选择通过配置创建的对象的范 围,而不必去处理 Java 类级别对象的范围。Bean 可以被定义部署成一种范围:开箱,Spring Framework 支持五种范围,里面的三种仅仅通过感知 web 的 ApplicationContext 可用。 下列的范围支持开箱。你也可以创建自定义范围(4.5.5 节)。 /** 也就是要在MyValueCalculator中实现用来覆盖已有的方法 computeValue(String) */ public class ReplacementComputeValue implements MethodReplacer { public Object reimplement(Object o, Method m, Object[] args) throws Throwable { // 获取输入值,并处理它,返回一个计算的结果 String input = (String) args[0]; ... return ...; } } String java.lang.String String Str 表 4.3 Bean 的范围 范围 描述 单例(4.5.1 节) (默认的)为每一个Spring 的IoC容器定义一个 bean为独立对象的实例。 原型(4.5.2 节) 定义可以有任意个对象实例的 bean。 请求(4.5.4.2 节) 定义生命周期为一个独立 HTTP 请求的 bean;也就是说,每一个 HTTP 请求有一个 bean 的独立的实例而不是一个独立的 bean。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。 会话(4.5.4.3 节) 定义一个生命周期为一个 HTTP Session 的独立的 bean。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。 全局会话(4.5.4.4 节) 定义一个生命周期为一个全局的 HTTP Session 的独立的 bean。典型地 是仅仅 使用 portlet 上下文时可用。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。 线程范围的 bean 在 Spring 3.0 当中,线程范围是可用的,但是它默认是不注册的。要获取更多信息,请 参考 SimpleThreadScope 的 JavaDoc 文档。对于如何注册这个范围或其它自定义范围的做法, 请参考 4.5.5.2 节,“使用自定义范围”。 4.5.1 单例范围 仅仅共享被管理的bean的一个实例,所有使用一个或多个id来匹配 bean的结果对bean 请求,由 Spring 容器返回指定 bean 的实例。 另外一种方式,当你定义一个范围是单例的 bean 后,Spring 的 IoC 容器通过 bean 的定 义创建了这个对象的一个实例。这个独立的实例存储在单例 bean 的缓存中,所有对这个命 名 bean 的后续的请求和引用都返回缓存的对象。 Spring 中对单例 bean 的概念和四人帮(Gang of Four,GoF)设计模式书中定义的单例 模式是不同的。GoF 的单例硬编码了对象的范围,也就是特定的类仅仅能有一个实例被每一 个 ClassLoader 来创建。Spring 中单例 bean 的范围,是对每一个容器和 bean 来说的。这 就意味着在独立的 Spring 容器中,对一个特定的类创建了一个 bean,那么 Spring 容器通过 bean 的定义仅仅创建这个类的一个实例。在 Spring 中,单例范围是默认的范围。要在 XML 中定义单例的 bean,可以按照如下示例来编写: 4.5.2 原型范围 非单例,原型范围的 bean 就是当每次对指定 bean 进行请求时,一个新的 bean 的实例 就会创建。也就是说,bean 被注入到其它 bean 或是在容器中通过 getBean()方法来请求时就 会创建新的 bean 实例。作为一项规则,对所有有状态的 bean 使用原型范围,而对于无状 态的 bean 使用单例范围。 下图讲述了 Spring 的原型范围。数据访问对象(DAO)通常不是配置成原型的,因为典 型的 DAO 不会持有任何对话状态;仅仅是对作者简单对单例图的重用。 下面的示例在 XML 中定义了原型范围的 bean: 和其它范围相比,Spring 不管理原型 bean 的完整生命周期;容器实例化,配置并装配 原型对象,并把他给客户端,对于原型 bean 的实例来说,就没有进一步的跟踪记录了。因 此,尽管初始化生命周期回调方法可以对所有对象调用而不管范围,那么在原型范围中,配 置销毁生命周期回调是不能被调用的。客户端代码必须清理原型范围的对象并且释放原型 bean 持有的系统资源。要让 Spring 容器来释放原型范围 bean 持有的资源,可以使用自定义 bean 后处理器(4.8.1 节),它持有需要被清理的 bean 的引用。 在某些方面,关于原型范围 bean 的 Spring 容器的角色就是对 Java new 操作符的替代。 所有之后生命周期的管理必须由客户端来处理。(关于 Spring 容器中 bean 的生命周期的详 细内容,可以参考 4.6.1 节,“生命周期回调”) 4.5.3 单例 bean 和原型 bean 依赖 当你使用单例范围的 bean 和其依赖是原型范围的 bean 时,要当心依赖实在实例化时 被解析的。因此,如果依赖注入了原型范围的 bean 到单例范围的 bean 中,新的原型 bean 就被实例化并且依赖注入到单例 bean 中。原型实例是唯一的实例并不断提供给单例范围的 bean。 假设你想单例范围的 bean 在运行时可以重复获得新的原型范围 bean 的实例。那么就 不能将原型范围的 bean 注入到单例 bean 中,因为这个注入仅仅发生一次,就是在 Spring 容器实例化单例 bean 并解析和注入它的依赖时。如果你在运行时需要原型 bean 新的实例 而不是仅仅一次,请参考 4.4.6 节,“方法注入”。 4.5.4 请求,会话和全局会话范围 request,session 和 global session 范围仅仅当你使用感知 Web 的 Spring ApplicationContext 实现类可用(比如 XmlWebApplicationContext)。如果你在 常规的 Spring IoC 容器中使用如 ClassPathXmlApplicationContext 这些范围,那么 就会因为一个未知的 bean 的范围而得到 IllegalStateException 异常。 4.5.4.1 初始化 Web 配置 要支持 request,session 和 global session 级别(Web 范围的 bean)范围的 bean,一些细微的初始化配置就需要在定义 bean 之前来做。(这些初始化的设置对标准的 范围,单例和原型,是不需要的。) 如果来完成这些初始化设置是基于你使用的特定 Servlet 容器的。 如果使用 Spring 的 Web MVC 来访问这些范围的 bean,实际上,是由 Spring 的 DispatcherServlet 或 DispatcherPortlet 来处理请求的,那就没有特殊的设置了: DispatcherServlet 和 DispatcherPortlet 已经可以访问所有相关的状态。 如果你使用支持 Servlet 2.4 规范以上的 Web 容器,在 Spring 的 DispatcherServlet(比如, 当使用 JSF 或 Struts 时)之外来处理请求,那么就需要添加 javax.servlet.ServletRequestListener 到 Web 应用的 web.xml 文件的声明中: ... org.springframework.web.context.request.RequestContextLi stener ... 如果你使用老版的 Web 容器(Serlvet 2.3 ), 那 么 就 使 用 提 供 的 javax.servlet.Filter 实 现 类 。 如 果 你 想 在 Servlet 2.3 容 器 中 , 在 Spring 的 DispatcherServlet 外部的请求中访问 Web 范围的 bean,下面的 XML 配置代码片段就必须包 含在 Web 应用程序的 web.xml 文件中。(过滤器映射基于所处的 Web 应用程序配置,所以 你必须以适当的形式来修改它。) DispatcherServlet,RequestContextListener 和 RequestContextFilter 所有都可以做相同的事情,即绑定 HTTP 请求对象到服务于请求的 Thread 中。这使得 bean 在请求和会话范围内对后续的调用链可用。 4.5.4.2 请求范围 考虑一下下面的 bean 定义: Spring 容器通过使用 LoginAction bean 的定义来为每个 HTTP 请求创建 LoginAction bean 的新的实例。也就是说,LoginAction bean 是在 HTTP 请求级别的范 围。你可以改变创建的实例内部的状态,怎么改都可以,因为从相同的 LoginAction bean 定义中创建的其它实例在状态上不会看到这些改变;它们对单独的请求都是独立的。当请求 完成处理,bean 在请求范围内被丢弃了。 4.5.4.3 会话范围 考虑一下下面的 bean 定义: Spring 容器通过使用 UserPreferences bean 的定义来为每个 HTTP 会话的生命周期 创建 UserPreferences bean 的新的实例。换句话说,UserPreferences bean 在 HTTP .. requestContextFilter org.springframework.web.filter.RequestCont extFilter requestContextFilter /* ... session 级别的范围内是有效的。正如 request-scoped 的 bean,你可以改变创建的实 例内部的状态,怎么改都可以,要 知 道其 它 HTTP session 实例也是使用从相同 UserPreferences bean 的定义中创建的实例,它们不会看到状态的改变,因为它们对单 独的 HTTP session 都是独立的。当 HTTP session 最终被丢弃时,那么和这个 HTTP session 相关的 bean 也就被丢弃了。 4.5.4.4 全局会话范围 考虑一下下面的 bean 定义: global session 范围和标准的 HTTP Session 范围(上面 4.5.4.3 节讨论的)类似, 但仅能应用于基于 portlet 上下文的 Web 应用程序。portlet 规范定义了全局的 Session 的 概念,该会话在所有 portlet 之间所共享来构建一个单独的 portlet Web 应用程序。在 global session 范围内定义的 bean 的范围(约束)就会在全局 portlet Session 的生命周期内。 如果你编写了一个标准的基于 Servlet 的 Web 应用,并且定义了一个或多个有 global session 范围的 bean,那么就会使用标准的 HTTP Session 范围,也不会抛出什么错误。 4.5.4.5 各种范围的 bean 作为依赖 Spring 的 IoC 容器不仅仅管理对象(bean)的实例,而且也装配协作者(依赖)。如果 你想注入(比如)一个 HTTP 请求范围的 bean 到另外一个 bean 中,那么就必须在有范围 bean 中注入一个 AOP 代理。也就是说,你需要注入一个代理对象来公开相同的公共接口作为有 范围的对象,这个对象也可以从相关的范围(比如,HTTP 请求范围)中获取真实的目标对 象并且委托方法调用到真正的对象上。 注意 你不需要使用来结合 singleton 和 prototype 范围的 bean 。 如 果 你 想 为 单 例 bean 尝试创建有范围的代理,那么就会抛出 BeanCreationException 异常。 在下面的示例中,配置仅仅只有一行,但是这对理解“为什么”和其中的“怎么做”是 至关重要的。 要创建这样的代理,就要插入一个子元素到有范围的 bean 的 定义中。(如果要选择基于类的代理,那么需要在类路径下放置 CGLIB 的类库,参考下面的 “选择创建代理类型”部分和“附录 C,基于 XML Schema 的配置”)为什么 request, session,globalSession 和自定义范围级别的 bean 需要元 素?我们来看看下面的单例 bean 定义,并将它和你需要定义的上述范围来比较一下。(下面 的 userPreferences bean 的定义是不完整的。) 在上面 的例 子中 ,单 例 bean userManager 是用 HTTP session 范围 的 bean userPreferences 的引用注入的。这里突出的一点就是 userManager bean 是单例的; 那么对于每个容器来说,它只会被实例化一次,并且它的依赖(本例中只有一个依赖,就是 userPreferences bean)也只会被注入一次。这就意味着 userManager bean 只会对同 一个 userPreferences 对象进行操作,也就是说,是最初被注入的那一个对象。 这并不是你想要的行为,即注入生命周期范围短的 bean 到生命周期范围长的 bean 中, 比如注入 HTTP session 范围的协作 bean 作为依赖到单例 bean 中。相反,你需要一个单 独的 userManager 对 象 , 生 命 周 期 和 HTTP session 一 样 , 你 需 要 一 个 userPreferences 对象来特定于 HTTP session。因此容器创建对象,公开相同的公共 接口,而 UserPreferences 类(理想情况下是 UserPreferences 实例的对象)从范围 机制(HTTP 请求,session 等等)来获取真正的 UserPreferences 对象。容器注入这 个代理对象到 userManager bean 中,这个 bean 不能感知 UserPreferences 的引用就 是代理。在这个示例中,当 UserManager 实例调用依赖注入的 UserPreferences 对象 的方法时,那么其实是在代理上调用这个方法。然后代理从(在本例中)HTTP session 中 获取真实的 UserPreferences 对象,并且委托方法调用到获取的真实的 UserPreferences 对象中。 因此,当注入 request 范围,session 范围和 globalSession 范围的 bean 到协作 对象时,你需要下面的,正确的,完整的配置: 选择创建代理类型 默认情况下,当Spring 容器为被元素标记的bean创建代理时, 基于 CGLIB 的类的代理就被创建了。这就是说在应用程序的类路径下需要有 CGLIB 的类库。 注意:CGLIB 代理仅仅拦截公有的方法调用!不会调用代理中的非公有方法;它们不会 被委托到有范围的目标对象。 另外,你可以配置 Spring 容器有范围的 bean 创建标准的基于 JDK 接口的代理,通过指 定元素的 proxy-target-class 属性值为 false。使用基于 JDK 接口的代理意味着你不需要在应用程序的类路径下添加额外的类库来使代理生效。然而, 它也意味着有范围 bean 的类必须实现至少一个接口,并且注入到有范围 bean 的所有协作 者必须通过它的一个接口来引用 bean。 关于选择基于类或基于接口的代理的更多详细信息,可以参考 8.6 节,“代理机制”。 4.5.5 自定义范围 在 Spring 2.0 当中,bean 的范围机制是可扩展的。你可以定义自己的范围,或者重新定 义已有的范围,尽管后者被认为是不良实践而且你不能覆盖 内建 的 singleton 和 prototype 范围。 4.5.5.1 创建自定义范围 要 整 合 自 定 义 范 围 到 Spring 的容器中,那么需要实现 org.springframework.beans.factory.config.Scope 接口,这会在本节中来介绍。 对于如何实现你自己的范围的想法,可以参考 Spring Framework 本身提供的 Scope 实现和 Scope 的 JavaDoc 文档,这里会介绍你需要实现的方法的更多细节。 Scope 接口有四个方法来从作用域范围中获取对象,从作用域范围中移除对象还有允 许它们被销毁。 下面的方法从底层范围中返回对象。比如,会话范围的实现,返回会话范围的 bean(如 果它不存在,在绑定到会话后为将来的引用,这个方法返回 bean 的新的实例)。 下面的方法从底层范围中移除对象。比如会话范围的实现,从底层的会话中移除会话范 围的 bean。对象应该被返回,但是如果特定名字的对象没有被发现的话可以返回 null。 下面的方法注册当被销毁或者指定范围内的对象被销毁时,相关范围应用执行的回调函 数。参考 JavaDoc 文档或者 Spring 范围实现来获取更多关于销毁回调的信息。 下面的方法得到一个底层范围的会话标识符。这个标识符对每种范围都是不同的。对于 会话范围的实现,这个标识符可以是会话标识符。 4.5.5.2 使用自定义范围 在你编写并测试一个或多个自定义 Scope 实现之后,你需要让 Spring 容器感知你的新 的范围。下面的方法是用 Spring 容器注册新的 Scope 的核心方法。 这个方法在 ConfigurableBeanFactory 接口中声明,也在大多数的 ApplicationContext 实现类中可用,通过 BeanFactory 属性跟着 Spring。 registerScope(..)方法的第一个参数是和范围相关的唯一名称;在 Spring 容器本 身这样的名字就是 singleton 和 prototype。registerScope(..)方法的第二个参数 是你想注册并使用的自定义 Scope 实现的真实实例。 假设你编写了自定义的 Scope 实现,之后便如下注册了它。 注意 下面的示例使用了 SimpleThreadScope,这是包含在 Spring 当中的,但模式是没有 注册的。这些指令和你自己自定义的 Scope 的实现是相同的。 之后就可以为你的自定义 Scope 创建符合 Spring 规则的 bean 的定义: 使用自定义的 Scope 实现,并没有被限制于程序注册。你也可以进行声明式的 Scope 注册,使用 CustomScopeConfigurer 类: Object get(String name, ObjectFactory objectFactory) Object remove(String name) void registerDestructionCallback(String name, Runnable destructionCallback) String getConversationId() void registerScope(String scopeName, Scope scope); Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope); 注意 当在 FactoryBean 的实现中放置时,那就是工厂 bean 本身被放 置到范围中,而不是从 getObject()方法返回的对象。 4.6 自定义 bean 的性质 4.6.1 生命周期回调 为了和容器管理bean的生命周期进行交互,你可以实现Spring 的InitializingBean 和 DisposableBean 接口。容器为前者调用 afterPropertiesSet()方法,为后者调 用 destroy()方法在 bean 的初始化和销毁阶段允许 bean 执行特定的动作。你也可以和容 器达到相同的整合,而不需要通过使用初始化方法和销毁方法的对象定义元数据来耦合你的 类到 Spring 的接口中。 从内部来说,Spring Framework 使用了 BeanPostProcessor 的实现来处理任何回调 接口,它可以发现并调用合适的方法。如果你需要自定义特性或其它生命周期行为,Spring 不会提供开箱,你可以自行实现 BeanPostProcessor 接口。要获取更多信息,可以参考 4.8 节,“容器扩展点”。 除了初始化和销毁回调,Spring 管理的对象也会实现 Lifecycle 接口,所以那些对象 可以参与到启动和关闭过程,来作为容器自己的生命周期。 生命周期回调接口在本节中来说明。 4.6.1.1 初始化回调 org.springframework.beans.factory.InitializingBean 接口允许 bean在 所有必须的属性被容器设置完成之后来执行初始化工作。InitializingBean 接口中仅仅 指定了一个方法: 推荐不要使用 InitializingBean 接口,因为它不必要地将代码耦合到 Spring 中。 另外,可以指定一个 POJO 初始化方法。在基于 XML 配置元数据的情形中,你可以使用 init-method 属性来指定签名为无返回值,无参数的方法的名称。比如,下面的定义: 这和下面的做法是完全相同的: 但是没有耦合任何代码到 Spring 中。 4.6.1.2 销毁回调 实现 org.springframework.beans.factory.DisposableBean 接口,当容器 包含的 bean 被销毁时,允许它获得回调。DisposableBean 接口中仅仅有一个方法: void afterPropertiesSet() throws Exception; public class ExampleBean { public void init() { // 做一些初始化工作 } } public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { //做一些初始化工作 } } 推荐不要使用DisposableBean接口,因为它不必要地将代码耦合到Spring 中。另外, 可以指定一个由 bean 支持的通用的方法。使用基于 XML 配置元数据,你可以使用 元素中的 destroy-method 属性。比如,下面的定义: 这和下面的做法是完全相同的: 但是没有耦合代码到 Spring 中。 4.6.1.3 默认的初始化和销毁方法 当使用 Spring 指定的 InitializingBean 和 DisposableBean 回调接口来编写初 始化和销毁方法回调时,典型地做法就是编写名称为 init(),initialize(),dispose() 等方法.理想的情况,这样的生命周期回调方法的名称在一个项目中是标准化的,那么所有 的开发人员可以使用相同的方法名称来保证一致性。 你可以配置 Spring 容器在每个 bean 中来查看命名的初始化和销毁回调方法名称。这就 是说,作为应用程序开发人员,你可以编写应用类并且使用初始化回调方法 init(),而不 需要在每个 bean 的定义中来配置 init-method="init"属性。当 bean 被创建时(并按 照前面描述的标准的生命周期回调合约),Spring 的 IoC 容器调用这个方法。这个特性也对 初始化和销毁方法回调强制执行了一致的命名约定。 假设你的初始化回调方法命名为 init(),销毁回调方法命名为 destroy()。那么你 就可以按照如下示例来装配类: void destroy() throws Exception; public class ExampleBean { public void cleanup() { // 做一些销毁工作 (比如释放连接池的连接) } } public class AnotherExampleBean implements DisposableBean { public void destroy() { // 做一些销毁工作 (比如释放连接池的连接) } } public class DefaultBlogService implements BlogService { private BlogDao blogDao; public void setBlogDao(BlogDao blogDao) { this.blogDao = blogDao; } 顶层元素的 default-init-method 属性的存在,就会让 Spring 的 IoC 容 器意识到在 bean 中的一个叫做 init 的方法就是初始化回调方法。当一个 bean 被创建和装 配时,如果 bean 类有这样一个方法,那么它就会在合适的时间被调用。 相似地(也就是在 XML 中),你可以使用顶层元素 的 default-destroy-method 属性来配置销毁回调方法。 在已经存在的 bean 类中,有命名差异的回调方法,你可以指定(也就是在 XML 中) 本身的 init-method 和 destroy-method 属性来覆盖默认的方法。 Spring 容器保证了在给 bean提供所有的依赖后,配置的初始化回调方法会立即被调用。 因此初始化回调也被称为原始的 bean 引用,也就意味着 AOP 的拦截器等尚未应用到 bean 中。目标 bean 首先被完全创建,之后 AOP 代理(比方说)和它的拦截器链就会被应用上。 如果目标 bean 和代理分开来定义了,你的代码仍然可以绕开代理和原始目标 bean 来交互。 因此,引用拦截器到初始化方法中会是不一致的,因为这么来做了会和它的代理/拦截耦合 目标 bean 的合生命周期,当你的代码直接和原始目标 bean 交互时,会留下奇怪的语义。 4.6.1.4 组合生命周期机制 在Spring 2.5中,对控制bean的生命周期行为有三种选择:InitializingBean(4.6.1.1 节)和 DisposableBean(4.6.1.2 节)回调接口;自定义 init()和 destroy()方法; @PostConstruct 和@PreDestroy 注解(4.9.6 节)。你可以组合这些机制来控制给定的 bean。 注意 如果对一个 bean 配置了多个生命周期机制,并且每种机制都用不同的方法名称来配置, 那么每个配置方法就会以下面列出的顺序来执行。然而,如果配置了相同的方法名称,比如 说,init()对于初始化方法,多于一个生命周期机制,那么这个方法就执行一次,这在前 面章节解释过了。 相同的 bean 配置了不同初始化方法的多个生命周期机制,那么就按下面顺序调用:  使用@PostConstruct 注解的方法  由 InitializingBean 回调接口定义的 afterPropertiesSet()方法 // 这是(毋庸置疑的)初始化回调方法 public void init() { if (this.blogDao == null) { throw new IllegalStateException("The [blogDao] property must be set."); } } }  自定义配置的 init()方法 销毁方法也是同样顺序调用:  使用@PreDestroy 注解的方法  由 DisposableBean 回调接口定义的 destroy ()方法  自定义配置的 destroy ()方法 4.6.1.5 启动和关闭回调 Lifecycle 接口定义了对于任意有它自己生命周期需求(比如,开始和结束一些后台 进程)对象的必要方法: 任何 Spring 管理的对象可以实现这个接口。那么,当 ApplicationContext 本身启动和关 闭时,它会级联那些定义在上下文中,对所有生命周期实现的调用。它会委托给 LifecycleProcessor 来做: 要注意 LifecycleProcessor 本身是扩展了 Lifecycle 接口。而且它添加了两个 方法来对上下文的刷新和关闭做出反应。 启动和关闭调用的顺序是很重要的。如果一个“依赖”关系存在于两个任意对象间,那 么需要依赖的一方会在它的依赖启动之后启动,并且在它的依赖关闭之前停止。然而,有时 直接的依赖关系是未知的。你可能仅仅知道确定类型的对象应该优先于另外一种类型对象的 启动。在那些情形下,SmartLifecycle 接口定义了另外一种选择,命名为 getPhase() 方法,在它的父接口 Phased 中来定义。 当启动时,最低层的对象首先启动,当停止时,是相反的。因此,实现了 SmartLifecycle 接口,并且 getPhase()方法返回 Integer.MIN_VALUE 的对象就会在最 先开始和最后停止的中间。在范围的另外一端,Integer.MIN_VALUE 的层次值表示对象 应该最后启动并且最先停止(可能是因为它取决于其它进程的执行)。当考虑层次值时,知 public interface Lifecycle { void start(); void stop(); boolean isRunning(); } public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); } public interface Phased { int getPhase(); } public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); } 道任意“普通”对象并且没有实现 Lifecycle 接口的默认的层次是 0,这也是很重要的。 因此,任意负的层次值就表示对象应该在那些标准组件之前启动(在它们之后停止),对于 任意正的层次值,反之亦然。 正如你看到由 SmartLifecycle 定义的停止方法接收回调。任何实现类必须在实现类 关闭进程完成之后,调用回调方法 run()。这使得必要时可以进行异步关闭,因为默认的 LifecycleProcessor 接口的实现,DefaultLifecycleProcessor,,会等待每个阶 段一组对象来调用回调的超时值。每个阶段的默认超时是 30 秒。你可以在上下文中定义名 为“lifecycleProcessor”的 bean 来覆盖默认的生命周期进程实例。如果你仅仅想去修改超时 时间,那么定义下面的 bean 就足够了: 正如所提到的,LifecycleProcessor 接口定义了刷新和关闭上下文的回调方法。后 者将驱动关闭进程,就好像明确地调用了 stop()方法,但是当上下文关闭时它才会发生。另 一方面,‘刷新’回调使得 SmartLifecycle bean 的另外一个特性可用。当上下文刷新时 (在所有对象都被实例化和初始化后),才会调用回调方法,在那时,默认的生命周期进程 会检查每个 SmartLifecycle 对象的 isAutoStartup()方法返回的布尔值。如果是 “true”,那时对象将会被启动,而不是等待明确的上下文调用或它自己的 start()方法(不像 上下文刷新,对标准的上下文实现来说,上下文启动不会自动发生)。“阶段”值和任意“依 赖”关系会按照上面所描述的相同方式来决定启动的顺序。 4.6.1.6 在非 Web 应用中,优雅地关闭 Spring IoC 容器 注意 本节仅对非 Web 应用来说。在相关 Web 应用程序关闭时,Spring 的基于 Web 的 ApplicationContext 实现已经有代码来优雅地关闭 Spring 的 Ioc 容器了。 如果你在非 Web 应用环境中使用 Spring 的 IoC 容器;比如,在富客户端桌面环境中; 你在 JVM 中注册了一个关闭的钩子。这么来做就保证了优雅地关闭并且在单例 bean 中调用 相关销毁方法,那么所有资源都会被释放。当然,你必须正确配置并实现这些销毁回调方法。 要注册一个关闭钩子,可以调用 AbstractApplicationContext 类中 声明的 registerShutdownHook()方法: import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationConte xt; public final class Boot { public static void main(final String[] args) throws Exception { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"beans.xml"}); // 对上面的上下文添加一个关闭的钩子... ctx.registerShutdownHook(); 4.6.2 ApplicationContextAware 和 BeanNameAware 当 ApplicationContext 创建实现了 org.springframework.context.ApplicationContextAware 接口的类时,这个类 就被提供了一个 ApplicationContext 的引用。 因此 bean 就可以编程来操作创建它们的 ApplicationContext ,通过 ApplicationContext 接口,或者转换到这个接口的已知子类型,比如 ConfigurableApplicationContext,会公开更多的功能。一种使用方式就是编程式获 取其它的 bean。有时候这种能力是很有用的。然而,通常你应该避免这么来做,因为它会 耦合代码到 Spring 中,还没有协作者作为属性提供给 bean 的控制反转的风格。应用上下文 的其它方法提供了访问文件资源,公共应用事件和访问消息资源的方法。这些补充特性为在 4.14 节,“应用上下文的补充功能”来说明。 在 Spring 2.5 中,自动装配是获取 ApplicationContext 引用的另外一种方式。“传 统的”constructor 和 byType 自动装配模式(在 4.4.5 节,“自动装配协作者”中说明的) 可以分别为构造方法参数或 setter 方法参数提供 ApplicationContext 类型的依赖。为 了更大的灵活性,包括自动装配字段和多参数方法的能力,还有使用新的基于注解的自动装 配特性。如果字段,构造方法或者普通方法进行@Autowired 注解,而且你这么做了, ApplicationFactory 就会自动装配到期望 BeanFactory 类型的字段,构造方法参数 或方法参数。要获取更多信息,可以参考 4.9.2 节,“@Autowired 和@Inject”。 当应用上下文创建实现了 org.springframework.beans.factory.BeanNameAware 接口的类时,这个类会被 提供一个在相关对象中定义的命名引用。 回 调 函 数 在 普 通 bean 的属性被填充之后并且在如 InitializingBean 的 afterPropertiesSet 或自定义初始化方法的初始化回调之前被调用。 4.6.3 其它 Aware 接口 除了上面讨论的 ApplicationContextAware 和 BeanNameAware,Spring 还提供 // 这里运行应用程序... // 主方法退出,钩子在应用关闭前优先被调用... } } public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; } public interface BeanNameAware { void setBeanName(string name) throws BeansException; } 了很多 Aware 接口,它们允许 bean 来告诉容器它们需要确定的基础的依赖。最重要的 Aware 接口在下面总结出来了-作为一个通用的规则,名称就很好地说明了依赖的类型: 表 4.4 Aware 接口 名称 注入的依赖 何处解释 ApplicationContextA ware 声明 ApplicationContext 4.6.2 节, “ ApplicationContextA ware 和 BeanNameAware” ApplicationEventPub lisherAware 包含在 ApplicationContext 内的事件发布 4.14 节,“应用上下文的补充 功能” BeanClassLoaderAwar e 用于加载 bean 类的类加载器 4.3.2 节,“实例化 bean” BeanFactoryAware 声明 BeanFactory 4.6.2 节, “ ApplicationContextA ware 和 BeanNameAware” BeanNameAware 声明 bean 的名称 4.6.2 节, “ ApplicationContextA ware 和 BeanNameAware” BootstrapContextAwa re 容器运行的资源适配器 BootstrapContext。典型地 是仅仅在 JCA 感知的 ApplicationContext 中可 用。 第 24 章,JCA CCI LoadTimeWeaverAware 在加载时为处理类定义的织入 器 8.8.4 节,“Spring Framework 中使用 AspectJ 进行加载时织 入” MessageSourceAware 解析消息配置的策略(支持参 数化和国际化) 4.14 节,“应用上下文的补充 功能” NotificationPublish erAware Spring JMX 通知发布 23.7 节,“通知” PortletConfigAware 容 器 运 行 的 当 前 的 PortletConfig。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效 第 19 章,Portlet MVC 框架 PortletContextAware 容 器 运 行 的 当 前 的 PortletContext。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效 第 19 章,Portlet MVC 框架 ResourceLoaderAware 为低级别的资源访问配置的加 载器 第 5 章,资源 ServletConfigAware 容 器 运 行 的 当 前 的 ServletConfig。仅在感知 第 16 章,Web MVC 框架 Web 的 Spring 的 ApplicationContext 中有 效 ServletContextAware 容 器 运 行 的 当 前 的 ServletContext。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效 第 16 章,Web MVC 框架 请再次注意绑定你的代码到 Spring API 的这些接口的使用,并不再遵循控制反转风格。 因此,它们被推荐使用到基础的 bean 中,那些 bean 需要编程式地访问容器。 4.7 Bean 定义的继承 bean 的定义可以包含很多配置信息包括构造方法参数,属性值和容器指定的信息,比 如初始化方法,静态工厂方法名称等。子 bean 定义继承从父 bean 中获得的配置元数据。 子 bean 可以覆盖一些值或者添加其它所需要的。使用父子 bean 定义可以节省很多输入。 实际上,这是一种模板形式。 如 果 你 编 程 式 地 使 用 ApplicationContext 接口,子 bean 的 定 义 可 以 由 ChildBeanDefinition 类代 表。很多用户不使用这个级别的方法,而是在类似于 ClassPathXmlApplicationContext.中声明式地配置 bean 的信息。当使用基于 XML 的配置元数据时,你可以使用 parent 属性来标识一个子 bean,使用这个属性的值来标识 父 bean。 如果没有指定一个子 bean 使用父 bean 的类,但也可以覆盖它。在这种情形中,子 bean 的类必须和父 bean 兼容,也就是说,它必须接受父 bean 的属性值。 子 bean 的定义继承构造方法参数值,属性值,还有父 bean 的方法覆盖,添加新值的 选择。任何你指定的初始化方法,销毁方法,和/或 static 工厂方法设置会覆盖对应父 bean 中的设置。 剩下的设置通常是从子 bean 来定义:依赖,自动装配模式,依赖检测,单例,范围, 延迟初始化。 前面的示例明确地使用了 abstract 属性来标识了父 bean 的定义。如果父 bean 没有 指定类,那么明确地标识父 bean 就必须要有 abstract,如下所示: 父 bean 不会被实例化,因为它自己是不完整的,而且也明确地被 abstract 标记。当 bean 的定义是 abstract 这样的,那么它也仅仅被用做纯 bean 定义的模板,作为子 bean 定义的父 bean。尝试使用这种自身是 abstract 的父 bean,作为另外一个 bean 参考的属 性来指代它,或者使用父 bean 的 id 来明确使用 getBean()方法调用,会返回错误。相似 地,容器内部的 preInstantiateSingletons()方法忽略了抽象 bean 的定义。 注意 默认情况下,预实例化所有单例 bean。因此,如果你有仅仅想用作模板的(父)bean, 而且这个 bean 指定了一个类,那么必须将 abstract 属性设置为 true,这点是很重要的。否 则,应用上下文就会(尝试)预实例化 abstract 的 bean。 4.8 容器扩展点 通常情况下,应用程序开发人员不需要编写 ApplicationContext 实现类的子类。 相反,Spring 的 IoC 容器可以通过插件的方式来扩展,就是实现特定的整合接口。下面的几 个章节会来说明这些整合接口。 4.8.1 使用 BeanPostProcessor 来自定义 bean BeanPostProcessor 接口定义了你可以提供实现你自己的(或覆盖容器默认的)实 例逻辑,依赖解析逻辑等的回调方法。如果你想在 Spring 容器完成实例化,配置和初始化 bean 之后实现一些自定义逻辑,那么你可以使用一个或多个 BeanPostProcessor 实现类 的插件。 你可以配置多个 BeanPostProcessor 实例,而且你还可以通过设置 order 属性来 控制这些 BeanPostProcessor 执行的顺序。仅当 BeanPostProcessor 实现了 Ordered 接口你才可以设置设个属性;如果你想编写你自己的 BeanPostProcessor,你 也应该考虑实现 Ordered 接口。要了解更多细节,可以参考 JavaDoc 文档中的 BeanPostProcessor 和 Ordered 接口。 注意 BeanPostProcessor 操作 bean(对象)实例;那也就是说,Spring 的 IoC 容器 实例化 bean 的实例,之后 BeanPostProcessor 来做它们要做的事情。 BeanPostProcessor 的范围是对于每一个容器来说的。如果你使用了容器继承, 那么这才有所关联。如果你在一个容器中定义了 BeanPostProcessor,那么它仅仅 会在那个容器中后处理 bean。换句话说,一个容器中定义的 bean 不会被另外一个容器 中定义的 BeanPostProcessor 来进行后处理,即便是两个容器都是相同继承链上的 一部分。 要修改真正的 bean 定义(也就是说,定义 bean 的蓝图),你可以使用 4.8.2 节,“使 用 BeanFactoryPostProcessor 来自定义配置元数据”描述的 BeanFactoryPostProcessor 来进行。 org.springframework.beans.factory.config.BeanPostProcessor 接口 由两个回调方法构成。如果在容器中有一个类注册为后处理器,对于容器创建的每个 bean 的实例,后处理器从容器中获得回调方法,在容器初始化方法之前(比如 InitializingBean 的 afterPropertiesSet()方法和任意声明为初始化的方法)被调用,还有在 bean 初始化回调之后 被调用。后处理器可以对 bean 实例采取任何动作,包括完整忽略回调。通常来说 bean 的 后处理器会对回调接口进行检查,或者会使用代理包装 bean。一些 Spring 的 AOP 基类也会 作为 bean 的后处理器实现来提供代理包装逻辑。 ApplicationContext 会自动检测任意实现了 BeanPostProcessor 接口的 bean 定义的配置元数据。ApplicationContext 注册这些 bean 作为后处理器,那么它们可以 在 bean 创建之后被调用。Bean 的后处理器可以在容器中部署,就像其它 bean 那样。 BeanPostProcessor 和 AOP 自动代理 实现了 BeanPostProcessor 接口的类是特殊的,会被容器不同对待。所有它们 参照的 BeanPostProcessor 和 bean 会在启动时被实例化,作为 ApplicationContext 启 动 阶 段 特殊的 一 部 分 。 接 下 来 , 所 有 的 BeanPostProcessor 以排序的方式注册并应用于容器中的其它 bean。因为 AOP 自动 代理作为 BeanPostProcessor 本身的实现,它们为自动代理资格的直接引用的既不 是 BeanPostProcessor 也不是 bean,因此没有织入它们的方面。 对于这样的 bean,你应该看到一个信息级的日志消息:“Bean foo 没有由所有 BeanPostProcessor 接口处理的资格(比如:没有自动代理的资格)”。 下面的示例展示了如何在 ApplicationContext 中编写,注册和使用 BeanPostProcessor。 4.8.1.1 示例:BeanPostProcessor 风格的 Hello World 第一个示例说明了基本的用法。示例展示了一个自定义的 BeanPostProcessor 实现 类来调用每个由容器创建 bean 的 toString()方法并打印出字符串到系统的控制台中。 自定义 BeanPostProcessor 实现类的定义: package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.BeansException; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // 简单地返回实例化的bean public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException { return bean; // 这里我们可能返回任意对象的引用... } 注意 InstantiationTracingBeanPostProcessor 仅仅是简单地定义。它也没有 命名,因为它会像其它 bean 那样被依赖注入。(前面的配置也可以定义成 Groovy 脚本支持 的 bean。Spring 2.0 动态语言支持在第 27 章中来详细说明) 下面示例的 Java 应用程序执行了前面配置的代码: public Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } } import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationConte xt; import org.springframework.scripting.Messenger; public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml"); Messenger messenger = (Messenger) ctx.getBean("messenger"); System.out.println(messenger); } } 上面应用程序的输出类似于如下内容: 4.8.1.2 示例:RequiredAnnotationBeanPostProcessor 配合自定义的 BeanPostProcessor 实现使用回调接口或者注解,是扩展 Spring IoC 容器的一种通用方式。Spring 的 RequiredAnnotationBeanPostProcessor 就是一个 例子 – 一种 BeanPostProcessor 实现,随着 Spring 一起发布,来保证 bean 中被(任意) 注解所标记的 JavaBean 属性在真正(配置)注入时有值。 4.8.2 使用 BeanFactoryPostProcessor 自定义配置元数据 下一个扩展点我们要来看看 org.springframework.beans.factory.config.BeanFactoryPostProcessor。 这个接口的 语义和那 些 BeanPostProcessor 是相 似的,但有一个主要的不同点: BeanFactoryPostProcessor 操作 bean 的配置元数据;也就是说 Spring 的 IoC 容器允许 BeanFactoryPostProcessor 来读取配置元数据并在容器实例化 BeanFactoryPostProcessor 以外的任何 bean 之前可以修改它。 你可以配置多个 BeanFactoryPostProcessor,并且你也可以通过 order 属性来 控 制 这 些 BeanFactoryPostProcessor 执行的顺序。然而,仅当 BeanFactoryPostProcessor 实现 Ordered 接口时你才能设置这个属性。如果编写你 自己的 BeanFactoryPostProcessor,你也应该考虑实现 Ordered 接口。参考 JavaDoc 文档来获取 BeanFactoryPostProcessor 和 Ordered 接口的更多细节。 注意 如果你想改变真实的 bean 实例(也就是说,从配置元数据中创建的对象),那么你 需要使用 BeanPostProcessor(在上面 4.8.1 节,“使用 BeanPostProcessor 来自定义 bean ”中描述)来代替。在 BeanFactoryPostProcessor (比如使用 BeanFactory.getBean())中来使用这些 bean 的实例虽然在技术上是可行的,但 这么来做会引起 bean 过早实例化,违反标准的容器生命周期。这也会引发一些副作用, 比如绕过 bean 的后处理。 而且,BeanFactoryPostProcessor 的范围也是对每一个容器来说的。如果你 使用了容器的继承的话,这就是唯一相关的点了。如果你在一个容器中定义了 BeanFactoryPostProcessor,那么它只会用于在那个容器中的 bean。一个容器中 Bean 的定义不会被另外一个容器中的 BeanFactoryPostProcessor 后处理,即便 两个容器都是相同继承关系的一部分。 当在 ApplicationContext 中声明时,bean 工厂后处理器会自动被执行,这就可以 对定义在容器中的配置元数据进行修改。Spring 包含了一些预定义的 bean 工厂后处理器, 比如 PropertyOverrideConfigurer 和 PropertyPlaceholderConfigurer.。自定 义的 BeanFactoryPostProcessor 也可以来用,比如,注册自定义的属性编辑器。 ApplicationContext 会自动检测任意部署其中,且实现了 Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961 BeanFactoryPostProcessor 接口的 bean。在适当的时间,它用这些 bean 作为 bean 工厂后处理器。你可以部署这些后处理器 bean 作为你想用的任意其它的 bean。 注意 和 BeanPostProcessor 一样,通常你不会想配置 BeanFactoryPostProcessor 来进行延迟初始化。如果没有其它 bean 引用 Bean(Factory)PostProcessor,那么后 处理器就不会被初始化了。因此,标记它为延迟初始化就会被忽略,即便你在元 素声明中设置default-lazy-init属性为true,那么Bean(Factory)PostProcessor 也会正常被初始化。 4.8.2.1 示例:PropertyPlaceholderConfigurer 你可以使用来对使用了标准Java Properties格式的分离文件中定义的 bean来声明属 性值。这么来做可以使得部署应用程序来自定义指定的环境属性,比如数据库的连接 URL 和密码,不会有修改容器的主 XML 定义文件或其它文件的复杂性和风险。 考虑一下下面这个基于 XML 的配置元数据代码片段,这里的 dataSource 就使用了占 位符来定义。这个示例展示了从 Properties 文件中配置属性的方法。在运行时, PropertyPlaceholderConfigurer 就会用于元数据并为数据源替换一些属性。指定替 换的值作为${属性-名}形式中的占位符,这里应用了 Ant/log4j/JSP EL 的风格。 而真正的值是来自于标准的 Java Properties 格式的文件: 因此,字符串${jdbc.username}在运行时会被值’sa’替换,对于其它占位符来说也是相同 的,匹配到了属性文件中的键就会用其值替换占位符。 PropertyPlaceholderConfigurer 在很多 bean 定义的属性中检查占位符。此外,对 占位符可以自定义前缀和后缀。 jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root 使用 Spring 2.5 引入的 context 命名空间,也可以使用专用的配置元素来配置属性占 位符。在 location 属性中,可以提供一个或多个以逗号分隔的列表。 PropertyPlaceholderConfigurer 不仅仅查看在 Properties 文件中指定的属 性。默认情况下,如果它不能在指定的属性文件中发现属性,它也会检查 Java System 属性。 你可以通过设置 systemPropertiesMode 属性,使用下面整数的三者之一来自定义这种 行为:  never(0):从不检查系统属性  fallback(1):如果没有在指定的属性文件中解析到属性,那么就检查系统属性。这是默 认的情况。  override(2):在检查指定的属性文件之前,首先去检查系统属性。这就允许系统属性覆 盖其它任意的属性资源。 查看 PropertyPlaceholderConfigurer 的 JavaDoc 文档来获取更多信息。 类名替换 你可以使用 PropertyPlaceholderConfigurer 来替换类名,在运行时,当你 不得不去选择一个特定的实现类时,这是很有用的。比如: 如果类在运行时不能解析成一个有效的类,那么在即将创建时,bean 的解析就失 败了,这是 ApplicationContext 在 对 非 延 迟 初 始 化 bean 的 preInstantiateSingletons()阶段发生的。 4.8.2.2 示例:PropertyOverrideConfigurer PropertyOverrideConfigurer , 另 外 一 种 bean 工 厂 后 处 理 器 , 类 似 于 PropertyPlaceholderConfigurer,但不像后者,对于所有 bean 的属性,原始定义可 以有默认值或没有值。如果一个 Properties 覆盖文件没有特定 bean 的属性配置项,那么 就会使用默认的上下文定义。 注意,bean 定义是不知道被覆盖的,所以从 XML 定义文件中不能立即明显反应覆盖配 置。在多个 PropertyOverrideConfigurer 实例的情况下,为相同 bean 的属性定义不 classpath:com/foo/strategy.properties custom.strategy.class=com.foo.DefaultStrategy 同的值,那么最后一个有效,这就是覆盖机制。 属性文件配置行像这种格式: 比如: 这个示例文件可以用于包含了 dataSource bean 的容器,它有 driver 和 url 属性。 复合属性名也是支持的,除了最终的属性被覆盖,只要路径中的每个组件都是非空的(假 设由构造方法初始化)。在这个例子中... ...foo bean 的 fred 属性的 bob 属性的 sammy 属性的值设置为标量 123。 注意 指定的覆盖值通常是文字值;它们不会被翻译成 bean 的引用。当 XML 中的 bean 定义 的原始值指定了 bean 引用时,这个约定也适用。 使用 Spring 2.5 引入的 context 命名空间,可以使用专用的配置元素来配置属性覆盖: beanName.property=value dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb foo.fred.bob.sammy=123
还剩83页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

1820066354

贡献于2015-06-16

下载需要 6 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf