最新的, 更新的笔记, 支持的版本和其余主题,独立的发布版本等, 是在Github Wiki 项目维护的.html
总览 历史, 设计哲学, 反馈, 入门.java
核心 IoC容器, 事件, 资源, 国际化(i18n), 验证, 数据绑定, 类型转化, Spring表达式语言(SpEL), 面向切面编程(AOP).mysql
测试 Mock对象, 测试上下文框架(TestContext framework), Spring MVC 测试, WebTestClient.git
数据访问 事务, DAO支持, JDBC, ORM, 编组XML.github
Web Servlet Spring MVC, WebSocket, SockJS, STOMP 消息.web
Web Reactive Spring WebFlux, WebClient, WebSocket.正则表达式
集成 Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Cache.算法
语言 Kotlin, Groovy, 动态语言(Dynamic languages).spring
内容:sql
Spring 简化了Java企业应用的建立. 能够提供在企业应用环境下Java生态所需的一切, 同时也支持Kotlin和Groovy做为JVM的替代语言, 根据实际须要,也能够建立多种不一样的架构.(architecture). 从Spring Framwork 5.0 开始, Spring须要JDK 8+ 而且已经为JDK9提供开箱即用支持
Spring提供了普遍的应用场景. 在大型企业应用中,应用每每要存在很长一段时间,而且不得不运行在一个升级周期超出开发人员控制的JDK和服务器上. 而其余的应用则使用内嵌服务器单独运行jar包,或者部署在云环境. 还有一些应用可能独立部署, 根本不须要服务器(例如批处理或集成负载).
Spring是开源的.背后有长期大量而活跃的根据实际应用案例而提交的反馈.这将帮助Spring成功长期进化.
"Spring"意思是在不一样环境中不一样的东西. 可以用来指代Spring项目自己(这是它的发端起始点). 随着时间推移, 其余创建在Spring之上的项目被建立出来. 一般咱们称"Spring", 实际上是指全部这些项目. 本文档主要聚焦基础: 也就是Spring框架自己.
Spring框架分模块. 能够根据状况选择须要的模块. 核心模块是核心容器, 包含配置模型和依赖注入机制. 还有更多,Spring 框架提供了对不一样应用架构的基础支持. 包含消息,事务和持久化,还有Web. 它还包含基于Servlet的MVC框架, 同时提供了对应的交互式框架Web Flux.
关于模块的提醒: Spring框架jar文件容许JDK9支持的模块路径("Jigsaw"). 在此类应用中, Spring Framework 5 的jar文件带有自动模块名称清单. 它定义了独立于jar工件的语言级别的模块名称(“spring.core”,“spring.context”等). 固然, Spring在JDK8和9的类路径上均可以正常工做.
Spring 是为了回应早期复杂的J2EE规范于2003年诞生. 有人认为 Java EE 和 Spring 是竞争关系,实际上,Spring是Java EE 的补充. Spring的编程模型并非彻底拥抱Java EE平台规范, 而是当心地有选择地从EE生态中集成了独立的规范:
Servlet API (JSR 340)
WebSocket API (JSR 356)
Concurrency Utilities (JSR 236)
JSON Binding API (JSR 367)
Bean Validation (JSR 303)
JPA (JSR 338)
JMS (JSR 914)
若是须要的话,还有 JTA/JCA 作事务协调
Spring Framework 还支持依赖注入(JSR 330)和普通注解(JSR 250)规范. 这些规范的实现由开发人员能够用来替换Spring默认提供的机制.
Spring Framework 5.0 开始起, Spring要求Java EE 7以上(e.g. Servlet 3.1+, JPA 2.1+).同时使用新的Java EE 8(e.g. Servlet 4.0, JSON Binding API)以上的新api提供开箱即用.这就保证了Spring彻底兼容Tomcat8和9, WebSphere9, 还有JBoss EAP 7.
慢慢的,Java EE 在开发中的角色发生了变化. 早期Java EE 和 Spring建立的程序是被部署到应用程序服务器上. 而今天, 归功于Spring Boot, 应用程序以devops或云的方式建立,使用内嵌的Servlet容器, 并且常常变化.自从Spring Framework 5 , WebFlux程序甚至都不须要直接调用Servlet Api了, 它能够运行在非Servlet规范的容器(如Netty)中.
Spring是持续革新和进化的. 超出Spring Framework, 有不少其余项目如Spring Boot, Spring Security,Spring Data,Spring Cload, Spring Batch,还有不少. 每一个项目都有它本身的源码仓库, 问题跟踪和发布周期. 从spring.io/projects 能够看到全部项目的列表.
当你学习一个框架的时候, 不只要知道它能干什么, 更重要的是知道它所遵循的原则. 下面是Spring Framework遵循的指导原则.
在全部层面提供选择权. Spring容许你尽可能延迟设计选择. 例如, 你能够经过配置而不是修改代码就替换掉数据持久化的提供程序.这也一样适用于其余基础设施概念并能集成不少三方API.
容纳不一样的观点. Spring 拥抱伸缩性, 它并不坚持认为事情应该就这样作. 根据观点不一样, 它提供了普遍的应用选择.
保持强大的向后兼容性. Spring演化通过精心设计和管理, 能够防止版本之间出现破坏性改变. Spring支持必定范围版本的JDK和第三方库. 便于维护依赖于Spring的程序和库.
关心API设计. Spring团队花费大量精力和时间设计API, 使其直观而且能保持多个版本和持续不少年.
高质量的编码, Spring强调有意义的, 实时的,准确的javadoc. 是极少数声称代码简洁且包之间没有循环依赖的项目之一.
对于如何操做或诊断或调试问题, 咱们强烈建议使用StackOverflow, 而且咱们有一个问题页清单, 列出了使用建议. 若是你彻底肯定Spring Framework有问题或者想提交特性, 请使用JIRA问题跟踪.
若是你解决了问题或者修正了建议, 你能够在GitHub上发起PR. 总之,请记住, 除了微不足道的问题,咱们但愿有个问题存根进行讨论并记录下来供将来参考.
更多问题请参看顶级页面"贡献"页上的指南.
若是你刚开始使用Spring, 你可能想要经过建立一个Spring Boot的项目开始Spring之旅. Spring Boot提供了一个快速(也是固化的)方式建立生产就绪的 Spring 程序, 它基于Spring 框架, 信奉约定优于配置,而且设计为快速启动运行.
你能够使用start.spring.io来生成基础项目, 或者按照"入门"指南一步步建立, 例如"Getting Started Building a RESTful Web Service". 这些指南只关注于当前主题任务, 能够比较容易的理解, 不少都是Spring Boot项目. Spring portfolio还包含其余项目, 当你解决特定问题时你可能会考虑关注下相关的项目.
这部分指导文档涵盖了Spring Framework不可或缺的全部技术
这些技术中最重要的,是Spring Framework的Ioc容器. 在吃透了Spring Framework 的IoC容器以后,紧接着是理解Spring的AOP技术. Spring Framework有其自身的AOP框架, 概念上很好理解而且可以知足实际应用中80%的热点须要.
Spring提供了AspectJ集成(这是目前特性最为丰富,固然也是Java企业领域最成熟的AOP实现).
本章涵盖Spring的IoC容器.
本节涵盖了Spring Framework对IoC原则的实现. DI是与其密切相关的另外一个概念. IoC是一个处理过程,经过这个过程,对象只能经过构造函数参数, 工厂方法参数或在从工厂方法构造或返回的对象实例上设置的属性来定义他们的依赖关系. 当建立这些bean时, 容器去注入这些依赖. 这个过程从根本上反转了由对象自身去控制它所需依赖的方式, 经过直接类构造或相似Service Locator模式的机制.
org.springframework.beans
和 org.springframework.context
这两个包是IoC容器的基础. BeanFactory
接口提供了可以管理任何对象类型的高级配置机制. ApplicationContext
是 BeanFactory
的一个子类接口. 增长如下功能:
WebApplicationContext
.简言之, BeanFactory
提供了配置框架和基本的功能, ApplicationContext
增长了诸多企业特性功能. ApplicationContext
是 BeanFactory
的一个完整超集, 在本章中专门用于Spring IoC容器的描述. 若是想用BeanFactory
代替ApplicationContext
能够参看后面有关BeanFactory
的内容.
Spring中,构成你程序的骨架而且被Spring IoC容器管理的对象被称为beans. bean就是一个被Spring IoC容器实例化,装配和管理的对象. bean也能够简单的是应用中诸多对象中的一个.bean和他们之间的依赖被映射到容器的配置元数据中.
org.springframework.context.ApplicationContext
接口表明了Spring IoC容器而且负责实例化,配置,组装bean. 容器经过读取配置元数据获取指令来实例化,配置,组装bean.配置元数据使用XML,Java注解或者Java代码的方式表现.它容许您表达组成应用程序的对象以及这些对象之间丰富的依赖.
Spring提供了ApplicationContext
接口的几个实现. 在独立应用中, 一般会建立一个ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例.XML是传统的定义配置的格式, 你也能够经过一小段XML配置来启用这些支持的格式, 指定容器使用Java注解或者代码格式配置.
在不少的应用场景下, 并不须要显式的实例化一个或多个Spring的IoC容器. 例如, 在Web应用中,web.xml
文件中大概八行相似的样板化的XML描述就足够了(参看Web程序中便捷的ApplicationContext实例). 若是你使用Spring Tool Suite(一种Eclipse加强开发环境), 可以很轻松地用几回点击鼠标和几个按键生成这样的样板配置.
下图从较高层次展现了Spring如何工做. 你的程序类和配置元数据时结合在一块儿的, 所以,当ApplicationContext
建立并实例化后, 你就有了一个可执行系统或程序.
如同上图展现的, Spring IoC 容器使用配置元数据. 配置元数据表现了你做为开发者如何告知Spring容器去实例化,配置并组装程序中的对象.
配置元数据以传统而直观的XML格式提供, 这是本节大部份内容传达的关于Spring IoC容器的关键概念和特性.
XML不是惟一容许描述元数据的格式. Spring IoC 容器已经弱化了配置以何种格式书写. 当今,许多开发人员更愿意在程序中选择Java配置的方式.
如何使用其余格式的配置,能够参考下面的信息:
@Configuration
,@Bean
,@Import
,@DependsOn
注解.Spring配置由至少一个,或典型的超过一个由容器管理的bean的定义. XML格式使用<bean/>
元素配置这些beans, 它嵌套在顶层<beans/>
元素里面. Java配置则包含在使用@Configuration
注解的class中,并使用@Bean
注解方法.
这些bean定义与构成你程序的对象相吻合. 例如, 你定义服务层对象,数据访问层对象,变现层对象如Struts Action
实例, 基础对象如Hibernate SessionFactories
, JMS 队列等. 通常不会在容器中定义细粒度的域对象.由于这一般是DAO或业务逻辑层的任务去建立和加载这些对象. 尽管如此, 你能够使用AspectJ去配置在容器以外建立对象.参看在Spring中使用AspectJ依赖注入领域对象.
下面例子展现了XML配置的基本格式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here 1. id是区分bean的一个字符串标识符 2. class 定义bean的类型,使用全限定类名 --> </bean> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions go here --> </beans>
id
属性的值指向协做的对象. 本例中没有明确写出.可参看依赖项.
ApplicationContext
构造参数中的定位参数字符串是让容器从外部变量加载配置. 参数能够是多种资源格式, 例如本地文件系统, Java CLASSPATH
等.
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
学习Spring容器后, 你可能想要了解下Spring的Resource
抽象, 它提供了一种便捷的从URI格式的资源中读取流的机制. 尤为是,Resource
路径一般用来构建程序上下文, 这点可参看"程序上下文和资源路径"
下面例子展现了服务层对象的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- services --> <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="itemDao" ref="itemDao"/> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for services go here --> </beans>
接下来的例子展现了数据访问层配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for data access objects go here --> </beans>
前面例子中, 服务层由PetStoreServiceImpl
类和两个数据读取对象JpaAccountDao
和JpaItemDao
(根据JPA对象关系映射标准). name
指类中的属性,表示bean的名称, ref
元素指向另外一个bean定义. 在id
与ref
元素之间的联系代表了对象间的协做依赖关系. 关于对象依赖配置的更多细节, 参看"依赖项".
使用多个xml文件定义bean是有用的. 一般各自的xml文件能分别表示逻辑层或架构中的一个模块.
你能够使用程序上下文构造器从全部这些XML片断中加载bean的定义. 构造器获取多个resource
资源位置, 就像咱们在上节展现的那样. 或者, 使用一个或多个<import/>
元素从其余文件中加载bean定义. 下面展现了如何这样作:
<beans> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
上面的例子中, 外部bean定义是从这几个文件加载的: services.xml
,messageSource.xml
,themeSource.xml
. 这些文件的路径相对于导入他们的文件, 所以services.xml
必须是与导入文件处在相同路径目录下. 就像你看到的, '/'能够忽略. 虽然路径是相对的,但尽可能不要使用'/'. 导入的这些文件的格式必须符合Spring的Schema, 须要有顶级<beans/>
元素. 必须是有效的XML的bean定义.
能够但不提倡在父级目录中使用'../'引用文件. 这样会在当前应用程序外建立依赖文件.特别不提倡使用classpath:
URLs(例如,classpath:../services.xml
),运行时解析时会选择最近的根路径而且转到它的父目录.Classpath的配置可能会错误地引导到其余的目录下. 能够使用绝对路径替代相对路径,例如file:C:/config/services.xml
或 classpath:/config/services.xml
. 但这样就不灵活地将路径耦合到程序配置中了.通常的作法是能够使用一种间接方式-例如占位符"${...}", 这样系统JVM能够在运行时解析到正确路径上
命名空间自己提供了导入指令特性. 比纯bean定义更高级的配置特性Spring也有提供. 例如context
和util
命名空间.
外部化元数据配置的更高级例子, bean定义也能够使用Spring的Groovy Bean Definition DSL, 因Gails框架而熟知. 下面演示了".groovy"文件中典型的配置的方式:
beans { dataSource(BasicDataSource) { driverClassName = "org.hsqldb.jdbcDriver" url = "jdbc:hsqldb:mem:grailsDB" username = "sa" password = "" settings = [mynew:"setting"] } sessionFactory(SessionFactory) { dataSource = dataSource } myService(MyService) { nestedBean = { AnotherBean bean -> dataSource = dataSource } } }
这种配置的风格大致上与XML配置相同, 甚至支持XML命名空间. 能够经过importBeans
指令从XML文件导入bean定义.
ApplicationContext
接口是一个能管理注册的bean以及他们之间依赖的高级工厂. 经过方法T getBean(String name, Class<T> requiredType)
, 能够获取Bean的实例.
ApplicationContext
容许读取bean定义并访问他们, 以下所示
// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); // retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class); // use configured instance List<String> userList = service.getUsernameList();
Groovy配置的启动也相似. 不过它有Groovy风格的不一样上下文实现(同时也支持XML). 下面展现了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变量是GenericApplicationContext
, 其中包含读取器代理, 例如: 对于XML文件, 它使用XmlBeanDefinitionReader
读取. 以下例:
GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); context.refresh();
对于Groovy, 但是使用GroovyBeanDefinitionReader
, 以下所示:
GenericApplicationContext context = new GenericApplicationContext(); new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy"); context.refresh();
在相同的ApplicationContext
中能够混合使用这些读取代理器, 从而从不一样资源中读取配置.
能够使用getBean
获取bean的实例. ApplicationContext
接口还有一些其余的方法获取bean, 可是理想状态下, 你的程序应该永远不要使用它们. 确实, 你的程序代码压根不该该调用getBean
方法,所以就一点也不会依赖Spring API. 例如, Spring为多种Web框架的组件集成提供DI功能, 例如controller和JSF管理的Bean, 容许你经过元数据声明依赖的bean(相似包装(autowiring)注解).
Spring IoC 容器管理一到多个bean. 这些bean是根据你提供给容器的配置建立的. (例如, 经过XML格式的<bean />
定义)
在容器内部, 这些bean定义表现为BeanDefinition
对象. 其包含以下信息 :
元数据被解析为一系列组成bean定义的属性, 下面表格列出了这些属性:
表 1. Bean定义中的属性
属性 | 参看 |
---|---|
Class | 初始化bean |
Name | 命名bean |
Scope | Bean的做用域 |
Constructor arguments | 依赖注入 |
Properties | 依赖注入 |
Autowiring mode | 自动装配协做对象 |
Lazy initialization mode | 懒加载Bean |
Initialization method | 初始化回调 |
Destruction method | 销毁回调 |
bean定义包含如何建立特定bean, 除此以外ApplicationContext
的实现容许将容器外建立的bean注册进来. 这是经过getBeanFactory()
方法访问ApplicationContext的 BeanFactory , 该方法默认返回DefaultListableBeanFactory
实现. DefaultListableBeanFactory
支持经过registerSingleton(..)
和 registerBeanDefinition(..)
方法注册. 尽管能够这样作,应用程序通常仍是单纯使用规则的bean定义元数据.
bean元数据和单例的手工支持必须尽早注册, 这是为了容器可以在自动装配和其余自省阶段合理解析.重写已经存在的元数据以及已经存在的单例在某些级别上是支持的, 但运行时注册新的bean(与工厂的并发访问)没有获得正式的支持,并且可能致使并发访问异常或bean状态不一致,或二者都有.
每一个bean都有一个或多个标识符. 容器内这些标识符必须是惟一的. 通常一个bean只有一个标识符. 可是也能够有多个,多出来的标识符是别名.
XML配置中,id
或name
属性用来作标识符. id
用来精确指定一个id. 按照习惯, 这些名称是字母数字组成的('myBean', 'someService', etc.), 但他们也能够包含特殊字符. 若是你想给bean指定别名,你能够将他们赋值给name
属性, 用逗号,分号或者空格分割. 在Spring3.1以前, id
是定义为一个xsd:ID
类型, 只能是字母. 自从3.1开始将其定义为xsd:string
类型. 注意id
的惟一性依然是容器强制的, 而不是XML解析器.
给beanname
或id
属性不是必须的. 若是没有指定, 容器会为bean生成一个惟一名称. 尽管这样, 若是你想经过名称引用bean, 或者经过ref
元素或者是Service Locator风格的查找, 你就必须给bean指定名称. 不给bean指定名称的动机是使用内部类和自动装配.
bean命名约定
给bean命名遵循与给实例域命名相同的约定. 也就是,使用小写字母开头的骆驼命名法. 例如:
accountManager
,accountService
,userDao
,loginController
等.
坚持命名bean能够使你的配置易读易理解. 还有, 若是使用Spring AOP, 当给名字相关的一些列bean应用通知时也会有很大帮助.
扫描类路径时, Spring会给未命名组件生成名称, 遵循前面描述的规则: 本质上是取类名,而后将首字符小写. 当有多余一个字母而且第一个和第二个字母都是大写时将保留大小写. 这些规则定义在java.beans.Introspector.decapitalize
(Spring使用)
在bean定义内, 你能够给它指定多个名称, 能够使用给id
指定一个名称, 同时也能够给name
指定多个(使用分隔符).使用这些名称指定的bean都是等效的, 在某些状况下也是有用的, 例如: 让应用程序中的每一个组件经过使用特定于该组件自己的bean名称来引用公共依赖项。
然而在定义bean的时候为其指定别名有时候并不够. 有时候须要将别名bean定义外的其余地方指定. 一般的例子是, 在大型系统内各个子系统分别有各自配置, 每一个子系统有一组其bean的定义. XML的配置中,你能够使用<alias/>
元素实现. 以下:
<alias name="fromName" alias="toName"/>
本例中, bean的名称(同一容器)被命名为fromName
, 在使用别名定义后, 这个bean经过toName
也能够引用.
例如, 子系统A引用了一个数据源叫subsystemA-dataSource
. 子系统B引用数据源叫subsystemB-dataSource
. 当主程序都使用这两个系统时, 主程序引用了数据源myApp-dataSource
. 这三个数据源都指向相同的对象, 你能够将下面的别名配置添加到元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/> <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
如今, 虽然每一个组件和主程序都经过一个名称引用了各自惟一的数据源, 而且能保证不会与其余定义冲突(有效建立了命名空间), 然而实际上他们引用的同一个对象.
Java 配置
若是使用Java配置,
@Bean
注解能够提供别名, 参看:如何使用@Bean
注解.
bean定义的本质是建立对象的配方. 当须要时容器将查看bean的配方, 并使用该bean的定义封装的配置元数据来建立(或获取)实际对象.
若是使用XML配置, 要实例化的对象的类型是经过<bean/>
节点的class
属性来指定的. class
属性(对应到BeanDefinition
实例是Class
属性)一般是强制的. (例外的状况请参看:使用工厂方法实例化,和Bean定义的继承.) 有两种使用Class
属性的方法:
new
操做.内部类名称
若是你想要为一个静态内部类配置bean, 你就必须使用内部类的双名称.
例如, 你有个类定义为
SomeThing
,在com.example
包下.SomeThing
有个静态内部类为OtherThing
, 那它的class
属性的值将是com.example.SomeThing$OtherThing
注意内部类和外部类之间须要使用
$
字母分割.
当使用构造器建立bean时. 全部标准类均可用且都是可与Spring兼容的. 也就是开发时不须要实现任何接口或者遵循特定的编程风格. 简单定义为一个class便可. 尽管如此, 根据使用的IoC容器, 可能你须要定义一个默认构造器.
IoC容器能够管理任何你想要被托管的类. 它不只限于管理JavaBeans. 大多数Spring用户喜欢在属性后定义getter和sertter模块. 你也能够在容器中定义非bean风格的类. 例如, 若是你想使用遗留代码中彻底不遵循JavaBean规范的链接池, Spring也是能够管理的.
使用XML配置指定bean定义 以下:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
更多关于构造函数参数和对象构造后属性的赋值, 参看:依赖注入.
当定义使用静态工厂构建的bean时, 须要使用class
属性指定包含静态工厂方法的类, 而且使用factory-method
属性指定工厂方法.你能够调用该方法(带可选参数, 后面有表述)返回一个对象, 接着这个对象就能够像使用构造器建立出来的同样使用了. 一种这么使用bean定义的场景是在遗留代码中调用静态工厂.
下面指定了经过调用静态工厂方法建立bean的配置. 这个定义没有指定返回类型, 只是指定了这个类包含静态方法. 在本例中, createInstance()
方法必须是静态方法. 下面例子展现了如何指定静态方法:
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
下面展现了使用上述定义的类的代码:
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
有关静态方法的参数和对象从工厂返回后属性的赋值, 参看: 依赖和配置细节.
与静态工厂方法实例化相似.容器能够经过调用已存在的bean的非静态工厂方法去建立bean. 要使用这种机制, 能够将class
留空, 而且在factory-bean
属性指定当前(或父或祖先)容器中包含用来建立对象的工厂方法的bean. 使用factory-method
属性设置工厂方法的名称. 下面展现了怎么配置:
<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <!-- the bean to be created via the factory bean --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
下面代码展现了对应的java类:
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } }
工厂类能够有多个工厂方法的定义, 以下所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> <bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
下面是对应的java类:
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }
关于工厂bean自己如何经过DI管理和配置, 参看:依赖和配置细节.
Spring文档中,"factory bean"(工厂bean)是指Spring容器中配置的用来经过实例或静态工厂方法建立对象的bean. 相比而言, FactoryBean
(注意大小写)指Spring特有的FactoryBean
今后处开始升级到5.2.0版本
典型的企业应用不是由单个对象组成的(或用Spring语法来讲的bean).就算是最简单的程序也有一些终端用户看起来相互合做的对象来呈现.接下来的这节阐述如何在一个真实的系统中定义一系列的bean, 从而让他们协做达成目标.
依赖注入是一个处理过程, 在对象被构造后或者从工厂返回后, 仅仅经过构造函数参数, 工厂方法的参数或者属性赋值的方式来定义他们的依赖(也就是与其余对象协做). 容器在建立bean后注入他们的依赖. 这个处理过程本质上是bean本身去使用类构造和服务定位模式管理初始化和定位它的依赖项的反转(所以叫控制反转).
使用DI原则的代码是清晰的, 而且作到了与提供的依赖项更有效地解耦. 对象不本身定位查找依赖项, 也不知道依赖项的位置和类型.所以, 你的类就更容易被测试, 特别是依赖于接口和抽象类的状况下, 容许你单元测试中使用桩对象或模拟实现.
DI有两个主要的变种: 构造函数依赖注入和属性Setter依赖注入.
构造函数注入是经过容器调用有若干参数的构造函数完成的, 每一个参数表明一个依赖项. 调用带参的静态工厂方法构造bean与此十分相似, 这里讨论对待构造函数构造和静态工厂方法构造是类似的. 下例展现了构造器注入的类定义:
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
注意这个类没啥特别之处. 它自己就是一个没有实现容器相关接口,基类或使用注解的普通POJO.
构造参数是经过类型解析匹配的. 若是bean的构造参数没有潜在的二义性, 那么在bean中定义的参数顺序就是bean初始化时的参数顺序. 看下面的代码:
package x.y; public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { // ... } }
假设ThingTwo
和ThingThree
没有继承关系, 没有潜在的二义性. 所以, 下面的配置能很好的起做用, 在<constructor-arg/>
元素中你不须要制定构造参数的索引或明确制定其类型.
<beans> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg ref="beanTwo"/> <constructor-arg ref="beanThree"/> </bean> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> </beans>
当另外一个bean被引用时, 类型是已知的,匹配能发生(就像前面例子中的处理过程). 当使用简单类型时,例如<value>true</value>
, Spring不能决定值的类型, 所以没法自动匹配. 再看下面的类:
package examples; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
构造参数类型匹配
上述场景中, 若是使用type
属性给参数指定了类型, 容器就能经过类型匹配. 以下所示:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
构造参数索引
能够使用index
属性指定构造函数的参数, 以下:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
另外, 若是参数有多个简单类型, 能够使用索引解决多个简单类型参数的二义性.
参数是从0开始的.
构造参数名称
也能够使用指定构造参数名称消除二义性, 以下:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
记住, 要不受限制的使用此功能, 你的代码必需要启用debug标记编译, 这样Spring才能从构造函数查找参数名称. 若是不能或不想启用debug标记, 能够使用@ConstructorProperties
JDK注解显式添加到构造函数的参数上. 看起来如同下面例子:
package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
Setter方式的注入是调用无参构造函数实例化bean或静态工厂方法返回bean以后, 再由容器调用bean的setter方法.
下例展现了只能用纯setter方式进行注入的类定义. 这个类是传统的java. 是一个没有实现容器相关的接口,基类或添加注解的POJO.
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on the MovieFinder private MovieFinder movieFinder; // a setter method so that the Spring container can inject a MovieFinder public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
ApplicationContext
支持构造函数注入和Setter注入. 也支持经过构造函数注入部分依赖后再由Setter注入. 你使用BeanDefinition
类的形式配置依赖, 这个类与PropertyEditor
实例协做将属性从一种格式转化为另外一种格式. 尽管如此, 大多Spring用户不会直接使用这些类(在编程方面), 而是使用XML bean
定义, 或者注解注释组件(也就是使用@Component
,@Controller
等),或者用@Configuration
注解的类中使用@Bean
的java代码配置. 这些资源将在内部转化为BeanDefinition
实例并用于在IoC容器实例加载.
构造器注入仍是Setter注入
由于能够同时混用构造器和Setter注入, 一种比较好的原则是: 强制的依赖使用构造器注入, 可选的依赖则能够使用setter或配置方法. 注意: setter方法上使用
@Required
注解将使得对应属性成为必须的依赖.
Spring团队提倡构造器注入, 这会让你实现的程序组件是不可变对象而且能够保证依赖不是
null
. 更多的好处是, 构造器注入返回给客户端的组件老是彻底被初始化的状态. 还有个边缘效应, 就是大量的构造函数参数是一种很差的代码味道, 暗示着这个类承载了太多的责任而且须要被合理的重构, 实现关注点分离.
Setter注入应该在依赖是可选的前提下优先使用, 这些依赖能够被赋以合理的默认值. 另外, 非null验证必需要在代码使用依赖的全部地方进行. 使用setter注入的一个好处是: setter方法使得对象的重配置和重注入更易于控制. 经过JMX MBeans管理就是一种setter注入的案例.
使用DI使得特定类更加有意义. 有时候, 当你使用三方类库时, 你并无源代码, 此时你将手握DI选择权. 例如, 若是三方类没有暴露任何setter方法, 那么使用构造器注入将是惟一途径.
容器按照下面阐述的对bean依赖进行解析:
ApplicationContext
被建立, 而且使用配置的bean元数据进行初始化. 配置元数据能够是XML,Java code或注解.int
, long
, String
, boolean
等.Spring容器在建立后对每一个bean的配置进行校验. 尽管如此, 在bean被建立后bean的属性才会被赋值. 单例域的而且设置为预实例化(默认状况)的bean在容器建立后被建立. 域的定义参看bean的做用域. 除此以外, bean只有在被请求时才建立. 一个bean的建立会潜在地致使bean的整个图被建立, 也就是bean的依赖,它的依赖的依赖等都被建立和分配. 注意: 依赖解析的不匹配可能会后期表现出来, 也就是第一次建立受影响的bean时.
循环依赖
若是用占主导地位的构造器注入, 就可能会致使没法解析的循环依赖.
例如: 类A须要类B,经过构造器参数注入, 相反类B也须要类A,经过构造器注入. 若是配置A,B相互注入给对方, Spring的IoC容器就会在运行时检测到循环引用, 并抛出
BeanCurrentlyInCreationException
.
一种处理这种状况的方法是编辑源代码, 将一个的setter注入改成构造器注入. 相应地避免使用构造器注入而仅仅使用setter. 换句话说, 虽然不提倡, 但能够使用setter注入配置循环依赖.
不一样于典型的案例(即没有循环依赖), 在bean A和bean B之间的循环依赖将迫使bean在彻底实例化自身前将其注入给对方(这是典型的鸡生蛋蛋生鸡的场景).
整体上你能够信任Spring去作正确的事情. 它在容器加载期间检测配置问题, 如不存在的bean或循环依赖.当bean被真正建立后, Spring会尽可能延后设置属性和解析依赖. 这意味着在容器正确加载后, 若是你请求的bean有问题或它的依赖有问题,可能会抛出异常--例如, bean抛出缺失或无效属性的异常. 这种潜在的配置问题的延迟致使的不可见性就是为何ApplicationContext
的实现默认会预实例化单例bean. 这种会致使前期一些时间和内存消耗的代价能换来配置问题的及时发现, 就是在ApplicationContext
被建立时, 而不是之后调用bean时. 你也能够覆盖这种预初始化的配置为延迟初始化.
若是没有循环依赖, 当一个或多个协做的bean被注入到依赖的bean里面, 每一个协做bean实际上是优先于依赖bean就被彻底配置好了. 这意味着若是A依赖B, 容器会在调用A的setter方法以前彻底配置好B. 换句话说,这个bean被实例化了(若是它不是预实例化的),它的依赖被设置了, 而且他的生命周期函数(如配置的初始化函数或初始化bean回调函数)也被调用了.
下例使用XML配置setter形式的DI. 一小段bean的定义以下:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面展现了对应的ExampleBean
类:
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 id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater ref attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面展现了对应的ExampleBean
类定义:
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; } }
在bean定义的构造函数参数用来经过类ExampleBean
的构造函数参数注入.
如今改变下例子, 不用构造函数注入, 而使用静态工厂方法返回对象实例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面展现了对应的ExampleBean
类:
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
给静态工厂方法的参数是<constructor-arg/>
元素提供的, 就像使用构造函数同样. 工厂方法返回的实例类型不须要与包含静态工厂的类的类型一致(本例一致). 一个(非静态)实例工厂本质上使用相同方式(除了使用factory-bean
而不是class
属性),因此咱们不讨论这些细节.
在前面的章节中提到, 你能够定义bean的属性或者经过构造函数参数去引用另外的bean(协做者)或者在行内书写数据值. 为了能这样作, Spring的XML配置能够使用<property/>
和<constructor-arg/>
元素包含在bean定义元素中.
元素<property/>
的属性value
能够指定为书写或构造参数的纯值数据. Spring的转化服务用来将这些值从String
转化为合适的类型. 下例展现了一组这样的配置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- results in a setDriverClassName(String) call --> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="masterkaoli"/> </bean>
下例使用p-
命名空间展现更简洁的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/> </beans>
上面的XML更为精简. 类型转化是发生在运行时而不是设计时, 除非你使用可以在定义bean时支持属性自动完成的IDE(就像IntelliJ IDEA 或 Spring Tool Suite). 而这样的IDE辅助是咱们提倡的.
也能够配置java.util.Properties
类型的实例, 以下:
<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean>
Spring 容器转化内部<value/>
元素的内容为java.util.Properties
的实例, 这是经过使用JavaBeans的PropertyEditor
机制来实现的. 这是一个捷径, 也是几种Spring团队喜欢使用嵌套<value/>
而不是value
属性风格的状况之一.
idref
元素idref
元素仅是一种防止错误的方法, 能够将容器中的另外一个bean的id(一个字符串,不是引用)传递给属性或构造参数. 以下所示:
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean"/> </property> </bean>
上面bean的定义彻底等效于(在运行时)下面的片断:
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean"/> </bean>
第一种形式优于第二种, 由于使用idref
标签可以让容器在部署期间检查引用的bean是否是真的存在. 第二种状况下, 传递给bean名为client
的targetName
属性的值不会被校验. 仅仅是在client
被实际实例化的时候会发生类型转化(大多数状况下是严重错误). 若是client
bean是个原型bean, 则只有在部署容器好久以后才会发现错误和由此产生的异常.
idref
元素的local
属性再也不支持4.0版本的beans XSD, 由于它再也不提供常规bean
引用的值. 当升级到4.0时, 请修改现有的idref local
引用, 修改成idref bean
.
<idref/>
元素带值的一个地方(至少在Spring2.0以前的版本中)是在ProxyFactoryBean
定义中AOP拦截器的配置. 当你为了防止拼写错拦截器ID而指定拦截器名称时使用<idref/>
元素.
ref
元素是在<constructor-arg/>
或者 <property/>
中的不可更改的元素. 在这里, 你将另外一个由容器管理的bean(协做者)做为引用的值赋给一个bean的属性. 被引用的bean做为引用被赋值给这个bean的属性, 而且它是在被赋值前就按需初始化的. (若是这个协做者是个单例的话,它已经被容器初始化了).全部引用最终都是另外一个对象的引用. 做用域和校验则取决于你是否经过bean
,local
,partent
属性为另外一个对象指定ID或名称.
经过<ref/>
tag的bean
属性指定目标bean是常见的方式, 而且容许其在同一个容器或父容器中建立任何bean的引用. 无论是否是配置在XML格式的文件. bean
属性的值能够是目标bean的ID后者是name中的任一值. 下面展现了ref
元素:
<ref bean="someBean"/>
经过parent
属性建立的引用指定目标bean是在当前容器的父容器. 其值多是目标bean的id或name的其中任一值. 目标bean必须在当前容器的父容器中. 主要使用这个属性的场景是: 当你使用了有层次的容器而且在父容器中经过代理proxy包装了一个同名的父bean. 下面是一对使用parent
的例子:
<!-- in the parent context --> <bean id="accountService" class="com.something.SimpleAccountService"> <!-- insert dependencies as required as here --> </bean>
<!-- in the child (descendant) context --> <bean id="accountService" <!-- bean name is the same as the parent bean --> class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref parent="accountService"/> <!-- notice how we refer to the parent bean --> </property> <!-- insert other configuration and dependencies as required here --> </bean>
4.0的beans XSD 后
ref
元素的local
属性再也不支持, 所以不须要用它为一个正常的bean
引用提供值了. 当升级到4.0时请注意修改现有的ref local
到ref bean
.
<bean/>
元素若是定义在<property/>
或者<constructor-arg/>
内部, 则表示定义了内部bean, 以下所示:
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
内部bean定义不须要指定ID或name, 若是指定了, 容器也不会使用它们做为bean的标识符. 容器也会在建立时忽略它们的scope
标记, 由于内部bean一般都是匿名的, 而且老是跟外部bean一块儿建立. 通常不可能去单独的访问内部bean, 或者将他们注入到协做bean而不是包装bean中.
做为旁例, 从一个自定义的scope中获取到销毁回调是可能的, 例如对于一个单例bean中做用域为request-scope的内部bean. 内部bean的建立是与外部bean的建立是绑定的, 可是销毁回调使它特定于request生命周期. 这并非一个广泛的场景, 内部bean通常是与包含它的bean有着相同的做用域.
<list/>
,<set/>
,<map/>
和 <props/>
元素分别 对应于Java集合类型(Collection
)的List
, Set
, Map
, 和 Properties
. 下例展现其用法:
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
字典map的键值,或者集set的值, 能够是下面元素的任一:
bean | ref | idref | list | set | map | props | value | null
Spring容器支持合并集合. 开发人员能够定义一个父的<list/>
,<set/>
,<map/>
和 <props/>
元素,而且子元素的<list/>
,<set/>
,<map/>
和 <props/>
能够继承和覆盖父集合的值. 也就是说, 子集合的值是父集合与子集合元素的合并, 子集合的元素覆盖了父集合中的值.
本节讨论父子bean的合并机制. 读者要是不了解父子bean的定义能够参看相关章节,而后再回来.
下例展现了集合的合并:
<beans> <bean id="parent" abstract="true" class="example.ComplexObject"> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.com</prop> <prop key="support">support@example.com</prop> </props> </property> </bean> <bean id="child" parent="parent"> <property name="adminEmails"> <!-- the merge is specified on the child collection definition --> <props merge="true"> <prop key="sales">sales@example.com</prop> <prop key="support">support@example.co.uk</prop> </props> </property> </bean> <beans>
注意merge=true
的使用, 它在beanchild
的<props>
元素中名为adminEmails
. 当child
被容器解析和初始化后, 最终的实例将有一个adminEmails
的Properties
集合, 包含了合并父集合与子集合中adminEmails
的所有元素. 下面展现告终果:
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
子Properties
集合的值继承了父<props/>
, 而且子集合中support
值覆盖了父集合中的值.
这种合并行为也一样相似于<list/>
, <map/>
, 和 <set/>
等集合类型. 在<list/>
元素的特定状况下, 其语义与List
集合类型相关联(也就是一个一系列值的有序集合). 父集合的值优先于子集合的值. 在Map
, Set
, 和 Properties
类型状况下, 元素间不存在顺序. 所以,容器内部使用构筑在Map
,Set
,Properties
实现类型之上的无序集合类型.
不能合并不一样的集合类型(如Map
和List
进行合并). 若是这样作, 会抛出相应异常. merge
属性必须在继承或者较低的子定义上. 指定在父集合定义上的merge
是多余的,也不会产生指望的合并.
从Java5的泛型集合开始, 你能够使用强类型的集合了. 也就是声明一个值包含(例如)String 类型的元素集合成为可能. 若是使用Spring的DI去注入一个强类型集合, 你能够获得Spring类型转化支持, 将先前添加到Collection
的元素转化为正确的类型. 下例展现了用法:
public class SomeClass { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<beans> <bean id="something" class="x.y.SomeClass"> <property name="accounts"> <map> <entry key="one" value="9.99"/> <entry key="two" value="2.75"/> <entry key="six" value="3.99"/> </map> </property> </bean> </beans>
当beansomething
的account
属性准备注入时, 它的泛型类型信息被反射获取到. 所以, Spring的类型转化基础设施辨别出元素的值是Float
, 而且将字符串值(9.99,2.75 和 3.99
)转化为实际的Float
类型.
Spring将properties的空参数视为空字符串. 下面的XML配置片断设置email
属性为空值("").
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
上面的例子等效于下面的Java代码:
exampleBean.setEmail("");
<null/>
元素处理null值. 以下所示:
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
上面代码等效于:
exampleBean.setEmail(null);
p-命名空间能够在bean
元素的属性中使用,(而不是嵌套的<property/>
元素), 能够用来描述属性值或协做bean.
Spring支持用命名空间扩展配置格式, 这基于XML架构定义. 这节讨论的bean
配置格式定义在XML架构文档中. 尽管如此, p-命名空间没有定义在XSD文件中, 值存在于Spring的核心.
下面展现了两段XML(第一段是标准的XML, 第二段是p-命名空间), 他们有相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="someone@somewhere.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="someone@somewhere.com"/> </beans>
这个例子展现了bean定义中有个p-命名空间叫email
. 这实际是告诉Spring有个属性声明. 如前面提到的, p-命名空间没有架构定义, 所以你能够用属性的名字设置标签属性名称.
下面展现了两个bean定义中同时引用了另外一个bean.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" ref="jane"/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" p:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>
这个例子包含了不止是用p-命名空间的一个属性, 并且使用了声明属性引用的格式. 第一个bean定义使用<property name="spouse" ref="jane"/>
建立了从beanjohn
到jane
的引用, 第二个定义则使用了p:spouse-ref="jane"
来完成相同的事情. 本例中, spouse
是属性名, 同时-ref
代表这不是一个表值而是对另外一个bean的引用.
p-命名空间不如标准xml格式灵活. 例如, 声明属性引用的这种格式与使用
ref
的格式冲突. 而标准的XML则不会. 咱们强烈建议你细心选用合适的方式, 与团队沟通来避免产生同时使用三种格式的XML文档.
相似于p-命名空间. 从Spring3.1开始, c-命名空间容许将构造参数配置在行内, 而不是单独嵌套的<constructor-arg/>
元素内.
下例使用c:
命名空间实现与构造函数注入相同的事情:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> <!-- traditional declaration with optional argument names --> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg name="thingTwo" ref="beanTwo"/> <constructor-arg name="thingThree" ref="beanThree"/> <constructor-arg name="email" value="something@somewhere.com"/> </bean> <!-- c-namespace declaration with argument names --> <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="something@somewhere.com"/> </beans>
c:
命名空间使用与p:
命名空间相同的约定来设置构造函数参数(对于引用使用-ref
后缀).类似的, 须要声明在XML文件中, 虽然在XSD架构中未定义(它存在于Spring的内核中).
对于少数构造函数名称不可用的状况(一般是没有debug信息的已编译二进制文件),能够使用参数索引, 以下:
<!-- c-namespace index declaration --> <bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree" c:_2="something@somewhere.com"/>
鉴于XML语法, 索引必须以
_
开头, 由于XML属性名称不能以数字开头(虽然一些IDE能够). 相应的,元素<constructor-arg>
中也可以使用索引数字,但不经常使用, 由于声明时的顺序已经足够了.
实践中, 构造函数解析机制对于匹配参数已经足够了, 因此除非你真正须要, 咱们建议使用名称标记贯穿整个程序.
在设置bean属性时能够使用复合或者嵌套的属性名称. 只要路径下全部组件不为null
便可. 以下定义:
<bean id="something" class="things.ThingOne"> <property name="fred.bob.sammy" value="123" /> </bean>
something
bean有个fred
属性, fred
又有个bob
属性, bob
有个sammy
属性, 最终sammy
属性被赋值为123
. 为了能使其起做用, 在bean被构建后, fred
和其bob
必须不为null
.不然NullPointerException
将被抛出.
depend-on
若是一个bean是另外一个bean的依赖, 也就意味着一个bean会做为另外一个bean的属性值. 在XML配置中你使用<ref/>
元素来配置. 但有时候bean之间的依赖关系不是直接的. 举个例子, 一个类的静态初始化器须要被触发,好比对于数据库驱动注册. depends-on
属性可以显式的迫使一个或多个bean的初始化, 这发生在使用了这个元素的bean初始化以前. 下例使用depends-on
展现只有一个bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
对于多个依赖, 能够为depends-on
属性指定多个值(用分号,空格, 逗号分隔)
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on
属性可以指定初始化期间的依赖. 仅在单例bean中, 指定相应的销毁期依赖. 用depends-on
关系定义的依赖bean将被首先销毁, 优先于它自身修饰的bean. 所以,depends-on
能够用来控制关闭顺序.
默认状况下,ApplicationContext
实现会立马建立和配置全部单例bean, 做为其初始化步骤的一部分. 一般,预初始化时使人满意的, 由于配置和环境错误能够被及时发现, 而不是通过几小时,几天. 当这种行为不使人满意时, 你能够经过将bean定义为延迟加载而阻止预初始化发生. 一个延迟加载的bean告知IoC容器,这个bean是在第一次请求时建立,而不是容器启动时.
XML中, 经过<bean/>
元素的lazy-init
属性控制这种行为. 以下:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.something.AnotherBean"/>
当上面的配置被ApplicationContext
处理时, 其启动时lazy
bean不会被当即初始化, 而not.lazy
bean将当即被初始化.
尽管如此, 当一个延迟初始化bean是一个非延迟初始化bean的依赖时, ApplicationContext
在启动时建立了延迟bean, 由于它必须知足单例的依赖. 延迟初始化bean被注入到非延迟的单例bean中了.
也能够在容器级别经过<beans/>
元素的default-lazy-init
属性控制延迟加载行为. 以下所示:
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
Spring容器可以自动装配协做bean之间的关系. 你可让容器经过检查ApplicationContext
中的内容自动解析协做bean. 自动装配有以下好处:
自动装配能够显著减小指定属性和构造参数的须要. (对于本章其余地方讨论的bean模板等机制也是有价值的).
自动配置能够随着对象发展而更新配置. 例如, 若是须要向类添加依赖,则能够自动知足依赖而不需手工配置. 所以,自动装配在开发阶段颇有用, 不会在代码变得更稳定时拒绝切换到显式书写的选项.
当时用XML配置时(参看依赖注入), 你能够经过<bean/>
元素的autowire
属性为一个bean指定自动装配模式. 自动装配功能有四种模式. 你能够任选其一. 下表描述了这四种模式:
表2. 自动装配模式
模式 | 说明 |
---|---|
no | (默认)不使用自动装配. bean的引用必须使用ref 元素. 对于较大部署不推荐修改这个模式设置. 由于显式指定的协做者提供了各大的控制权和可读性. 某种意义上是一个系统的架构文档. |
byName | 经过属性名称自动装配. Spring经过名称查找须要自动装配的bean. 例如: 若是一个bean定义按名字自动装配, 而且包含了一个master 属性(也就是同时有setMaster(..) 方法), Spring会查找名为master 的bean定义, 而且将其设置到属性. |
byType | 若是容器中有匹配property类型的bean就自动装配. 若是多于一个, 将抛出致命异常, 这代表可能不该该使用byType 匹配. 若是没有匹配的bean, 则忽略(属性不被赋值) |
constructor | 与byType 相似可是是提供给构造函数参数的. 若是容器中没有精确类型的bean, 致命错误将发生 |
经过byType
和constructor
装配模式, 你能够装配数组和泛型集合. 这种状况下, 容器中全部类型匹配的候选对象都将提供以知足依赖. 对于Map
,若是key的类型是String
,你就能够自动装配. 一个自动装配的Map
实例的值是由全部匹配类型的bean组成的, 这个实例的key包含对应bean的名称.
自动装配使用在贯穿整个项目的过程当中能工做得很好. 若是不是广泛使用, 而只是使用到一两个bean上时会搞得开发人员头晕.
参考自动装配的限制和不足:
property
和constructor-arg
设置的显式依赖老是会覆盖自动装配. 不能自动装配简单类型,String
和Class
(或者这些类型的数组). 这个限制是专门设计的.
比起显式装配, 自动装配精确度较低. 虽然, 正如前面表格提到的, Spring很是当心地避免在多个指望结果下致使的二义性进行猜想. Spring管理的对象间的关系以及不是显式文档定义的了.
装配信息多是不可用的, 对于从Spring容器生成的文档的工具而言.
容器内多个bean定义可能会匹配到自动装配的setter类型或构造参数类型.对于数组,或者Map
实例, 这不是一个问题. 然而对于指望单一值的依赖, 这种二义性不能被随意处理, 若是没有惟一bean可用,异常将会抛出.
对于后面的几种场景, 你可能有以下几个选择:
抛弃自动装配, 拥抱显式装配
设置bean的autowire-candidate
为false
以防止自动装配, 正像下一节描述的.
经过设置<bean/>
元素的primary
属性为true
将其定义为优先匹配对象.
使用有更多细粒度控制的注解驱动配置, 如在注解配置中描述的.
在单个bean级别, 你能够从自动装配中排除bean.在XML配置中, 能够经过设置bean的autowire-candidate
为false
. 容器能使得特定bean排除在自动装配以外(包括注解格式的配置如@Autowired
).
autowire-candidate
属性被设计为仅对基于类型的装配有效. 对于经过name引用的显式引用无效, 即便指定的bean没有被标记为候选也会被解析. 结果就是当名字匹配时, 经过name自动装配仍然会注入bean.
你能够经过基于bean名称的模式匹配去限制bean的自动装配. 根级别元素<beans/>
经过属性default-autowire-candidate
接收一个或多个模式.例如:限制名称以Repository
结尾的bean的候选状态, 能够使用模式*Repository
. 能够经过逗号分隔多个模式. bean元素上的autowire-candidate
属性的值true
或false
老是有优先权. 对于这些bean, 模式规则不生效.
这些技术对于从没想要经过自动装配注入的bean是有用的. 这并不意味着被排除的bean本身不能经过自动装配所配置, 而是它自己将不会做为bean的候选装配给其余bean.
大多数应用场景中, 容器中的不少bean都是单例的. 当一个单例的bean须要与另外一个单例bean协做, 或者一个非单例bean须要与另外一个非单例bean协做时, 通常须要经过将一个bean做为另外一个bean的属性来处理依赖关系. 当bean的生命周期不一样时将会发生问题. 假设一个单例bean A须要一个非单例(原型)bean B, 也许每一个方法都有调用. 容器只建立A一次, 所以只有一次机会设置它的属性. 一旦有用到, 容器不能老是使用B的新实例提供给A.
一种解决方案就是抛弃依赖注入. 你能够使一个Bean A 经过实现接口ApplicationContextAware
被容器所感知, 而且a经过getBean("B")
请求容器每次都得到到b的新实例.下面代码演示了这种方法:
// a class that uses a stateful Command-style class to perform some processing package fiona.apple; // Spring-API imports 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) { // grab a new instance of the appropriate Command Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } protected Command createCommand() { // notice the Spring API dependency! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
上面代码并不使人满意, 由于业务代码与Spring框架耦合在一块儿了. 方法注入, 是一种Spring容器更为高级的特性, 可以让你更聪明滴处理此类问题.
你能够从这篇博客中获取到更多方法注入的动机.
查找方法注入是重写容器管理的bean并查找另外一个容器中的命名bean将其返回的能力. 查找通常是一个原型bean, 就像在前面章节讨论的. Spring框架经过CGLib库的二进制代码动态生成子类重写方法.
为了能让动态的子类工做, 须要为其生成子类的bean不能是final
, 同时须要覆盖的方法也不能是final
.
进行单元测试时, 若是有abstract
方法的类须要你本身去定义子类而且提供abstract
方法的桩实现.
具体方法也是须要能组件扫描的, 这就须要获取具体类.
另外一个关键的限制查找方法和工厂方法,特别是与配置类中的@bean
注解方法不兼容. 这种状况下, 容器再也不控制建立实例所以也就不能在运行时建立子类.
在前面的CommandManager
类的代码片断中, Spring容器动态覆盖实现了createCommand()
方法. 类CommandManager
没有任何Spring依赖, 以下所示;
package fiona.apple; // no more Spring imports! public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
在客户端代码中包含了须要注入的方法, 这个被注入的方法须要以下的格式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
若是方法是abstract
修饰的, 子类将实现这个方法, 不然动态生成的子类将重写定义在源类中的实际方法. 以下所示:
<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="myCommand"/> </bean>
id定义为commandManager
的bean调用其createCommand()
方法, 在其须要myCommand
实例的时候. 你必须当心地部署beanmyCommand
为原型bean. 若是它是一个单例, 则每次返回的都是相同的实例.
也能够使用组件注解的方式, 你能够经过@Lookup
注解在方法上, 以下所示:
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup("myCommand") protected abstract Command createCommand(); }
或者, 更规范地, 你能够信任目标bean经过返回的类型解析获得目标bean.
public abstract class CommandManager { public Object process(Object commandState) { MyCommand command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract MyCommand createCommand(); }
注意一般你须要用一个桩实现来声明这种带注释的查找方法, 这样他们才能与Spring组件扫描兼容, 默认状况下, 抽象类会被扫描忽略. 这个限制不适于显式注册或显式导入bean类.
另外一个访问不一样做用域的方法是使用
ObjectFactory
/Provider
链接点. 参看: 不一样做用域的bean依赖.
你能够发现
ServiceLocatorFactoryBean
(在包org.springframework.beans.factory.config
)是颇有用的.
一种比查找方法注入不那么有用的形式是在一个管理bean中使用另外一个方法实现去替换方法. 你能够跳过这节, 直到你须要这种机制再回来看.
使用XML配置的元数据时, 你能够使用replaced-method
元素替换一个已经存在的方法. 请看下面的类定义, 它有个方法computeValue
须要被重写:
public class MyValueCalculator { public String computeValue(String input) { // some real code... } // some other methods... }
实现接口org.springframework.beans.factory.support.MethodReplacer
提供了一个新的方法定义,以下所示:
/** * meant to be used to override the existing computeValue(String) * implementation in MyValueCalculator */ public class ReplacementComputeValue implements MethodReplacer { public Object reimplement(Object o, Method m, Object[] args) throws Throwable { // get the input value, work with it, and return a computed result String input = (String) args[0]; ... return ...; } }
须要部署的源bean定义和须要覆盖的方法应该按以下方式组合:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator"> <!-- arbitrary method replacement --> <replaced-method name="computeValue" replacer="replacementComputeValue"> <arg-type>String</arg-type> </replaced-method> </bean> <bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你能够为元素<replace-method/>
须要覆盖的方法签名指定一个或多个<arg-type/>
元素. 只有类中的方法被重载而且有多个的时候才须要参数签名. 为了方便, String类型的参数能够只是一个缩写, 例如,下面的写法都匹配java.lang.String
java.lang.String String Str
由于参数的个数常常能区分出可能的选择, 所以这种缩写能节省大量输入时间, 经过一个短字符串来匹配一个参数类型.
当建立bean定义的时候, 实际上你就有个经过bean定义去建立类真实实例的配方. bean定义是配方的想法是很是重要的, 由于它意味着,你能够经过一个配方去建立多个实例.
经过bean的定义, 你不只能够控制插入到对象的依赖和配置值, 还能够控制经过bean定义的对象的做用域. 这种方式是强大而灵活, 由于你能够选择经过配置生成的对象的做用域, 而不是必须在class级别操做对象的做用域. bean能够定义为几个做用域其中的一个. Spring框架支持六种做用域, 四种仅能够用在web类型的ApplicationContext
中. 你也能够自定义scope.
下面表格描述了支持的做用域:
表 3. Bean 做用域
Scope | Description |
---|---|
singleton | (默认)对于每一个IoC容器来讲, 处理每一个bean定义的对象实例仅有一个 |
prototype | 处理一个bean定义能够有多个对象实例 |
request | 处理对于每一个HTTP请求仅有一个实例. 也就是对于每一个bean定义, 每一个HTTP请求都有它本身的实例. 仅仅在Web类型的Spring ApplicationContext 中是可用的. |
session | 处理在一个HTTPSession 范围内一个bean的定义. 仅仅在Web类型的Spring ApplicationContext 中是可用的. |
application | 处理在ServletContext 级别的bean的定义. 仅仅在Web类型的Spring ApplicationContext 中是可用的. |
websocket | 处理在WebSocket 级别的bean的定义, 仅仅在Web类型的Spring ApplicationContext 中是可用的. |
从Spring3.0开始, 线程级别的做用域是可用的, 但不是默认注册的. 关于更多请参看
SimpleThreadScope
的文档. 关于如何注册这个做用域或者其余自定义做用域的指导, 请参看自定义做用域.
容器内仅仅有一个共享的bean实例, 而且全部经过bean定义中的id或者id列表仅能匹配出惟一的特定bean的实例.
换个说法, 当你定义了一个bean,而且将其设置为singleton做用域时, Spring IoC容器建立了bean定义的惟一实例. 这个惟一实例是存储在此类单例bean的缓存中的, 而且全部子请求和引用都会返回缓存的bean. 下面的图展现了单例bean如何工做:
Spring单例bean的概念不一样于GOF模式书中的单例模式. GoF单例硬编码了对象的做用域, 因此对于每一个ClassLoader,有且仅有一个bean实例被建立. Spring的单例bean做用域对于每一个容器每一个bean来讲有且仅有一个. 这意味着, 若是你在每一个容器中定义一个指定的bean, 容器将为每一个bean的定义生成一个且仅有一个bean的实例. 单例做用域是Spring默认的. 要用XML定义一个单例bean, 你能够参看下面定义:
<bean id="accountService" class="com.something.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
非单例的原型做用域bean将在每次请求的时候建立一个新的实例. 也就是这个bean被注入另外一个bean或者经过容器的getBean()
方法调用获取. 原则上, 使用须要保持状态的bean时使用原型做用域, 使用状态无关的bean时使用单例bean.
下图说明了Spring的单例做用域:
(DAO对象不是典型的原型做用域, 由于一个DAO不保持任何会话状态. 咱们重用单例的说明图是很容易的)
下面例子定义了XML格式的原型bean
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
同其余做用域相比, Spring不会管理原型bean的完整生命周期. 容器除了将其实例化,配置,另外还有组装和将其提供给client外, 不会有更多的原型实例的任何记录了. 所以, 虽然初始化回调方法由全部做用域的对象都会调用, 但在原型模式来讲, 配置的销毁回调方法不会被调用. 客户端代码必须清理原型对象并释放被其占用的任何昂贵的资源. 为了使Spring容器能获取到原型bean占用的资源, 尝试使用自定义的post-处理器, 这个处理器维护这须要被清理的bean引用.
在某些方面, Spring容器对于原型bean的做用是对new操做符的代替. 过去全部的生命周期管理都是客户端来维护. (关于Spring中bean生命周期的更多信息, 参看:生命周期回调)
当使用有原型bean作为依赖的单例bean时, 记住依赖是在实例化的时候解析的. 所以,若是你将一个原型bean注入单例bean, 那这个原型bean是做为新的bean被初始化了, 而且注入到了单例bean中. 这个原型bean是单例bean的独占实例.
尽管如此, 假设你想要在运行期间为单例bean屡次重复获取原型bean. 你不能讲原型bean注入到单例bean中, 由于注入只发生了一次, 就在容器初始化单例bean并解析他的依赖的时候. 若是你须要一个运行时的原型bean, 参看:方法注入.
request
,session
,application
,websocket
做用域只有在web类型的Spring ApplicationContext
(好比XmlWebApplicationContext
)实现中可用.若是在标准的Spring容器中使用这些做用域, 好比ClassPathXmlApplicationContext
, 则IllegalStateException
将会因未知做用域而抛出.
为了支持bean的这几个做用域(request
,session
,application
,websocket
(web做用域bean)), 在定义bean时还须要作少许的配置. (对于标准的做用域singleton
和prototype
,初始化设置是不须要的)
如何完成初始化设置取决于你特定的Servlet环境.
若是你使用Spring web MVC访问做用域bean, 实际上,请求是由Spring的DispatcherServlet
处理的, 不须要其余的设置. DispatcherServlet
已经暴露了全部的相关状态.
若是你使用Servlet 2.5 的Web容器, 当不使用DispatcherServlet
处理时(好比使用JSF或Struts), 你须要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener
. 对于Servlet 3.0+, 能够经过接口WebApplicationInitializer
编程完成. 或者做为替换,包括使用旧的容器的话, 在web.xml
文件中添加以下声明:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
或者, 若是监听器设置有异常, 考虑使用Spring的RequestContextFilter
. filter映射依赖于包含其的web程序配置, 因此你必须合理修改下. 下面列出web程序的部分配置:
<web-app> ... <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>
DispatcherServlet
,RequestContextListener
,还有RequestContextFilter
都作了一样的事情, 即绑定Http请求对象到服务请求的Thread
. 这使得bean在调用链中能够使用request-和session-做用域.
参考以下XML配置bean:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器使用loginAction
定义为每一个HTTP请求建立bean LoginAction
的实例. 也就是loginAction
bean在HTTP请求级别做用域. 你能够随意修改实例的内部状态,由于被loginAction
bean定义实例化出来的其余对象看不到这些修改. 他们是特定于单独的request的. 当请求完成处理后, request做用域的bean就被抛弃了.
当使用注解驱动的组件或java代码配置时, @RequestScope
注解能用来分配给一个request
做用域的组件. 下面例子展现了如何使用:
@RequestScope @Component public class LoginAction { // ... }
参看下面XML配置的bean定义:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器经过userPreferences
bean的定义为每一个HTTP Session
生成UserPreferences
bean的实例. 换句话说, userPreferences
bean 在 HTTP Session
做用域级别. 跟request做用域bean同样, 你能够随意修改实例的内部状态, 而其余由同一个userPreferences
bean定义生成的 HTTP Session
的实例不会看到这些变化, 由于他们特定于单独的 HTTP Session
. 当 HTTP Session
最终再也不使用时, 对应的Session做用域的bean也就再也不使用了.
当使用注解驱动组件或java代码配置时, 你能够使用@SessionScope
注解到session
做用域的相关组件上.
考虑下面XML的bean定义:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring容器使用appPreferences
bean的定义建立了AppPreferences
bean的实例. 也就是appPreferences
bean是在ServletContext
级别的, 而且做为一个标准的ServletContext
属性存储. 这相似于Spring单例bean, 但有两点重要的不一样: 它是在每一个Servlet
上的单例, 不是在每一个Spring'ApplcationContext'上的单例(可能在任何web程序中有多个),而且实际上它是暴露的而且所以是做为ServletContext
属性可见的.
当使用注解驱动或者java代码配置时, 你能够使用@ApplicationScope
注解到application
组件上. 以下所示:
@ApplicationScope @Component public class AppPreferences { // ... }
Spring容器不只管理对象(beans)的初始化, 也包含其组装协做者(依赖项). (举例来讲)若是你想要将一个HTTP request做用域的bean注入到另外一个更长生命周期的bean中, 你能够选择注入一个AOP代理对象代替bean. 也就是说, 你须要使用暴露了与做用域对象相同接口的代理对象注入, 这个代理对象可以从相关做用域中获取到真实对象(例如一个HTTP请求)而且委托调用真实对象上的方法.
在单例bean之间你也能够使用
<aop:scoped-proxy/>
, 经过一个中间代理引用, 这个代理是可序列化的,所以能在反序列化时从新获取目标单例bean.
当在一个
prototype
做用域的bean上声明<aop:scoped-proxy/>
时 , 每一个请求共享代理对象的方法将会将其引导到新建实例上并调用.
同时, 在做用域安全的方式下, 从一个短做用域访问bean, 做用域代理并非惟一的方法. 你也能够用
ObjectFactory<MyTargetBean>
声明你的注入点(也就是,构造器或setter参数或者自动装配域), 容许经过getObject()
在须要对象时获取当前实例-- 而不是持有其实例或单独存储之.
做为扩展的变体, 你能够声明
ObjectProvider<MyTargetBean>
, 这个对象有几个访问方式的变体, 包含getIfAvailable
和getIfUnique
.
JSR-330 中这被叫作
Provider
而且用来与Provider<MyTargetBean>
一块儿使用, 而且用get()
方法获取. 关于JSR-330更多请参考这里.
下面例子中只有一行, 但理解其在背后的缘由比怎么作更重要:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- an HTTP Session-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.something.UserPreferences" scope="session"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> <!--这里定义了代理--> </bean> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="userService" class="com.something.SimpleUserService"> <!-- a reference to the proxied userPreferences bean --> <property name="userPreferences" ref="userPreferences"/> </bean> </beans>
为了建立代理, 须要插入一个子元素<aop:scoped-proxy/>
到做用域bean的定义中. (参看建立代理的类型选择和XML配置方式). 为何定义在request
,session
或者自定义做用域上的bean须要<aop:scoped-proxy/>
? 考虑下面单例bean的定义并对比上面表述的做用域(注意下面userPreferences
bean定义是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/> <bean id="userManager" class="com.something.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
上面例子中, 单例bean(userManager
)注入了HTTP Session
-做用域的bean中(userPreferences
). 这里的突出点是userManager
是单例的: 它是每一个容器惟一的, 而且它的依赖(在本例中是userPreferences
bean)仅仅注入了一次. 这意味着userManager
bean都是在相同的userPreferences
对象上进行操做的(也就是最初被注入的那个).
这不是你想要的结果, 当将一个短生命周期的bean注入到长生命周期的bean中时(例如, 注入一个HTTP Session
做用域的协做bean到单例bean中). 相反, 你须要一个单例的userManager
对象, 而且对于HTTPsession
生命周期, 你须要userPreferences
对象并将其指定为HTTP Session
. 所以, 容器建立了一个暴露了与UserPreferences
类相同公共接口的对象.(理想状况下这个对象是UserPreferences
实例), 这个对象可以从做用域机制中(HTTP request,session等)获取到真实的对象. 容器将代理对象注入到userManager
bean, 它并不知道UserPreferences
引用是个代理. 本例中, 当UserManager
实例调用注入对象UserPreferences
的方法时, 实际上它是调用代理的方法. 代理而后获取从HTTP Session
做用域(本例中)的真实UserPreferences
对象, 并调用真实对象上的方法.
所以, 你须要下面的配置(完整而正确), 当你注入request-
和session-
做用域的bean时, 以下:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean> <bean id="userManager" class="com.something.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
默认状况下, 当Spring容器用标记为<aop:scoped-proxy/>
元素的bean建立代理时, 一个CGLIB类代理对象就被建立了.
CGLIB代理仅拦截公共调用! 不调用代理上的非公共方法. 他们不会被代理到目标做用域对象上.
或者, 你能够使用标准JDK基于接口的代理为做用域bean配置容器. 经过将元素<aop:scoped-proxy>
的属性proxy-target-class
设置为false
. 使用JDK基于接口的代理意味着你不须要在classpath下引用多余的库. 尽管如此, 这也意味着做用域bean必须实现至少一个接口而且全部注入的的做用域bean必须经过接口引用. 下例展现了如何使用接口代理:
<!-- DefaultUserPreferences implements the UserPreferences interface --> <bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session"> <aop:scoped-proxy proxy-target-class="false"/> </bean> <bean id="userManager" class="com.stuff.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
关于选择基于类的仍是基于接口的代理, 请参看: 代理机制.
bean的做用域机制是可扩展的. 你能够自定义或者从新定义已经存在的做用域, 虽然稍后会知道这不是最佳实践并且你不能重写内置的singleton
和prototype
做用域.
为了集成自定义的做用域到容器中, 你须要实现接口org.springframework.beans.factory.config.Scope
, 这个接口将会在本节描述. 对于如何实现自定义做用域的观念, 参看Spring框架实现的Scope
实现还有Scope
java文档, 里面解释了更多须要实现的方法.
Scope
接口有四个方法, 用来从做用域获取,移除,销毁对象.
以session做用域的实现为例, 返回session做用域bean(若是不存在,方法在这个实例将在绑定到session后以备未来引用, 而后返回一个新实例).下面的方法从潜在做用域返回对象:
Object get(String name, ObjectFactory objectFactory)
以session做用域的实现为例, 从潜在的session做用域删除bean. 对象应该被返回, 但若是指定名称的bean找不到, 你能够返回null. 下面的方法将从潜在做用域删除对象:
Object remove(String name)
下面的方法为做用域注册了回调, 将在它被销毁或者看成用域内的指定对象销毁时执行:
void registerDestructionCallback(String name, Runnable destructionCallback)
关于销毁回调, 能够参看java文档或者Spring做用域实现的代码.
下面方法包含了从潜在做用域获取会话id:
String getConversationId()
这个标识符在每一个做用域都不一样, 对于session做用域实现, 这个标识符能够是session标识符.
在你编码并测试一个或多个自定义做用域实现后, 你须要使Spring容器可以感知到你的做用域. 下面方法是为Spring容器注册一个新做用域的核心方法:
void registerScope(String scopeName, Scope scope);
这个方法是在接口ConfigurableBeanFactory
声明的, 在Spring的大多数对于接口ApplicationContext
的实现中, 经过BeanFactory
属性能够获取到.
registerScope(..)
方法的第一个参数是相关做用域的惟一名称. Spring内置的此类名称是singleton
和prototype
. 方法的第二个参数是自定义Scope
实现的实例, 这个实例就是你想注册和使用的.
假设你写好了自定义的Scope
实现, 而且像下一个例子同样注册到了容器中.
下一个例子使用
SimpleThreadScope
, 其包含在Spring中但没有默认注册. 这个教程与你自定义的scope
实现是相同的.
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
你能够建立bean的定义, 并将自定义的scope实现依附其中. 以下:
<bean id="..." class="..." scope="thread">
自定义的Scope
实现不只限于使用编程方式注册, 你也能够使用声明方式注册, 经过CustomScopeConfigurer
类进行注册, 以下所示:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread"> <bean class="org.springframework.context.support.SimpleThreadScope"/> </entry> </map> </property> </bean> <bean id="thing2" class="x.y.Thing2" scope="thread"> <property name="name" value="Rick"/> <aop:scoped-proxy/> </bean> <bean id="thing1" class="x.y.Thing1"> <property name="thing2" ref="thing2"/> </bean> </beans>
当你在
FactoryBean
实现中使用<aop:scoped-proxy/>
时,FactoryBean
自身是做用域下的bean, 不是从getObject()
方法返回的对象.
Spring框架提供了一些接口供你自定义bean的特性. 本节按下面方式组织:
ApplicationContextAware
和 BeanNameAware
Aware
接口为了与容器管理的bean的生命周期交互, 你能够实现Spring InitializingBean
和 DisposableBean
接口. 容器会为前者调用afterPropertiesSet()
,为后者调用destroy()
以使bean执行在bean初始化和销毁时应该作的操做.
在现代Spring程序中,JSR-250 的
@PostConstruct
和@PreDestroy
注解被认为是获取生命周期回调的最佳实践. 使用这些注解意味着你的bean不会耦合到Spring特定的接口中. 更多信息请参看:使用@PostConstruct
和@PreDestroy
.
若是你不想用JSR-250注解但仍然想去除耦合, 考虑元数据中使用
init-method
和destroy-method
.
在内部, Spring框架使用接口BeanPostProcessor
的实现来处理他发现的任何回调接口而且调用合适的方法. 若是你须要自定义特性或者修改Spring没有默认提供的其余生命周期行为, 你能够实现BeanPostPocessor
. 更多信息请参看:容器扩展点.
除了初始化和销毁回调外, Spring管理的对象也能够实现Lifecycle
接口, 以便那些对象能参与容器自身生命周期驱动的启动和中止过程.
生命周期回调接口将在本节描述.
org.springframework.beans.factory.InitializingBean
接口可以在容器设置了bean的全部必要属性后执行初始化工做. InitializingBean
接口只有一个方法:
void afterPropertiesSet() throws Exception;
咱们强烈建议不要使用InitializingBean
接口,由于它没必要要地耦合到了Spring中. 相反, 咱们建议使用@PostConstruct
注解或者指定一个POJO 初始化方法. 若是使用XML配置, 你能够使用init-method
属性指定一个没有参数的方法名称. Java代码配置的话, 你能够使用@Bean
上的 initMethod
属性. 参看:获取生命周期回调. 参考下例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { public void init() { // do some initialization work } }
上面的例子和下面的例子(由两个清单组成)有相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }
这样,上面的头两个例子代码没有与Spring耦合.
实现org.springframework.beans.factory.DisposableBean
接口使得一个bean能在包含它的容器销毁时回调. DisposableBean
接口只有一个方法:
void destroy() throws Exception;
咱们建议你不要使用DisposableBean
回调接口, 由于代码没必要要的耦合到Spring了. 相反, 咱们建议使用@PreDestroy
注解, 或者在bean定义中指定一个普通方法. 使用XML配置的话你能够使用元素<bean/>
上的destroy-method
属性. 使用java代码配置的话, 你能够使用@Bean
注解上的destroyMethod
属性. 参看获取生命周期回调. 示例以下:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like releasing pooled connections) } }
上面的定义等同于下面的定义:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work (like releasing pooled connections) } }
上面的两个定义与Spring代码没有耦合.
你能够为
<bean>
元素分配给属性destroy-method
一个专门的值(inferred
(推断)), 这个值指示Spring自动去探测特定bean上的公共close
或shutdown
方法. (任何实现了java.lang.AutoCloseable
或java.oi.Closeable
的类将会匹配到) 你也能够为<beans>
元素分配给属性default-destroy-method
一个特定值, 用来在全部bean上应用动做(参看默认初始化和销毁方法). 注意, 这个java代码配置的默认行为.
当你不用Spring特定的InitializingBean
和 DisposableBean
接口回调的初始化和销毁方法时, 你通常编写方法的名称相似init()
,initialize()
,dispose()
等. 理论上, 这些生命周期回调方法是标准化的贯穿于整个项目, 这样开发人员就能使用相同的方法并保持一惯性.
你能够配置Spring容器查找每一个bean上的命名初始化和销毁方法. 这意味着你做为开发人员能编写程序类并使用名为init()
的初始化方法回调, 而并不须要为每一个bean定义上指定init-method="init"
. Spring容器在bean被建立时调用这个方法(并遵循前面描述的标准生命周期回调). 这个特性也须要强制执行一向的初始化和销毁方法回调的命名约定.
假设你的初始化回调方法叫init()
而且你销毁回调方法名叫destroy()
. 你的类将按下面例子阻止:
public class DefaultBlogService implements BlogService { private BlogDao blogDao; public void setBlogDao(BlogDao blogDao) { this.blogDao = blogDao; } // this is (unsurprisingly) the initialization callback method public void init() { if (this.blogDao == null) { throw new IllegalStateException("The [blogDao] property must be set."); } } }
你能够像下面这样使用那个类:
<beans default-init-method="init"> <bean id="blogService" class="com.something.DefaultBlogService"> <property name="blogDao" ref="blogDao" /> </bean> </beans>
顶级元素<beans/>
上的属性default-init-method
促使Spring容器去识别在bean上的init()
方法做为初始化回调函数. 当bean被建立和组装后, 若是bean有这么个方法, 它就将在恰当的时候执行.
你也能够在顶级元素<beans/>
上使用default-destroy-method
, 相似地去配置(XML)销毁回调函数.
在已经存在的bean上, 已经有按约定命名的回调方法, 你能够使用<bean/>
元素上指定init-method
和destroy-method
重写默认的方法.
Spring容器担保在bean的依赖所有被提供后可以当即调用配置的初始化回调. 所以,初始化回调发生在原始bean引用上, 这意味着AOP注入尚未应用到bean上. 一个完整的目标bean是首先被建立而后一个AOP代理注入链被引用. 若是目标bean和代理被单独定义, 你的代码就可以绕开代理与目标bean交互. 所以, 在init
方法上应用注入是不合逻辑的, 由于这样将耦合目标bean的生命周期到它的代理或注入器, 而且当你的代码与原始的目标bean直接交互时留下奇怪的语义.
从Spring2.5开始, 控制bean生命周期行为有三种选择:
InitializingBean
和 DisposableBean
init()
和 destroy()
方法@PostConstruct
和 @PreDestroy
. 你能够组合这些机制去控制bean.若是给一个bean配置了多种生命周期机制,而且每一个机制都使用了不一样的方法名, 那么每一个配置方法将会按本提示后面给出的顺序执行. 尽管如此, 若是为多个机制配置了相同的方法名-- 例如, 初始化方法的
init()
-- 那该方法将只执行一次, 就如上面所阐述的.
为同一个bean配置多个生命周期机制, 初始化方法将按下面顺序执行:
@PostConstruct
的方法InitializingBean
定义的afterPropertiesSet()
方法init()
方法销毁方法按相同的顺序执行:
@PreDestroy
的方法DisposableBean
定义的destroy()
destroy()
对于拥有本身生命周期须要的任何对象, 接口Lifecycle
定义了必不可少的方法(例如启动和中止某些后台进程).
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何Spring管理的对象均可以实现接口Lifecycle
. 而后, 当ApplicationContext
自身收到开始和中止信号时(例如,运行时的中止/重启场景), 它将级联调用上下文定义的全部Lifecycle
实现. 这是经过委托给LifecycleProcessor
完成的, 以下所示:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
注意, LifecycleProcessor
自己是接口Lifecycle
的扩展. 它添加了两个方法在上下文刷新和关闭时交互.
注意, 标准的
org.springframework.context.Lifecycle
接口是一个明确的规约, 是为了启动和中止通知使用, 并无在上下文刷新时应用自动启动. 对于特定bean的自动启动更细粒度的控制(包括启动阶段), 考虑实现接口org.springframework.context.SmartLifecycle
代替.
同时请注意, 中止通知不能保证必定在销毁时会发生. 在标准的中止过程下,全部的
Lifecycle
bean在通常的销毁回调被传播前首先接收一个中止通知. 尽管这样, 在上下文生命周期热刷新时或者在抛弃刷新尝试时, 只有销毁方法会被调用.
启动和中止调用的顺序多是比较重要的. 若是两个对象间存在依赖关系, 依赖方将在被依赖方以后启动, 而且在被依赖方以后中止. 不过有时候直接依赖是不知道的. 你可能仅仅知道一些类型的对象的优先于另外一些类型的对象启动. 这种状况下, SmartLIfecycle
接口定义了另外一种选项, 在其父类接口Phased
上指定getPhase()
方法. 下面的代码展现了Phased
接口:
public interface Phased { int getPhase(); }
下面展现了SmartLifecycle
接口:
public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }
当启动时, 底层阶段的对象优先启动. 当中止时, 顺序相反. 所以, 一个实现了接口SmartLifecycle
而且其方法getPhase()
返回Integer.MIN_VALUE
将会在第一个启动的, 并是最后一个中止的. 在此范围的另外一端,也就是Integer.MAX_VALUE
将代表对象是最后启动的而且是最早中止的(可能由于它依赖于要运行的其余进程). 当提到阶段值的时候, 重要的一点是任何"正常"Lifecycle
对象若是没有实现SmartLifecycle
接口的话,这个阶段值的默认为0. 所以, 任何负值的对象将在那些标准组件以前启动(在其后中止). 若是正值则相反.
经过SmartLifecycle
接收一个中止方法. 任何实现必须在对象的中止过程完成后调用回调的run()
方法. 这就使程序拥有了异步中止能力, 由于接口LifecycleProcessor
的默认实现DefaultLifecycleProcessor
在每一个阶段中等待该组对象的超时值以调用该回调. 每一个阶段的超时时间是30秒. 你能够经过定义一个名为lifecycleProcessor
的bean来重写默认的生命周期处理器. 若是你想仅仅是修改超时时间, 定义以下:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>
就像前面提到的, LifecycleProcessor
接口也定义了上下文刷新和关闭的回调方法. 后者驱动关闭过程,就好像stop()
方法被显式调用, 但它发生在上下文关闭时. 另外一方面, 刷新回调赋予了SmartLifecycle
bean的另外一种特性. 当上下文被刷新(在全部对象建立和初始化后), 回调就被调用了. 此时, 默认的生命周期处理器检查每一个SmartLifecycle
对象的isAutoStartup()
方法返回的bool值. 若是是true
, 对象将马上启动而不是等到上下文或者其自身的start()
方法显式调用(与上下文刷新不一样,上下文启动并非标准上下文实现的标准实现). phase
值和任何依赖关系决定了前面描述的启动顺序.
这节内容仅仅应用于非web程序. Spring 基于web的
ApplicationContext
实现已经提供了当web程序关闭时优雅地关闭容器的代码.
若是你在一个非web程序中(例如, 一个胖客户端桌面环境)使用Spring容器, 注册一个JVM的中止钩子. 这样作能够保证你优雅地关闭, 并调用单例bean上关联的销毁方法使全部资源可以释放. 你必须依然要正确配置和实现那些销毁回调.
为了注册中止的钩子, 调用接口ConfigurableApplicationContext
声明的方法registerShutdownHook()
, 以下所示:
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); // add a shutdown hook for the above context... ctx.registerShutdownHook(); // app runs here... // main method exits, hook is called prior to the app shutting down... } }
ApplicationContextAware
和 BeanNameAware
当ApplicationContext
建立了一个实现了org.springframework.context.ApplicationContextAware
接口的对象实例时, 这个实例就提供了对ApplicationContext
的引用. 下面代码是接口ApplicationContextAware
的定义:
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
所以, bean可以经过编程方式操做建立了他们的ApplicationContext
, 经过ApplicationContext
接口或经过能转化为这个接口子类型的对象(例如: ConfigurableApplicationContext
,这个接口暴露了更多的功能). 其中一个用途是对其余bean进行编程检索. 有时候这种能力是有用的. 但整体上你应该避免使用, 由于它会耦合你的代码到Spring而且不能遵循IoC风格, 在那里协做bean做为bean的属性. ApplicationContext
的其余方法提供了访问文件,发布程序事件,还有访问MessageSource
等功能. 这些附加特性参见ApplicationContext
的附加功能.
自从Spring2.5开始, 自动装配是另外一种能获取ApplicationContext
引用的替代方式. "传统的"constructor
和byType
装配模型(如同在装配协做者一节描述的)能分别为构造器参数或setter方法参数提供ApplicationContext
类型依赖. 更多的灵活性, 包括自动装配字段和多参数方法, 使用新的基于注解的装配特性. 若是你这么作了, 带有@Autowired
注解的域, 构造函数或方法, 那ApplicationContext
就自动装配到一个指望ApplicationContext
类型的属性域,构造函数参数, 或者方法参数中去了. 更多信息请参见使用@Autowired
.
当ApplicationContext
建立了实现org.springframework.beans.factory.BeanNameAware
接口的类实例后, 这个类就拥有了在定义时为其指定的名字引用. 下面代码展现了接口BeanNameAware的定义:
public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
回调在bean的属性被布局后可是在InitializingBean
, afterPropertiesSet
或自定义初始化方法以前被调用.
Aware
接口除了(以前讨论的)ApplicationContextAware
和BeanNameAware
外, Spring提供了一系列Aware
接口以使bean向容器代表他们须要特定的基础设施依赖. 做为广泛法则, 这个名称代表了依赖类型. 下面表格简要列出最重要的Aware
接口:
表4 Aware接口
名称 | 注入的依赖 | 解释文档索引 |
---|---|---|
ApplicationContextAware | 声明ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | 封装的ApplicationContext的事件发布 | ApplicationContext的附加功能 |
BeanClassLoaderAware | 用来加载bean的类加载器 | 实例化bean |
BeanFactoryAware | 声明BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 声明的bean的名称 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | 容器运行其中的资源适配器BootstrapContext, 通常仅JCA可用的ApplicationContext中可用 | ![]() |
LoadTimeWeaverAware | 加载时为处理类定义的加载器 | Spring框架中使用AspectJ加载时织入 |
MessageSourceAware | 配置的解析消息的资源策略(支持参数化和国际化) | ApplicationContext的附加功能 |
NotificationPublisherAware | Spring JMX 提醒发布者 | 提醒 |
ResourceLoaderAware | 底层访问资源的配置加载器 | Resources |
ServletConfigAware | 容器运行其中的当前ServletConfig ,仅在web-aware的Spring ApplicationContext 中可用 |
Spring MVC |
ServletContextAware | 容器运行其中的当前ServletContext ,仅在web-aware的Spring ApplicationContext 中可用 |
Spring MVC |
请再次注意, 使用这些接口将使你的代码耦合到Spring API, 而且也不遵循IoC风格. 所以咱们建议他们当须要访问容器时使用基础设施bean.
bean的定义包含不少配置信息, 包含构造函数参数, 属性值, 还有特定于容器的信息, 例如初始化方法,静态工厂方法等. 一个bean的子类定义继承了父定义中的配置数据. 子类定义能够覆盖或者按需添加数据. 使用父子定义能够节省不少编码. 事实上, 这是一种模型.
若是你使用编程方式使用ApplicationContext
接口, 子类bean的定义是经过ChildBeanDefinition
类来表现的. 许多用户不在这个层面使用它. 相反, 他们经过在诸如ClassPathXmlApplicationContext
的类中生命配置bean. 当你使用基于XML的配置元数据时, 你能够经过parent
属性指明子类bean定义, 其值是父bean的定义名称. 下例展现了如何这么作:
<bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <!--注意`parent`属性--> <bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBean" init-method="initialize"> <property name="name" value="override"/> <!-- the age property value of 1 will be inherited from parent --> </bean>
若是没有指定,子类bean定义将使用父类定义的bean类,但依然能够覆盖它. 后一种状况下, 子类必须兼容父类(也就是必须接受父类的属性值).
子类继承了父bean的做用域,构造函数参数值, 属性值还有方法重写, 同时能够添加新的值给它. 任何你指定的做用域,初始化方法,销毁方法或者static
工厂方法配置都将覆盖掉父类的配置.
剩下的配置老是从子类定义获取: 依赖,装配模式,依赖检查,单例, 懒加载.
上面例子中父类定义使用abstract
属性显式标记父bean是抽象的. 若是父bean不指定类型, 显式标记父bean为abstract
是必须的, 以下所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBeanWithoutClass" init-method="initialize"> <property name="name" value="override"/> <!-- age will inherit the value of 1 from the parent bean definition--> </bean>
父bean不能被实例化本身, 由于它不完整, 且显式标记为abstract
了. 当一个定义是abstract
的时, 它仅仅是为子定义提供的一个纯净的模板bean定义. 视图尝试单独使用这个抽象bean, 或者将它引用为另外一个bean的ref
属性, 或者使用父bean的id执行getBean()
方法将返回错误. 类似的, 容器内部的preInstantiateSingletons()
方法将忽略定义为抽象的bean.
ApplicationContext
默认预初始化全部的单例. 所以, 重要的一点是(至少对于单例bean)若是你有个(父)bean定义, 而且仅仅想把它做为模板使用, 而且给他指定了类名, 你就必须保证设置了abstract属性为true, 不然应用程序上下文将(尝试)预实例化这个abstract
bean.
一般, 攻城狮不须要ApplicationContext
的子类实现. 相反, Spring容器能够经过实现特定接口的方式扩展. 如下几节描述了这些接口.
BeanPostProcessor
自定义beanBeanPostProcessor
接口定义了一些回调方法, 你能够实现提供你本身的(或者覆盖默认的)初始化逻辑, 依赖解析逻辑等. 若是你想要在容器完成初始化, 配置和实例化bean以后实现一些本身的逻辑, 你能够插入一个或多个BeanPostProcessor
的实现.
你能够配置多个BeanPostProcessor
实例, 并可以经过order
属性的值来控制这些实例执行的顺序.若是BeanPostProcessor
实现了Ordered
接口, 你就能够设置这个属性了. 若是你写了本身的BeanPostProcessor
, 你就也应该考虑实现Ordered
接口. 更多细节可参看javadoc关于BeanPostProcessor
和Ordered
接口的部分. 也能够参看: 编程注册BeanPostProcessor
的实例.
BeanPostProcessor
实例做用在bean或对象上. 也就是Spring容器实例化一个bean实例后BeanPostProcessor
实例开始作他们的工做.
BeanPostProcessor
是容器级别的做用域. 仅仅是你使用容器层次结构的时候才是有意义的. 若是你在容器中定义了一个BeanPostProcessor
,它将仅仅处理这个容器中的bean. 换句话说, 定义在一个容器中的bean不能被其余容器中的BeanPostProcessor
处理, 就算这俩容器都是相同等级层次架构的一部分.
为了改变bean的定义(也就是定义bean的蓝图), 你须要使用
BeanFactoryPostProcessor
, 这在使用BeanPostProcessor
来自定义配置原数据中有描述.
org.springframework.beans.factory.config.BeanPostProcessor
接口包含两个回调方法. 当一个post-processor的bean被注册到容器后, 对于每一个本容器实例化出的bean, post-processor都将从容器获取到回调方法, 是在容器初始化方法以前(例如InitializingBean.afterPropertiesSet()
或者任何声明为init
的方法)将被调用, 且在bean实例化回调方法以后. post-processor能够作任何事情, 包括彻底忽略回调方法. 一个bean post-processor一般检查回调接口, 或者使用代理来包装bean. 一些Spring AOP 基础设施类就是实现为post-processor, 为了提供代理包装逻辑.
ApplicationContext
自动探测在配置元数据中任何实现了BeanPostProcessor
接口的bean. ApplicationContext
将做为post-processor注册了这些bean, 以便晚些时候在bean建立的时候调用. bean post-processor可以以和其余bean相同的方式部署到容器中.
注意当在配置类上使用@Bean
注解的工厂方法声明BeanPostProcessor
时, 工厂方法的返回类型必须是实现类型自己, 或者至少是接口org.springframework.beans.factory.config.BeanPostProcessor
, 明确表示bean的post-processor属性. 不然, ApplicationContext
不能在彻底建立它时经过类型自动探测. 由于BeanPostProcessor
须要被早点实例化, 从而被用于其余bean的初始化过程当中, 因此这种早期探测是相当重要的.
编程的方式注册
BeanPostProcessor
实例
建议注册
BeanPostProcessor
的方式就是前面提到的,经过ApplicationContext
自动检测, 同时你也能够用编程的方式使用ConfigurableBeanFactory
的方法addBeanPostProcessor()
进行注册. 当你须要评估注册前的条件逻辑, 或者想要在一个层次结构中跨上下文拷贝post-processor时, 编程的方式就颇有用了. 注意, 编程添加的BeanPostProcessor
实例并不关心Ordered
接口. 这里, 是注册的顺序决定了执行的顺序. 同时注意, 编程注册的BeanPostProcessor
接口老是在那些自动检测注册的接口以前处理, 就算显式指定了顺序也是如此.
BeanPostProcessor
实例和AOP自动代理
实现了
BeanPostProcessor
接口的类是特殊的而且被容器区别对待. 全部BeanPostProcessor
实例和他们直接饮用的bean都是在启动时实例化的, 做为ApplicationContext
启动阶段的一个特殊部分. 接下来, 全部BeanPostProcessor
实例将被以一种风格注册并应用到全部容器的bean上. 由于AOP自动代理是做为BeanPostProcessor
自身的一个实现. 因此BeanpostProcessor实例和它们直接引用的bean都不符合自动代理的条件,所以,它们中没有织入的方面。
对于这样的bean, 你可能看到以下日志信息: Bean someBean 不符合全部
BeanPostProcessor
接口处理的条件(例如:不符合自动代理).
若是你有经过自动装配或
@Resource
注解(可能会回到自动装配)方式装配bean到BeanPostProcessor
中, Spring在查找类型匹配的依赖候选时, 可能会访问到不指望的bean, 使得他们不符合自动代理或者其余bean post-processor处理. 例如, 若是你有个@Resource
的依赖项, 其中字段或setter名称并不直接对应到bean的声明名称, 而且不使用name属性, 那么Spring将按类型访问其余bean来匹配它们.
下面的例子展现了在ApplicationContext
中如何编码, 注册和使用BeanPostProcessor
.
BeanPostProcessor
-风格的Hello World程序第一个例子是基本用法. 展现了一个自定义的BeanPostProcessor
实现调用每一个容器建立的bean的toString()
方法并将结果打印到控制台.
下面是代码:
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
下面beans
元素使用了InstantiationTracingBeanPostProcessor
:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd"> <lang:groovy id="messenger" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/> </lang:groovy> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
注意InstantiationTracingBeanPostProcessor
仅仅是声明了. 它没有名字, 而且应该它是个bean, 它可以依赖注入给任何其余bean. (上面配置定义了个bean, 是使用Groovy脚本的形式,Spring动态语言支持的细节请看:动态语言支持)
下面java程序运行上面的代码和配置:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; 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); } }
输出以下:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
RequiredAnnotationBeanPostProcessor
使用回调接口或注解与自定义BeanPostProcessor
实现合力是扩展Spring容器的常见作法. 一个例子是Spring的RequiredAnnotationBeanPostProcessor
, 一个BeanPostProcessor
的实现, 它是随Spring发行附带的, 能确保标记为任意注释的beans上的javabean属性是实际上配置为依赖注入的一个值.
BeanFactoryPostProcessor
自定义配置元数据下一个扩展点咱们看一下org.springframework.beans.factory.config.BeanFactoryPostProcessor
. 这个接口的语法相似于其余的BeanPostProcessor
, 但有个主要不一样: BeanFactoryPostProcessor
操做bean的元数据. 也就是, Spring容器使用BeanFactoryPostProcessor
读取配置元数据并可能在容器实例化任何除BeanFactoryPostProcessor
以外的bean以前改变它.
你能够配置多个BeanFactoryPostProcessor
实例, 并能够经过order
属性控制这些实例的执行顺序. 虽然,你仅仅须要设置实现了Ordered
接口的BeanFactoryPostProcessor
的这个属性. 若是你编写了本身的BeanFactoryPostProcessor
, 你就应该考虑实现Ordered
接口. 更多细节参看:BeanFactoryPostProcessor
和Ordered
的java文档.
若是你想修改实际的bean实例(也就是经过配置元数据建立的对象), 那你就须要使用
BeanPostProcessor
(以前章节讨论的经过BeanPostProcessor
自定义bean). 虽然技术上能够使用BeanFactoryPostProcessor
与bean实例协做(例如, 经过使用BeanFactory.getBean()
), 但这样作将致使早产的bean实例, 侵犯了标准的Spring容器生命周期.这样容易致使反作用, 例如绕开bean处理.
同时
BeanFactoryPostProcessor
实例是容器级别的做用域. 这仅仅在使用容器层次结构的时候是有意义的. 若是你容器中定义了一个BeanFactoryPostProcessor
, 那他就只在这个容器定义的bean起做用.在一个容器中的bean定义不能被另外一个容器中的BeanFactoryPostProcessor
处理, 就算两个容器都是相同层次结构的一部分.
一个bean工厂的post-processor, 当它在ApplicationContext
中声明时自动执行, 为了给定义在容器中的元数据作出修改. Spring包含不少预约义的工厂post-processor, 例如PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
. 你也能够使用自定义的BeanFactoryPostProcessor
--例如, 注册自动以的属性编辑器.
ApplicationContext
自动检测那些部署到其中的, 实现接口BeanFactoryPostProcessor
的bean. 在恰当的时候, 它使用这些bean为bean工厂的post-processor. 你能够象其余bean同样部署这些post-processor bean.
与使用
BeanPostProcessor
同样, 你一般不想要配置BeanFactoryPostProcessor
为延迟加载. 若是没有其余bean引用一个Bean(Factory)PostProcessor
, 那post-processor将不会被初始化. 所以, 将其标记为延迟初始化是会被忽略的, 而且Bean(Factory)PostProcessor
将会被更早的初始化, 就算你设置了<beans/>
元素的default-lazy-init=true
.
PropertySourcesPlaceholderConfigurer
经过使用标准的java Properties
格式, 你能够使用PropertySourcesPlaceholderConfigurer
来扩展独立在文件中的bean定义中的属性值. 这么作能令人们部署程序去自定义的特定环境的属性, 例如数据库URLs和密码, 而不须要复杂的有风险的去修改主要的XML定义文件.
请看下面的XML格式的配置元数据片断, 里面有个使用占位符的DataSource
值被定义:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="locations" value="classpath:com/something/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
示例展现了从一个外部Properties
文件中配置的属性. 在运行时, PropertySourcesPlaceholderConfigurer
被用来替换数据源中的一些属性值. 须要替换的属性是以一种${properties-name}
的格式指定, 遵循Ant和log4j以及JSP EL风格.
真正的值来自另外一个标准的Properties
文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
所以, 字符串${jdbc.username}
在运行时被'sa'替换, 而且其余的与键匹配的占位符值都给替换了. PropertySourcesPlaceholderConfigurer
检查properties占位符和bean的定义属性. 并且, 你能够自定义占位符前缀和后缀.
随着Spring2.5版本的context
命名空间, 你能够使用专门的配置元素去配置属性占位符. 你能够为location
属性提供一个或多个用逗号分隔的值列表. 以下:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不只查找指定的properties文件. 默认状况下, 若是它不能从指定的属性文件中找到, 它会继续查找SpringEnvironment
属性和标准的System
属性:
你能够使用
PropertySourcesPlaceholderConfigurer
去替换类的名称, 这有时候是有用的, 当你不得不在运行时选择一个特定的类实现的时候. 下面展现了这种状况:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/something/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.something.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
若是这个类不能在运行时被解析为一个有效的类, 解析失败发生在当它将要被建立时, 对于一个非延迟加载的bean, 也就是在
ApplicationContext
的preInstantiateSingletons()
阶段.
PropertyOverrideConfigurer
PropertyOverrideConfigurer
是另外一个bean工厂post-processor, 相似于PropertySourcesPlaceholderConfigurer
, 但又不一样于后者, 对于bean的properties, 最初的定义能够没有默认值或者压根就没值. 若是一个覆盖的Properties
文件没有bean的properties合适的入口, 那么默认的上下文定义将被使用.
注意, bean的定义并不知道被覆盖, 因此覆盖配置被使用的xml定义文件并非当即就可见. 万一有多个PropertyOverrideConfigurer
实例为同一属性定义了多个, 那依据覆盖机制最后一个将获胜.
properties文件配置的行格式以下:
beanName.property=value
下面是这种格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
示例文件能被定义了一个名字为dataSource
的bean使用, 其中包含了driver
和url
属性.
组合属性名称也是受支持的, 只要路径path的每一个被复写的部分都是非空, 最终属性除外(假设是使用构造器初始化的). 在接下来的例子中, beantom
的fred
属性的bob
属性的sammy
属性被设置为123
.
tom.fred.bob.sammy=123
指定的覆写的值老是字面量. 他们不会被解析为bean的引用. 当xml bean定义中指定一个bean的引用时,这个约定一样被使用.
自从Spring 2.5的context
命名空间开始, 就能够使用一个明确的配置元素去配置属性了. 以下所示:
<context:property-override location="classpath:override.properties"/>
FactoryBean
自定义实例化逻辑你能够为自己是工厂的类实现接口org.springframework.beans.factory.FactoryBean
.
FactoryBean
接口是可插入Spring容器实例化逻辑的一个点. 若是你有比较复杂的代码, 这种代码使用java比使用一对xml文件更好的表达, 你就能够本身去建立本身的FactoryBean
, 将复杂的实例化代码写进这个类, 并将这个类插入到容器中.
FactoryBean
接口提供了额三个方法:
Object getObject()
: 返回工厂建立的实例对象. 这个实例多是共享的, 取决于工厂返回的是单例仍是原型.
boolean isSingleton()
: 若是这个FactoryBean
返回单例就是true
,不然是false
.
Class getObjectType()
: 返回对象的类型, 这个对象是getObject()
方法返回的. 或者返回null
, 若是类型不能预知.
FactoryBean
的观念和接口在Spring框架中多处使用. Spring自身提供的就总有50多个实现.
当你须要问容器请求接口FactoryBean
的真实实例自己, 而不是它生产的bean时, 在调用ApplicationContext
的方法getBean()
方法将返回在bean的id
前面冠以&
. 因此, 对于一个id为myBean
的FactoryBean
, 调用getBean("myBean")
将返回FactoryBean
的产品, 而调用getBean("&myBean")
返回FactoryBean
实例自己.
Spring中是否注解配置要优于XML的配置
基于注解的配置是否优于XML, 关于注解的介绍引出了这个问题. 简短的答案是"看状况". 长一点的答案是:每种方式都有其优缺点, 常常地, 这取决于开发人员决定哪一种方式更适合他们. 基于他们定义的方式, 注解提供了许多上下文, 这致使了更短而更闭联的配置. 而XML更精通于不接触代码或无需从新编译而组装组件.一些开发人员更喜欢将装配接近源码的方式, 而其余人则认为注解的类再也不是POJO了, 并且配置变得零散而难以控制.
无论何种选择, Spring都能兼容两种方式甚至混用它们. 值得指出,经过java配置选项, Spring使得注解在一个非侵入方式下使用, 不须要触碰目标组件的源码, 至于工具, 全部的配置方式都被Spring Tool Suite 支持.
一种替换XML配置的方式是基于注解的配置, 后者依赖于二进制字节码原数据装配组件而不是使用尖括号的声明. 不一样于XML描述装配, 开发人员将配置转移到组件类内部,经过注解在类上, 方法上或域字段上. 就像在示例:RequiredAnnotationBeanPostProcessor
中同样, 使用BeanPostProcessor
与注解协做是一种扩展Spring 容器的广泛方法. 例如, Spring2.0赋予了使用@Required
注解强制必填属性的能力. Spring2.5使得使用相同的通常模式来驱动Spring DI 成为可能. 本质上, @Autowired
注解提供了在自动装配一节的描述相同的能力, 但拥有更细粒度的控制和更广的适用性. Spring2.5一样添加了JSR-250注解的支持,例如@PostConstruct
和@PreDestroy
. Spring 3.0 添加了对JSR-330(java的依赖注入)注解支持, 包含在javax.inject
包下如@Inject
和@Named
. 这些注解的细节能够在相关章节找到.
注解注入在XML注入以前执行. 所以, XML配置将覆盖了经过两种方式装配的注解.
一般, 你能够像分离的bean定义同样注册他们, 但你也能够隐式的经过包含在XML配置中的下面标签来注册. (注意将context
命名空间包含进去):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
(隐式注册的post-processor包含AutowiredAnnotationBeanPostProcessor
, CommonAnnotationBeanPostProcessor
, PersistenceAnnotationBeanPostProcessor
, 还有前面提到的RequiredAnnotationBeanPostProcessor
.)
<context:annotation-config/>
仅仅查找相同应用上下文定义的bean上的注解. 这意味着若是你为一个DispatcherServlet
的WebApplicationContext
上添加标签<context:annotation-config/>
, 它仅仅在你的Controller上检查@Autowired
, 而不会在service上. 可参看:DispatcherServlet获取更多信息.
@Required
注解引用在bean的setter方法上, 以下:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
这个注解代表, 受影响的bean的属性必须在配置时就肯定, 无论是经过bean定义中的显式的属性值仍是自动装配. 若是没有找到值,容器将抛出异常. 这容许早期和显式失败, 避免了NullPointerException
等诸如此类的实例. 咱们依然建议你将断言放置在bean类内部(例如, 放在初始化方法里面). 这样就强制了必填属性的引用和值, 就算你在容器外使用类也是如此.
@Required
注解在Spring framework 5.1开始起被否认了, 更倾向于必填设置经过构造函数(或使用随bean属性的setter方法InitializingBean.afterPropertiesSet()
的自定义实现) .
@Autowired
JSR 330 的注解
@Inject
可以替换在本节例子中使用的Spring注解@Autowired
. 这里(bean的标准注解)有更多细节.
你能够将@Autowired
注解应用到构造函数, 以下所示:
public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
从Spring 框架4.3 开始, 若是目标bean只定义了一个构造函数, 那么
@Autowired
注解就不是必须的. 但若是有多个构造函数, 那至少须要注解其中的一个来告诉容器使用哪一个.
你也能够将@Autowired
注解应用到传统的setter方法上, 以下:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
你也能够将注解应用到具备任意名称和多个参数的方法, 以下:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
也能够将@Autowired
应用到域而且能够和构造方法混用, 以下
public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired private MovieCatalog movieCatalog; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
确保你的目标组件(如
MovieCatalog
或CustomerPreferenceDao
)和使用@Autowired
注解的地方是类型一致的. 不然,注入可能会由于运行时没有匹配到类型而失败.
对于XML定义的bean或组件类经过classpath扫描, 容器一般能够预知类型. 但对于
@Bean
工厂方法, 你须要确保返回类型要足够明确. 对于那些实现了多个接口的组件, 或隐式的引用到具体实现类型的组件, 考虑在工厂方法返回值将其声明为特定类型(至少要指定为与注入点引用的bean须要的类型).
你也能够为须要一个数组的域或方法提供一组特定类型的bean, 这些bean从ApplicationContext
中获取. 以下:
public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... }
一样能够引用于集合类, 以下:
public class MovieRecommender { private Set<MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
若是你想指定集合数组中的元素顺序, 你能够给目标bean实现
org.springframework.core.Ordered
接口或者使用@Order
或者标准的@Priority
注释. 不然他们的顺序遵循容器中定义的相关目标bean注册的顺序.
你能够在目标类级别上声明
@Order
注解, 以及在@Bean
方法上, 这多是单独的bean定义(在使用同一个bean类的多个定义的状况下).@Order
值可能影响注入点的优先级, 但要知道, 它不影响单例的启动顺序, 而这是由依赖关系和@DependsOn
声明决定的正交关系.
注意标准的
javax.annotation.Priority
注解不能在@Bean
级别上使用, 由于它不能声明在方法上. 它的语义能够经过@Order
值与每一个类型的单一bean上使用@Primary
相结合来建模.
甚至类型化的Map
实例也是能够自动包装的, 只要期待的key值是String
类型. Map的值包含了全部指望类型的实例, 而key值包含了相关bean的名称, 以下所示:
public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
默认状况下, 若是没有候选bean, 自动装配将会失败(也就是默认必填). 默认的行为是将注解方法, 构造函数和域看作所需的依赖. 你也能够像以下所示的那样去改变其行为:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required = false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
每一个类仅有一个构造函数能够被标记为必填, 但能够有多个注解的构造函数. 那种状况下, 每一个都将被做为候选, 而且Spring使用最大参数的构造函数, 这个构造函数能够知足依赖.
建议将
@Autowired
所需的必填属性置于@Required
注解之上. 必填属性代表属性不是为了自动装配必需要的. 属性将被忽略若是它不能被自动装配. 而@Required
, 另外一方面它更强, 它能够以容器支持的任何方式设置属性. 若是没有值注入, 相关的异常将会抛出.
做为替换, 你能够使用java8的java.util.Optional
来表达一个非必填属性依赖. 以下所示:
public class SimpleMovieLister { @Autowired public void setMovieFinder(Optional<MovieFinder> movieFinder) { ... } }
从Spring 5.0 开始, 你也能够使用@Nullable
注解了(任何包中任何类型的注释, 例如JSR-305的:javax.annotation.Nullable
) :
public class SimpleMovieLister { @Autowired public void setMovieFinder(@Nullable MovieFinder movieFinder) { ... } }
您也能够为众所周知的依赖项接口使用@Autowired
: BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
, ApplicationEventPublisher
,还有MessageSource
. 这些接口和他们的扩展接口, 例如:ConfigurableApplicationContext
或者ResourcePatternResolver
, 会被自动解析, 不须要特殊的设置步骤. 下面例子展现了自动装配一个ApplicationContext
对象:
public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... }
@Autowired
,@Inject
,@Resource
, 和@Value
是被Spring的BeanPostProcessor
实现所处理. 这意味着你不能将这些注释应用到你本身的BeanPostProcessor
或者BeanFactoryPostProcessor
类型. 这些类型必须显式的使用XML或Spring@bean
方法装配.
@Primary
注释进行微调由于按类型的自动装配可能会有多个候选, 因此有必要在选择过程当中有更多控制. 一种达成此目标的方式是使用@Primary
注解. @Primary
代表当多个bean候选都符合单例依赖时, 这个特定的bean应当被自动装配给引用. 若是确实有一个主要的bean, 那它就成为了自动装配的值.
参看下面的配置, 定义了firstMovieCatalog
做为主要的MovieCatalog
:
@Configuration public class MovieConfiguration { @Bean @Primary public MovieCatalog firstMovieCatalog() { ... } @Bean public MovieCatalog secondMovieCatalog() { ... } // ... }
使用上面的配置, 下面的MovieRecommender
被自动装配了firstMovieCatalog
:
public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; // ... }
相关的bean定义以下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog" primary="true"> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
当使用按类型自动封装时, 若是有几个不一样的实例而只有一个主要的实例被肯定, @Primary
是一种有效的方法.当你须要在选择过程当中有更多控制时, 能够使用Spring的@Qualifier
注解. 你能够使用它的几个相关参数来缩小类型匹配范围, 这样对于任一参数选择其特定的bean. 在最简单的示例中, 它能够是一个普通的描述值, 以下:
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... }
你也能够指定@Qualifier
注解到单独的构造函数参数上, 以下:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main")MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
下面示例展现了相关的bean定义:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> <!--用main指定的值被具备相同名称的构造函数参数自动装配--> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!--用action指定的值被具备相同名称的构造函数参数自动装配--> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
做为一种回退匹配, bean的名称被认为是默认的指定值. 所以,你能够定义一个
id
为main
的bean替代内部的指定元素, 结果是同样的. 虽然你能够使用bean名称引用特定的bean,@Autowired
根本上是使用可选语义的修饰语进行类型驱动注入.这就意味着限定的值,即使有名称后备, 老是会限定在匹配类型范围内. 他们语义上不会表达为对一个beanid
的引用. 好的限定值是main
,MEMA
或persistent
, 表达独立于bean的id
外的一种特定角色, 这个bean的id多是自动生成的匿名定义, 就像前面例子中同样.
限定还可被用到集合, 就像前面讨论的同样, 如:
Set<MovieCatalog>
. 这种状况下, 全部匹配的bean, 根据声明的限定, 是做为集合注入. 这暗示了限定符不须要是惟一的. 相反他们构成了过滤标准. 例如, 你能够使用相同的限定值"action"定义多个MovieCatalog
bean, 全部他们都被注入到使用@Qualifier("action")
注解的集合Set<MovieCatalog>
中.
在类型匹配的候选时, 让限定值依赖bean名称选择, 不须要在注入点添加@Qualifier
注解. 若是没有其余解析指令(如限定符或primary标记), 对于非单一依赖的状况, Spring经过目标bean的名称并选择相同名称的候选来匹配注入点名称(也就是,域名称或参数名称).
也就是说, 若是你想经过名称来表达注解驱动的注入, 就不须要使用@Autowired
, 就算他能在类型匹配的候选中进行选择. 相反, 使用JSR-250 的 @Resource
注解, 这个注解语义上定义了特定目标bean的惟一名称, 声明的类型和匹配过程无关. @Autowired
有不一样的语义: 在经过类型选择bean后, 特定的String
限定的值被认为仅仅是侯选中的(例如, 匹配account
限定将依赖于相同的限定标签).
对于自己定义为集合的bean, 如Map
或数组类型, @Resource
是好的解决方法, 经过名字引用特定的集合或数组bean. 也就是说, 自从4.3版本开始, 你也能够使用@Autowired
类型匹配算法来匹配Map
和数组类型, 只要元素类型信息保存在@Bean
的返回签名类型或集合继承中. 在这种状况下, 如上一段所述, 你能够使用限定值在相同类型中进行选择.
从4.3开始, 对于注入, @Autowired
也被认为是自引用的(也就是引用返回到当前被注入的bean上). 注意,自我注入是个回退. 通常的对其余组件的依赖每每具备优先权. 从这个意义上说, 自我引用不在通常候选范围所以历来不是主要的. 相反,他们通常是最后的选择. 实际上你应该使用自我引用做为最后的手段(例如经过bean的事务代理调用同一实例的其余方法). 在这种场景下, 考虑重构受影响的方法到单独的代理bean中. 想法, 你能够使用@Resource
, 这能够经过其惟一名称获取到当前bean的代理.
@Autowired
被用到域,构造函数,还有多个参数的方法上, 容许经过限定注解在参数级别缩小范围. 相比之下, @Resource
仅被支持在域和bean属性的setter方法上使用. 所以, 你应该在你注入目标是个构造函数或多参数方法上使用限定.
你也能够建立自定义的限定注解. 定义了一个注解, 并给其定义上添加了@Qualifier
, 以下:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }
接下来你就能够将自定义限定添加到域或参数上, 以下:
public class MovieRecommender { @Autowired @Genre("Action") private MovieCatalog actionCatalog; private MovieCatalog comedyCatalog; @Autowired public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) { this.comedyCatalog = comedyCatalog; } // ... }
接下来, 你能够为候选bean定义提供信息了. 你能够添加<qualifier/>
标签做为<bean/>
标签的子元素, 而后为其指定type
和value
来匹配你自定义的注解. 类型经过注解类的全限定名匹配. 最为替换, 若是没有名字冲突, 你能够使用短名称. 下面例子展现了所有两种方式:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="Genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="example.Genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
在类路径扫描和组件管理中, 你能够看到XML定义的基于注解的限定元数据. 特别的, 请参看使用注解提供限定元数据.
一些状况下, 使用没有值的注解就足够了. 当注解服务于更广泛的目标而且能应用于多个不一样类型的依赖时更为有用. 例如, 你可能须要提供一个离线目录供搜索, 当没有网络接入时. 首先, 定义简单的注解, 以下:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
而后, 添加注解到域或属性上, 以下:
public class MovieRecommender { @Autowired @Offline //此处添加了注解@Offline private MovieCatalog offlineCatalog; // ... }
如今bean定义仅仅须要一个限定type
, 以下所示:
<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!--这个元素指定了限定--> <!-- inject any dependencies required by this bean --> </bean>
你也能够定义自定义限定注解, 其能够接收更多的命名属性或替换简单的value
属性. 若是多个属性值被指定到一个域或参数上, 自动包装时, bean的定义必须匹配全部这些属性值才能做为候选. 参看以下所示的定义:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }
这个例子中Format
是枚举, 定义以下:
public enum Format { VHS, DVD, BLURAY }
自动包装的域是用自定义限定注解的, 其包含两个属性:genre
和format
, 以下所示:
public class MovieRecommender { @Autowired @MovieQualifier(format=Format.VHS, genre="Action") private MovieCatalog actionVhsCatalog; @Autowired @MovieQualifier(format=Format.VHS, genre="Comedy") private MovieCatalog comedyVhsCatalog; @Autowired @MovieQualifier(format=Format.DVD, genre="Action") private MovieCatalog actionDvdCatalog; @Autowired @MovieQualifier(format=Format.BLURAY, genre="Comedy") private MovieCatalog comedyBluRayCatalog; // ... }
最终, bean定义应该包含匹配的限定值. 这个示例也展现了你能够使用bean的元数据属性来代替<qualifier/>
元素. 若是可用, <qualifier/>
和他的属性优先, 但自动封装机制回退则使用<meta/>
标签的值, 若是没有限定存在的话. 以下所示定义中后面两个:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Action"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Comedy"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="DVD"/> <meta key="genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="BLURAY"/> <meta key="genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> </beans>
另外, 对于@Qualifier
注解, 你能够使用java泛型类型做为限定的隐式格式. 例如, 你有以下配置:
@Configuration public class MyConfiguration { @Bean public StringStore stringStore() { return new StringStore(); } @Bean public IntegerStore integerStore() { return new IntegerStore(); } }
假设前面的bean实现了一个泛型接口(也就是Store<String>
和Store<Integer>
), 你能够@Autowire
这个Store
接口, 而且泛型被用来做为限定, 以下:
@Autowired private Store<String> s1; // <String> qualifier, injects the stringStore bean @Autowired private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型限定也用来自动装配集合列表,Map
接口和数组. 下面例子自动装配了泛型List
:
// Inject all Store beans as long as they have an <Integer> generic // Store<String> beans will not appear in this list @Autowired private List<Store<Integer>> s;
CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
, 容许你注册你本身自定义的限定注解类型, 就算它们不是被Spring的@Qualifier
注解的. 下例展现了如何使用CustomAutowireConfigurer
:
<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier</value> </set> </property> </bean>
AutowireCandidateResolver
经过下面的方式决定自动装配候选:
autowire-candidate
值<beans/>
元素上可用的default-autowire-candidates
模型@Qualifier
的注解以及经过CustomAutowireConfigurer
注册的自定义注解当多个bean被限定为自动装配的候选, 首要项的决策遵循: 若是候选中存在primary
属性且值为true
, 则它被选中.
@Resource
注入Spring支持使用JSR-250规范的@Resource
注解进行注入, 其(javax.annotation.Resource
)可以用在域或者setter方法上. 这是JavaEE的一种广泛模式: 例如, 在JSF-管理的bean和JAX-WS端点中. Spring为Spring管理的bean也支持这种模式.
@Resource
获取name属性. 默认Spring将这个值做为bean名称注入. 换句话说, 它遵循按名称语义, 以下所示:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") // 这里注入了一个@Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
若是没有明确指定名称, 默认名称就是从域的名字或者setter方法导出. 在域状况下, 它使用域名称, 在setter方法状况下, 它获取bean属性名. 下面例子展现了名为movieFinder
的bean注入到setter方法:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
注解提供的名称将会被接收
CommonAnnotationBeanPostProcessor
通知的ApplicationContext
解析为bean的名称. 名称能够经过JNDI解析, 若是你显式配置了Spring的SimpleJndiBeanFactory
. 尽管能够, 但咱们建议你依赖默认的行为并使用Spring的JNDI查找来保证间接级别.
在没有显式的名称指定的@Resource
使用场景下, 相似于@Autowired
,@Resource
查找主类型匹配而不是命名bean, 而且也能够解析熟知的依赖:BeanFactory
, ApplicationContext
, ResourceLoader
, ApplicationEventPublisher
, 和 MessageSource
接口.
所以, 下例中,customerPreferenceDao
域首先查找一个名为"customerPreferenceDao"的bean 而且回退到主类型匹配的类型CustomerPreferenceDao
:
public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; // `context`域是注入了熟知的依赖类型`ApplicationContext` public MovieRecommender() { } // ... }
@PostConstruct
和@PreDestroy
CommonAnnotationBeanPostProcessor
不只识别@Resource
注解, 也能够识别JSR-250生命周期注解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
. Spring 2.5 引用的, 对于这些注解的支持提供了一种相应的生命周期回调机制, 如以前的初始化回调和销毁回调章节有描述的. 假设ApplicationContext
注册了CommonAnnotationBeanPostProcessor
, 一个添加了这些注解的方法将会被调用, 调用点与相应的Spring生命周期接口方法或显式声明回调方法相同. 下面例子中, 缓存经过初始化方法预热并经过销毁方法清除:
public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... } }
关于组合使用不一样的生命周期机制, 能够参看组合生命周期机制.
如同
@Resource
,@PostConstruct
和@PreDestroy
注解是从JDK6到8成为标准的java类库的一部分. 尽管这样, 整个javax.annotation
包从JDK9的核心java模块中独立了, 并最终在JDK11中被删除.若是要用, 则须要从Maven中心引用javax.annotation-api
架构, 简单的将其添加到类路径下.
本章中大部分例子都将使用XML来指定用于Spring容器生产BeanDefinition
的配置元数据. 前面章节(基于注解的容器配置)阐述如何经过代码级别的注解来生成大量配置元数据. 就算在那些示例中, 最基础的bean定义依然是显式定义在XML文件中的, 而注解仅仅是驱动了DI. 本节描述一种隐式的经过扫描类路径方法探测候选组件的方式. 候选组件是一组按必定过滤规则匹配的并被容器注册的相关bean定义. 这就移除了使用XML来完成bean注册的方式. 相反, 你能够使用注解(例如:@Component
), AspectJ类型表达式, 或者你自定义的过滤规则来筛选哪些类定义能注册到容器中.
Spring 3.0 开始. 经过Spring的Java配置项目提供的一些特性成为了Spring核心的一部分. 这就容许你使用Java而不是传统的XML来定义bean. 能够参看
@Configuration
,@Bean
,@Import
, 和@DependsOn
注解示例来了解如何使用这些特性.
@Component
和更多注解@Repository
注解是为不少履行一个仓储角色(等同于数据访问对象或DAO)的诸多类上的一个标记. 使用这个标记的一个用法就是自动解析异常, 如同在异常解析一节描述的.
Spring 提供了更多注解: @Component
, @Service
, 和 @Controller
. @Component
是通常的使用在Spring管理的组件上的注解. @Repository
, @Service
, 和 @Controller
是比@Component
更具针对性的用例(各自分别在持久化,服务和表现层使用). 所以, 你能够在你的组件上使用@Component
, 但使用@Repository
, @Service
, 和 @Controller
替代将使你的类在工具处理时或联合使用方面时更合理. 例如, 这些注解提供了理想的目标切入点. @Repository
, @Service
, 和 @Controller
也能在未来Spring版本中提供更多的语义.所以, 若是你在你的服务层中选择使用@Component
或 @Service
, @Service
是更明确的更好的选项. 相似的, 如前所述, @Repository
在持久层已经获得异常解析方面的支持.
许多Spring提供的注解能够做为元注解用到你的代码中. 元注解是能够应用到另外一个注解上的注解. 例如, 前面提到的@Service
注解就是使用@Component
元注解的, 以下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component // Component 将致使 @Service 与 @Component 同样被对待 public @interface Service { // .... }
你也能够组合使用元注解用来建立组合注解. 例如, Spring MVC 的 @RestController
是由@Controller
和@ResponseBody
组合的.
更多的, 组合注解可以选择从新定义元注解的属性来自定义. 若是你想要仅暴露元注解中的一些属性的话这就是有用的. 例如, Spring的@SessionScope
注解硬编码了scope的名称为session
, 但仍然容许自定义proxyMode
. 以下是SessionScope
的定义:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Scope(WebApplicationContext.SCOPE_SESSION) public @interface SessionScope { /** * Alias for {@link Scope#proxyMode}. * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}. */ @AliasFor(annotation = Scope.class) ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }
而后你能够不声明proxyMode
来使用@SessionScope
:
@Service @SessionScope public class SessionScopedService { // ... }
你也能够重写proxyMode
的值, 以下所示:
@Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) public class SessionScopedUserService implements UserService { // ... }
更多细节请看wiki页面的Spring注解编程模型
Spring能自动探测更多的类并使用ApplicationContext
注册相关的BeanDefinition
实例.例以下面的两个类对于这样的自动探测是合适的:
@Service public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } @Repository public class JpaMovieFinder implements MovieFinder { // implementation elided for clarity }
为了自动探测并注册相关的bean, 你须要添加@ConponentScan
到@Configuration
类上, basePackages
属性时两个类通用的包(若是多个包的话,你能够使用逗号或分号或空格分割的列表,将每一个类的父包包含其中).
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig{ ... }
为了简洁, 上面例子能够使用注解的值属性(也就是
@ComponentScan("org.example")
).
下面是对应的XML:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/> </beans>
使用
<context:component-scan>
隐式的启用了<context:annotation-config>
的功能. 当使用<context:component-scan>
时就不须要在包含<context:annotation-config>
元素了.
类路径包的扫描须要相关的目录包含在类路径下. 当你使用Ant打包JARs是, 确保没有为JAR任务打开"仅文件"开关. 同时, 类路径目录可能会在一些环境下因为安全策略而没有暴露--例如, JDK1.7.0-45的独立app或更高(须要信任类库设置在你的清单中, 参看http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
在JDK9的模块路径(Jigsaw)下, Spring类路径扫描一般按预期执行. 尽管如此, 请确保你的组件类导出在
module-info
下. 若是你指望Spring调用你类中的非公有变量, 请确保他们是'opened'(也就是,在module-info
描述符下,他们使用opens
声明替换exports
声明).
更多的, 当你使用component-scan元素时, AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
二者都是隐式包含的. 这意味着这两个组件被自动探测并一块儿包装了--都没有任何XML提供的bean配置元数据.
你能够经过包含
annotation-config
属性并设置为false
来关闭AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
.
默认状况下, 用@Component
, @Repository
, @Service
, @Controller
标记的类, 或者使用@Component
元注解的自定义注解标记的类是探测的候选组件. 尽管这样, 你也能够经过应用过滤器修改和扩展行为. 为注解@ComponentScan
添加includeFilters
或excludeFilters
参数(或者component-scan
元素的include-filter
或 exclude-filter
子元素). 每一个元素都须要type
和expression
属性, 下面的表格描述了过滤选项:
表5. 过滤类型
过滤类型 | 示例表达式 | 描述 |
---|---|---|
annotation (default) | org.example.SomeAnnotation | 目标组件上, 在类级别上出现的注释 |
assignable | org.example.SomeClass | 目标组件要被其扩展或实现的类或接口 |
aspectj | org.example..*Service+ | 与目标组件匹配的一个AspectJ表达式 |
regex | org.example.Default.* | 与目标组件类名称匹配的正则表达式 |
custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter 接口的自定义实现 |
下面例子展现了忽略全部@Repository
注解并使用"stub"仓储代替的配置:
@Configuration @ComponentScan(basePackages = "org.example", includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), excludeFilters = @Filter(Repository.class)) public class AppConfig { ... }
下面例子是等效的XML配置:
<beans> <context:component-scan base-package="org.example"> <context:include-filter type="regex" expression=".*Stub.*Repository"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> </beans>
你也能够经过在注解上设置
useDefaultFilters=false
或者给元素<component-scan/>
设置use-default-filters="false"
来使默认过滤器失效. 这实际上也使得使用@Component
,@Repository
,@Service
,@Controller
, 或@Configuration
注解的类的自动探测失效了.
Spring组件也能为容器贡献bean定义元数据. 你能够经过使用@Configuration
注解的类里面的@Bean
注解来定义bean元数据. 下面例子展现了这种方式:
@Component public class FactoryMethodComponent { @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } public void doWork() { // Component method implementation omitted } }
上面的类是Spring的一个组件,work()
方法中它有应用特定的代码. 它也贡献了一个bean的定义,有个引用方法publicInstance()
的工厂方法. @Bean
注解标识了工厂方法和其余bean定义的属性, 如经过@Qualifier
指定的限定值. 其余方法级别的注解多是特定的@Scope
,@Lazy
以及自定义限定注解.
除了组件初始化角色, 你也能够将
@Lazy
注解放在由@Autowired
或@Inject
标记的注入点上. 在这个上下文中, 它最终注入了一个延迟解析代理.
自动装配域和方法是受支持的, 如前所述, 同时对@Bean
方法有更多的自动封装的支持. 以下所述:
@Component public class FactoryMethodComponent { private static int i; @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } // use of a custom qualifier and autowiring of method parameters @Bean protected TestBean protectedInstance( @Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) { TestBean tb = new TestBean("protectedInstance", 1); tb.setSpouse(spouse); tb.setCountry(country); return tb; } @Bean private TestBean privateInstance() { return new TestBean("privateInstance", i++); } @Bean @RequestScope public TestBean requestScopedInstance() { return new TestBean("requestScopedInstance", 3); } }
示例自动为类型为String
的参数country
自动装配了另外一个名为privateInstance
的bean的age属性值. Spring表达式语言元素经过注解#{<expression>}
定义了值. 对于@Value
注解, 表达式解析器在解析表达式文本的时候查找bean的名称.
从Spring4.3开始, 你可能须要声明一个参数类型为InjectionPoint
的工厂方法(或者它的特定的子类如DependencyDescriptor
), 来访问请求的注入点来触发当前bean的建立. 注意这种仅适用于真实的bean实例建立, 对于已存在实例的注入不适用. 做为其结果, 这个特性对于原型bean更有意义. 对于其余做用域, 工厂方法仅看到scope内触发建立新bean实例的注入点(例如, 触发建立延迟加载单例bean的依赖项).这种状况下你能够谨慎的使用注入点元数据. 下面例子展现了如何使用InjectionPoint
:
@Component public class FactoryMethodComponent { @Bean @Scope("prototype") public TestBean prototypeInstance(InjectionPoint injectionPoint) { return new TestBean("prototypeInstance for " + injectionPoint.getMember()); } }
普通Spring组件中的@Bean
方法与其余在Spring@Configuration
类中的其余同辈们处理是不一样的. 不一样之处在于, @Component
类没有用CGLIB加强用来切入方法和域的调用. CGLIB代理是一种调用@Configuration
类中@Bean
注解的方法和域来建立引用到相关对象的bean元数据的方法.此类方法不会被普通的java语义调用, 而是为了提供非正常的生命周期管理和Spring的Bean代理而贯穿容器, 即便是经过编程方法调用@Bean
方法引用其余bean时. 相反, 调用@Component
类中的@Bean
方法或域时使用标准java语义, 没有其余特定CGLIB的处理或应用其余限制.
你可能声明
@Bean
为static
, 容许调用它们,而无需将其包含的配置类做为实例建立。这在定义post-processor的bean时有特殊的意义(例如, 类型BeanFactoryPostProcessor
或BeanPostProcessor
), 由于这样的bean会被容器生命周期中更早的初始化, 而且在这个点上应该避免触发其余配置部分.
调用静态的
@Bean
方法历来不会被容器切入, 就算是@Configuration
类(如前所述), 因为技术限制: CGLIB 子类仅仅能覆盖非静态方法. 所以, 对另外一个@Bean
方法的直接调用具备java语义, 结果一个独立的实例直接由工厂方法返回.
@Bean
方法的Java语言的可见性不会当即对Spring容器中的bean定义产生影响. 你能够自由的在非@Configuration
类中声明工厂方法, 或者在任何地方声明静态方法. 尽管能够,@Configuration
类中普通的@Bean
方法须要是可覆盖的-- 也就是他们不能被声明为private
或者final
.
在提供的组件或配置类中
@Bean
方法能够被发现, 一样在java8的由组件和配置类实现的接口中的默认实现方法也能够. 从Spring4.2开始, 这就容许在组合复杂配置时有更大的灵活性, 甚至经过Java8的默认办法使多继承成为可能.
最终,一个类中可能持有相同bean的多个方法, 做为多工厂方法的安排, 这些取决于于运行时可用依赖. 这与选择最佳构造函数或配置中的工厂方法是相同的算法: 在构造时会选择具备最大数量的依赖项的变量,即容器如何在多个
@Autowired
构造函数之间进行选择。
当一个组件被做为扫描过程的一部分探测到时, 它的bean名称经过扫描者知道的BeanNameGenerator
策略生成. 默认状况下,任何Spring的注解(@Component
, @Repository
, @Service
, 和 @Controller
)包含一个名字value
所以提供了相应bean定义的名称.
若是注解不包含value
或被其余组件探测(例如自定义过滤器), 默认的bean名称生成器会返回首字母小写的不合格类名. 例如, 付过下面的组件类被探测到, 他们的名字应该是myMovieLister
和movieFinderImpl
.
@Service("myMovieLister") public class SimpleMovieLister { // ... }
@Repository public class MovieFinderImpl implements MovieFinder { // ... }
若是你不想依赖默认的bean命名策略, 你能够提供自定义的bean命名策略. 首先,实现
BeanNameGenerator
接口, 而且保证包含了无参构造函数. 接着, 当配置扫描者的时候提供全限定类名, 以下例子中的注解和bean定义展现的:
@Configuration @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" /> </beans>
做为通用规定, 考虑使用注解指定名称, 当其余组件显式引用到它时. 另外一方面,当容器负责装配时自动生成的名称就足够了.
Spring托管组件同样, 默认的和大多数通用的scope是singleton
. 虽然如此, 有时候你须要经过@Scope
指定一个不一样的做用域. 你能够为其指定名字, 以下所示:
@Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... }
@Scope
注解仅在具体的bean类上(注解的组件)或工厂方法上(@Bean
方法)是自省的. 相比于XMLbean定义, 没有bean定义继承的概念, 而且类级别的继承与元数据不相干.
在Spring上下文中更多的关于web特定的做用域如"request","session"等,请参看Request,Session,Application和WebSocket做用域. 这些做用域有预置的注解, 你也能够使用Spring元注解的方式组合你本身的做用域注解: 例如, 使用@Scope("prototype")
自定义注解,可能须要声明一个自定义的scope-proxy模式.
为做用域解析提供一个自定义策略而不是依赖注解的方式, 你能够实现接口
ScopeMetadataResolver
. 确保包含一个无参构造函数. 接着你在配置扫描的时候提供一个全限定名, 以下例子中的注解和bean定义所示:
@Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/> </beans>
当使用非单例做用域时, 也许必要为做用域的对象生成代理. 缘由在做用域bean做为依赖项中有描述. 为了达到此目的, component-scan元素的一个scoped-proxy属性时可用的. 三种可能的值分别是: no
,interfaces
,和targetClass
. 例如, 下面配置将生成标准的JDK动态代理:
@Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" scoped-proxy="interfaces"/> </beans>
在使用修饰符来微调基于注解的装配中讨论了@Qualifier
注解. 那一节中的例子展现了在解析自动装配的候选的时候, @Qualifier
注解和自定义修饰注解提供的更好的控制. 由于这些例子基于XML bean定义, 修饰元数据是经过在做为候选的bean
元素中使用qualifier
或 meta
子元素来实现的. 当依赖于类路径扫描的自动探测组件时, 你能够在候选类上添加限定元数据注解. 下面三个例子展现了这种技术:
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component @Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component @Offline public class CachingMovieCatalog implements MovieCatalog { // ... }
对大多数基于注解的变体, 记住注解元数据是绑定到类定义自身的, 使用XML容许给相同类型的多个bean提供不一样的限定元数据, 由于元数据是提供给每一个实例,而不是每一个类型.
虽然类路径扫描是很快的, 对于大型程序, 也能够经过在编译时建立一个静态候选列表在提供启动性能. 在这种模式下,自动扫描的全部模块必须必须使用这种机制.
@ComponentScan
或<context:component-scan>
指令必须保持不变, 以请求上下文扫描特定包中的候选. 当ApplicationContext
探测到这么个索引, 则自动使用它而不是扫描类路径.
生成索引, 要给每一个包含被扫描的目标组件的模块添加一个额外的依赖. 下例展现了Maven中的配置:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-indexer</artifactId> <version>5.2.0.BUILD-SNAPSHOT</version> <optional>true</optional> </dependency> </dependencies>
对于Gradle4.5或更早版本, 依赖须要声明在compileOnly
配置中, 以下例子所示:
dependencies { compileOnly "org.springframework:spring-context-indexer:5.2.0.BUILD-SNAPSHOT" }
对于Gradle4.6及之后版本, 依赖须要声明在
annotationProcessor
配置中, 以下例子所示:
dependencies { annotationProcessor "org.springframework:spring-context-indexer:5.2.0.BUILD-SNAPSHOT" }
处理过程将生成一个META-INF/spring.components
文件, 包含在jar文件.
当在你的IDE中使用此模式的时候,
spring-context-indexer
必须做为注解处理器注册, 来确保当候选组件更新时索引页同步更新.
当
META-INF/spring.components
存在在类路径下时, 索引自动生效. 若是索引是特定对某些库(或用例)可用但不是为整个程序构建时, 你能够经过spring.index.ignore
设置为true
来回退到标准的类路径排列(好像根本不存在索引), 不管是做为系统属性或设置在类路径下的spring.properties
文件.
Spring3.0 开始, Spring提供了对JSR-330标准注解(依赖注入)的支持. 这些注解与Spring注解以一样的方式被扫描. 使用它们你须要在类路径下引入相关的jar.
若是你使用Maven,
javax.inject
在标准的Maven仓储下是可用的(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/). 你能够添加下面依赖到pom.xml:
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
@Inject
和@Named
依赖注入代替@Autowired
注解, 也能够使用@javax.inject.Inject
以下:
import javax.inject.Inject; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } public void listMovies() { this.movieFinder.findMovies(...); ... } }
如同@Autowired
, 你能够将@Inject
使用在域级别,方法级别和构造参数级别. 并且你可能声明你的注入点为一个Provider
, 容许按需访问较短做用域的bean或者经过Provider.get()
调用延迟访问其余bean. 下例提供了上面例子中的一个变体:
import javax.inject.Inject; import javax.inject.Provider; public class SimpleMovieLister { private Provider<MovieFinder> movieFinder; @Inject public void setMovieFinder(Provider<MovieFinder> movieFinder) { this.movieFinder = movieFinder; } public void listMovies() { this.movieFinder.get().findMovies(...); ... } }
若是你想为注入的依赖项使用限定名称, 能够使用@Named
注解, 以下例:
import javax.inject.Inject; import javax.inject.Named; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(@Named("main") MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
如同@Autowired
,@Inject
也能够与java.util.Optional
或@Nullable
一同使用. 在这里这是更合适的, 由于@Inject
没有required
属性. 下面两个例子展现了如何使用@Inject
和@Nullable
:
public class SimpleMovieLister { @Inject public void setMovieFinder(Optional<MovieFinder> movieFinder) { ... } }
public class SimpleMovieLister { @Inject public void setMovieFinder(@Nullable MovieFinder movieFinder) { ... } }
@Named
和@ManagedBean
: 标准可替换@Component
的注解替换@Component
, 也能够使用@javax.inject.Named
或javax.annotation.ManagedBean
, 以下所示:
import javax.inject.Inject; import javax.inject.Named; @Named("movieListener") // @ManagedBean("movieListener") could be used as well public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
使用没有指定名称的@Component
是很广泛的. @Named
能够用一样的风格, 以下:
import javax.inject.Inject; import javax.inject.Named; @Named public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
当使用@Named
或 @ManagedBean
时, 你能够像使用Spring注解同样使用组件扫描, 以下所示:
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { ... }
不一样于
@Component
, JSR-330 的@Named
和 JSR-250 的ManagedBean
注解不能组合使用. 你应该使用Spring场景模型或者建立自定义注解.
使用标准注解, 你应该知道一些显著的特性是不可用的, 以下表所示:
表6 Spring组件模型元素与JSR-330变体的区别
Spring | Javax.inject.* | javax.inject限制/说明 |
---|---|---|
@Autowired | @Inject | @Inject 没有'required'属性. 能够用Java8的Optional 替代 |
@Component | @Named/@ManagedBean | JSR-330不提供组合模型,仅仅是一种标识命名模型的方式 |
@Scope("singleton") | @Singleton | JSR-330的默认做用域相似Spring的prototype . 因此为了保持与Spring默认的一致性, JSR-330 的bean声明在容器中默认是singleton 的. 为了使用非singleton 的其余做用域, 你须要使用Spring的@Scope 注解. javax.inject 也提供了@Scope注解.不过这个仅仅用来建立你本身的注解时使用. |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier 仅是为了建立自定义限定的元注解.具体的string 类型的限定符(如同Spring的@Qualifier 带有一个值)能够是经过javax.inject.Named 关联 |
@Value | - | 无等效的 |
@Required | - | 无等效的 |
@Lazy | - | 无等效的 |
ObjectFactory | Provider | javax.inject.Provider 是Spring的ObjectFactory 的替代, 但仅仅有一个短的方法get() . 它也能够用在与Spring@Autowired 或无注解构造函数以及setter方法上组合使用 |
这节内容涵盖了如何在java代码中使用注解配置容器. 包含下面主题:
@Bean
和@Configuration
AnnotationConfigApplicationContext
初始化Spring容器@Bean
注解@Configuration
注解PropertySource
抽象@Bean
和@Configuration
Spring中的基于Java的配置支持和核心是@Configuration
注解类和@Bean
注解的方法.
@Bean
注解用来代表一个方法实例化,配置,并初始化出有Spring容器管理的新对象.对于熟悉Spring的<beans/>
XML配置, @Bean
注解扮演了与<bean/>
元素相同的角色. 你能够与Spring@Component
一块儿使用@Bean
注解方法. 即便如此, 他们老是跟@Configuration
bean一块儿使用的.
@Configuration
注解的类代表它的主要目的是做为bean定义的源. 并且@Configuration
容许经过调用同一个类中的其余@bean
方法来定义bean间的依赖关系. 可能最简单的@Configuration
类以下:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
上面的AppConfig
类等同于下面的<beans/>
XML配置;
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
全@Configuration vs 轻@Bean模式
当
@Bean
方法在没有被@Configuration
注解的类中声明时, 它们会做为轻型模式被处理. 在@Component
中的bean方法或在普通旧的类中的bean方法会被认为是轻型的, 与包含的类有不一样的主要目的, 而且@Bean
方法是种额外的好处.例如, 服务组件可能经过各合适的组件类附加的@Bean
方法暴露管理视图给容器. 此情景下,@Bean
方法是通用的工厂方法机制.
不一样于全
@Configuration
, 轻量@Bean
方法不能声明内部bean依赖. 相反, 他们操做包含他们组件的内部状态, 以及可选择的它们声明的参数. 这样的@Bean
方法所以不能调用其余@Bean
方法. 每一个这个的方法仅仅是对特定bean的引用的工厂方法, 不须要任何运行时的特定语义. 积极的一个影响是运行时没有CGLIB子类被应用, 所以在类设计方面没有任何限制(也就是,包含类多是final
的等等).
广泛场景下,
@Bean
方法在@Configuration
类内部声明, 要保证full模式老是被使用并跨方法引用所以直接关联到容器生命周期管理. 这就阻止了相同的@Bean
方法经过普通Java调用, 从而帮助减小lite模式下难以追踪的bug发生.
@Bean
和@Configuration
注解在接下来的章节中会更深的讨论. 首先, 咱们扫描了经过使用Java配置的方式建立容器的方式.
AnnotationConfigApplicationContext
初始化Spring容器接下来的章节记录了Spring的AnnotationConfigApplicationContext
, Spring3.0引入的. 多功能的ApplicationContext
实现能接收不只仅是Configuration
类做为输入, 还有@Component
类以及JSR-330元数据注解的类.
当@Configuration
类做为输入时, @Configuration
类自己被做为bean定义注册并将其内部全部的@Bean
方法也被注册为bean定义.
当@Component
和JSR-330类做为输入时, 他们被注册为bean定义, 而且假定如@Autowired
或@Inject
的DI元数据在这些类的内部按需使用.
与Spring的XML文件用来被初始化ClassPathXmlApplicationContext
时的输入同样, 你能够使用@Configuration
类做为初始化AnnotationConfigApplicationContext
时的输入. 这容许彻底不适用XML的方式, 以下所示:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
如前面提到的, AnnotationConfigApplicationContext
不只限于@Configuration
类. 任何@Component
或者JSR-330注解类均可以做为构造的参数, 以下所示:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
前面的实例假设了MyServiceImpl
,Dependency1
和 Dependency2
使用了诸如@Autowired
的Spring DI注解.
register(Class<?> ... )
编程构建容器你能够经过无参构造实例化AnnotationConfigApplicationContext
,而后经过register()
方法配置它. 这种方法在编程构建AnnotationConfigApplicationContext
是特别有用. 以下所示:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
scan(String…)
开启组件扫描开启组件扫描, 你能够在@Configuration
类上注解以下:
@Configuration @ComponentScan(basePackages = "com.acme") // 这个注解启动了组件扫描 public class AppConfig { ... }
有经验的Spring用户可能更熟悉XML声明, 它是从Spring的context:
命名空间, 以下例子所示:
<beans> <context:component-scan base-package="com.acme"/> </beans>
在前面例子中, com.acme
包被扫描以查找任何@Component
注解的类, 还有那些注册在Spring容器内的bean定义.AnnotationConfigApplicationContext
暴露的scan(String…)
方法拥有一样的组件扫描能力, 以下所示:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
记住
@Configuration
类是使用@Component
元注解的, 因此他们是扫描的候选. 在前面的例子中, 假设AppConfig
声明在com.acme
包内部(包括更深层次的包内), 在调用scan()
期间它被选中. 依赖refresh()
, 它全部的@Bean
方法被处理并注册在容器内部.
AnnotationConfigWebApplicationContext
支持WEB程序一种WebApplicationContext
的AnnotationConfigApplicationContext
变体是AnnotationConfigWebApplicationContext
. 当配置ContextLoaderListener
Sevlet监听器, Spring MVC DispatcherServlet
等时能够使用这个实现. 下面的web.xml
片断配置一个典型的Spring MVC Web程序(注意contextClass
context-param 和 init-param 的使用).
<web-app> <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- Bootstrap the root application context as usual using ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Declare a Spring MVC DispatcherServlet as usual --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- map all requests for /app/* to the dispatcher servlet --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
@Bean
注解@Bean
是方法级别的注解, 而且与XML 的 <bean/>
元素同源. 这个注解支持一些<bean/>
属性,如* init-method * destroy-method * autowiring * name
.
在@Configuration
注解的或者在@Component
注解的类中均可以使用@Bean
注解.
要声明bean, 你能够在一个方法上添加@Bean
注解. 在ApplicationContext
内部, 这个方法将被它的返回值类型注册到容器的bean定义. 默认bean的名字和方法名称是同样的. 下面示例展现了@Bean
的声明:
@Configuration public class AppConfig { @Bean public TransferServiceImpl transferService() { return new TransferServiceImpl(); } }
上面的配置是等同于下面XML的配置:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
两种方式都使得名为transferService
的bean在ApplicationContext
可用, 绑定到一个类型为TransferServiceImpl
的实例, 以下所示:
transferService -> com.acme.TransferServiceImpl
你也能够使用接口(或基类)声明@Bean
方法返回类型, 以下所示;
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
然则, 这限制了预测类型的可见性为接口类型(TransferService
). 而后, 容器仅知道一次完整类型(TransferServiceImpl
),受影响的bean单例被建立了. 非延迟加载的单例bean实例化根据他们声明的顺序初始化, 因此,依赖于当其余组件尝试匹配一个没有声明的类型时, 你可能看到不一样类型的匹配结果(如@Autowired TransferServiceImpl
, 仅在transferService
被实例化时解析).
若是你持续经过声明的服务接口引用, 你的
@Bean
返回类型可能安全地参与到设计决策中. 然而, 对于实现了多个接口或对于潜在引用它们实现类型的组件, 更安全的是尽量的指定返回类(至少指定引用到bean的注入点时须要的类型)
@Bean
注解方法可能还有随意数量的描述建立bean所需依赖的参数. 例如, 若是咱们的TransferService
须要一个AccountRepository
, 咱们能够具象出使用带有一个参数的方法, 以下:
@Configuration public class AppConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } }
这种解析机制比较其构造函数依赖而言至关, 更多细节参看相关章节.
任何用@Bean
注解的类定义都支持通常的生命周期回调, 而且能够使用JSR-250@PostConstruct
和@PreDestroy
注解. 参看JSR-250注解获取更多细节.
通常的Spring的生命周期回调也彻底支持. 若是bean实现了InitializingBean
,DisposableBean
,或者Lifecycle
, 他们各自的方法就会被容器调用.
标准的*Aware
接口(如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)也是彻底支持的.
@Bean
注解支持指定任意的初始化和销毁回调方法, 如同XML中在<bean/>
元素上的init-method
和destroy-method
属性, 以下:
public class BeanOne { public void init() { // initialization logic } } public class BeanTwo { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public BeanOne beanOne() { return new BeanOne(); } @Bean(destroyMethod = "cleanup") public BeanTwo beanTwo() { return new BeanTwo(); } }
默认Java配置定义的bean有个公有的
close
或shutdown
方法自动参与销毁回调. 若是你定义了相关的方法又不想在容器关闭时调用,你能够添加@Bean(destroyMethod="")
到bean的定义来关闭默认的(inferred
)推断模式.
你可能想在获取JNDI资源时默认这么作, 这种资源的生命周期是在程序外管理的. 特别的, 确保在操做
DataSource
时老是这么作, 由于在Java EE 程序服务器上已知是有问题的.
下面示例展现了如何阻断自动的销毁回调:
@Bean(destroyMethod="") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); }
此外, 使用
@Bean
方法, 一般使用编程方式使用JNDI查找, 也使用Spring的JndiTemplate
或JndiLocatorDelegate
帮助类或者直接的JNDIInitialContext
, 但不是JndiObjectFactoryBean
变量(这将迫使你声明返回类型为FactoryBean
而不是真实的目标类型, 增长其余@Bean
方法中跨引用调用的难度, 这些方法原本要引用提供的资源).
在上面的BeanOne
示例代码中, 构造时调用init()
方法是等效的, 以下所示:
@Configuration public class AppConfig { @Bean public BeanOne beanOne() { BeanOne beanOne = new BeanOne(); beanOne.init(); return beanOne; } // ... }
直接与java操做时, 你能够作任何你想要作的事情, 而不需依赖容器生命周期.
Spring包含@Scope
注解因此你能够指定bean的做用域.
@Scope
注解你能够为@Bean
注解的bean定义指定做用域. 能够使用在bean做用域中指定的标准做用域.
默认做用域是singleton
, 但能够覆盖, 以下:
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
@Scope
和 scoped-proxy
经过做用域代理, Spring提供了一种方便的方法与做用域依赖协做. 最简单的方式是在XML配置<aop:scoped-proxy/>
添加建立的代理.在Java中配置bean使用@Scope
注解的proxyMode
属性提供了等效的支持. 默认没有代理(ScopedProxyMode.NO
), 但能够指定ScopedProxyMode.TARGET_CLASS
或ScopedProxyMode.INTERFACES
.
若是将XML中的引用文档(参看做用域代理)移植到Java的@Bean
, 它看起来以下:
// an HTTP Session-scoped bean exposed as a proxy @Bean @SessionScope public UserPreferences userPreferences() { return new UserPreferences(); } @Bean public Service userService() { UserService service = new SimpleUserService(); // a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences()); return service; }
默认配置的类使用@Bean
方法名称做为结果Bean的名称. 这个功能能够使用name
属性覆盖, 以下:
@Configuration public class AppConfig { @Bean(name = "myThing") public Thing thing() { return new Thing(); } }
如在命名Bean中讨论的, 有时候迫切须要给bean指定多个名称, 也就是别名. @Bean
的name
属性能够接收一个String类型的数组来达到这个目的. 下面例子展现了一个bean中的多个别名:
@Configuration public class AppConfig { @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
有时候为bean提供更多细节的描述是有用的. 特别是在bean暴露给监控的时候(也许经过JMX).
为@Bean
添加描述, 你能够使用@Description
注解, 以下:
@Configuration public class AppConfig { @Bean @Description("Provides a basic example of a bean") public Thing thing() { return new Thing(); } }
@Configuration
注解@Configuration
是一个代表对象是bean定义源的类级注解. @Configuration
经过公有的@Bean
注解方法声明类. 调用@Configuration
类内的@Bean
方法也能够用来定义内部bean依赖. 参看基本概念:@Bean
和@Configuration
的基本介绍.
当bean有对其余bean的依赖时, 表达这种依赖如同一个调用另外一个bean方法这样简单, 以下所示:
@Configuration public class AppConfig { @Bean public BeanOne beanOne() { return new BeanOne(beanTwo()); } @Bean public BeanTwo beanTwo() { return new BeanTwo(); } }
前面的例子中, beanOne
经过构造函数接收一个beanTwo
.
这种内部bean依赖声明的方法仅适用于在
@Configuration
内部定义的@Bean
. 适用寻常的@Component
类不能声明内部bean依赖的状况.
如前面提到的, 查找方法注入是应该少用的高级特性. 在一个单例bean拥有原型bean依赖的场景它是有用的. 使用java配置为实现这种模式提供了天然的意义. 以下展现了如何使用查找方法注入:
public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
经过使用java配置, 你能够建立CommandManager
的子类, 其createCommand()
方法被覆盖, 这个方法用来查找一个原型指令对象. 以下所示:
@Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } }
下面的两个例子, 此例中一个@Bean
注解方法被调用两次.
@Configuration public class AppConfig { @Bean public ClientService clientService1() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientService clientService2() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientDao clientDao() { return new ClientDaoImpl(); } }
clientDao()
在clientService1()
和clientService2()
分别调用一次. 由于这个方法生成一个新的ClientDaoImpl
并返回, 你可能预测会有两个实例(每一个服务一个). 这种概念是有问题的: Spring中, bean默认是singleton
做用域. 这就是魔法时刻: 全部@Configuration
类都在启动时由CGLIB
生成子类了. 子类中, 在它调用父类方法并建立实例前, 子方法检查任何缓存(做用域内)的bean.
根据你bean做用域不一样, 这种行为可能不一样. 咱们这里只讲singleton.
从Spring3.2开始, 不须要添加CGLIB到类路径了, 由于CGLIB类已经被打包到
org.springframework.cglib
并直接包含在Spring核心JAR中了.
因为在启动时CGLIB动态添加特性, 有一些限制. 特别的, 配置Configuration类必须不是final的. 虽然, 从4.3开始, 配置类中任何构造函数都是被容许的, 包含使用
@Autowired
或单个的默认构造函数.
若是你想避免CGLIB的限制, 考虑将你的
@Bean
方法声明在非@Configuration
类中(例如使用@Component
类代替).@Bean
之间的跨方法调用就再也不被拦截了, 因此你必须在构造函数和方法级别彻底依赖DI.
Spring基于Java的配置特性使得能够组合注释, 这能减小配置的复杂性.
@Import
注解很像XML配置中<import/>
元素被用来辅助模块化配置, @Import
注解容许从其余配置类加载@Bean
定义, 以下:
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
如今, 在初始化上下文时, 不须要指定ConfigA.class
和ConfigB.class
, 仅仅ConfigB
须要显示提供, 以下所示:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }
这种方法简化了容器初始化, 由于仅仅一个类须要处理, 而你不须要记住构造时潜在的大量@Configuration
类.
从Spring4.2开始,
@Import
也支持普通的组件类, 相似于AnnotationConfigApplicationContext.register
方法. 若是想避免组件扫描这就是有用的, 经过使用少许配置类做为入口显式定义你的全部组件.
@Bean
定义中注入依赖前面的例子能够运行可是过度简化. 在大多数实际场景中, bean依赖会跨Configuration类. 当使用XML时, 这不是问题, 由于没有编译介入, 你能够声明ref="someBean"
并信任Spring会在初始化时处理好它. 当使用@Configuration
类时, java编译器在配置模式下设置了约束, 引用其余bean必须符合java语义.
幸运的是, 解决这个问题很简单. 咱们已经讨论的, @Bean
方法能够有任意的描述依赖的参数. 考虑下面的真实场景, 它有几个@Configuration
类, 每一个依赖描述在其余配置中:
@Configuration public class ServiceConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { @Bean public AccountRepository accountRepository(DataSource dataSource) { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
有其余方法能够获得相同的结果. 记住@Configuration
类仅仅是容器中的另外一个bean: 这意味着他们能够获得跟其余bean同样的好处, 能够使用如@Autowired
或@Value
注入等特性.
确保以这种方式注入的依赖关系只属于最简单的类型。
@Configuration
类在上下文初始化过程当中处理得很是早,经过这种方式强制注入依赖项可能会致使意外的早期初始化。尽量使用基于参数的注入,如前面的示例所示。
同时请特别当心的使用
BeanPostProcessor
和BeanFactoryPostProcessor
定义. 这些应该老是声明为static @Bean
方法, 不触发容器配置类的初始化. 不然,@Autowired
或@Value
不会在配置类上工做, 由于它过早的做为bean被建立了.
下面展现一个bean如何被另外一个bean自动装配:
@Configuration public class ServiceConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { private final DataSource dataSource; @Autowired public RepositoryConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
在
@Configuration
类上进行构造参数注入是从Spring4.3开始支持的. 注意若是仅有一个构造参数就不须要指定@Autowired
在目标bean定义上.在前面例子中,@Autowired
无需在RepositoryConfig
上指定.
前面的场景中, 使用@Autowired
工做的很好而且提供了急需的模块化, 但明确的决定包装bean在哪里依然是模糊的. 例如, 开发人员查找ServiceConfig
, 你怎么指定@Autowired AccountRepository
定义在哪里? 代码里面没有明确, 而且这还好. 记住Spring Tool Suite 提供了工具化能呈现包装的图标, 这多是你须要的所有. 你的Java IDE也能简单的找到全部的声明和AccountRepository
类的使用以及很快展现@Bean
方法和返回值.
万一这种模糊性不可接受而且你想从IDE直接从@Configuration
类导航到另外一个, 就要考虑包装配置类自己了, 以下:
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { // navigate 'through' the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); } }
前面提到的这种状况, AccountRepository
是彻底显式定义的. 虽然, ServiceConfig
如今被紧密耦合到RepositoryConfig
了. 这是折中. 经过使用基于接口或抽象类的@Configuration
类使用, 紧耦合能或多或少获得缓解. 以下:
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean public DataSource dataSource() { // return DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
如今ServiceConfig
松耦合到子类DefaultRepositoryConfig
, 而且IDE的内置功能依然可用: 你能够轻易找到RepositoryConfig
的实现. 用这种方法, 导航@Configuration
类和他们的依赖于一般的导航基于接口的代码没什么差异.
若是你想对特定bean的启动建立产生影响, 考虑声明他们为
@Lazy
的(对于首次访问而不是启动)或者@DependsOn
的(确保其余bean在当前bean前建立, 超出后者直接依赖的含义).
@Configuration
类或@Bean
方法根据随机的系统状态, 条件化的启动和禁止整个@Configuration
类或单独的@Bean
方法是有用的. 一个广泛的例子就是,当profile在Spring的Environment
来使用@Profile
注解来激活bean(参看Bean定义Profiles获取更多).
@Profile
注解其实是使用更灵活的@Conditional
注解来实现的. @Conditional
注解代表特定的org.springframework.context.annotation.Condition
实现应该在@Bean
被注册前咨询到.
接口Condition
的实现提供了一个matches(...)
方法, 返回true
,false
. 例如, 下面代码展现了@Profile
使用到的Condition
实现:
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // Read the @Profile annotation attributes MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; }
查看@Conditional
java文档获取更多细节.
Spring的@Configuration
支持并非为了100%达到替换XML配置的目的. 一些设施, 例如Spring的XML命名空间, 保留了配置容器更理想的方式. 万一XML配置更方便或更须要, 你有一个选择: 要么容器是XML为中心的方式,例如ClassPathXmlApplicationContext
, 或者是一个使用AnnotationConfigApplicationContext
和@ImportResource
注解导入XML的以Java配置为主的方式.
@Configuration
类的XML为主方式可能用XML去启动Spring容器并用ad-hoc钩子方式包含@Configuration
类更好. 例如, 在大型的现有的代码中使用XML, 按需建立@Configuration
类并从已有的XML文件中包含进来更为容易. 本节的后面, 咱们会讲述在XML为主的程序使用@Configuration
的这种选择.
声明@Configuration
类为普通的<bean/>
元素
记住@Configuration
类是容器中的最终的bean定义. 在这个系列的例子中, 咱们建立了名为AppConfig
的@Configuration
类并做为<bean/>
定义包含在system-test-config.xml
中. 由于<context:annotation-config/>
是开着的, 容器识别出@Configuration
注解并合理处理了定义在AppConfig
中的@Bean
方法.
下面例子展现了Java中的通常配置:
@Configuration public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public TransferService transferService() { return new TransferService(accountRepository()); } }
下面例子展现了system-test-config.xml
文件的一部分:
<beans> <!-- enable processing of annotations such as @Autowired and @Configuration --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
下面是jdbc.properties
文件的可能的内容:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
在
system-test-config.xml
文件中,AppConfig
<bean/>
没有声明id
属性. 这样是可接受的, 它不是必要的, 没有其余bean引用它, 而且也不可能显式经过名字被容器获取. 类似的,DataSource
bean仅仅是按类型装配, 全部显式的id
不是严格须要的.
使用context:component-scan/来拾取@Configuration
类
由于@Configuration
是用@Component
做为元注解的, 基于@Configuration
的类自动做为组件扫描的候选. 使用前面描述的例子中的场景, 咱们能够从新定义system-test-config.xml
来获取组件扫描的好处. 注意,在本例中, 咱们不须要显式声明<context:annotation-config/>
由于<context:component-scan/>
启动了相同的功能.
下面例子展现了修改后的system-test-config.xml
文件:
<beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
@ImportResource
导入XML的@Configuration
类为主的方式程序中用了@Configuration
类做为主要的配置容器的机制, 但仍然可能须要使用少许XML. 这种场景下, 你能够使用@ImportResource
并仅定义你须要的XML. 这样就能够用Java为中心的方式配置容器并将XML保持到最小. 下面例子(包含一个配置类,一个定义了bean的xml文件, 一个properties文件,以及主类main
)展现了如何使用@ImportResource
注解完成Java配置并按需使用XML:
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } }
properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }
Environment
接口是集成到容器的一个抽象, 抽象出两个关键的程序环境方面: profiles 和 properties.
profile是一个命名的, bean定义的逻辑组,这些bean只有给定的profile激活才注册到容器中的. 无论是在XML仍是经过注解定义, bean可能会被分配给一个profile. 关联到profile的Environment
对象角色是决定哪些profile当前是激活的, 还有哪些profile应该是默认激活的.
在大多数程序中, properties扮演了一个重要的角色, 而且源于一些列初始来源: properties文件, JVM系统属性,系统环境变量, JNDI,servlet上下文参数,ad-hoc属性对象, Map
对象等. 关联到properties的Environment
对象提供给用户一种便捷的服务接口, 来配置属性源和解析他们.
Bean定义profile提供了一种核心容器的机制, 容许不一样环境中不一样bean的注册. "environment"这个单词对不一样的用户有不一样的意义, 而且在不少用例下有用, 包含:
开发时在内存数据库进行, QA或生产则查找JNDI中相同的数据库
在性能环境部署程序时, 注册监控组件
对于客户A和客户B部署不一样的自定义实现.
考虑第一种用例, 在特定程序中须要须要一个DataSource
. 在测试环境, 配置可能以下组织:
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("my-schema.sql") .addScript("my-test-data.sql") .build(); }
如今考虑在QA或生产环境部署程序, 假设程序的生产数据源注册在服务器的JNDI目录. 咱们的dataSource
bean如今以下:
@Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }
问题是如何根据当前环境转换这两个变量. 随着时间的过去, Spring的用户想出不少方法来实现, 一般依赖于系统环境变量和XML包含了${placeholder}
语句的<import/>
来解决, 根据环境变量的值不一样解析出正确的文件路径. Bean定义profile提供了解决此类问题的一种核心特性.
若是咱们总结上面的用例, 咱们以须要在不一样上下文中注册不一样的bean定义结束. 你可能会说你想在情况A时注册一个profile,而情况B时注册另外一个profile. 咱们修改你的配置以达到这一需求.
@Profile
@Profile
注解能代表一个组件对于一种或多种激活的profile是有效的. 使用前面的例子, 咱们重写dataSource
配置以下:
@Configuration @Profile("development") public class StandaloneDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } }
@Configuration @Profile("production") public class JndiDataConfig { @Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
如早前提到的,
@Bean
方法,你一般经过使用Spring的JndiTemplate/JndiLocatorDelegate
或直接是JNDI的InitialContext
来编程实现JNDI查找, 但不使用JndiObjectFactoryBean
, 由于这个变量会强制你声明返回值为FactoryBean
类型.
profile的字符串可能包含一个简单的profile名称(如production
)或者profile表达式. profile表达式容许更复杂的逻辑(如product & us-east
). 下面操做符支持profile表达式:
!
: 表示非&
: 表示并|
: 表示或不适用圆括号的话你不能混用
&
和|
. 例如production & us-east | eu-central
不是有效的表达式. 必须使用production & (us-east | eu-central)
.
你能够使用@Profile
做为元注解来建立组合注解. 下面例子定义了一个@Production
注解, 能够用来替代@Profile("production")
:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
若是一个
@Configuration
类标记为@Profile
, 那全部关联到这个类的@Bean
方法和@Import
都会被绕开, 除非一个或多个特定的profile被激活. 若是@Component
或@Configuration
类被@Profile({"p1", "p2"})
标记, 那这些类将不会被处理,除非'p1'或'p2'被激活. 若是profile被非操做符!
做为前缀, 那注解的元素会在profile非激活状态下注册. 例如, 给定@Profile({"p1", "!p2"})
,注册将在profile'p1'激活或者profile'p2'非激活状态下发生.
@Profile
也能在方法级别声明用来包含配置类中一个特定的bean(例如, 特定bean的替换变量), 以下:
@Configuration public class AppConfig { @Bean("dataSource") @Profile("development") // `standaloneDataSource`方法仅在`development`激活状态可用 public DataSource standaloneDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } @Bean("dataSource") @Profile("production") // `jndiDataSource`方法仅在`production`激活状态可用 public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
使用@Bean
方法上的@Profile
, 一种特殊场景可能会应用: 对于具备相同方法名称重载的@Bean
方法(相似构造函数重载), @Profile
条件须要在全部重载方法上声明. 若是条件不一致, 则仅在重载方法中第一个声明处起做用. 所以@Profile
不能被用在选择具备参数签名的方法. 同一bean的全部工厂方法之间的解析在建立时遵循Spring的构造函数解析算法。
若是你想用不一样的profile条件定义不一样的bean, 请使用不一样的java方法名称, 这些名称经过使用@Bean
的name属性指向同一个bean名称, 如前面的例子所示. 若是参数签名都同样(例如, 全部的变量都有无参构造方法), 这是在有效的java类中表示这种安排的惟一方法(由于只能有一个特定名称和参数签名的方法).
XML相对应的是<beans>
元素的profile
属性. 咱们上面的例子能够被重写为XML文件, 以下:
<beans profile="development" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xsi:schemaLocation="..."> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans>
<beans profile="production" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>
也可能为了不分开定义在不一样的<beans/>
元素, 因此定义在同一个文件中, 以下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions --> <beans profile="development"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans>
spring-bean.xsd
被限制仅仅做为文件的最后元素出现. 这可能有助于XML文件中避免混乱.
对应的XML配置不支持前面提到的profile表达式. 虽然, 能够经过使用
!
操做符. 也可能经过嵌套的profile用"and"来完成逻辑与, 以下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions --> <beans profile="production"> <beans profile="us-east"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans> </beans>
上面例子中,
dataSource
bean在production
和us-east
都被激活状况下暴露出来.
如今咱们升级了配置, 咱们依然须要通知Spring哪一个profile被激活. 若是咱们启动样例程序, 咱们会看到一个NoSuchBeanDefinitionException
异常抛出, 由于容器找不到名叫dataSource
的bean.
能够有几种方式激活profile, 但最直接的是经过使用ApplicationContext
的Environment
API . 以下展现了如何这么作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("development"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();
更多的, 你也能够经过属性spring.profiles.active
激活profile, 这是经过系统变量, JVM系统属性, web.xml
中定义的servlet参数, 甚至是JNDI中的配置(查看PropertySource
抽象).在集成测试中, 激活profile能够经过使用在spring-test
模块上的@ActiveProfiles
注解激活(参看使用环境profile配置上下文).
注意profile不是一个"是...或者..."的命题. 你能够一次启动多个profile. 编程方式来讲, 你能够提供多个profile名称给setActiveProfiles()
方法, 接收一个String...
变量, 以下激活了多个profile:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
spring.profiles.active
也能够接受一个逗号分隔的profile名称, 以下:
-Dspring.profiles.active="profile1,profile2"
默认profile是默认状况下表现出的profile. 考虑下面的例子:
@Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); } }
若是没有profile激活, dataSource
被建立. 你能够看到这是一种对于一个或多个bean定义的默认方式. 若是任意profile启用了, 默认profile就不会被启用.
你能够经过使用Environment
上的setDefaultProfiles()
修改默认profile的名称. 或者,经过使用属性spring.profiles.default
声明.
PropertySource
抽象Environment
抽象提供了从property源经过配置架构查找操做的能力. 以下:
ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsMyProperty = env.containsProperty("my-property"); System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
上面的代码片断中, 咱们看到从高层次的方式来查找Spring的my-property
是否在当前环境中定义. 为回答这个问题, Environment
对象从一系列PropertySource
对象中查找. PropertySource
是一个对key-value对的简单的抽象. Spring的StandardEnvironment
使用两个PropertySource 对象进行配置--一个是JVM系统属性(System.getProperties())还有一个是系统环境变量(System.getenv()).
这些默认的属性源是为
StandardEnvironment
存在, 在独立程序中,StandardServletEnvironment
是由更多的包含servlet配置和servlet上下文参数的属性源生成. 它可选地启动JndiPropertySource
.参看java文档.
具体的, 当你使用StandardEnvironment
时, env.containsProperty("my-property")
将返回true, 若是运行时有my-property
系统属性或my-property
环境变量存在的话.
查找是按层次的. 默认系统属性优先于环境变量. 所以, 若是
my-property
碰巧同时出现, 在调用env.getProperty("my-property")
, 系统属性胜出并返回. 注意属性值不是合并, 而是彻底被优先的值覆盖.
对于普通
StandardServletEnvironment
, 所有层级以下, 最上面事最高优先级的:
DispatcherServlet
上下文)java:comp/env/
入口)-D
命令行参数)最重要的, 整个机制是可配置的. 也许你有自定义的propertis源想要集成到这里. 这么作的话, 继承和实例化你本身的PropertySource
并为当前Environment
添加PropertySources
. 以下:
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());
前面代码中, MyPropertySource
被以最大优先级添加. 若是它包含my-property
属性, 这个属性将被找到并返回, 优先于任何其余PropertySource
中的my-property
. MutablePropertySources
API暴露了不少容许精确操做property源的方法.
@PropertySource
@PropertySource
注解提供了方便的添加PropertySource
到Spring的Environment
的机制.
给定的app.properties
文件包含了键值对testbean.name=myTestBean
, 紧接着的@Configuration
类使用@PropertySource
调用testBean.getName()
返回myTestBean
:
@Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } }
任何在@PropertySource
源路径下的${…}
都将解析为一组已经注册到环境中的属性源, 以下:
@Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } }
假设my.placeholder
在其余的属性源中定义并已经注册(例如, 系统属性或环境变量), 那这个占位符将解析为相关的值. 若是没有, 那么default/path
将会被使用, 若是没有默认属性指定和解析, 那IllegalArgumentException
将抛出.
@PropertySource
注解是可重复的, 根据Java8的约定. 虽然能够, 但全部@PropertySource
注解须要在相同层级声明, 直接在配置类或者做为元注解自定义注解. 混用元注解和指令注解不建议, 由于指令注解实际上覆盖了原注解.
历史上, 元素中的占位符只能依据JVM系统属性或环境变量解析. 这已经不是问题了. 由于Environment
抽象经过容器集成, 经过它解析占位符是简单的途径. 这意味着你能够用你喜欢的方式配置解析方式. 你也能够修改查找系统属性和环境变量的优先级, 或者移除他们. 适当的话你还能够添加你本身的源.
具体的, 下面的语句无论哪里定义了customer
, 只要他在Environment
中有效就能工做:
<beans> <import resource="com/bank/service/${customer}-config.xml"/> </beans>
LoadTimeWeaver
LoadTimeWeaver
被Spring用来在加载到JVM时动态转化类.
启用加载时织入, 你能够添加@EnableLoadTimeWeaving
到一个@Configuration
类上. 以下:
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
对应的XML配置,能够使用context:load-time-weaver
元素.
<beans> <context:load-time-weaver/> </beans>
一旦为ApplicationContext
配置了任何在ApplicationContext
内部均可以实现LoadTimeWeaverAware
的bean,从而在收到在加载时织入的实例. 组合使用Spring的JPA支持时这一点颇有用, 对于JPA类转化时在加载时织入的地方多是必要的. 访问LocalContainerEntityManagerFactoryBean
的文档有更详细介绍. 对AspectJ 加载时织入, 参看使用AspectJ加载时织入.
ApplicationContext
的更多功能在本章的描述中, org.springframework.beans.factory
包提供了管理和操做bean的基本功能, 包括以编程的方式. org.springframework.context
包添加了接口ApplicationContext
, 这个接口扩展了BeanFactory
, 另外扩展了其余接口的功能,用来在一个更应用程序向的风格中提供附加功能. 许多人以一种彻底声明的方式使用ApplicationContext
, 并非编程的建立它, 而是依赖支持类如ContextLoader
来自动实例化ApplicationContext
,将其做为Java EE Web程序启动的一个过程.
在一个倾向框架的风格中增强BeanFactory
功能, context包也提供了以下功能:
MessageSource
以i18n风格访问消息ResourceLoader
接口访问资源,如URLs和文件ApplicationEventPublisher
接口实现事件发布, 实现了ApplicationListener
接口的命名bean.HierarchicalBeanFactory
接口, 使得每一个聚焦于特定的层, 如web层MessageSource
国际化ApplicationContext
接口扩展了叫MessageSource
的接口,于是提供了国际化("i18n")功能. Spring也提供了HierarchicalMessageSource
接口, 这个接口能层次性解析消息. 总之这些接口提供的功能依赖于Spring有效处理消息解析的能力. 这些定义在接口的方法包含:
String getMessage(String code, Object[] args, String default, Locale loc)
: 用来从MessageSource
获取消息的基本方法. 当指定区域没有消息时, 默认的消息将被采用. 使用标准类库MessageFormat
提供的功能, 任何传入的参数将替换值.
String getMessage(String code, Object[] args, Locale loc)
: 本质上跟上面方法同样但有个区别: 没有默认消息指定. 若是没有消息发现, NoSuchMessageException
将被抛出.
String getMessage(MessageSourceResolvable resolvable, Locale locale)
: 上面方法中使用的属性被包装到一个叫MessageSourceResolvable
的类里面, 这个你可用在本方法中.
当ApplicationContext
被加载, 它就自动加载上下文中定义的MessageSource
bean. 这个类必须有名字messageSource
. 若是找到这个bean, 全部前面的方法的调用将委托给消息源. 若是没有消息源被找到, ApplicationContext
尝试找到包含相同名称的父容器. 若是找到了, 这个bean就做为MessageSource
使用. 若是ApplicationContext
不能找到任何消息源, 则空的DelegatingMessageSource
被初始化用来接受上面定义的方法的调用.
Spring提供了两个MessageSource
实现, ResourceBundleMessageSource
和StaticMessageSource
. 两者都实现了HierarchicalMessageSource
以便能处理嵌套消息. StaticMessageSource
不多使用但提供了编程的方式添加消息到源的方法. 下例展现了ResourceBundleMessageSource
:
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans>
这个示例假设你有三个源叫format
,exception
和windows
在你的类路径下. 任何解析消息的请求都是以经过对象ResourceBundle
解析消息的标准JDK方式处理的. 本例的目的,假设上面的两个消息文件以下:
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
下面例子展现了编程执行MessageSource
功能. 记住全部的ApplicationContext
实现也是MessageSource
实现, 因此能够被转化为MessageSource
接口.
public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("message", null, "Default", null); System.out.println(message); }
上面的程序运行结果以下:
Alligators rock!
总之, MessageSource
定义在一个名叫beans.xml
的文件中, 这个文件在你类路径的根目录下. messageSource
bean定义经过basenames
属性引用到一系列资源bundles. 这三个文件分别叫format.properties
, exceptions.properties
, 还有windows.properties
存在于你的类路径下, 经过basenames
属性传递.
下面例子展现了传递给消息查找的参数. 这些参数转化为String
对象并插入查找的占位符中.
<beans> <!-- this MessageSource is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <!-- lets inject the above MessageSource into this POJO --> <bean id="example" class="com.something.Example"> <property name="messages" ref="messageSource"/> </bean> </beans>
public class Example { private MessageSource messages; public void setMessages(MessageSource messages) { this.messages = messages; } public void execute() { String message = this.messages.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message); } }
execute()
方法调用的结果以下:
The userDao argument is required.
提及国际化("i18n"), Spring的变量MessageSource
实现遵循与标准JDK的ResourceBundle
相同的区域解析和回退规则. 总之, 继续前面定义的messageSource
示例,若是您想解析针对英语消息(en-GB
), 那么你须要分别建立format_en_GB.properties
, exceptions_en_GB.properties
, 和 windows_en_GB.properties
.
通常的, 区域解析是受程序的周围环境管理. 接下来的例子中, 依赖于(英国)的消息将被手工的解析:
# in exceptions_en_GB.properties argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); }
运行结果以下:
Ebagum lad, the 'userDao' argument is required, I say, required.
你也能够使用MessageSourceAware
接口获取任何定义的MessageSource
的引用. 任何在ApplicationContext
中定义的MessageSourceAware
接口实现将在程序上下文的bean建立和配置时使用MessageSource
注入.
做为
ResourceBundleMessageSource
的替换, Spring提供了ReloadableResourceBundleMessageSource
类. 这个变量支持文件格式化但比起基于标准JDK的ResourceBundleMessageSource
实现更灵活. 特别的, 它容许从Spring资源路径读取文件(不只是类路径)而且支持热加载property文件(将其有效缓存). 查看java文档ReloadableResourceBundleMessageSource
获取更多信息.
经过ApplicationEvent
类和ApplicationListener
接口提供了ApplicationContext
的事件处理. 若是bean实现了ApplicationListener
接口并部署在上下文中, 每当ApplicationEvent
发布到ApplicationContext
中时, 这个bean就获得通知了. 本质上, 这是标准的观察者模式.
从Spring4.2开始, 事件相关基础设施被明显加强了, 而且提供了基于注解的模型,也能够发布任意事件.(也就是说对象不必扩展自
ApplicationEvent
). 当这样的对象发布后, 咱们为你包装在一个事件中.
下面表格描述了Spring提供的标准事件:
表7 内置事件
Event | Explanation |
---|---|
ContextRefreshedEvent | 当ApplicationContext 被初始化或者刷新后发布(如经过使用ConfigurableApplicationContext 接口的refresh() 方法调用).这里,"initialized"意味着全部的bean被加载,post-processor被探测并激活, 单例被预初始化, ApplicationContext 对象准备就绪可用. 只要上下文没有被关闭, 刷新会被屡次触发, 所选的ApplicationContext 事实上支持热刷新. 例如XmlWebApplicationContext 支持热刷新,但GenericApplicationContext 不支持 |
ContextStartedEvent | 在ApplicationContext 启动后, 使用接口ConfigurableApplicationContext 的方法start() 将发布该事件.这里, "启动"意味着全部的Lifecycle bean接收到显式的启动信号. 通常而言, 这个信号在显式中止后用来重启bean, 但它也用来启动那些没有被配置为自动启动的bean(例如, 没有被初始化启动的组件). |
ContextStoppedEvent | 当ConfigurableApplicationContext 接口的方法stop() 调用后ApplicationContext 被中止是发布该事件. 这里"中止"意味着全部Lifecycle bean接收一个显式的中止信号. 结束的上下文可能经过start() 调用重启 |
ContextClosedEvent | 当ConfigurableApplicationContext 接口的方法close() 调用后,当ApplicationContext 关闭时发布. 这里, 关闭 意味着全部单例bean被销毁. 关闭的上下文生命结束. 它不可能被刷新或重启 |
RequestHandledEvent | 一个WEB特定的事件,告诉全部HTTP请求被处理的bean. 这个事件在请求完成后被发布. 这个事件仅仅对于使用了DispatcherServlet 的web程序有用. |
你也能够建立和发布你自定义的事件. 下面的例子展现了扩展ApplicationEvent
基类的一个简单类:
public class BlackListEvent extends ApplicationEvent { private final String address; private final String content; public BlackListEvent(Object source, String address, String content) { super(source); this.address = address; this.content = content; } // accessor and other methods... }
发布自定义的ApplicationEvent
, 能够调用ApplicationEventPublisher
上的publishEvent()
方法. 通常这是经过建立一个实现ApplicationEventPublisherAware
的类并将其注册为一个Spring bean. 以下所示:
public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void setBlackList(List<String> blackList) { this.blackList = blackList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String content) { if (blackList.contains(address)) { publisher.publishEvent(new BlackListEvent(this, address, content)); return; } // send email... } }
在配置时, Spring探测到实现了ApplicationEventPublisherAware
接口的EmailService
, 并自动调用setApplicationEventPublisher()
. 实际上,传入的参数是Spring容器自身. 你经过ApplicationEventPublisher
接口与之交互.
为了接收自定义的ApplicationEvent
, 你能够建立实现了ApplicationListener
接口的类并注册为bean. 以下所示:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } }
注意ApplicationListener
是使用自定义事件为泛型的参数的(前面例子中的BlackListEvent
).这意味着onApplicationEvent()
方法能保持类型安全, 防止任何类型转换. 只要愿意你能够注册任意多的监听器, 但要注意,默认状况下事件监听器是同步接收事件的. 这意味着publishEvent()
方法会阻塞直到全部监听器处理完事件. 一个同步和单线程的好处是,当监听器获取到一个事件, 它就会在发布者的事务上下文可用时进行操做. 若是其余事件发布策略时必要的, 可参看Spring接口ApplicationEventMulticaster
接口文档.
下面例子展现了用来注册和配置上面所述每一个类的bean定义:
<bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="blacklist@example.org"/> </bean>
整体来看, 当emailService
的sendEmail()
方法被调用后, 若是有任何在黑名单邮件消息, 自定义的事件BlackListEvent
就发布了. blackListNotifier
bean做为ApplicationListener
被注册, 并接收BlackListEvent
, 这里能够通知到恰当的某些部分.
Spring的事件机制是为了在相同上下文内部bean之间的简单通讯. 尽管如此, 对于大多数复杂的企业集成需求, 独立维护的Spring集成项目提供了完整的对构建轻量,面向模式的, 事件驱动的架构的支持, 能够构建在熟知的Spring编程模型之上.
从Spring4.2开始, 你能够在任意的经过EventListener
注解的bean方法上注册事件监听器. BlackListNotifier
能够重写以下:
public class BlackListNotifier { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } @EventListener public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } }
这个方法签名再次声明了它监听的事件类型, 但此次, 没有实现特定的接口并有灵活的名称. 这个事件类型也能够经过泛型缩小范围. 只要在继承架构内真实类型能够解析泛型参数便可.
若是你的方法应该监听多个事件或者你想用无参来定义它, 这个事件类型也能够指定在注解上面. 以下所示:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { ... }
经过使用condition
属性能够为运行时添加过滤, 能够定义一个SpEL
表达式, 用来匹配特定事件调用该方法.
下面例子展现了咱们的notifier能够重写, 仅在content
属性等于my-enent
的时候来调用:
@EventListener(condition = "#blEvent.content == 'my-event'") public void processBlackListEvent(BlackListEvent blEvent) { // notify appropriate parties via notificationAddress... }
每一个SpEL
表达式依赖于专有上下文解析. 下表列出上下文能够用的项目, 这些你能够用来条件化处理事件:
表8 事件SpEL可用的元数据
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
Event | root object | 其实是ApplicationEvent |
#root.event |
Arguments array | root object | 用来调用目标的参数(数组) | #root.args[0] |
Argument name | evaluation context | 方法参数名字. 若是由于某种缘由,名称不可用(例如由于debug信息),参数名称也能够基于#a<#arg> 使用, 其中#arg 表明参数索引(从0开始) |
#blEvent 或 #a0 (你也能够使用#p0 或#p<#arg> 记号做为别名) |
注意#root.event
提供了访问底层事件的入口, 就算你的方法签名实际指向发布的任意对象.
若是你须要发布另外一个事件的结果做为事件, 你能够改变方法签名, 使其返回要发布的事件, 以下:
@EventListener public ListUpdateEvent handleBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress and // then publish a ListUpdateEvent... }
这个特性不支持异步事件监听.
这个新方法为每一个上面方法处理的BlackListEvent
发布了一个ListUpdateEvent
事件. 若是须要发布若干事件, 则须要返回事件的Collection
.
若是你想要一个特定的监听器去异步处理事件, 你能够重用标准的@Async
支持. 以下:
@EventListener @Async public void processBlackListEvent(BlackListEvent event) { // BlackListEvent is processed in a separate thread }
当使用异步事件时有以下限制:
若是事件监听器抛出Exception
, 它将不会传递给调用者, 查看AsyncUncaughtExceptionHandler
有更多细节.
此类监听器不能发送回复. 若是你须要将处理结果做为事件发布, 须要手工将ApplicationEventPublisher
注入.
若是你须要一个监听器在另外一个以前调用, 你能够添加@Order
注解到方法上, 以下:
@EventListener @Order(42) public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... }
你也能够使用泛型深化定义事件结构. 考虑使用EntityCreatedEvent<T>
其中T
是建立的真实实体. 例如, 你能够仅仅经过EntityCreatedEvent
为Person
建立事件监听器:
@EventListener public void onPersonCreated(EntityCreatedEvent<Person> event) { ... }
由于类型擦除, 只有在触发的事件对事件侦听器筛选的泛型参数进行转述时, 此操做才有效(也就是, 形如class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
).
在特定场景下, 若是全部事件容许相同的结构(如前面例子中的事件案例),这可能会变得至关乏味. 在相似示例中, 你能够实现ResolvableTypeProvider
来指导框架跨越运行时环境所能提供的. 以下:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityCreatedEvent(T entity) { super(entity); } @Override public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); } }
不满是
ApplicationEvent
, 能够是你发送的任意对象事件.
为了以最优来使用应用程序上下文的使用和理解,您应该使用Spring的Resource
抽象(如Resource一章中所描述的)从新调整.
程序上下文是一个ResourceLoader
,用来加载Resource
对象. Resource
本质上是个JDKjava.net.URL
类的特性加强版本. 实际上, Resource
的实现封装了java.net.URL
实例. Resource
能以一种透明的方式获取任何位置的底层资源, 包含类路径, 系统路径, 任何使用标准URL描述的地方, 还有其余的变量. 若是资源路径字符串是个没有特殊前缀的字符串, 那么这些资源的来源是特定的,而且适合于实际的应用程序上下文类型.
你能够配置一个实现了指定借口ResourceLoaderAware
的bean部署到应用上下文, 用来自动在程序上下文初始化时做为ResourceLoader
传入. 你也能够暴露类型为Resource
的属性, 用来访问静态资源. 他们像其余属性同样被注入. 当bean被部署后, 你能够像简单的String
路径同样指定这些Resource
属性, 并依赖自动从字符串转化为真实的Resource
对象.
位置路径或提供给ApplicationContext
参数的路径其实是资源字符串, 而且简单的形式下, 将根据特定的上下文实现被合适的对待. 如ClassPathXmlApplicationContext
将以类路径对待一个简单的路径. 你能够使用特定的前缀迫使定义从类路径或URL加载, 无论真实的上下文是什么.
经过使用如ContextLoader
你能够建立ApplicationContext
实例. 固然, 你也能够使用ApplicationContext
的其中一个实现类编程方式建立ApplicationContext
.
你能够经过ContextLoaderListener
注册ApplicationContext
, 以下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
监听器检查contextConfigLocation
参数. 若是参数不存在, 默认使用/WEB-INF/applicationContext.xml
. 当这个参数存在时, 监听器经过预约义的字符(逗号,分号,空格)分割字符串并使用它们来做为应用上下文查找的路径. Ant风格的路径模式也支持. 例如:/WEB-INF/*Context.xml
(对于包含在WEB-INF
目录下的全部的以Context.xml
结尾的文件)和/WEB-INF/**/*Context.xml
(对于全部WEB-INF
目录下任何子目录的文件).
ApplicationContext
为JavaEE RAR 文件部署Spring的ApplicationContext
为JavaEE RAR 文件是可能的, 将封装上下文和全部须要的类和库JAR文件到一个JAVA EE RAR部署单元. 这等同于启动了一个单独的ApplicationContext
(仅在Java EE 环境宿主), 可以访问Java EE 服务器设施. RAR部署是一种更天然的替代方式去部署一个没有头文件的WAR文件, 实际上, 没有任何HTTP入口的WAR文件常常用在Java EE环境中, 用来启动一个ApplicationContext
.
对于不须要HTTP入口但有消息端点和定时任务的程序上下文, RAR部署是理想化的. 这类上下文中的bean能使用程序服务器资源, 好比JTA事务管理和JNDI绑定JDBCDataSource
实例以及JMSConnectionFactory
实例, 而且也能注册平台的JMX服务器--都是经过Spring标准事务管理和JNDI还有JMX支持设施的. 经过Spring的TaskExecutor
抽象, 应用组件也能够与应用服务器JCAWorkManager
交互.
关于RAR部署的更多配置细节能够查看文档SpringContextResourceAdapter
类.
对于一个简单的将ApplicationContext部署为Java EE RAR 文件来讲:
打包全部程序类到RAR文件(标准的JAR文件, 只是后缀不一样). 添加所需的库JAR到RAR架构的根下. 添加META-INF/ra.xml
部署描述符(见javadocSpringContextResourceAdapter
)还有相关的XML定义文件(典型的如META-INF/applicationContext.xml
).
将结果RAR文件丢到应用程序服务器的部署路径下.
此种部署单元是自包含的. 他们没有暴露给外部组件, 甚至没暴露给相同程序的其余模块. 与基于RAR的
ApplicationContext
交互一般经过它与其余模块共享的JMS目标发生. 基于RAR的ApplicationContext
也可能定时做业或与文件系统种的文件交互. 若是它须要容许从外界同步访问, 它能够(举例来讲)处处RMI端点,端点是用来在相同机器上被其余模块使用的.
BeanFactory
BeanFactory
API提供了基本的Spring IoC容器功能. 它的专门的约定几乎是用来与Spring其余部分或相关三方框架集成, 在高级的容器GenericApplicationContext
种, 它的DefaultListableBeanFactory
实现是一个关键的代理.
BeanFactory
和相关的接口(如BeanFactoryAware
,InitializingBean
,DisposableBean
)是与其余组件重要的集成点. 不须要任何注解或反射, 他们容许有效的在容器和它的组件间交互. 应用级别的bean可能使用相同的回调接口但通常更喜欢声明式的DI, 无论经过注解或经过编程方式配置.
注意核心BeanFactory
API和它的DefaultListableBeanFactory
实现不会假设配置格式或任何使用的组件注解. 全部这些调剂经过扩展(如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)并在共享的做为元数据呈现的BeanDefinition
对象上操做. 这是使得Spring容器灵活可扩展的关键.
BeanFactory
或者 ApplicationContext
?本节解释了BeanFactory
和ApplicationContext
容器级别的不一样和启动时的含义.
无论你是否有理由, 你应该使用ApplicationContext
, GenericApplicationContext
和它的子类AnnotationConfigApplicationContext
是为自定义启动更通常的实现. 这些是Spring核心容器通常目的而言主要的入口点: 配置文件加载, 触发类路径扫描, 编程注册bean定义和注解类, 还有(从5.0开始)注册函数式bean定义.
由于ApplicationContext
包含了BeanFactory
的全部功能, 它通常是更建议使用的, 除非有全部bean的处理控制的需求. 在ApplicationContext
(如GenericApplicationContext
实现)内, 按惯例几种bean被探测(也就是, 经过名字或类型-特别的,post-processors), 而DefaultListableBeanFactory
并不知道任何特定的bean.
对许多容器扩展特性来讲, 如注解处理和AOP代理, BeanPostProcessor
扩展点是关键. 若是你仅仅使用普通的DefaultListableBeanFactory
, post-processors默认不会被探查和激活. 这种状况多是疑惑的, 由于你的配置没有任何错误. 固然此场景下 , 容器须要经过附加的设置彻底启动.
下面列出了经过BeanFactory
和ApplicationContext
接口和实现提供的特性.
表9 特性矩阵
Feature | BeanFactory |
ApplicationContext |
---|---|---|
bean实例化/装配 | 是 | 是 |
集成生命周期管理 | 否 | 是 |
BeanPostProcessor 自动注册 |
否 | 是 |
BeanFactoryPostProcessor 自动注册 |
否 | 是 |
MessageSource 便捷访问(对于内部) |
否 | 是 |
内置ApplicationEvent 发布机制 |
否 | 是 |
显式使用DefaultListableBeanFactory
注册一个post-processor, 你须要编程方式调用addBeanPostProcessor
以下:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions // now register any needed BeanPostProcessor instances factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); factory.addBeanPostProcessor(new MyBeanPostProcessor()); // now start using the factory
为一个普通DefaultListableBeanFactory
应用BeanFactoryPostProcessor
, 你须要调用它的postProcessBeanFactory
方法, 以下:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);
两个例子中, 显式注册步骤是没必要要的, 这就是在Spring背景的程序中, ApplicationContext
变量比DefaultListableBeanFactory
更好用. 特别是当一个典型企业应用中依赖于BeanFactoryPostProcessor
和BeanPostProcessor
实例进行扩展时.
AnnotationConfigApplicationContext
有通用的post-processor注册并可能在底层经过配置注解引入更多的processor, 如@EnableTransactionManagement
. 在Spring基于注解的抽象级别, bean postprocessor的概念变成了一个微小的内部容器细节.
本章涵盖了Spring如何处理资源以及在Spring中你如何与资源打交道的内容. 包含下面议题:
ResourceLoader
ResourceLoaderAware
接口Java的标准java.net.URL
类和用来处理多种URL前缀的标准处理器, 很不幸对于底层资源的访问不是足够的. 例如, 没有标准的URL
实现能用来访问须要从类路径下或者相关的ServletContext
下的资源. 虽然能够注册为特定的URL
前缀的处理器(相似已经存在的处理http:
前缀的处理器), 但通常比较复杂, 而且URL
接口仍然缺少一些急需的功能, 例如检查指向资源是否存在的方法.
Spring的Resource
接口意味着对于底层资源的抽象来讲功能更丰富的接口. 下面列出了Resource
接口定义:
public interface Resource extends InputStreamSource { boolean exists(); boolean isOpen(); URL getURL() throws IOException; File getFile() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
如同Resource
接口定义的, 它扩展了InputStreamSource
接口, 以下列出这个接口的定义:
public interface InputStreamSource { InputStream getInputStream() throws IOException; }
一些重要的从Resource
接口而来的方法是:
getInputStream()
: 定位和打开资源, 返回从资源读取的InputStream
. 它期待每一个调用返回全新的InputStream
. 调用者有义务关闭流.exists()
: 返回boolean
指示这个资源其实是否物理上存在.isOpen()
: 返回boolean
指示资源是否表现为处理打开的流. 若是true
,InputStream
不能被屡次读取,并且进读取一次而后关闭以免资源泄漏. 返回false
则一般对通常资源实现, 同时可能返回异常InputStreamResource
.getDescription()
: 返回资源的描述, 当处理资源时用于错误的输出. 一般时全限定文件名或资源的url.其余方法容许你获取实际的表示资源的URL
或File
对象(若是底层实现是兼容的并支持此功能).
Spring自身使用Resource
抽象扩展做为许多方法签名的参数类型. 其余相同API中(如多个ApplicationContext
实现)的方法使用String
或简单格式来建立Resource
到上下文实现, 或者, 经过特殊的Stirng路径上的前缀, 来使调用者指定必须建立使用的特定Resource
.
Resource
接口用在不少Spring或与Spring协做, 它自己是颇有用的工具类, 可用于你的代码中, 用来访问资源, 就算你代码不知道或在乎Spring的其余部分. 这使得你的代码耦合到Spring中, 但它真的仅仅耦合了其中一小部分工具类, 这些类提供了更有用的URL
的替换并能提供其余相同目的的三方库.
Resource
抽象功能上不替换. 它作了封装. 例如,UrlResource
封装了RUL并使用封装的URL
工做.
Spring包含以下Resource
实现:
UrlResource
ClassPathResource
FileSystemResource
ServletContextResource
InputStreamResource
ByteArrayResource
UrlResource
UrlResource
封装了java.net.URL
而且能够用来访问能够用URL
访问的对象, 如文件,HTTP目标,FTP目标等.全部URL都有一个标准的String
表现, 有标准化的前缀能够用来代表URL类型. 包含file:
来访问文件路径, http:
访问经过HTTP协议的资源, ftp:
访问FTP资源等.
UrlResource
经过显式使用UrlResource
构造函数来建立, 但一般, 在你调用包含一个表示路径的String
参数API方法时就隐式建立了. 对于后者, JavaBeans PropertyEditor
最终决定建立Resource
哪一个类型.若是路径包含熟知的前缀(如:classpath:
), 他就为这个前缀建立了一个特定的Resource
. 那若是没有可识别的前缀, 就假定它是一个标准的URL字符串并建立UrlResource
.
ClassPathResource
这个类表示应该从类路径包含的资源. 它使用线程上下文类加载器, 给定的类加载器,或者一个为加载资源的类.
若是类路径资源在文件系统, 不在包含在jar文件并无被解压(被servlet引擎或环境)到文件系统中, 类路径资源Resource
实现支持做为java.io.File
解析. 为处理这种状况, Resource
实现老是支持做为java.net.URL
解析.
ClassPathResource
过显式使用UrlResource
构造函数来建立, 但一般, 在你调用包含一个表示路径的String
参数API方法时就隐式建立了. 对于后者, JavaBeans PropertyEditor
识别出特定在字符串路径上的前缀classpath:
,并建立一个ClassPathResource
.
FileSystemResource
这是一个对java.io.File
和java.nio.file.Path
处理的Resource
实现.支持做为File
或URL
解析.
ServletContextResource
这是一个对ServletContext
资源的Resource
实现, ServletContext
资源解析为在相关web程序根路径内的相对路径.
支持流访问或URL访问, 但仅在web程序结构解压而且资源以及存在于物理文件系统时容许java.io.File
访问. 无论是否解压到文件系统或直接从Jar或其余的相似数据库的地方(这是可能的)访问, 都依赖于Servlet容器.
InputStreamResource
InputStreamResource
是对于一个给定InputStream
的Resource
实现. 仅用于没有合适的Resource
实现时. 特别的, 更应该用ByteArrayResource
或其余基于文件的Resource
实现.
相比其余的Resource
实现, 这是一个对以及打开资源的描述. 所以, isOpen()
返回true
. 若是你须要保持资源描述符或者你须要屡次读取流就不要使用它.
ByteArrayResource
这是对给定字节数组的Resource
实现. 它为给定数组建立一个ByteArrayInputStream
.
对于从任何给定的byte数组而不须要从单独使用InputStreamResource
时是颇有用的.
ResourceLoader
ResourceLoader
接口被实现为可以返回(加载)Resource
的对象. 下面列出ResourceLoader
接口定义:
public interface ResourceLoader { Resource getResource(String location); }
全部程序上下文均实现了ResourceLoader
接口. 因此, 全部程序上下文能够包含Resource
实例.
当你在特定的程序上下文调用getResource()
方法时, 而且指定的位置路径没有特殊的前缀, 你就获得一个特定于上下文的Resource
类型. 例如, 假设下面代码依赖于ClassPathXmlApplicationContext
执行.
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
依赖于ClassPathXmlApplicationContext
, 代码返回一个ClassPathResource
. 若是相同的方法依据FileSystemXmlApplicationContext
实例执行, 他将返回FileSystemResource
. 对于一个WebApplicationContext
, 他将返回ServletContextResource
. 每种上下文对应每种特定返回的对象.
结果, 你能够使用一种恰当的特定上下文的风格加载资源.
另外一方面, 你可能也须要强制使用ClassPathResource
, 无论上下文类型是什么, 经过指定特定的classpath:
前缀, 以下:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
类似的, 你能够强制经过指定任意标准的java.net.URL
前缀来使用UrlResource
. 下面一对实例使用file
和http
前缀:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
下面表格简要列出从String
对象到Resource
对象的转化策略:
表10 资源字符串
前缀 | 示例 | 解释 |
---|---|---|
classpath: | classpath:com/myapp/config.xml | 从类路径加载 |
file: | file:///data/config.xml | 以url格式从文件系统加载. 查看FileSystemResource 附加说明 |
http: | http://myserver/logo.png | 从url加载 |
(none) | /data/config.xml | 依赖于底层ApplicationContext 类型 |
ResourceLoaderAware
接口ResourceLoaderAware
接口是特定的回调接口, 用来标识指望有ResourceLoader
引用提供的组件. 下面展现了接口定义:
public interface ResourceLoaderAware { void setResourceLoader(ResourceLoader resourceLoader); }
当一个类实现ResourceLoaderAware
而且部署到应用上下文中(受Spring管理)时, 它就被上下文认做ResourceLoaderAware
. 引用上下文接着调用setResourceLoader(ResourceLoader)
, 并将自身做为参数传入(记住, Spring全部应用上下文都实现了ResourceLoader
接口).
由于ApplicationContext
时一个ResourceLoader
, bean也能够实现ApplicationContextAware
并使用现有的上下文直接加载资源. 尽管如此, 通常而言,若是这就是你想达到的目的, 最好使用特定的ResourceLoader
接口(被认为是个工具接口). 代码将仅耦合到资源加载接口而不是整个SpringApplicationContext
接口.
在程序组件中, 你也能够依赖ResourceLoader
自动装配, 做为ResourceLoaderAware
实现的替代. 传统的constructor
和byType
自动装配模式(如在自动装配协做者中描述的)能为构造函数参数或setter方法提供ResourceLoader
. 问了更多的灵活性(包括自动装配域和多参数方法), 考虑使用基于注解的自动装配特性. 这种状况下,ResourceLoader
自动装配给 使用@Autowired
注解的任何域,构造参数或方法参数. 更多信息参看:使用@Autowired
.
若是一个bean将要经过某种动态方式断定和提供资源路径, 可能对于这个bean使用ResourceLoader
接口加载资源就是有意义的. 例如, 考虑加载某类型的模版, 这种模版资源须要依赖于用户角色. 若是资源是固定的, 那就能够彻底排除ResourceLoader
接口的使用, 暴露bean须要的的Resource
属性, 并指望他们被注入.
而后注入这些属性的简单之处在于,全部应用程序上下文都注册并使用一个特殊的JavaBeans PropertyEditor
,它能够将字符串路径转换为资源对象. 因此, 若是myBean
有个类型为Resource
的模版属性, 它能够被配置为一个简单的字符串, 以下:
<bean id="myBean" class="..."> <property name="template" value="some/resource/path/myTemplate.txt"/> </bean>
注意资源路径没有前缀. 因此, 由于程序上下文将会被做为ResourceLoader
, 资源将经过ClassPathResource
,FileSystemResource
,ServletContextResource
, 依赖于具体的上下文类型.
若是你须要强制使用特定的Resource
类型,你能够使用前缀. 下面两个例子展现了如何强制一个ClassPathResource
和UrlResource
(后者被用来访问文件系统文件):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
本节涵盖了如何建立有资源的上下文, 包含使用XML的捷径, 如何使用通配符, 和其余细节.
一个程序上下文构造器(对于一个特定的应用上下文类型)通常用一个字符串或字符串数组做为资源的位置路径, 如组成上下文的定义的XML文件.
当这个位置路径不含有前缀时, 特定的Resource
类型从这个路径构建并加载bean定义依赖,并且时特定于上下文的. 例如, 考虑下面的例子, 建立了一个ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
bean的定义从类路径加载, 由于使用了ClassPathResource
. 再考虑下面的例子, 建立了FileSystemXmlApplicationContext
:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
如今bean定义从文件系统路径加载(本例中, 相对于当前工做目录).
注意特定的类路径前缀或URL前缀覆盖了默认的加载bean定义的Resource
类型, 考虑下面例子:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用FileSystemXmlApplicationContext
从类路径加载定义. 虽然如此, 仍然是一个FileSystemXmlApplicationContext
. 若是所以做为ResourceLoader
来使用, 任何没有前缀的路径都将被做为文件系统路径对待.
ClassPathXmlApplicationContext
构建实例--捷径ClassPathXmlApplicationContext
类暴露了不少构造函数来方便地初始化. 基本概念是, 你能够仅仅提供一个包含XML文件名自己的字符串数组(没有引导路径信息),或者提供一个Class
. ClassPathXmlApplicationContext
接着从给定class获取路径信息.
有以下目录结构:
com/ foo/ services.xml daos.xml MessengerService.class
下面例子展现了ClassPathXmlApplicationContext
实例组合从名字为services.xml
和 daos.xml
的文件中(在类路径下)整合bean的定义初始化.
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}, MessengerService.class);
查看ClassPathXmlApplicationContext
文档获取更多构造函数的明细.
程序上下文构造函数中资源路径能够是简单的路径(如前面的例子), 每个都一对一映射到目标Resource
, 或者相反, 可能存在指定的"classpath*:"前缀, 或者内部Ant风格的正则表达式(经过使用SpringPathMatcher
工具匹配). 后面两个都是有效的通配符.
这种机制的一种用处就是, 当你须要在一个组合风格的应用集中使用. 全部组件都能'publish'应用上下文定义片断到熟知的路径中, 而且当最终上下文使用相同的路径前缀classpath*:
建立时, 全部组合的片断都自动被获取.
注意, 构造函数中的通配符是特定于资源路径并在构造时被解析. 它跟Resource
类型自己没有关系, 你不能使用classpath*:
前缀去构建实际的Resource
, 一个资源在某一时刻仅对应一个资源.
路径能够包含Ant风格的模式, 以下:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
若是路径包含Ant风格的模式, 解析器遵循更为复杂的处理过程去试图解析通配符. 它为路径生成一个Resource
资源到最近的非通配符片断并包含了一个URL. 若是URL不是jar:
URL或者容器特定的变体(如WebLogic中的zip:
, WebSphere中的wsjar
等), 一个java.io.File
包含进来并用于经过文件系统遍历解析通配符. 若是是jar URL, 解析器也能够得到到一个java.net.JarURLConnection
并解析这个jar URL,而后遍历jar文件的内容.
若是特定的路径已是一个文件URL(因基类ResourceLoader
是文件系统而隐式的或显式的), 通配符确保在一个彻底可移植的风格下工做.
若是指定的是一个类路径, 解析器必须包含最近的非通配符路径片断URL, 这是经过调用Classloader.getResource()
完成的. 由于这仅仅是一个路径的节点(不是最终文件), 它其实是没有明肯定义的一种URL. 实践中, java.io.File
一般表示目录(类路径资源解析为文件路径资源)或某jar URL(类路径解析为jar路径). 一样, 其中有种可移植的概念.
若是jar URL包含最近的非通配片断, 解析器必须能获取到java.net.JarURLConnection
或者手工转化为jar URL, 从而能访问jar的内容并解析通配符. 这在一些环境下会失败, 咱们强烈建议特定环境下的jar资源通配符解析在特定环境下测试后再最终依赖它.
classpath*:
前缀当构建一个XML的应用上下文时, 位置字符串可能使用classpath*:
前缀, 以下:
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
特定的前缀指定了全部匹配到的类路径资源必须被包含进来(内部, 这经过调用ClassLoader.getResources(…)
实现), 并最终合并到上下文定义中.
通配符类路径依赖于底层类加载器的
getResource()
方法. 如今不少程序服务器提供了他们自身的加载器实现, 因此这种行为可能有所差异, 特别是处理jar文件时.classpath*
是否工做的一个简单测试是使用加载器去加载类路径下的一个jar文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")
. 尝试这类测试, 当文件有相同的名称但处于不一样路径下时. 万一返回的结果不对, 检查服务器文档的设置, 这些设置可能会影响类加载器行为.
你也能够在路径其余部分中使用PathMatcher
模式来组合classpath*:
(例如, classpath*:META-INF/*-beans.xml
). 这种状况下, 解析策略至关简单: ClassLoader.getResources()
用来解析最后的非通配路径片断来获取全部类加载器继承中匹配的全部资源, 而后对于每种资源外, 对通配符子路径使用前面描述的路径匹配器解析策略。
注意classpath*:
当组合使用Ant格式模式时, 只有在模式开始以前,至少一个根目录才能可靠地工做,除非实际的目标文件驻留在文件系统中。这意味着相似classpath*:*.xml
之类的模式可能不会从JAR文件的根检索文件,而可能只从扩展目录的根检索文件。
Spring检索类路径条目的能力源于JDK的ClassLoader.getResources()
方法,该方法只返回空字符串的文件系统位置(指示搜索的潜在根路径)。Spring也在JAR文件中评估 URLClassLoader
加载器运行时配置和java.class.path
清单,但这并不必定会致使可移植行为。
对类路径包的扫描要求在类路径中存在相应的目录条目。当您用Ant构建JAR时,不要激活JAR任务的仅文件开关。此外,类路径目录在某些环境(例如,JDK1.7.0_45及更高版本上的独立应用程序)中可能不会根据安全策略公开(这须要在清单中设置“受信任的库”)。参见http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(jigsaw)上,Spring的类路径扫描通常按预期工做。将资源放在专用目录中也是很是值得推荐的,避免了搜索JAR文件根级别的上述可移植性问题。
具备classpath:
的Ant模式:若是要搜索的根包在多个类路径位置中可用,则不能保证资源找到匹配的资源。考虑如下资源位置示例:
com/mycompany/package1/service-context.xml
如今考虑可能有人想要Ant模式中找到文件:
classpath:com/mycompany/**/service-context.xml
这样的资源可能只位于一个位置,可是当使用前面的示例这样的路径试图解析它时,解析器将关闭getResource("com/mycompany");
返回的(第一个)url;。若是此基本包节点存在于多个类加载器位置,则可能不存在实际的结束资源。所以,在这种状况下,您应该更喜欢使用具备相同Ant样式的ClassPath*:
模式,它搜索包含根包的全部类路径位置。
FileSystemResource
附加说明FileSystemResource
没有附加到FileSystemApplicationContext
(也就是,当FileSystemApplicationContext
不是真正的ResourceLoader
)时, 它对待绝对路径和相对路径能按预期处理. 相对路径相对于当前工做目录,而绝对路径相对于文件系统的根。
当FileSystemApplicationContext
是ResourceLoader
时, 因向后兼容而有所变化. FileSystemApplicationContext
强制全部依附的FileSystemResource
实例以相对路径方式对待路径, 无论他们是否之前缀"/"开头. 实践中, 下面两种方式是等同的:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml");
下面两种方式也是等同的(就算区别开他们有意义, 由于一个是相对的一个是绝对的):
FileSystemXmlApplicationContext ctx = ...; ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...; ctx.getResource("/some/resource/path/myTemplate.txt");
实践中, 若是你须要绝对路径, 你应该避免使用FileSystemResource
或FileSystemXmlApplicationContext
. 并经过file:
前缀强制使用UrlResource
. 以下展现了如何这么作:
// actual context type doesn't matter, the Resource will always be UrlResource ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");