1. 实例化容器
bean的概述
包限定类名:通常bean是实现类。
bean
行为配置元素,说明bean
在容器中的行为方式(范围、生命周期回调等)对
bean
完成其工作所需的其他bean
的引用,这些引用也称为协作者和依赖项要在新创建的对象中设置其他配置,例如,池的大小限制或者要在管理连接池的bean中使用的连接数
property | explained |
---|---|
class | 实例化的bean |
name | bean的名称 |
scope | 范围 |
constructor arguments | 依赖注入 |
properties | 依赖注入 |
autowiring mode | 自动注入 |
initialization method | 初始化回调 |
destruction method | 销毁回调 |
根据定义的bean
信息,通过ApplicationContext
实现类,创建对象并注入到容器中。通过BeanFactory
中的getBeanFactory
方法返回DefaultListableBeanFactory
。 DefaultListableBeanFactory
支持registerSingleton(..)
和registerBeanDefinition(..)
方法。
的元数据,以便容器在自动注入和检查的时候正确的推断出来他们。虽然在一定程度上支持覆盖现有元数据和现有单利实例,但是官方不支持在运行期,注册新的bean
依赖注入
依赖注入(DI)是一个过程,在此过程中,对象仅通过构造函数参数、工厂方法参数或对象实例构造或从工厂方法返回后在其上设置的属性来定义其依赖关系(即与之协同工作的其他对象)。然后,容器会在创建Bean
时注入这些依赖关系。从根本上说,这一过程是 Bean
本身通过直接构建类或服务定位器模式来控制其依赖关系的实例化或位置的逆过程(因此被称为控制反转)。 采用DI原则,代码会更简洁,当对象提供其依赖关系时,解耦会更有效,对象不会查找其依赖关系,也不知道依赖关系的位置或类别。因此,你的类变得更容易测试,尤其是当依赖的是接口或抽象基类时,这就允许在单元测试中使用存根或模拟实现。 依赖注入有两种主要形式:基于构造器的依赖注入和基于set方法的依赖注入。
bean的范围
当你定义bean时,你就创建了一个配方,用于创建改bean定义的类的实例。bean定义一个配方,这个概念非常重要,因为它意味着,与类一样,你可以通过一个配方创建许多对象实例。 你不仅可以控制根据特定bean定义创建的对象中要插入的各种依赖关系和配置值,还可以控制根据特定bean定义创建的对象的作用域。这种方法即强大又灵活,因为你可以通过配置来选择创建对象的作用域,而不必再java类级别上确定对象的作用域。可以将bean定义为多个作用域之一中部署。spring Framework支持六中作用域,其中四种只有再使用AplicationContext时才可用。你还可以创建自定义作用域。 下表描述了支持的范围:
scope | description |
---|---|
signleton | 为每个spring ioc容器的实例化一个bean |
prototype | 将单个bean定义的作用域扩展到任意数量的对象实例 |
request | 将单个bean定义作用域扩展到单个http请求的生命周期。也就是说,每个http请求都有自己的bean实例,该实例是根据单个bean定义创建的。仅仅在spring applicationContext的上下文中有效 |
session | 将单个bean作用的作用域扩展到httpsession的生命周期.仅在web=aware spring applicationContext中有效 |
application | 将单个bean定义的作用域扩展到servletContext的生命周期。仅在web=aware spring applicationContext中有效 |
websocket | 将单个bean定义的作用域扩展到websocket的生命周期。仅在web=aware spring applicationContext中有效 |
singleton
单例bean
只管理一个共享实例, spring
容器会返回所有对id
符合bean
定义的bean
的请求。 换个说话,当你定义一个bean
定义将其作用域设为单例时, springIoc
容器就会为该bean
定义所定义的对象创建一个实例。这个单一实例存储在此类单利bean的缓存中,对该命名bean的所有后续请求和引用都会返回缓存对象。 spring
的单利bean
概念不同于四人帮模式中定义的单利模式。gof单例对象的作用域进行了硬编码,因此每个classLoader
只能创建一个特定类的实例。 spring
单利的作用域最好描述为每个容器和每个bean
。这意味,如果你的单个spring
容器中为一个特定类定义了一个bean
,那么spring
容器为该bean
定义所定义的类创建一个且仅有一个实例。单利作用域是spring
的默认作用域。
prototype scope
bean部署的非单例原型作用域会导致每次对特定bean剔除请求时都会创建一个新的bean实例,也就是说,该bean被注入到另外一个bean中,或者你通过容器上的getBean() 方法调用来请求该bean。通常,你应该对所有有状态的bean使用原型作用域,对无状态的bean使用单例作用域。 数据访问对象dao通常不配置为原型,因为单例的dao不持有任何对话状态。对我们来说,复用单例更为容易。 与其他作用域不同,spring不会观察原型bean的整个生命周期。容器会对原型对象进行实例化、配置和其他组装,然后将其交给客户端,将不再记录该原型实例。因此,尽管初始化生命周期回调方法会在所有对象上调用,而与作用域无关,但就原型而言,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型作用域对象,并释放原型bean持有的昂贵资源。要让spring容器释放原型作用域bean持有的资源,可以尝试使用自定义bean后处理器,该处理器只有需要清理的bean的引用。
singleton beans with prototype-bean dependencies
当你使用依赖于原型bean的单例作用域bean时,请注意依赖关系是在实例化时解析的。因此,如果你将一个原型作用域bean依赖注入到一个单利作用域bean中,就会实例化一个新原型bean,然后将其依赖注入到单例bean中。原型实例是提供给单利作用域bean的唯一实例。 但是,假设你希望单利作用域bean在运行时重复获取原型作用域bean的新实例。你不能将原型作用域bean依赖注入到你的单例bean中,因此注入只会发生一次,即spring容器实例化单利bean并解析和注入其依赖关系时。如果你需要在运行时多次使用原型bean的新实例,请参考方法注入。
request、session、application、websocket scopes
request
, session
, application
和 websocket
作用域仅在使用web-aware的 Spring ApplicationContext
实现(如 XmlWebApplicationContext
)时可用。如果将这些作用域用于常规的spring IOC容器,则会抛出关于bean未知scope的IllegalStateException
异常。
初始化web配置
在定义bean之前需要一些小的初始化配置。 如何完成初始化设置取决你的特定servlet环境。 如果你在springWebMvc(实际上在spring dispatcherServlet处理的请求中)访问作用域bean,则无需进行特殊设置。dispatcherServlet已经公开所有的相关状态。 如果使用的是Servlet2.5web容器,且请求是在spring的dispatcherServlet之外处理(例如JSF或Struts时),那么久需要注册org.springframework.web.context.request.RequestContextListener
。对于servlet3。0以上版本,可以使用WebApplicationInitializer接口以编程方式完成注册。或者对于旧版容器,可以在web applications中的web.xml文件中添加一下声明:
另外,如果你的监听器设置存在问题,可以考虑使用spring的requestContextFilter。过滤器映射取决于周围的网络应用配置,因此必须根据情况进行更改。下面的列表显示了网络应用程序的过滤器部分:
dispatcherSservlet,requestContextListener 和 RequestContextFilter的作用完全相同,即把http请求对象绑定到为该请求提供服务的thread上。这样,请求和会话作用域的bean旧可以在调用链的更深处使用。
Request scope
spring容器会使用loginAction bean定义为每个http请求创建一个新的LoginAction Bean实例。也就是说,LoginAction bean的作用域就是http请求级别。你可以随便更改所创建实例的内部状态,因为根据相同的LoginAction Bean定义创建的其他实例不会看到这些状态变化。这些变化只针对单个请求。请求处理完成后,该请求作用域的bean将被丢弃。 使用注解驱动的组件或java配置时,可使用@RequestScope注解将组件分配到request作用域。下面的实例展示了如何做到这一点。
session scope
在单个http session的声明周期内,spring容器会使用userPreferences bean定义创建UserPreferences bean的新实例。换句话说,UserPreferences bean的有效作用域是http session级别。与请求作用域bean一样,你可以随心所欲的更改所创建实例的内部状态,因为其他使用相同UserPreferencesBean 定义创建的实例的http session实例不会看到这些状态更改,因为这些更改是针对单个http session的。当http session最终被丢弃时,该特定http session作用域的bean也会被丢弃。
使用注解驱动的组件或java配置时,可以使用@SessionScope注解将组件分配到session作用域。
application scope
Spring容器会在整个web应用程序中使用一次appPreferences bean定义来创建AppPreferences Bean的新实例。也就是说,appPreferences Bean的作用域是ServletContext级别,并存储为常规的ServletContext属性。这一点类似于spring得的单利bean,但有两个重要区别:它是每个ServletContext的单利,而不是Spring ApplicationContext的单利。而且他实际上是暴露的,因此作用ServletContext属性可见。 使用注解驱动的组件或java配置时,可以使用@ApplicationScope注解将组件分配到application作用域。下面的实例展示了如何做到这一点:
WebSocket scope
WebSocket作用域与WebSocket会话的生命周期相关联,适用于STOMP over WebSocket应用程序
Scoped Beans as Dependencies
spring ioc容器不仅管理对象的实例化,还管理协作者的链接。如果你想将http请求作用域的bean注入到另一个作用域更长的bean中,你可以选择注入一个aop代理来代替作用域bean。也就是说,你需要注入一个代理对象,他与作用域对象公开相同的公共接口,但也能从相关作用域(Http请求)检索真正的目标对象,并将方法调用委托给真正的对象。
自定义bean的性质
spring框架中提供了许多接口,你可以使用他们自定义bean的性质。本届将对它们进行如下分组:
生命周期回调
applicationContextAware和BeanNameAware
其他Aware的接口
生命周期回调
要与容器对bean生命周期的管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器会为前者调用afterPropertiesSet()接口,为后者调用destroy() 接口,以便让Bean在初始化和销毁Bean时执行某些操作。 在内部,spring框架使用BeanPostProcessor实现来处理它能找到的任何回调接口,并调用相应的方法。如果需要Spring默认不提供的自定义功能或其他声明周期行为,可以自行实现BeanPostProcessor。更多信息,请参阅容器扩展点。 除了初始化和销毁回调外,Spring管理的对象还可以实现Lifecycle接口,这样这些对象就可以根据容器自身的生命周期参与启动和关闭过程。
初始化回调
通过org.springframework.beans.factory.InitializingBean
接口,bean可以在容器设置了bean的所有必要性质后执行初始化工作。InitializingBean接口指定了一个方法:
我们建议你不要使用InitializingBean接口,因为它不必要地将代码与Spring联系在一起。另外,我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有void无参数签名的方法名称。在java配置中,你可以使用@Bean的initMethod属性。请参阅接受生命周期回调。请看下面的实例:
销毁回调
通过实现org.springframework.beans.factory.DisposableBean
接口,Bean可以在包含它的容器被销毁时获得回调。DisposableBean接口制定了一个方法:
我们建议你不要使用DisposableBean回调接口,因为它不必要地将代码与Spring联系一起。另外,我们建议使用@PreDestroy注解或指定Bean定义支持的通用方法。对于基于XML的配置元数据,可以使用 上的destroy-method属性。对于java配置,你可以使用@Bean的destroyMethod属性。
默认初始化和销毁方法
在编写不使用特定于Spring的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调时,通常会编写名称为init() 、initialize()和dispose()的方法。initialize(),dispose()等等。理想情况下,此类声明周期回调方法的名称在整个项目中标准化,以便所有开发人员使用相同的方法名称,确保一致性。 你可以配置spring容器在每个Bean会“查找”命名的初始化和销毁回调方法名称。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用名为init()
的初始化回调,而无需再每个bean定义中配置init-method="init"属性。当创建Bean时,Spring IOC容器会调用该方法(并且符合前面描述的标准声明周期回调契约)。此功能还为初始化和销毁方法回调执行了一致的命名规范。 Spring容器确保在Bean与所有依赖关系一起提供后,立即调用配置的初始化回调。因此,初始化回调是在原始bean引用上调用的,这意味着AOP拦截器等尚未应用到Bean上。首先创建完整的目标Bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,你的代码甚至可以绕过代理与原始目标Bean进行交互。因此,将拦截器应用到init方法是不一致的,因此这样做会将目标Bean的生命周期与其的代理或拦截器联系起来,当你的代码直接与原始目标Bean交互时,会留下奇怪的语义。
生命周期机制的组合
从spring2.5开始,有三种控制bean生命周期的行为的选项:
InitialzingBean和DisposableBean回调接口。
自定义init()和destroy()方法
@PostConstruct和@PreDestroy注解。你可以结合这些机制来控制给定的Bean。
为同一个Bean配置的多个生命周期机制,具有不同的初始化方法,其调用方式如下:
注解为@PostContruct的方法。
afterPropertiesSet()由InitializingBean回调接口定义
自定义配置的init()方法
销毁方法的调用顺序:
@PreDestroy的方法
destroy()由DisposableBean回调接口定义。
自定义配置的destroy()方法
启动和关闭回调
lifecycle接口定义了任何对象的基本方法,这些对象都有自己的生命周期要求(例如启动和停止某些后台进程): 任何spring管理对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接受到启动和停止信号时(例如,运行时的停止/重启场景),它会将这些调用级联到该上下文中定义的所有Lifecycle实现。为此,他将调用委派给LifecycleProcessor,如下表所示:
请注意,LifecycleProcessor本身就是Lifecycle接口的扩展。他还添加了另外两个方法,用于上下文刷新和关闭做出反应。
启动和关闭调用的顺序可能很重要。如果两个对象之间存在“依赖”关系,则依赖方在其依赖方之后启动,在其依赖方之前停止。但有时,直接依赖是未知的。你可能只知道某种类型的对象应在另外一种我俩凯尔对象之前启动。在这种情况下,SmartLifecycle接口定义了另外一个选项,即在其超级接口Phased上定义的getPhase() 方法。下面的列表显示了Phased接口的定义:
下面的列表显示了SmartLifecycle接口的定义:
启动时,相位最低的无体现启动。停止时,顺序相反。因此,如果一个对象实现了SmartLifecycle,且其getPhase() 方法的返回值为Integer.MIN_VALUE,呢么该对象将属于最先启动和最后停止的对象。而在另一端,相位值为Integer.MAX_VALUE则表示该对象硬最后启动,最先停止(可能是因为他一来与其他进程的运行)。在考虑相位值时,同样重要的要知道,任何未实现SmartLifecycle的正常Lifecycle对象的默认相位是0.因此,任何负相位值都标示一个对象应在这些标准组件之前开始(并在他们之后停止)。反之,任何正相位值都是如此。
由SmartLifecycle定义的stop方法接受一个回调。任何实现都必须在该实现的关闭进程完成后调用该回调的run() 方法。由于LifecycleProcessor接口DefaultLifecycleProcessor的默认实现会在超时值内等待每个阶段中的对象组调用该回调,因此在必要时可以实现异步关机。每个阶段的默认超时值为30秒。你可以通过在上下文定义名为LifecycleProcessor的Bean来覆盖默认的生命周期处理器实例。如果只想修改超时,定义一下内容即可:
如前所述,LifecycleProcessor接口定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就像显式调用stop() 一样,单他发生在上下文关闭时。另一个方面,刷新回调实现了SmartLifecycleBean的另一个功能。当上下文被刷新时(在所有对象被实例化和初始化后),该回调将被调用。此时,默认生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果true,则在此时启动该对象,而不是等待显式调用上下文或其自身的start() 方法(与上下文刷新不同,标准上下文实现不会自动启动上下文)。如前所述,phase值和任何“依赖”关系确定了启动顺序。
在非web应用程序中优雅关闭Spring IOC容器
如果在非web应用程序环境(例如客户端桌面环境)中使用Spring的Ioc容器,请想jvm注册一个关闭钩子。这样做可以确保优雅关闭,并调用单例Bean上的相关destroy方法,从而释放所有资源。你仍然必须正确配置和实现这些销毁回调。 要注册关闭钩子,请调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下例所示:
ApplicationContextAware 和 BeanNameAware
当ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware
接口的对象实例时,该实例将获得对ApplicationContext的引用。下面的列表显示了ApplicationContextAware接口的定义:
因此bean可以通过ApplicationContext接口或将引用投向该接口的已知自雷(如ConfigurableApplicationContext,它提供了额外的功能),以变成方式操作创建他们的ApplicationContext。其中一个用途是以编程方式检索其他Bean。有事这个功能非常有用。但是一般情况下,你应该避免使用这个功能,因为它将代码与Spring联系在一起,而且不遵循反转控制风格(Inversion of Control),在这种风格中,协作者是作为属性提供给Bean的。ApplicationContext的其他方法其他了访问文件资源、发布应用程序事件和访问MessageSource的功能。这些附加功能将在ApplicationContext的附加功能中进行描述。 自动不羡慕是获取ApplicationContext引用另外一种方法。传统的constructor和byType自动布线模式可分别为构造器参数或设置方法参数提供ApplicationContext类型的依赖关系。要获得更大的灵活性,包括自动连接字段和多个参数方法的能力,请使用基于注解的自动连接功能。如果这样做,ApplicationContext将自动连接到字段、构造函数参数或方法参数中,如果有关字段、构造函数或方法带有@Autowired注解,则该字段或方法参数期望使用ApplicationContext类型。有关详细信息,请参考@Autowired 当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将获得对其关联对象定义中定义的名称的引用。下面的列表显示了BeanNameAware接口的定义:
该回调会在普通bean属性填充后调用,但会在初始化回调(InitializingBean.afterPropertiesSet()或自定义init方法)之前调用。
其他Aware接口
处了ApplicationContextAware和BeanNameAware,spring还提供了大量Aware回调接口,这些接口可让Bean向容器表明他们需要某种基础架构依赖。一般来说,名称表示依赖类型。下表总结了最重要的Aware接口:
name | injected dependency | explained in |
---|---|---|
ApplicationContextAware | 声明ApplicationContext | ApplicationContextAware和BeanNameAware |
ApplicationEventPublisherAware | 外层ApplicationContext的时间发布者 | ApplicationContext的其他功能 |
BeanClassLoaderAware | 用于加载Bean类的类加载器 | InstantiatingBeans实例化 |
BeanFactoryAware | 声明BeanFactory | Beanfactory API |
BeanNameAware | 声明bean的名称 | applicationContextAware和BeanNameAware |
LoadTimeWeaverAware | 已定义织网程序,用于加载时处理类的定义 | spring框架中使用AspectJ进行加载时编织 |
messageSourceAware | 配置信息解析策略 | ApplicationContext的其他功能 |
NotificationPublisherAware | spring jmx通知发布器 | notifications通知 |
ResourceLoaderAware | 为第几访问资源配置加载器 | Resource资源 |
ServletConfigAware | 容器运行时当前ServletConfig。尽在网络感知Spring application中有效 | Spring MVC |
ServletContextAware | 容器运行的当前ServletContext。仅在网络感知SpringApplicationContext中有效。 |
请在此注意,使用这些接口会将的你的代码与SpringAPI联系在一起,并且不遵循反转控制风格。因此,我们建议需要以编程方式访问容器的基础架构Bean使用这些接口。
Bean定义继承
Bean定义可以包含大量配置信息,包括构造函数参数、属性值和特定于容器的信息,如初始化方法、静态工厂方法名称等。子bean定义继承父定义的配置数据。子定义可以根据需要覆盖某些值或添加其他值。使用父bean和子bean定义可以节省大量键入工作。实际上,这是一种模板形式。
如果你以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类标示。大多数用户不会再这个级别上使用他们。相反,他们会在ClassPathXmlApplicationContext类中声明式地配置Bean的定义。当你使用基于XMl的配置元数据时,你可以通过使用Parent属性来指示子Bean定义,并将父Bean指定为该属性值。下面实例展示了如果做到这一点:
如果未指定父类,子bean定义会使用父类定义中的Bean类,但也可以覆盖父类定义中的Bean类。在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。 子bean定义继承父bean的作用于、构造函数参数值、属性值和方法重载,并可添加新值。你指定的任何作用域、初始化方法、销毁方法或static工厂方法设置都会覆盖响应的父设置。 其余设置始终取自子定义:依赖、自动连接模式、依赖检查、单利和懒启动。 上例通过使用abstract属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要将父bean定义显示标记为abstract属性,如下例所示:
父bean由于不完成二无法单独实例化,因此也被明确标记为abstract。当定义为abstract时,它只能作为纯模板bean定义使用,该定义可作为自定义的父定义。如果师徒单独使用这样的abstract父bean,将它作为另外一个beanref属性引用,或使用父bean id进行显式getBean()调用,都会返回错误。同样,容器的内部preInstantiateSingletons()方法也会忽略定义为抽象的bean定义。
容器扩展点
通常情况下,应用程序开发人员无需子类化ApplicationContext实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring Ioc容器。接下来的几节将介绍这些集成接口。
使用BeanPostProcessor自定义Beans
BeanPostProcessor接口定义了回调方法,你可以同三国实现这些方法来提供自己的实例化逻辑、依赖关系解析逻辑等。如果你想在spring容器完成实例化、配置和初始化bean后实现一些自定义逻辑,你可以插入一个或多个自定义BeanPostProcessor实现。
你可以配置多个BeanPostProcessor实例,并通过设置order属性来控制BeanPostProcessor实例的运行顺序。只有当beanPostProcessor实现了Ordered接口时,才能设置该属性。如果编写自己的BeanPostProcessor,还应考虑实现Ordered接口。有关详细信息,请参考BeanPostProcessor和Ordered接口的javadoc。另请参阅有关以编程方式注册BeanPostProcessor实例的说明。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含两个回调方法。当这样的类作为后处理器在容器中注册时,对于容器创建的每个bean实例,后处理器都会在调用容器初始化方法(如InitializingBean.afterPropertiesSet()或任何生命的Init方法)之前和任何bean初始化回调之后从容器获得一个回调。后处理器可以通过bean实例采取任何操作,包括完全忽略回调。bean后处理器通常会检查回调接口,或者用代理来封装bean。一些Spring AOP基础架构类是作为Bean后置处理器实现的,一遍提供代理封装逻辑。 ApplicationContext会自动检测配置元数据中定义的、实现BeanPostProcessor接口的任何Bean。ApplicationContext将这些bean注册为后置处理器,一遍以后创建bean时调用他们。可以像部署其他bean一样在容器中部署bean后置处理器。 请注意,在通过使用配置类上的@bean工厂方法来声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或至少是org.springframework.beans.factory.config.BeanPostProcessor接口,从而清楚地表明该Bean的后处理器性质。否则,在完全创建之前,ApplicationContext无法通过类型自动检测它。由于beanProstProcessor需要尽早实例化,一遍应用上下文中其他Bean的初始化,因此这种早期类型检测至关重要。 第一个实例说明了基本用法。该实例显示了嗯自定以BeanPostProcessor实现,它在容器创建每个Bean时调用其toString() 方法,并将结果字符串打印到系统控制台。 下面的列表限时了自定义BeanPostProcess实现类的定义:
下面beans元素使用InstantiationTracingBeanPostProcessor:
请注意,InstantiationTracingBeanPostProcessor只是被定义了。因为它是一个Bean,所以可以想其他Bean一样依赖注入。(前面的配置还定义一个由Groovy脚本支持的Bean。有关Spring动态语言支持的详细信息,请参考“动态语言支持”一章。 下面的java应用程序将运行前面的代码和配置:
上述应用程序输出的结果如下:
将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展SpringIoc容器的常用方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor一种BeanPostProcessor实现,它随Spring发行版本一起提供,并自动连接注解字段、setter方法和任意配置方法。
使用BeanFactoryPostProcessor定制配置元数据
下一个扩展点事org.springframework.beans.factory.config.BeanFactoryPostProcessor.该接口的语义与BeanPostProcessor类似,但有一个主要区别:BeanFactoryPostProcessor对Bean配置元数据进行操作。也就是说,Spring Ioc容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改配置元数据。
你可以配置多个BeanFactoryPostProcessor实例,并通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是只有当BeanFactoryPostProcessor实现了Ordered接口时,才能设置改属性。如果你编写了自己的BeanFactoryPostProcessor,也应考虑实现Ordered接口。有关详细信息。请参考BeanFactoryPostProcessor和Ordered接口的javadoc。
当在ApplicationContext中生命Bean工厂后处理器时,它将自动运行,以便对定义容器的配置元数据应用更改。Spring包含大量预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertySourcesPlaceHolderConfigurer。你还可以使用自定义BeanFactoryPostProcessor例如,注册自定义属性编辑器。
ApplicationContext会自动检测部署到其中的、实现BeanFactoryPostProcessor接口的任何Bean。它会在适当的时候将这些Bean作用Beean工厂的后处理器。你可以像部署其他Bean一样部署这些后处理器Bean。
示例:类名替换 PropertySourcesPlaceholderConfigurer
你可以使用标准的java properties格式,使用PropertySourcesPlaceholderConfigurer将bean定义中的属性值外部化到单独的文件中。这样,部署应用程序的人员就可以自定义特定与环境的属性(如数据库Url和密码),而不必修改容器的主Xml定义文件或文件,既不复杂,也没有风险。 请看以下基于XML的配置元数据片段,其中定义了一个带有占位符值的DataSource:
该示例显示了从外部Properties文件配置的属性。运行时,元数据中会应用PropertySourcesPlaceholderConfigurer,以替换数据源的某些属性。要替换的值被指定为$形式的占位符,它遵循ant和log4j以及jsp el风格。
实际值来自另外一个标准javaProperties格式的文件:
因此,$字符串在运行时会被替换为“sa”值,这同样适用于与属性文件中的键相匹配的其他占位符值。PropertiySourcesPlaceholderConfigurer会检查Bean定义的大多数属性和属性中的占位符。此外,你还可以自定义占位符的前缀和后缀。 借助Spring2.5中引入的context命名空间,你可以使用专用的配置元素来配置属性占位符。你可以在location属性中以逗号分割的列表形式提供一个或多个位置,如下例所示:
PropertySourcesPlaceholderConfigurer不仅会在你指定的Properties文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它会检查Spring Environment属性和常规java System属性。
举例说明PropertyOverrideCOnfigurer
PropertyOverrideConfigurer是另外一个bean工厂后处理器,它与PropertySourcesPlaceholderConfigurer类似,单与后者不同的是,原始定义可能为Bean属性设置默认值或不设置任何值。如果覆盖Properties文件中我有某个Bean属性的条目,则会使用默认上下文定义。 请注意,bean定义并没有意识到被覆盖,因此从XML定义文件中并不能立即看出覆盖配置器正在被使用。如果多个PropertyOverrideCOnfigurer实例为同一个bean属性定义了不同的值,那么由于重载机制的存在,最后一个实例将胜出。 属性文件配置行的格式如下:
下面列出了一个格式示例:
此示例文件可与容器定义一起使用,容器定义包含一个名为datasource的Bean,改Bean具有driver和url属性。 只要出了被重写的最终属性外,路径中的每个组件都已非空(可能已被构造函数初始化),我们也支持复合属性名。在下面的示例中,tom bean的fred属性的sammy属性被设置为标量值123:
通过Spring2.5中引入的context命名空间,可能使用专用的配置元素来配置属性重载,如下例所示:
使用FactoryBean定制实例化逻辑
你可一位本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。 FactoryBean接口是SpringIoc容器实例化逻辑的可插入点。如果你有复杂的初始化代码,而这写代码最好用java而不是冗长的xml来表达,那么你可以创建自己的FactoryBean类,在该类中编写复杂的初始化,然后将自定义的FactoryBean插入容器。 FactoryBean接口提供了三种方法:
T getObject():返回该工厂创建的对象的实例。该实例可能是共享,这取决于该工厂返回的是单体还是原型。
boolean isSingleton():如果此FactoryBean返回单利,则返回true,否则返回false。此方法的默认实现返回true。
Class<?> getObjectType():返回getObject()方法返回的类型,如果实现不知道类型,则返回null。
FactoryBean概念和接口在Spring框架的许多地方都有使用。Spring本身随附了50多个FactoryBean接口的视线。 当你需要想容器询问实际的FactoryBean实例本身而不是它生成的bean时,请在调用ApplicationContext的getBean() 方法时,在Bean的id钱加上双引号(&)。因此,对于具有myBean的id的给定FactoryBean来说,在容器上调用getBean("myBean") 会返回FactoryBean的生成的对象,而调用getBean("&myBean")则会返回FactoryBean实例本身。
基于注解的容器配置
基于注解的配置提供了Xml设置的代替方案,它依赖字节码元数据来连接组件,而不是使用角括号生命。开发人员在相关类、方法或字段生命中使用注解,将配置移入组件类本身,而不是使用XMl来描述bean的配置。如实例中的AutowiredAnnotationBeanPostProcessor中提到,将BeanPostProcessor与注解结合使用是扩展SpringIoc容器的常用方法。例如,Spring2.0引入了使用@Required注解强制要求数据的可能性。Spring2.5采用了相同的通用方法来驱动Spring的依赖注入。从本质上,@Autowired注解提供了与《Autowiring Collaborators》中所属相同的功能,但具有更精细的控制和更广泛的实用性。Spring2.5还添加了对JSR-250注解的支持,如@PostConstruct和@PreDestroy。Spring3.0增加了对javax.inject包中包含的JSR-330注解的支持,如@Inject和@Named。有关这些注解的详细信息,请参见相关章节。
你可以一如既往地将后处理器注册为单独的bean定义,但也可以通过基于xml的Spring配置中包含一下标记来隐式注册后处理器(注意包含了context命名空间):
context:annotation-config/元素隐式注册以下后置处理器:
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EvenListenerMethodProcessor
@Required
@Required注解适用于bean属性设置器方法,如下例所示:
此注解表示受影响的bean属性必须在配置时通过bean定义中的显式属性值或通过自动布线来填充。如果受影响的bean属性尚未填充,容器将抛出异常。这允许急切和显式失败,避免以后出现NullPointerException实例或类似情况。我们仍然建议你将断言放入Bean类本身(例如,放入init方法中)。及时你在容器外使用该类,这样做也会强制执行这些必要的引用和值。
@Autowired
你可以将@Autowired注解应用于构造函数,如下例所示:
你还可以将@Autowired注解应用于传统的设置器方法,如下例所示:
你也可以将注解应用具有任意名称或多个参数的方法,如下例所示:
你也可以将@Autowired应用于字段,甚至与构造函数混合使用,如下例所示:
你还可以通过在期望使用特定类型数组的字段或方法中添加@Autowired注解,只是Spring从ApplicationContext中提供该类型的所有Bean,如下例所示:
正如下面的例子,同样适用于类型化集合:
如果你希望数组或列表中的目标按照特定顺序排序,你的目标Bean可以实现org.springframework.core.Ordered接口,或者使用@order或标准@Priority注解。否则,他们的顺序将遵循容器中相应目标bean定义的注册顺序。
你可以在目标类级别和@Bean方法上生命@Order注解,也可以为单个Bean定义声明@Order注解(在多个定义使用相同Bean类的情况下)。@Order值可能会影响注入点的优先级,但请注意,他们不会影响单利启动顺序,这是一个由依赖关系和@DependsOn生命确定戴尔正交问题。
只要预期键类型是String,即使是类型为Map的实例也可以自动连线。如下面的实例所示,映射值包含所有期望类型的Bean,而键则包含响应的Bean名称:
默认情况下,如果给定的注入点没有匹配候选bean,自动注入就会失败。对于已生命的数组、集合或映射,预计至少一个匹配元素。 默认行为是将注解的方法和字段视为必备的依赖关系。你可以更改这一行为,如下利所示,通过将不可满足的祝福点标记为氛围必备注入点(即通过将@Autowired中的required属性设置为false),使框架能够跳过不可满足的注入点:
如果非必填方法的依赖关系(或依赖关系之一,如果有多个参数)不可用,则改方法根部不会被调用。在这种情况下,非必填字段不会被填充,而是保留默认值。 注入的构造器和工厂方法参数是一种特殊情况,因为Spring的构造器解析算法可能会处理多个构造器,因此@Autowired中的required属性具有不同的含义。构造函数和工厂方法参数在默认情况下实际上是必须得,但在但构造函数情况下有一些特殊规则,例如,如果没有匹配的Bean可用,多元素注入点(数组、集合、映射)将解析为空实例。这允许采用一种常见的视线模式,即所有依赖关系都可以在一个唯一的多参数构造函数中声明,例如,声明为一个单一的公共构造函数,而不是@Autowired注解。
另外,你还可以通过java8的java.util.Optional来表达特定依赖关系的非必备性质,如下例所示:
从SpringFramework5.0开始,你还可以使用@Nullable注解(任何包中的任何类型,例如JSR-3.05中的javax.annotation.Nullable) 火种直接利用Kotlin内置的空安全支持:
你还可以使用@Autowired来表示众所周知的可解析依赖关系接口:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,无需特殊设置。下面的实例自动连接了一个ApplicationContext对象:
1.9.3使用@Primary微调基于注解的自动注入
由于按类型自动注入可能会产生多个候选对象,因此通常需要对选择过程进行更多控制。实现这一目的的方法之一使用Spring 的@Primary注解。@Primary标示,当多个Bean候选自动连接到单值依赖关系时,应有限考虑某个特定Bean。如果候选都中正好存在一个主要的,它就会成为自动注入的值。 下面的配置将firstMOvieCataLog定义为主MovieCatalog:
通过上述配置,一下MovieRecommender与firstMovieCatalog自动注入:
相应bean定义如下:
1.9.4使用限定微调基于注解的自动注入
@Primary是一种有效的方法,当可以确定一个主要候选者时,它可以通过多个实例的类型使用自动注入。如果需要对选择过程进行控制,可以使用Spring的@Qualifier注解。你可以将限定值与塔顶参数关联起来,缩小类型匹配集的范围,从而为每个参数选择特定的Bean。在最简单的情况下,这可以是一个普通的描述性值,如下例所示:
你还可以在单个构造函数参数或方法参数上指定@Qualifier注解,如下例所示:
下面的实例显示了响应的Bean定义:
对于后备匹配,Bean名称被视为默认限定符值。因此,你可以使用main的id代替嵌套限定符元素来定义Bean,从而获得相同的匹配结果。不过,尽管你可以使用这一约定通过名称来引用特定的bean,但@Autowired从根本上说是关于类型驱动注入和可选语义限定符的。这意味着,即使使用了Bean名称回退,限定赋值也始终在类型匹配集合中具有缩小范围的语义。他们在语义上并不表达对唯一Bean id的引用。好的限定符值是main或EMEA或persistent,他们表达了独立于Bean的特定组件的特征id,在匿名Bean定义(如上例中的定义)的情况下,这些限定符值可能会自动生成。 如前所述,限定符也适用于类型化集合,例如Set 。在这种情况下,根据声明的限定符,所有匹配的Bean都会作为集合注入。这意味着限定符不必是唯一的。相反,他们构成了筛选条件。例如,你可以定义多个具有相同限定值“action“的MovieCatalog Bean,所有这些Beban都会注入到注释为@Qualifier(“actino”)的Set 中。
在类型匹配候选中,让限定符值针对目标Bean名称进行选择不需要再注入点添加@Qualifier注解。如果没有其他解析指示符(如限定符或主标记),对于非唯一依赖情况,Spring会将注入点名称(即字段名称或参数名称)与目标Bean名称相匹配,并选择桶名候选(如果有的话)
也就是说,如果你打算通过名称来表达注解驱动的注入,请不要主要使用@Autowired,即使他能够通过bean名称在类型匹配候选者中进行选择。相反,请使用JSR-250@Resource注解,该注解在语义上的定义是通过唯一的名称来识别特定的目标组件,而声明的类型与匹配过程无关。@Autowired具有相同的语义:在按类型选择候选后,指定的string限定符值只在这些按类型选择的候选中被考虑(例如,将account限定符与标有相同限定符标签的进行匹配) 对于本身定义为集合、map或者数组类型的bean,@Resource是一个很好的解决方案,他通过唯一的名称来引用特定的集合和数组Bean。尽管如此,从4.3开始,你也可以通过Spring@Autowired类型匹配算法来匹配集合、map和数组类型,只要在@Bean返回类型签名或集合集成层次结构中保留元素信息即可。在这种情况下,你可以石笋限定符值自在同类集合中进行选择,如上一段所述。
在4.3开始,@Autowired还将自引用视为注入对象(即引用回当前注入的Bean)。请注意,自注是一种退路。对其他组件的常规依赖始终具有优先权。从这个意义上说,自引用并不参与常规候选选择,因此尤其永远不会是主要的。恰恰相反,他们的优先级总是最低的。在实践中,只有在万不得已的情况下才使用自引用(例如,通过bean的事务代理条用同一实例上的其他方法)。在这种情况下,可以考虑将受影响的方法分解到单独的委托Bean中。或者,你可以使用@Resource,这样可以通过其唯一名称获得返回当前的Beawn的代理。
尝试在同一配置类中注入@Bean方法的结果实际上也是一种自引用的情况。要么在实际需要的方法签名(而不是配置类中的自动连接字段)中懒散的解决此类引用,要么将受影响的@Bean方法声明为static方法,使其与包含的配置类实例及其声明周期解耦。否则,此类Bean只会在后备阶段被考虑,而其他配置类上的配置Bean会被选为主要候选(如果有的话)
@Autowired适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注解缩小范围。相反,@Resource仅智齿字段和具有单参数的Bean属性设置器方法。因此,如果你的注入目标是构造函数或多参数方法,则应坚持使用限定符。 你可以创建自己的自定义限定符注解。为此,请定义一个注解,,并在定义中提供@Qualifier注解,如下例所示:
然后,你就可以在自动注入的字段和参数提供自定义限定符,如下例所示:
接下来,你可以提供候选Bean定义穿的信息。你可以添加 标记作为 标记的子元素,然后指定type和value以匹配自定义限定符注解。类型将于注解的全限定类名相匹配。另外,如果不存在名称冲突的风险,也可以使用剪短的类名,下面的实例演示了这两种方法:
在“类路径扫描和托管组件”中,你可以看到基于注解的替代方法,而不是在xml中提供限定符元数据。具体请参阅使用注解提供限定符元数据。
在某些情况下,使用不含值的注解可能就足够了。当注解具有更通用的目的,并可应用于几种不同类型的依赖关系时,这可能会很有用。例如,你可以提供一个离线目标,以便在没有互联网连接的情况下进行搜索。首先,定义简单的注解,如下例所示:
然后将注解添加到要自动注解的字段或属性种,如下例所示:
现在,bean定义只需要一个限定符type,如下图所示:
你还可以定义自定义限定符注解,除简单的value属性外,或替代value属性接受命名的属性。如果在要自动连接的字段或参数上指定了多个属性值,Bean定义必须与所有这些属性值想匹配,才能被视为自动注入候选对象。例如,请看下面的注解定义:
在这种情况下,format是一个枚举,定义如下:
要自动注入的字段使用自定义限定符注解,并包含两个属性的值:genre和format,如下图所示:
最后,bean定义应包含匹配的限定符值。此时离还说明,你可以使用bean元属性来替代 元素。如果可用, 元素及其属性将优先使用,单如果没有此类限定符,自动注入机制将使用 标记中提供的值,例如下面实例中的最后两个bean定义:
1.9.5使用泛型作为自动注入限定符
除@Qualifier注解外,你还可以使用java泛型类型作为隐式限定。例如,假设你有一下配置:
建设前面的Bean实现了泛型接口(及Store 和Store ),则可以@Autowired Store接口,泛型被用作限定符,如下例所示:
通用限定符也适用于自动注入列表、map和数组。下面的实例自动连接了一个通用的list
1.9.6 使用CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许你注册自己的自定义限定符注解类型,即使这些类型没有使用Spring的@Qualifier注解。下面的事例展示了如何使用CustomAutowireConfigurer:
AutowireCandidateResolver通过以下方式确定自动注入候选方案:
每个bean定义的autowire-candidate值
元素上可用的任何default-autowire-candidates模式
使用CustomAutowireConfigurer注册的@Qualifier注解和任何自定义注解是否存在。
当多个Bean都符合自动注入候选条件时,确定“主要”的方法如下:如果候选者都正好有一个定义的Primary属性设置为true,呢么他就会被候选。
1.9.7使用@Resource注入内容
Spring还支持在字段或Bean属性设置方法上使用JSR-250@Resource注解(javax.annotaion.Resource)进行注入。这些Java EE中的一种常见模式:例如在JSF管理的Bean和JAX-WS端点中,Spring也为Spring管理对象支持这种模式。 @Resource包含一个name属性。默认情况下,Spring将该值解释为主要注入的bean名称。换句话说,正如以下示例所示,他在遵循的是名称的语义:
如果没有明确指定名称,则默认名称来自字段名称或设置方法。如果是字段,默认名称取自字段名。如果是设置器方法,则使用Bean属性名称。下面的实例将在其Setter方法中注入名movieFinder的Bean:
在没有指定明确名称的@Resource使用的唯一情况下,与@Autowired类似,@Resource查找主要类型匹配而不是特定命名的Bean,并解析众所周知的可解析依赖关系:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEvenPublisher和MessageSource接口。
因此,在下面的示例中,customerPreferenceDao字段首先查找名为“customerPreferenceDao”的Bean,然后返回到CustomerPreferenceDao类型的主类型匹配:
1.9.8使用@Value
@value通常用于外部注入属性:
配置如下:
以及下面的的application.Properties文件:
Spring提供了默认的宽松嵌入值解析器。他会尝试解析属性值,如果无法解析,属性名称(例如$)将作为值注入。如果要对不存在的值进行严格控制,则应声明PropertySourcePlaceholderConfigurer Bean,如下例所示:
如果谁发解析任何${}占位符,使用上述配置可确保spring初始化失败。还可以使用setPlaceholderPrefix、setPlaceholderSuffix或setValueSeparator等方法自定义占位符。
Spring提供的内置转换器支持可自动处理简单的类型转换(例如转换为integer或int).多个都好分隔值可自动转换为string数组,无需额外的工作。 可以提供如下默认值:
Spring BeanPostProcessor在幕后使用ConversionService来处理将@Value中的string值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的ConversionService bean实例,如下例所示:
当@Value包含Spel表达式时,该值在运行时动态计算,如下例所示:
SpEl还能使用复杂的数据结构:
1.9.9 使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅能识别@Resource注解,还能识别JSR-250声明周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。对这些注解的支持在Spring2.5引入,为初始化回调和销毁回调中描述的声明周期回调机制提供了替代的方案。只要在Spring ApplicationContext中注册了CommonAnnotationBeanPostProcessor,携带这些注解之一的方法就会与相应的Spring生命周期结构或显式声明的回调方法在声明周期的统一时刻被调用。在下面的实例中,缓存在初始化时被预填充,并在销毁时被清楚:
有关结合各种声明周期机制的效果和详细信息,请参考阅读生命周期机制。
与@Resource一样,从JDK6到8,@PostConstruct和@PreDestroy注解类型也是标准java库的一部分。但是整个javax.annotation包在jdk9中从核心java模块中分离出来,最终在JDK1中被删除。如果需要,现在需要通过maven Central获取javax.annotation-api构件,只需像其他库一样添加到应用程序的类路径中即可。
1.10 类路径扫描和托管组件
本章中的大多数实例都使用xml来指定配置的元数据,以生成spring容器中的每个BeanDefinition。上一届(基于注解的容器配置)演示了如何通过源代码级注解提供大量配置元数据。不过,即使在这些示例中,基本“bean定义也是在Xml文件中明确定义的,而注解值驱动依赖注入。本章将介绍一种通过扫描类路径隐式检测候选组件的方法。候选组件是与筛选条件相匹配的类,并在容器中注册了相应的Bean定义。这样就无需使用xml来执行Bean注册。相反,你可以使用注解(例如@Component)、AspectJ类型表达式或你自己的自定义筛选条件来选择那些类已在容器中注册了Bean定义。
3.0开始,SpringJavaConfig项目提供的许多功能都成为Spring框架核心的一部分。这样,你就可以使用java而不是传统的xml文件来定义Bean。请查看@Configuration、@Bean、@Import和@DependsOn注解,了解如何使用这些新功能。
1.10.1 @Component和更多的定型注解
@Repository注解是一个标记,用于符合存储库(也成为数据访问对象或DAO)角色或定型的任何类。该标记的用途之一是自动翻译异常,详见异常翻译。 Spring提供了更多的定型注解:@Component、@Service和@Controller。@COmponent的特化,用于更具体的用例(分别在持久层、服务层和表现层中)。因此,你可以使用@Component对组件类进行注解,但如果使用@Repostitory、@Service和@Controller对他们进行注解,你的泪将更适合由工具处理或与切面关联。例如,这些改版印象注解是pointcusts的理想目标。在spring框架的未来版本中,@Repository、@Service和@Controlelr还可以携带其他语义。因此,如果你存在服务层使用@Component或@Service之间做出选择,@service显然是更好的选择。同样,如上所述,@Repository已被支持作为持久层中自动异常翻译的标记。
1.10.2使用元注解和组合注解
Spring 提供的许多注解都可以在你自己的代码中作为元注解。元注解是一种可应用于另外一种注解的注解。例如,前面提到的@Service注解可以用@Component进行元注解,如下例所示:
你还可以组合元注解来创建“组合注解”。例如,Spring MVC中@RestController注解由@Controller和@ResponseBody组合。 此外,组合注解可以选择重新声明元注解属性,以便进行自定义。当你指向公开元注解的部分属性时,这一点尤其重要。例如Spring的@SessionScope注解将作用域名称硬编码为session,但仍允许自定义proxyMode的属性。下面的列表显示了SessionScope注解的定义:
然后,你就可以使用@SessionScope而无需声明proxyMode了,如下所示:
你还可以覆盖ProxyMode的值,如下所示
更多详情,请参阅Spring注解变成模型wiki页面。
1.10.3 自动检测类并注册Bean定义
Spring可以自动检测定型类,并使用ApplicationContext注册相应的BeanDefinition实例。例如,一下两个类符合自动检测的条件:
要自动检测这些类并注册相应的bean,你需要在@Configuration类中添加@ComponentScan,其中basePackages属性时两个物理的共同父类包 。(或者,你也可以指定一个逗号,分好或空格分隔列表,其中包括每个类的父类包)。
一下替代方案使用XML:
扫描类路径包需要类路径中存在响应的目录条目。在使用Ant构建jar时,请确保不要集合jar任务的仅文件开关。此外,在某些环境中,classpath目录可能不会根据安全策略暴露出来,例如jdk1.7.0——45及更高版本上的独立应用程序(需要在清单中设置“ Trusted-Library”--请参阅https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)
在JDK9的模块路径(jigsaw)上,Spring的类路径扫描一般都能如期的工作。不过,请确保你的组件类已在module-info描述符中导出。如果你希望spring调用类中的非公共成员,请确保它们是开放的(即在module-info描述符中使用opens声明而不是exports声明)
此外,当你使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都会被隐式包含。这意味着这两个组件都会被自动检测并关联的一起,而不需要在XML中提供任何bean配置元数据。
1.10.4使用过滤器自定义扫描
默认情况下,用@Component、@Repository、@Service、@Controller、@Configuration或本身用@Component注解的自定义注解的类是唯一呗检测到的候选组件。不过,你可能通过应用自定义过滤器来修改和扩展这种行为。将它们添加为@ComponentScan注解的includeFilters或excludeFilters属性(或作为XML配置中context:component-scan 元素的context:include-filter/或context:exclude-filter/ 子元素)。每个过滤元素都需要type和expression属性。下表描述了过滤选项:
filter type | example expression | description |
---|---|---|
annotation | org.example.SomeAnnotation | 在目标组件的类型级别上存在或元存在的注解 |
assignable | org.example.SomeClass | 目标组件可分配(扩展或实现)的类(或接口) |
aspectj | org.example..*Service+ | 目标组件匹配的AspectJ类型表达式 |
regex | org\.example\.Default.* | 与目标组件类匹配的regex表达式 |
custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter接口的自定义实现 |
下面的示例显示了忽略所有@Repository注解并使用“存根“存储库的配置:
下面列出了等效的XML:
1.10.5在组建中定义Bean的元数据
Spring组件还可以向容器贡献bean定义元数据。你可以使用用于在@Configuration注解的类中定义Bean元数据的@Bean注解来实现这一功能。下面的实例展示了如何做到这一点:
前面的类是一个spring组件,在其doWork() 方法中包含特定于应用程序的代码。不过,改类还提供了一个Bean定义,其中的工厂方法引用了publicInstance() 方法。@Bean注解通过@Qualifier注解表示了工厂方法和其他Bean定义属性,如限定赋值。可以指定的其他地方注解有@Scope、@Lazy和自定义限定符注解
出了用于组件初始化外,你还可以在标有@Autowired和@Inject的注入点上方@Lazy注解。在这种情况下,这将导致注入一个懒散的解决代理。然而,这种代理方法是相当有限的。对于复杂的懒散交互,尤其是于可选依赖关系相结合的情况,我们推荐使用ObjectProvider 代理。
如前面所述,我们支持自动注入方法和字段,并额外支持@Bean方法的自动注入。下面的示例展示了如何实现这一功能:
该示例将string方法参数country自动注入到另外一个名为privateInstance的Bean上的age属性的值。spring表达式语言元素通过符号#{ }定义属性的值。对于@Value注解,表达式解析器已预先配置为在解析表达式文本时查找bean名称。 从Spring Framework 4.3开始,你还可以声明InjectionPoint(或者更具体的子类:DependencyDescriptor)类型的工厂方法参数,以访问触发当前Bean创建的请求注入点。请注意,这只适用于Bean实例的实际创建,而不适用于现有实例的注入。因此,这一功能对原型作用域的Bean最有意义。对于其他作用域,工厂方法只能看到在给定作用域中触发创建新Bean实例的注入点(例如,触发创建懒惰单利Bean的依赖关系)。在这种情况下,你可以使用锁提供的注入点元数据并注意语义。下面的示例展示了如何使用InjectionPoint:
常规Spring组件中的@Bean方法与Spring @Configuration类中的方法处理方式不同。不同之处在于,@Component类没有使用CGLIB来拦截方法和字段的调用。CGLIB代理是在@Configuration类中调用@Bean方法内的方法或字段时创建协作对象的bean元数据引用的一种方法。这些方法不是以正常的java语义调用的,而是通过容器来提供Spring Bean通常的生命周期管理和代理,即使在通过对@Bean方法的变成调用来引用其他Bean时也是如此。相比之下,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的java语义,不适用特殊CGLIB的处理或其他限制。
你可以将@Bean方法声明为static方法,这样就可以条用这些方法,而无需将其包含的配置类创建为实例。这在定义后处理器Bean(例如BeanFactoryPostProcessor或BeanPostProcessor类型)时特别有意义,因为这些Bean会在容器生命周期的早期被初始化,因此应避免在此时触发配置的其他部分。
由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,甚至在@Configuration类中也不会(如本届前面所述):CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法具有标准的java语义,结果是工厂方法本身直接返回一个独立实例。
java语言中的@Bean方法的可见性不会对Spring容器中的Bean定义产生直接影响。你可以在非@Configuration类中随意声明你认为合适的工厂方法,也可以在任何地方声明静态方法。但是,@Configuration类中的常规@Bean方法必须是可以覆盖的,也就是说,他们不得声明为private或final方法。
最后,但各类可能会为同一个Bean保存多个@Bean方法,根据运行时可用的依赖关系来安排使用多个工厂方法。这与在其他配置场景中选择“最有利”的构造函数或工厂方法的算法相同:在构造时,我们会选择具有最多可满足依赖关系的变量,这与容器在多个@Autowired构造函数之间进行选择的方式类似。
1.10.6 命名自动检测组件
当组件在扫描过程中被自动检测到时,其Bean名称将由该扫描器已知的BeanNameGenerator策略生成。默认情况下,任何包含名称value的Spring定型注解(@Component、@Repository、@Service和@Controller)都会将改名称提供给响应的bean定义。 如果此类注解不包含名称value或任何其他检测到的组件(如自定义筛选器发现的组件),默认Bean名称生成器将返回未大写的非限定类名称。例如,如果检测到以下组件类,其名称将是myMovieLister和movieFinderImpl:
如果你不想依赖默认的Bean命名策略,你可以提供自定义的Bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描时提供完全合格的类名,正如一下注解和Bean定义示例所示。
一般来说,当其他组件可能会对其进行显式引用时,应考虑使用注解来指定名称。另一方面,只要容器负责注入,自动生成的名称就足够了。
1.10.8
@Qualifier注解将在《使用限定微调基于注解的自动配置》中讨论。该部分的示例演示了如何使用@Qualifier注解和自定义限定符注解在解析自动配置候选对象时提供细粒度控制。由于这些示例基于XMLBean定义,因此限定符元数据是通过使用xml中的Bean元素的qualifier或meta子元素在候选Bean定义中提供的。当依赖类路径扫描自动检测组件时,你可以通过候选类上的类型注解来提供限定符元数据。一下三个示例演示了这种技术:
1.10.9 生成候选组件索引
虽然类路径扫描速度非常快,但在编译时创建一个候选模块静态列表,可以提高大型应用程序的启动性能。在这种模式下,作为组件扫描目标的所有模块必须使用这种机制。
要生成索引,可在每个包含组件扫描指令目标组件的模块中添加额外的依赖关系。下面的示例展示了如何使用Maven进行此操作:
在gradle4.5及更早版本中,依赖管体系应在compileOnly配置中声明,如下例所示:
spring-context-indexer工具会生成一个META-INF/spring.components文件,改文件包含在jar文件中。
挡在类路径发现META-INF/spring.components文件时,所有会自动启动。如果某些类(或用例)的部分索引可用,但无法为整个应用程序构建索引,则可以通过将spring.index.ignore设置为true,作为jvm系统属性或通过SpringProperties机制,返回到常规的类路径安排(就像根本没有索引一样)
1.11使用JSR330标准注解
从Spring3.0开始,Spring支持JSR-330标准注解(依赖注入)。这些注解的扫描方式与Spring注解相同。要使用这些注解,你需要在类路径中包含相关的jars。
1.11.1使用@Inject和@Named进行依赖注入
如下所示,你可以使用@javax.inject.inject替代@Autowired:
与@Autowired一样,你可以在字段级、方法级和构造函数参数级使用@Inject。此外,你还可以将注入点声明为Provider,这样就可以按需访问作用域较短的Bean,或通过Provider.get() 调用懒散地访问其他Bean。下面的示例前面示例的一个变体:
如果你想为应注入的依赖关系使用限定名称,则应使用@Named注解,如下例所示:
与@Autowired一样,@Inject也可以与java.util.Optional或@Nullable一起使用。由于@inject没有required属性,因此这一点在这里更加适用。下面一对示例展示了如何使用@Inject和@Nullable:
1.11.2 @Named和@MannagedBean替代标准的注解@component
如下例所示,你可以使用@javax.inject.Named或javax.annotation.ManagedBean替代@Component:
使用@Component而不指定组件名称的情况非常普遍。如下例所示,@Named也可以类似的方式使用:
当你使用@Named或@ManagedBean时,你可以使用Spring注解完全相同的方式使用组件扫描,如下例所示:
1.11.3JSR-330标准注解的局限性
如下表所示,在使用标准注解时,有些重要功能是不可用的:
spring | javax.inject.* | javax.inject restrictions/comments |
---|---|---|
@Autowired | @Inject | @Inject没有必填属性。可使用java8中的Optional替代 |
@Component | @Named/@ManagedBean | JSR-330没有提供可组合模型,只是提供一个识别已命名组件的方法 |
@Scope("signleton") | @Singleton | JSR-330默认作用域与Spring的prototype类似。但是为了与Spring的一般默认值保持一致,在spring容器中声明的JSR-330Bean默认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或非注解构造器和设置器方法结合使用 |
1.12 基于java的容器配置
本章节介绍如何在java代码中使用注解来配置Spring容器。包括以下主题:
基本概念:@Bean和@Configuration
使用AnnotationConfigApplicationContext实例化Spring容器
使用@Bean注解
使用@Configuration注解
组成基于java的配置
Bean Definition Profiles
PropertySource抽象实例
使用@PropertySource
报表中的占位符决议
1.12.1基本概念:@Bean和@Configuration
Spring新java配置支持的核心工具是@Configuration注解的类和@Bean注解的方法。 @Bean注解用于表示一个方法实例化、配置和初始化了一个由Spring IOC容器管理的新对象。对于属性Spring的 XML配置的人来说,@Bean注解的作用于 元素相同。你可以在任何Spring@Component中使用@Bean注解发哈。不过,他们最常用于@Configuration Bean。 用@Configuration标记一个类,表明该类的主要目的是作为Bean定义的来源。此外@Configuration类可以通过调用同类中的其他@Bean方法来定义bean间的依赖关系。最简单的@Configuration类内容如下:
前面的AppConfig类等同于下面Spring XMl:
@Bean方法在没有使用@Configuration进行注解的类中声明时,他们被称为以精简模式处理。在@Component或甚至在普通类中声明的Bean方法被认为是精简的,包含类的主要目的不同,而@Bean方法只是其中的一种附加功能。例如,服务组件可以通过每个适用组件类上的附加@Bean方法想容器公开管理视图。在这种情况下,@Bean方法是一种通用的工厂方法机制。
在常见场景中,@Bean方法将在@Configuration类中声明,以确保始终适用“完整”模式,并因此将跨方法引用重定向到容器的生命周期管理中。这可以防止通过常规java调用意外调用同一个@BEan方法,从而有主意减少在精简模式下运行时难以跟踪的微妙错误。
下文将深入讨论@Bean和@Configuration注解。首先,我们将介绍使用基于java的配置创建的Spring容器的各种方法。
1.12.2 使用AnnotationConfigurationApplicationContext实例化Spring容器
下文将介绍Spring3.0中引入的Spring AnnotationConfigApplicationContext实现。这种通用的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受纯@Component类和使用JSR-330元数据注解的类。 当@Configuration类作为输入提供时,@Configuration类本身会注册为Bean定义,类中所有已声明的@Bean方法也会注册为bean定义。 当提供@Component和JSR-330类时,他们讲注册为bean定义,将假设必要时在这些类使用DI元数据,如@Autowired和@Inject。
结构简单
与实例化ClassPathXmlApplicationContext时使用SpringXml文件作为输入的方式相同,你可以在实例化AnnotationConfigApplicationContext时使用@Configuration类作为输入。正如下面的示例所示,这样就可以完全不适用XML来使用Spring容器了:
如前所述,AnnotationConfigApplicationContext并不局限只与@Configuration类一起工作。任何@Component或JSR-330注解的类都可以作为构造函数的输入,如下例所示:
前面的示例假设MyServiceImpl、Dependency1和Dependency2使用了Spring依赖注入注解,如@Autowired。
使用register程序构建容器
你可以使用无参数构造函数实例化AnnotationConfigApplicationContext,然后使用register() 方法对其进行配置。这种方法在以变成方式构建AnnotationConfigApplicationContext时特别有用。下面的示例展示了如何做到这一点:
使用scan(String...)启用组件扫描
要启用组件扫描,可以对@Configuration类进行如下注解:
在签名的示例中,我们将扫描com.acme包以查找任何@Component注解的类,并将这些类注册为容器中的Springbean定义。AnnotationConfigApplicationContext暴露了scan( string..)方法,以实现相同的组件扫描功能,如下例所示:
请记住,@Configuration类使用@Component元注解,因此他们是组件扫描的候选对象。在前面的示例中,假设AppConfig实在com.acme包(或其下的任何包)中声明的,那么它将在调用scan() 时被选中。在refresh()中,它的所有@bean方法都将被处理并注册为容器中的Bean定义。
使用AnnotationConfigApplicationContext支持网络应用程序
AnnotationConfigApplicationContext的WebApplicationContext变体与AnnotationConfigWebApplicationContext搭配使用。你可以在配置Spring ContextLoaderListener服务器监听器、SpringMVC DispatcherServlet等时使用次实现。下面的web.xml代码段配置了一个典型的SpringMVCWeb应用程序(注意使用了contextClass上下文参数和init参数):
1.12.3 使用@Bean注解
@Bean是方法级注解,是XML 元素的直接类似物。该注解支持 提供的部分属性,例如:
init-method启动方法
destroy-method销毁方法
autowiring 自动注入
name
你可以在@Configuration注解或@Component注解的类中使用@Bea注解。
Declaring a Bean 声明Bean
要声明bean,你可以使用@Bean注解来注解方法。你可以使用此方法在指定为方法返回值的类型的ApplicationContext中注册Bean定义。默认情况下,Bean名称与方法名称相同。下面的实例显示了一个@Bean方法声明:
前面的配置与下面的Spring Xml完全等价:
这两个生都会使名为transferService的Bean在ApplicationContext中可用,并绑定到TransferServiceImpl类型的对象实例,如下图所示:
你还可以使用默认方法来定义Bean。这样,通过在默认方法上实现带有Bean定义的接口,就可以组成bean的配置
你也可以使用接口(或基类)返回类型声明@Bean方法,如下例所示:
但是,这将预先类型雨泽的可见行限制为指定的接口类型(TransferService)。然后,只有当受影响的单利Bean实例化后,容器才会知道完整类型(TransferServiceImpl)。非懒惰的单利Bean会根据声明顺序被实例化,因此你可能会看到不同的类型匹配的结果,这取决于另一个组件合适试图通过非生命类型进行匹配(例如@autowired TransferServiceImpl,它只有在TransferService Bean被实例化才会解析)。
如果你始终通过已声明的服务接口来引用你的类型,那么你的@Bean返回类型可以安全地假如该设计决策。但是,对于实现多个接口的组件或可能通过其实现类型被引用的组件,更安全的做法是尽可能声明最具体地返回类型(至少与引用你的Bean的注入点所要求的一样具体)。
Bean 依赖关系
一个@Bean标注的方法可以拥有任意数量的参数,这些参数用于描述构建该Bean所需的依赖关系。例如,如果我们的TransferService需要AccountRepository,我们可能用一个方法参数来具体化这种依赖关系,如下例所示:
解析机制与基于构造函数的依赖注入机制基本相同。详情请参见相关章节。
接受生命周期回调
使用@Bean注解定义的任何类都支持常规生命周期回调,并可使用JSR-250中@PostConstruct和@PreDestroy注解。更多详情,请参阅JSR-250注解。 我们还完全支持常规的Spring生命周期回调。如果Bean实现了InitializingBean、DisposableBean 或Lifecycle,容器就会调用他们各自的方法。 标准的*Aware接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等)也完全支持。 @Bean注解支持指定任意初始化和销毁回调方法,就像SpringXml在bean 元素上init-method和destroy-method属性一样,如下例所示:
默认情况下,使用java配置定义的具有公共close或shutdown方法的bean会自动假如销毁回调。如果你又一个公共close或shutdown方法,并且不希望在容器关闭时调用它,你可以在bean定义中添加@Bean( destroyMethod=""")以禁用默认的(inferred)模式。
InitialContext但不适用JndiObjectFactoryBean变体(这会迫使你将返回类型声明成FactoryBean类型,而不是实际目标类型,从而使其更难用于其他@Bean方法中的交叉引用调用,因为这些方法打算在此引用所提供的资源)。
就上一条注释上面的实例中BeanOne而言,在构建过程中直接调用init()方法同样有效,如下例所示:
指定Bean范围
Spring包含@Scope注解,因此你可以指定Bean的作用域。
使用@Scope注解
你可指定使用@Bean注解定义的bean应具有特定的作用域。你可以使用Bean作用域部分中指定的任何标准作用域。 默认作用域是singleton,但你可以使用@Scope注解覆盖它,如下例所示:
@Scope和Scoped-proxy
Spring 提供了一种通过作用域代理处理作用域依赖关系的便捷方法。在使用XMl配置时,创建此类代理的最简单方法是使用aop:scoped-proxy/ 元素。使用@Scope注解配置ajva中的Bean时,可通过proxyMode属性提供同等支持。默认值是ScopedProxyMode.DEFAULT,它通常表示不创建作用域代理,除非在组件扫描指令级别配置了不同的默认值。你可以指定ScopedProxyMode.TARGET_CLASS、ScopedProxyMode.INTERFACES或ScopedProxyMode.NO. 如果将xml参数文档中的作用域代理实例移植到我们使用的java的@Bean中,它将与下面的示例相似:
默认情况下,配置类使用@Bean方法的名称作为生成Bean的名称。但是这一功能可以通过name属性来覆盖,如下例所示:
Bean别名
正如在“命名Bean”一文中所讨论的,有时需要给一个Bean起多个名字,也就是所谓的Bean别名。为此,@Bean注解的name属性接受一个字符串数组。下面的实力展示了如何为一个bean设置多个别名:
Bean描述
有时,对bean进行更详细的文本描述会很有帮助。这在出于监控目的的而暴露bean(也许是通过JMX)时尤其有用。 要为@Bean添加说明,可以使用@Description注解,如下利所示:
1.12.4使用@Configuration注解
@Configuration是一个类级别的注解,别是一个对象是Bean定义的来源。@Configuration类通过@Bean注解的方法声明Bean。对@Configuration类上@Bean方法的调用也可用于定义bean间的依赖关系。请参见基本概念:@Bean和@Configuration的一般介绍。
注入Bean依赖关系
当bean之间存在依赖关系时,表达这种依赖关系就像让一个bean方法调用另一个bean方法一样简单,如下例所示:
在前面的实例中,beanOne通过构造函数注入接受了对beanTwo的引用。
查找方法注入
如前所述,查找方法注入是一种高级功能,应该很少使用。它适用于单利作用域bean依赖于原型作用域bean的情况。使用java进行这种类型的配置为实现这种模式提供了一种自然的方法,下面的示例展示了如何使用查找方法注入:
通过使用java配置,你可以创建CommandManager的子类,其中的抽象createCommand() 方法将被重写,以便查找新的(原型)命令对象。下面的实例展示了入耳后做到这一点:
关于基于java的配置如何在内部运行的更多信息
请看下面的实例:其中显示了一个@Bean注解方案被调用了两次:
clientDao()在clientService1()和clientService2() 中分别被调用了一次。由于改方法会创建ClientDaoImpl的新势力并返回它,不要吃通常会有两个实例(每个服务一个)。这肯定会有问题:在Spring 中,实例化的Bean默认具有singleton作用域。这就是神奇之处:所有@Configuration类在启动时都会使用CGLIB进行子类化。在子类中,子方法在调用父方法并创建新实例之前,会首先检查容器中是否有任何缓存的(作用域)bean。
如果你希望避免任何CGLIB施加的限制,请考虑在非@Configuration类中声明@Bean方法(例如,在纯@Component类中声明)。这样,@Bean方法之间夸方法调用就不会被拦截,因此你必须完全依赖构造函数或方法级别的依赖注入。
1.12.5组成基于java的配置
Spring基于java的配置功能可让你编写注解,从而降低配置的复杂性。
使用@Import注解
正如Spring XMl文件中使用 元素来帮助模块化配置一样,@Import注解允许从另一个配置类加载@Bean定义,如下例所示:
现在,无需在实例化上下文时同时指定ConfigA.class和ConfigB.class,只需明确提供ConfigB即可,如下例所示:
这种方法简化了容器的实例化,因为只需要处理一个类,而不需要在构建过程中记住大量可能存在的@Configuration类。
自SpringFramework4.2起,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果你想使用几个配置类作为显式定义所有组件的入口点,从而避免组件扫描,name这一点尤其有用。
在导入的@Bean定义上注入依赖关系
前面的例子可行,单过于简单。在大多数实际应用场景中,Bean会跨配置类相互依赖。在使用XML时,这不是一个问题,因为不涉及编译器,你可以声明ref=“someBean”并相信Spring 会在容器初始化过程中解决这个问题。使用@Configuration类时,java编译器会对配置模型施加限制,即对其他bean的引用必须是有效的java语法。 幸运的是,解决这个问题非常简单。正如我们已经讨论过的,@Bean方法可以有任意数量的参数来秒你Bean依赖关系。请考虑一下更真实的场景,其中有多个@Configuration类,每个类都依赖于其他类中声明的Bean:
还有另外一种方法可以达到同样的效果。请记住,@Configuration类最终只是容器中的另一个Bean:这意味着他们可能像任何其他Bean一样利用@Autowired和@Value注入及其他功能。
此外,要特别小心通过@BEan进行的BeanPostProcessor和BeanFactoryPostProcessor定义。这些方法通常应声明我static@Bean方法,而不是触发其包含的配置类的实例化。否则,@Autowired和@Value可能对配置类本身不起作用,因为有可能在AutowiredAnnotationBeanPostProcessor之前将其创建为Bean实例。
下面的示例展示了如何将一个Bean自动连接到另一个Bean:
高质量的导入beans,便于导航
在前面的示例中,使用@Autowired可以很好地实现所需的模块化,但是确定自动注入的bean定义在何处声明,仍然有些模糊不清。例如,作为开发人员,在查看ServiceConfig时,你如何知道@Autowired AccountRepository Bean究竟在何处声明?代码中并没有明确说明,而这可能就是问题所在。请记住,Eclipse的Spring工具提供的工具可以呈现图标,显示所有内容是如何连接的,这可能就是你所需要的全部。此外,你的java ide可以轻松查找AccountRepository类型的所有声明何使用,并快速显示返回改类型的@Bean方法的位置。 如何你不能接受这种模糊性,并希望在IdE中从一个@Configuration类直接导航到另一个@Configuration类,则可以考虑自动为配置类本身注入。下面的示例展示了如何做到这一点:
在前面的情况中,AccountRepository的定义是完全明确的。但是,ServiceConfig现在与RepositoryConfig紧密相连。这就是权衡的结果。通过使用基于接口或基于抽象类的@Configuration类,可以在一定程度上缓解这种紧密耦合。请看下面的示例:
现在,ServiceConfig与具体的DefaultRepositoryConfig是松耦合的,内置ide工具仍然非常有用:你可以轻松获取RepositoryConfig实现的类型层次结构。这样浏览@Configuration类及其依赖关系与浏览基于接口的代码通常过程没有什么区别了。
如果你想影响某些Bean的启动创建顺序,可以考虑将其中一些Bean声明为@Lazy(用于在首次访问时创建,而不是在启动时创建),或者将某些其他Bean声明为@DependsOn(确保在当前Bean之前创建特定的其他Bean,而不是后者的直接依赖关系所暗示的那样)。
有条件地包含@Configuration类或@Bean方法
根据一些任意的系统状态,有条件的启动或禁用完整的@Configuration类,甚至是单个@Bean方法,通常非常有用。一个常见的例子是使用@Profile注解,只有在Spring Environment中启用了特定配置文件时才jihongyunBean(有关详细信息,请参阅Bean定义配置文件)。 @profile注解实际上是通过使用灵活的注解@Conditional来实现的。@Conditional注解指出了特定的org.springframework.context.annotation.Condition实现,在注册@Bean之前应参考这些实现。 Condition接口的视线提供了一个返回true或false的matches()方法。例如,一下列表显示了@Profile实际使用的Condition实现:
有关详细信息,请参阅@Conditional javadoc。
结合java和xml配置
Spring的@Configuration支持并不打算完全取代SpringXml。某些设施(如Spring xml命名空间)仍然是配置容器的理想方式。在XMl方便或必要的情况下,你可以选择:要么以 “以XML为中心”的方式实例化容器,例如使用ClassPathXmlApplicationContext;要么以“以java为中心”的方式实例化容器,例如使用AnnotationConfigApplicationContext和@ImportResource注解来根据需要导入XMl。
以XMl为中心使用@Configuration类
从Xml引导Spring容器并以临时方式包含@Configuration类可能更可取。例如,在使用SpringXMl的大型现有代码库中,根据需要创建@Configuration类并从现有XMl文件中包含他们会更容易。本节稍后,我们将介绍在这种“以xml为中心”的情况下使用@Configuration类的选项。
将@Configuration类声明为普通Spring 元素
请记住,@Configuration类最终是容器中的Bean定义。在这一系列实例中,我们创建一个名为AppConfig的@Configuration类,并将其作为 定义包含在system-test-config.xml中。由于context:annotaion-config/已开启,容器可以识别@Configuration注解,并正确处理AppConfig中声明的@Bean方法。 下面的示例展示了java中的一个普通配置类:
下面的示例显示了system-test-config.xml示例文件的一部分:
下面的示例显示了一个可能得jdbc.properties文件:
使用context:component-scan/获取@Configuration类
由于@Configuration是用@COmponent元注解的,因此@Configuration标注的类将自动成为组件扫描的候选对象。使用与上一示例相同的情况,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式地声明context:annotation-config/ ,因为context:component-scan/可以实现相同的功能。 下面的示例显示了修改后的system-test-config.xml文件:
@Configuration通过@ImportResource以类为中心使用XML
在以@Configuration类作为配置容器的主要机制的应用程序中,可能仍然需要至少使用一些xml。在这些情况下,你可以使用@ImportResource并定义所需的XMl。这样做可以实现“以java为中心”的容器配置方法,并将XML控制在最低限度。下面的实例(包括一个配置类、一个定义Bean的XMl文件、一个属性文件和main类)展示了如何使用@ImportResource注解来实现“以java为中心”的配置,并根据需要使用XML:
1.13 抽象环境
Evironment接口是集成在容器中的一个抽象概念,他对应用环境的两个关键方法进行建模:配置文件和属性。 配置文件是一组已命名的、符合逻辑的Bean定义,只有在给定的配置文件处于活动状态时才会在容器中注册。无论bean是用xml还是注解定义的,都会分配给一个配置文件。与这个配置文件相关的Environment对象的作用是确定呢些配置文件当前处于活动状态,以及那些配置文件硬默认处于活动状态。 属性在几乎所有应用程序中都扮演着重要的角色,其来源可能多种多样:属性文件、jvm系统属性、系统环境变量、JNDI、Servlet上下文参数、临时Properties对象、Map对象等。与属性相关的Environment对象的作用是为用户提供一个方便的服务接口,用于配置属性源并解析其中的属性。
1.13.1 Bean定义配置文件
Bean定义配置文件在核心容器中提供一种机制,允许在不同的环境中注册不同的Bean。环境“一词对不同的用户有不同的含义,这一功能可以帮助解决许多用例,包括:
在开发过程中使用内存数据源与在QA或生产过程中从JNDI查找相同的数据源相比。
仅在将应用程序部署到性能环境时注册监控基础架构。
为客户A和客户B的部署注册定制的Bean实现。
考虑实际应用中需要DataSource的第一个用例。在测试环境中,配置可能如下:
现在,假设应用程序的数据源已在生产应用程序服务器的JNDI目录中注册,请考虑如何将此应用程序部署到QA或生产环境中。我们的DataSource Bean现在看起来就像下面的列表:
问题在于如何根据当前环境在使用这两种辩题之间进行切换。随着时间的推移,Spring用户设计了许多方法来实现这一目标,通常依赖与系统环境变量和包含$标记的xml 语句的组合,这些标记根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是容器的一项核心功能,它为这一问题提供了解决方案。 如果我们将上例中关于特定环境Bean定义的用例加以推广,就会发现需要在某些上下文中注册某些bean定义,而在其他上下文中则不需要。可以说,你希望在A情况下注册特定的Bean定义配置文件,而在B情况下注册不同的配置文件。
使用@Profile
@Profile注解可以让你指明,当一个或多个指定配置文件处于活动状态时,组件才有资格注册。使用前面的实例,我们可以重写DataSource配置如下:
standaloneDataSource方法仅仅在development配置文件中可用。
jndiDataSource方法仅在production配置文件中可用。
standaloneDataSource方法仅在development配置文件中可用。
jndiDataSource方法仅在production配置文件中可用
配置文件中字符串可包含简单的配置文件名称(例如,production)或配置文件表达式。轮廓表达式允许表达更复杂的轮廓逻辑(例如,production&us-east)。配置文件表达式支持一下操作符:
!非
& 和
| 或
你可以将@Profile用作元注解,已创建自定义的组成注解。下面的实例定义了自定义@Production注解,你可以将其用作@Profile(" production")的直接替代:
如果@Configuration类被标记我@Profile,除非一个或多个指定的配置文件处于活动状态,否则与该类相关的所有@Bean方法和@Import注解都将被绕行。如果@COmponent或@Configuration类被标记为@Profile({" p1","p2"}) ,除非配置文件“p1”或“p2”已激活,否则不会注册或处理该类。如果给定的配置文件前缀有not操作符(!),则只有在该配置文件未激活的情况下,才会注册被注解的元素。例如,给定@Profile({" p1","!p2"})时,如果配置文件“p1”已激活或配置文件“p2”未激活,则注册发生。
@Profile也可以在方法级别声明,一遍只包含配置类中的一个特定Bean(例如,特定Bean的替代变体),如下例所示:
在@Bean方法上使用@Profile时,可能会出现一种特殊情况:如果重载了具有相同java方法名的@Bean方法(类似与构造函数重载),则需要在所有重载方法上一致声明@Profile条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,不能使用@Profile来选择具有特定参数签名的重载方法,而不是其他方法。同一Bean的所有工厂方法之间的解析在创建时遵循Spring的构造函数解析算法。
Xml bean定义介绍
与xml对应的是 元素的profile属性。签名的实例配置可以用两个xml文件重写,如下所示:
也可以避免分割,在同一个文件中嵌套 元素,如下例所示:
spring-bean.xsd被限制为只允许作为文件中的最后一个元素。这将有助于提供灵活性,而不会导致xml文件杂乱无章。
激活配置简介
现在我们已经更新了配置,但仍需只是spring启用那个配置文件。如果我们现在启动实例应用程序,就会看到NoSuchBeanDefinitionException抛出,因为容器无法找到名为dataSource的Spring Bean。 激活配置文件有多种方法,但最直接的方法是通过ApplicationContext Api以编程方式进行激活。下面的示例展示了如何实现这一功能:
此外,你还可以通过spring.profiles.active属性声明激活配置文件,该属性可通过系统环境变量、jvm系统属性、web.xml中的Servlet上下文参数或JNDI中的条目指定(请参阅PropertySource抽象)。在集成测试中,可以通过使用spring-test模块中的@ActiveProfiles注解来声明活动配置文件(请参阅使用环境配置文件的上下文配置)。 请注意,预案并非“非此即颇”。你可以同时激活带讴歌配置文件。通过编程,你可以想setActiveProfiles() 方法提供多个配置文件名称,该方法接受String...varargs。下面的实例激活了多个配置文件:
声明式地,spring.profiles.active可以接受以逗号分割的配置文件名称列表,如下例所示:
默认配置文件
默认配置文件表示默认启动的配置文件。请看下面的实例:
如果没有激活配置文件,则会创建dataSource。你可以将其视为一种为一个或多个bean提供默认定义方法。如果启用了任何配置文件,默认配置文件将不适合。 你可以通过在Environment上使用setDefaultProfiles()或声明使用spring.profiles.default属性来更改默认配置文件的名称。
1.13.2 PropertySource抽象
Spring的Environment抽象在可配置的属性层次结构上提供了搜索操作。请看下面的列表:
在前面的代码段中,我们看到了想Spring询问my-property属性是否已为当前环境定义的高级方法。要回答这个问题,Environment对象将对一组PropertySource对象执行搜索。PropertySource是对任何键值对来源的简单抽象,而Spring的StandardEnvironment则配置了两个PropertySource对象,一个代表JVM系统属性集(System.getProperties()) ,另一个代表系统环境变量集(System.getenv())。
这些默认属性源适用于StandardEnvironment,供独立应用程序使用。StandardServletEnvironment包含其他默认属性源,包括servlet配置、servlet上下文参数和JndiPropertySource(如果JNDI可用)。
具体来说,当你使用StandardEnvironment时,如果运行时存在my-property系统属性或my-property环境变量,对env.containsProperty(" my-property")的调用将返回true。
对于常见的StandardServletEnvironment,完整的层次结构如下,最高优先级的条目位于顶层:
ServletConfig参数
ServletContext参数
JNDI环境变量
JVM系统属性
JVM系统环境
最重要的是,整个机制是可配置的。也许你又一个自定义属性源,希望将其集成到词搜索中。为此,请事先并实例化你自己的PropertySource并将其添加到当前的Environment的PropertySource中。下面的实例展示了如何做到这一点:
在前面的代码中,MyPropertySource在搜索中的优先级最高。如果它包含了一个my-property属性,则会检测并返回属性,而不会返回任何其他PropertySource中的任何my-property属性。MutablePropertySourcesApi公开了许多方法,允许对属性源集合进行精准操作。
1.13.3 使用@PropertySource
@PropertySource注解为在Spring的Environment中添加PropertySource提供一种方便声明机制。 给定一个名为app.properties的文件,其中包含键值对testbean.name=myTestBean,下面的@Configuration类使用@PropertySource的方式是,调用testBean.getName() 返回myTestBean:
如一下示例所示,@PropertySource资源位置中的任何$占位符都会根据环境中已注册的属性源集合进行解析:
假设my.placeholder存在于已注册的某个属性源中(例如,系统属性或环境变量),占位符将被解析为响应的值。如果没有,则使用default/path作为默认值。如果为指定默认值,且无法解析属性,则会抛出IllegalArgumentException事件。
根据java8约定,@PropertySource注解是可重复的。但是,所有此类@PropertySource注解都需要在同一级别声明,要么直接在配置类上声明,要么作为元注解在同一自定义注解中声明。不建议混合使用直接注解和元注解,因为直接注解实际上会覆盖元注解。
1.13.4 报表中的占位符
一直以来,元素中占位符的值只能根据JVM系统属性或环境变量来确定。现在情况已不再如此。由于Environment抽象已集成到整个容器中,因此很容易通过它来路由占位符的解析。这意味着你可以任何方式配置解析过程。你可以更改通过系统属性和环境变量搜索的优先级,或者完全删除它们。你还可以根据需要添加自己的属性源。 具体来说,无论customer属性在何处定义,只要environment.net中有该属性,下面的语句就会起作用:
1.14注册LoadTimeWeaver
Spring使用LoadTimeWeaver在类加载到java虚拟机时对其进行动态转换。 要启用加载时编程,可以将@EnableLoadTimeWeaving添加到@Configuration类中,如下例所示:
另外,对于xml配置,你也可以使用context:load-teime-wwaver元素:
一旦为ApplicationContext进行配置,该ApplicationContext中的任何Bean都可以实现LoadTimeWeaverAware,从而接受对加载时编织器实例的引用。这在结合Spring的JPA支持时特别有用,因为在这种情况下,加载编织可能是JPA类转换所必须的。有关详细信息,请参考LocalContainerEntityManagerFactoryBean javadoc。有关AspectJ加载时编入的更多信息,请参阅Spring Framework中的AspectJ加载时编入。
1.15 ApplicationContext的附加功能
正如本章导言中所讨论的,org.springframework.beans.factory包提供了管理和操心偶Bean的基本功能,包括编程方式。org.springframework.context包添加ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口,以更面向应用程序框架的方式提供其他功能。许短发以完全声明的方式使用ApplicationContext,甚至不以编程方式创建它,而是依靠ContextLoader等支持类自动实例化ApplicationContext作为java eeweb应用程序正常启动过程的一部分。 为了以更加面向框架的方式增强BeanFactory功能,上下文包还提供了以下功能:
通过MessageSource接口以i18n风格访问消息。
通过ResourceLoader界面访问资源,如URL和文件
事件发布,即通过使用ApplicationEventPublish接口向实现ApplicationListener接口的Bean发布事件。
通过HierarchicalBeanFactory界面加载多个(分层)上下文,让每个上下文专注于一个特定层,如应用程序的网络层。
1.15.1 使用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时,它会自动搜索上下文中定义的MessageSourceBean。该Bean的名称必须是messageSource。如果找到了这样一个Bean,对前面方法的所有调用都将委托给消息源。如果没有找到消息源,ApplicationContext将尝试找到包含同名Bean的父类。如果找到了,它将使用该Bean作为MessageSource。如果ApplicationContext无法找到任何消息源,那么就会实例化一个空的DelegatingMessageSource以接受对上面定义的方法调用。 Spring提供了三种MessageSource实现:ResourceBundleMessageSource、ReloadableResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource以实现嵌套消息传递。StaticMessageSource很少使用,但它提供了向消息源加消息的编程方法。下面的实例显示了ResourceBundleMessageSource:
该示例假定你的classpath中定义了三个资源包,分别称为format、exceptions和windows。任何解析消息的请求都将通过ResourceBundle对象以JDK标准的消息解析方式进行处理。在示例中,假设上述两个资源包文件的内容如下:
下一个实例展示了运行MessageSource功能的程序。请记住,所有ApplicationContext的实现也是MessageSource的实现,因此可以转换为MessageSource接口。
上述程序的输出结果如下:
概括的说,MessageSource是在名为beans.xml的文件中定义的,该文件存在于classpath的根目录中。messageSource Beand定义通过basenames属性引用了许多资源包。在列表中传递给basenames属性的三个文件作为文件存在于classpath的根目录中,他们分别被称为format.properties、exceptions.properties和windows.properties。
下一个实例显示了传递给信息查找的参数。这些参数被转换为String对象,并插入到查找信息的占位符中。
调用execute()方法的输出结果如下:
关于国际化(“i18n”),Spring的各种MessageSource实现遵循与标准JDKResourceBundle相同的区域设置解析和回退规则。简而言之,继续前面定义的messageSource实例,如果要根据英国(en-GB)语言解析消息,则需要创建名为format_en_GB.properties、exceptions_en_GB.properties和windows_en_GB.properties的文件。 通常,本地语言解析由应用程序的周围环境管理。在下面的实例中,(英国)信息所对应的本地语言是手动指定的:
运行上述程序的输出结果如下:
你还可以使用MessageSourceAware接口获取任何已定义的MessageSource的引用。当创建和配置Bean时,在实现MessageSourceAware接口的ApplicationContext中定义的任何Bean都会注入应用程序上下文的MessageSource中。
作为ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置(而不是进班是从类路径)读取文件,并支持捆绑属性文件的热重载(同时在两者之间有效地缓存它们)。详情请参见:ReloadableResourceBundleMessageSource javadoc。
1.15.2标准事件和自定义事件
ApplicationContext中的事件处理事通过ApplicationContext类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的Bean部署到上下文中,那么每次ApplicationEvent发布到ApplicationContext时,该Bean就会收到通知。从本质上讲,就是标准的观察者设计模式。
下表描述了Spring提供的标准事件:
event | explanation |
---|---|
ContextRefreshedEvent | 在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)在这里,“初始化”意味着所有的bean都已加载,后处理器bean已被检测和激活,单利已被预实例化,并且ApplicationContext对象已准备就绪可供使用。只要上下文尚未关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这种热刷新。例如XmlWebApplicationContext支持热刷新,但是GenericApplicationContext不支持 |
ContextStartedEvent | 通过使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。这里的启动是指所有LifecycleBean都收到显式的启动信号。通常,该信号用于在显式定制后重新启动Bean,但也可用于启动未配置为自启动的组件(例如,初始化尚未启动的组件 |
ContextStoppedEvent | 通过使用ConfigurableApplicationContext接口的stop()方法定制ApplicationContext时发布。这里的“停止”是指所有Lifecycle Bean都收到了明确的停止信号。可以通过调用start()重新启动已停止的上下文 |
ContextClosedEvent | 通过使用ConfigurableApplicationContext接口的close()方法或通过JVM关闭钩子关闭ApplicationContext时发布。在这里,关闭意味着所有单利Bean将被销毁。一旦上下文被关闭,它的生命周期就结束了,无法刷新或重启。 |
RequestHandledEvent | 网络特定事件,告诉所有bean Http请求已得到处理。该时间在请求完成后发布。该事件仅适用于Spring的DispatcherServlet .Net Framework 2.0的web应用程序 |
ServletRequestHanddedEvent | RequestHandledEvent的子类,用于添加特定于Servlet的上下文信息 |
你还可以创建和发布自己的自定义事件。下面的实例展示了一个扩展Spring ApplicationEvent基类的简单类:
要发布自定义ApplicationEvent,请在ApplicationEventPublisher上调用publishEvent() 方法。通常,可以通过创建一个实现ApplicationEventPublisherAware的类并将其注册为SpringBean来实现。下面的实例展示了这样一个类:
在配置时,Spring容器会检测到EmailService实现了APplicationEventPublisherAware并自动调用setApplicationEventPublisher() 。实际上,传入的参数就是Spring容器本身。你是通过ApplicationEventPublisher接口与应用上下文交互的。 要接受自定义APplicationEvent,可以创建一个实现ApplicationListener的类,并将注册为Spring Bean。下面的示例展示了这样一个类:
请注意,ApplicationListener使用自定义事件的类型(上例中的BockedListEvent)进行了泛型参数化。这意味着onApplicationEvent() 方法可以保持类型安全,避免了任何向下传递的需要。你可以注册任意数量的事件监听器,但请注意,默认情况下,事件监听器会同步和单线程方法的一个优点是,当监听器接受事件时,如果事务上下文可用,它将在发布的事务上下文中运行。如果需要另一种事件发布策略,请参阅Spring的ApplicationEventMulticaster接口和SimpleApplicationEventMulticaster实现的javadoc以获取配置选项。 下面的示例显示了用于注册和配置上述每个类的bean定义:
综上所述,当调用emailService Bean的sendEmail()方法时,如果有任何电子邮件应被阻止,就会发布BockedListEvent类型的自定义事件。blockedListNotifier Bean注册为ApplicationListener接受BlockedListEvent事件,此时它可以通知相关方。
基于注解的事件监听器
你可以使用@EventListener注解在托管bean的任何方法上注册时间监听器。BlockedListNotifier可以重写如下:
方法签名再次声明了其监听的事件类型,但这次使用了一个灵活的名称,并且没有实现特定的监听器接口。事件类型也可以通过泛型来缩小,只要实际事件类型能在实现层次中解析你的泛型参数即可。 如果你的方法需要监听多个事件,或者你想定义一个不带任何参数的方法,也可以在注解中指定事件类型。下面的示例展示了如何做到这一点:
还可以通过使用注解中定义了Spel表达式的condition属性来添加额外的运行时筛选功能,该表达式应与实际调用特定事件的方法相匹配。 下面示例展示了如何重写我们的通知程序,使其仅在事件的content属性等于my-event时才被调用:
每个SPEL表达式都针对一个专用上下文进行求值。下表列出了上下文中可用的项目,以便于条件事件处理:
name | location | description | example |
---|---|---|---|
event | root object | 实际APplicationEvent | #root.event或event |
Argumments Array | root object | 用于调用方法的参数(对象数组)。 | #root.args或args;args[0]访问第一个参数等等 |
Argument name | evaluation context | 方法参数的名称。如果由于某种原因无法提供参数名(例如,编译后的字节码中没有调试信息),也可以使用#a<#arg>语法提供单个参数,其实<#arg>代表参数索引(从0开始) | #blEvent或#a0(也可以使用#p0或#p<#arg>)参数符号作为别名 |
请注意,#root.event使你可以访问底层事件,即使你的方法签名实际上指的是已发布的任意对象。 如果需要将一个事件作为处理另一个事件的结果发布,可以更改方法签名,返回应发布的事情,如下例所示:
异步监听器
如果你希望某个监听器异步处理事件,可以重复使用常规的@Async支持。下面的示例展示了如何做到这一点:
使用异步事件时注意以下限制:
如果异步事件监听器抛出Exception,它不会传播给调用者。有关详细信息,请参阅AsyncUncaughtExceptionHandler。
异步事件监听器方法不能通过返回值来发布后续时间。如果需要将另一个事件作为处理结果发布,请注入ApplicationEventPublisher以手动发布。
订阅监听器
如果需要在调用一个监听器之前调用另一个监听器,可以在方法中添加@Order注解,如下例所示:
由于类型擦除的原因,只有当触发的事件监听器所过滤的通用参数(即类似class PersonCreatedEvent extends EntityCreatedEvent {}的内容)时,此功能才会起作用。 在某些情况下,如果所有事件都遵循相同的结构(如前面示例中的事件),这可能会变得相当繁琐。在这种情况下,你可以实施ResolvableTypeProvider来引导框架,使其超过运行时环境所提供的范围。下面的事件展示了如何做到这一点:
1.15.3方便访问低级资源
为优化使用和理解应用程序上下文,你应该熟悉了Spring的Resource抽象,如资源中所述。 应用程序上下文中ResourceLoader,可用于加载Resource对象。Resource本质上是JDKjava.net.URL类功能更丰富的版本。事实上,在适当的情况下,Resource的视线封装了java.net.URL的实例。Resource可以透明的方式从几乎任何位置获取底层资源,包括从classpath、文件系统位置、可使用标准URL描述的任何位置一级其他一些变体。如果资源位置字符串是一个没有特殊前缀的简单路径,那么这些资源的来源僵尸特定的,并与实际应用上下文类型相适应。 你可以配置部署到应用上下文中的Bean,使其实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动回调,并将应用程序上下文本身作为ResourceLoader传入。你还可以公开Resource类型的属性,用于访问静态资源。它们会像其他属性一样被注入其中。你可以将这些Resource属性指定为简单的String路径,并依靠在部署Bean时将这些文本字符串 自动转换为实际的Resource对象。 提供给APplicationContext构造函数的位置路径实际上是资源字符串,在简单形式下,会根据具体的上下文实现进行适当处理。例如,ClassPathXmlApplicationContext将简单的位置路径视为classpath位置。你还可以使用带有特殊前缀的位置路径(资源字符串)来强制从classpath或URL加载定义,而与实际上下文类型无关。
1.15.4应用程序启动跟踪
APplicationContext管理Spring应用程序的声明周期,并围绕组件提供丰富得到编程模型。因此,复杂的应用程序可以拥有同样复杂的组件图和启动阶段。 使用特定指标追踪应用程序的启动步骤有助于了解启动阶段的时间使用情况,同时也可用于更好地了解整个上下文生命周期。 AbstractApplicationContext及其子类顺手ApplicationStartup工具,改工具可手机有关各个启动阶段的StartupStep数据:
应用程序上下文声明周期
bean生命周期(实例化、智能初始化、后处理)
应用事件处理
下面是在AnnotationConfigApplicationContext 中使用仪器的实例:
应用程序上下文已包含多个步骤。一旦记录下来,就可以使用特定工具手机、显示和分析这些启动步骤。如需现在启动步骤的完整列表,可查看专门的附录部分。 默认的ApplicationStartup实现是一个误操作变量,以尽量减少开销。这意味着默认情况下不会在应用程序启动期间收集任何指标。Spring Framework随附了使用Java Flight Recorder追踪启动步骤的实现:FlightRecorderApplicationStartup。要使用此变体,你必须在APplicationContext创建后立即将其实例配置到APplicationContext中。 如果开发人员提供自己的AbstractAplicationContext子类,或者希望收集更精准的数据,也可以使用ApplicationStartup基础设施。
要开始收集自定义StartupStep,组件可以直接从应用程序上下文中获取ApplicationStartup实例,使其组件实现ApplicationStartupAware或在任何注入点上请求ApplicationStartup类型。
1.15.5 方便网络应用程序ApplicationContext实例化
例如,你可以使用ContextLoader来声明创建ApplicationContext实例当然,你也可以通过使用ApplicationContext实现之一,以变成方式创建ApplicationContext实例。 你可以使用ContextLoaderListener注册ApplicationContext,如下例所示:
监听器会检查contextConfigLocation参数。如果改参数不存在,监听器将使用/WEB-INF/applicationContext.xml作为默认值。如果参数确实存在,监听器会使用预定义的分隔符(逗号、分号和空白)分隔string,并使用这些值作为搜索应用程序上下文的位置。此外,还支持ant风格的路径模式。例如,/WEB-INF/* Context.xml(搜索名称以Context.xml结尾、位于WEB-INF目录中的所有文件)和/WEB-INF/**/*Context.xml(搜索WEB-INF子目录中的所有此类文件)。
1.15.6 将Spring ApplicationContext部署为JAVA EE RAR文件
可以将Spring ApplicationContext部署为RAR文件,在JAVAEE RAR部署单元中封装上下文及其所需的所有Bean类和库JAR。这相当于引导一个独立的Application(仅托管在java EE环境中),使其能够访问java ee服务器设施。RAR部署是部署无头WAR文件(实际上是没有任何HTTP入口点的WAR文件,仅用于Java EE环境中引导Spring ApplicationContext)的一种更自然的替代方案。 RAR部署非常适合不需要HTTP入口点而只包含消息端点和计划作业的应用上下文。这种上下文中的Bean可以使用应用服务器资源,如JTA事务管理器和JNDI绑定的JDBC DataSource实例和JMS ConnectionFactory实例,还可以向平台的JMX服务器注册,所有这些都是通过Spring的标准事务管理、JNDI和JMX支持设施实现的。应用组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager进行交互。 有关RAR部署中设计的配置细节,请参阅SpringContextResourceAdapter类的javadoc。 以java EE RAR文件的形式简单部署Spring ApplicationContext:
将所有应用程序类打包成一个RAR文件(这是一个标准的JAR文件,但文件扩展名不同)
将所有需要的JAR类库添加到RAR存档的根目录中。
添加META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的javadoc中所示)和相应的SpringXml Bean定义文件(通常为META-INF/applicationContext.xml)
将生成的RAR文件放到应用服务器的部署目录中。
这种RAR部署单元通常是独立的。它们不会讲组件暴露给外部世界,甚至不会暴露给统一应用程序的其他模块。与机遇RAR的APplicationContext的交互通常是通过它与其他模块共享的jms进行的。例如,机遇RAR的ApplicationContext还可以安排一些工作或对文件系统中的新文件(或类似文件)做出反应。如果需要允许从外部进行同步访问,它可以导出RMI端点,这些端点可能会被同一台机器上的其他应用模块使用。
1.16 BeanFactory应用程序接口
BeanFactoryAPI为Spring的IOC功能提供了底层基础。它的特定合约主要用于与Spring和相关第三方框架的其他部分集成,而他的DefaultListableBeanFactory实现则是高层GenericApplicationContext容器中的一个关系委托。 BeanFactory和相关接口(如BeanFactoryAware、InitializingBean、DisposableBean)是其他框架组件的重点集成点。他们不需要任何注解,甚至不需要反射,因此可以在容器及其组件之间实现非常高效的交互。应用级Bean可以使用相同的回调接口,但通常更倾向于通过注解或变成配置进行声明式依赖注入。 请注意,核心BeanFactoryAPI级别及其DefaultListableBeanFactory实现并不假定要使用的配置格式或使用组件注解。所有这些功能都是通过扩展(如XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)实现的,并在作为核心元数据表示的共享BeanDefinition对象上运行。这正是Spring容器如此灵活和可扩展的本质所在。
1.16.1 BeanFactory或ApplicationContext
本节将解释BeanFactory和ApplicationContext容器级别之间的区别以及对引导的影响。 除非有充分的理由,否则你应该使用ApplicationContext和GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常用实现。这些是Spring核心容器的主要入口点,可用于所有常见用途:加载配置文件、触发类路径扫描、以编程方式注册Bean定义和注解类,一级注册功能性Bean定义。 由于ApplicationContext包含了BeanFactory的所有功能,因此通常建议使用ApplicationContext而不是普通的BeanFactory,除非需要对Bean处理进行完全的控制。在ApplicationContext(如GenericApplicationContext实现)中,会按照惯例(及根据bean名称或bean类型,尤其后处理器)检测到几种bean,而普通DefaultListableBeanFactory则与任何特殊Bean无关。 对于注解处理和AOP代理等许多扩展容器功能来说,BeanPostProcessor扩展点是必不可少。如果你只是用普通的DefaultListableBeanFactory,此类后置处理器默认情况下不会检测和激活。这种情况可能会令人困惑,因为你的Bean配置实际上没有任何问题。相反,在这种情况下,需要通过额外的设置对容器进行完全引导。 下面列出了BeanFactory和ApplicationContext接口和实现所提供的功能。
特点 | BeanFactory | ApplicationContext |
---|---|---|
Bean实例化/注入 | yes | yes |
总和生命周期管理 | no | yes |
自动BeanPostProcessor注册 | no | yes |
自动BeanFactoryPostProcessor注册 | no | yes |
方便的MessageSource访问(用于国际化) | no | yes |
内置ApplicationEvent发布机制 | no | yes |
要使用DefaultListableBeanFactory显式注册一个Bean后置处理器,你需要以编程方式调用addBeanPostProcessor,如下例所示:
要将BeanFactoryPostProcessor应用到普通DefaultListableBeanFactory中,需要调用其postProcessBeanFactory方法,如下例所示:
在这两种情况下,显式注册步骤都很不方便,这就是为什么在Spring支持的应用程序中,各种APplicationContext变体比普通DefaultListableBeanFactory更受青睐的原因,尤其是在典型的企业设置中,依赖BeanFactoryPostProcessor和BeanPostProcessor实例来扩展容器功能。
AnnotationConfigApplicationContext已注册了所有常用注解后置处理器,并可通过配置注解(如@EnableTransactionManagement)隐藏其他处理器。在Spring基于注解的配置模型的抽象层中,Bean后处理器的概念只是容器内部的一个细节。