10 Appendix
10.1 XML Schemas
附录的这一部分列出了与核心容器相关的XML模式。
10.1.1 the util Schema
顾名思义,util标记处理的是常见的实用配置问题,如配置集合、引用常量等。要使用util模式中的标记,你需要在Spring XML配置文件的顶部添加以下前言(代码段中的文本引用了正确的模式,这样你就可以使用util命名空间中的标记):
Using util:constant/
请看下面的bean定义:
前面的配置使用Spring FactoryBean实现(FieldRetrievingFactoryBean) 将Bean上isolation属性的值设置为java.sql.Connection.TRANSACTION_SERIALIZABLE常量的值。这一切都很好,但却显的冗长,而且(不必要地)向最终用户暴露了Spring的内部管道。 以下基于XML Schema的版本更加简洁,清楚地表达了开发人员的意图(“注入此常量值”),而且读起来更好:
Setting a Bean property or Constructor Argument from a Field Value
FieldRetrivingFactoryBean是一个FactoryBean,用于检索static或非静态字段值。它通常用于检索public static final常量,然后可用于设置另一个Bean的属性值或构造函数参数。 下面的示例展示了如何通过使用staticField属性来暴露static字段:
还有一种方便使用的形式,即把static字段指定为Bean名称,如下例所示:
这确实意味着我们不再有任何选择来决定bean id的名称(因此任何其他引用它的bean也必须使用这个较长的名称),但这种形式的定义非常简洁,而且非常便于用作内部bean,因为不必为bean引用指定id,正如下面的示例所示:
还有一种方便面使用的形式,即把static字段指定为Bean名称,如下例所示:
这确实意味着我们不再有任何选择来决定bean id的名称(因此任何其他引用它的bean也必须使用这个较长的名称),但这种形式的定义非常简洁,而且非常便于用作内部Bean,因为不必为bean引用指定id,正如下面的示例所示:
你还可以访问另一个Bean的非静态字段,如FieldRetrievingFactoryBean类的APi文档所述。 在Spring中,将枚举值作为属性或构造函数参数注入Bean中是很容易做到的。实际上,你不需要做任何事情,也不需要了解任何Spring内部知识(甚至不需要了解FieldRetrievingFactoryBean等类)。下面的枚举示例展示了注入枚举值是多么容易:
现在请看下面的PersisitenceContextType类型的设置器和相应的Bean定义:
Using util:property-path/
请看下面的例子:
前面的配置使用Spring FactoryBean实现(PropertyPathFactoryBean来创建名为testBean.age的Bean(类型为int),该Bean的值等于testBean的age属性。 现在请看下面的示例,它添加了一个util:property-path/元素:
<property-path/>
元素的path属性的值遵循beanName.beanProperty的形式。在这种情况下,它会获取名为testBean的Bean的age属性。该age属性的值是10。
Using util:property-path/ to Set a Bean Property or Constructor Argument
PropertyPathFactoryBean是一个FactoryBean,用于在给定的目标对象上评估属性路径。目标对象可以直接指定,也可以通过Bean名称指定。然后,你可以在另一个Bean定义中将此值用作属性值或构造函数参数。 下面的示例显示了按名称针对另一个Bean使用路径的情况:
在下面的示例中,将根据内部Bean对路径进行评估:
还有一种快捷表单,其中bean名称就是属性路径。下面的实例显示了快捷表单:
这种形式确实意味着bean的名称没有选择余地。对它的任何引用也必须使用相同的id,即路径。如果作为内部Bean使用,则完全没有必要引用它,正如下面的示例所示:
你可以在实际定义中专门设置结果类型。这在大多数情况下是不必要的,单有时会很有用。有关此功能的更多信息,请参阅javadoc。
Using util:properties/
请看下面的例子:
前面的配置使用Spring FactoryBean实现(PropertiesFactoryBean)来实例化java.util.Properties实例,其值从提供的Resource位置加载。 下面的实例使用了util:properties元素进行更简洁的表述:
Using util:list/
请看下面的例子:
下面的示例使用 util:list/ 元素进行了更简洁的表述:
您还可以通过使用 util:list/ 元素上的 list-class 属性,明确控制被实例化和填充的 List 的确切类型。例如,如果我们确实需要实例化 java.util.LinkedList ,我们可以使用以下配置:
如果没有提供 list-class 属性,容器将选择 List 实现。
Using util:map/
请看下面的例子:
前面的配置使用 Spring FactoryBean 实现( MapFactoryBean )创建了一个 java.util.Map 实例,该实例使用提供的 'sourceMap' 中的键值对进行初始化。 下面的示例使用 util:map/ 元素进行了更简洁的表述:
您还可以通过使用 util:map/ 元素上的 'map-class' 属性,明确控制被实例化和填充的 Map 的确切类型。例如,如果我们确实需要实例化 java.util.TreeMap ,我们可以使用以下配置:
如果没有提供 'map-class' 属性,容器将选择 Map 实现。
Using util:set/
请看下面的例子:
前面的配置使用 Spring FactoryBean 实现( SetFactoryBean )创建了一个 java.util.Set 实例,该实例使用提供的 sourceSet 中的值进行初始化。 下面的示例使用 util:set/ 元素进行了更简洁的表述:
您还可以通过使用 util:set/ 元素上的 set-class 属性,明确控制被实例化和填充的 Set 的确切类型。例如,如果我们确实需要实例化 java.util.TreeSet ,我们可以使用以下配置:
如果没有提供 set-class 属性,容器将选择 Set 实现。
10.1.2 The aop Schema
aop标记用于配置Spring中的所有AOP,包括Spring自带的基于代理的AOP框架和Spring与AspectJ AOP框架的集成。标题为“Spring的面向切面编程”的章节全面介绍了这些标记。 为完整起见,要使用aop模式中的标记,你需要在Spring XML配置文件的顶部添加以下前言(代码段中的文本引用了正确的模式,以便你可以使用aop命名空间中的标记):
10.1.3 The context Schema
context标记处理与管道相关的APplicationContext配置,也就是说,通常不是对最终用户很重要的Bean,而是在Spring中执行大量“粗活”的Bean,例如BeanFactoryPostProcessors。以下代码段引用了正确的模式,因此你可以使用context命名空间中的元素:
Using
该元素用于激活$占位符的替换,这些占位符将根据指定的属性文件(作为Spring资源位置)进行解析。该元素是一种便利机制,可为你设置PropertySourcePlaceholderConfigurer。如果你需要对特定的PropertySourcePlaceholderConfigurer设置进行更多控制,你可以自己显式地将其定义为一个Bean。
Using
该元素激活Spring基础框架,以检测Bean类中的注解:
@Autowired/@Inject,@Value和@Lookup
JSR-250的@Resource、@PostConstruct和@PreDestroy(如果有的话)
JAX_WS的@WebServiceRef和EJB3的@EJB(如果可用)
JPA的@PersistenceContext和@PersistenceUnit(如果可用)
Spring‘s @EventListener的@EventListener
或者,你也可以选择为这些注释显式激活单个BeanPostProcessor。
Using
该元素在基于注解的容器配置部分有详细介绍。
Using
该元素将在Spring框架中使用AspectJ进行加载时编织的章节中详细介绍。
Using
该元素在使用AspectJ对Spring的领域对象进行依赖注入的章节中有详细介绍。
Using
该元素在配置基于注解的MBean导出一节中有详细说明。
10.1.4 The Beans Schema
最后但并非最不重要的是beans模式中的元素。自Spring框架诞生之日起,这些元素就一直存在。这一不展示beans模式中各种元素的示例,因为依赖关系和配置中已经详细介绍了这些元素(实际上,整章都有介绍)。 请注意,你可以在 XML定义中添加零个或多个键值对。至于如何使用这些额外的元数据,完全取决于你自己的自定义逻辑(因此通常只有在你编写自己的自定义元素时才有用,如附录“XML模式编写”中所述)。 下面的示例显示了 元素在周围的 上下文中的情况(请注意,如果没有任何逻辑解释,元数据实际上是无用的)。
在前面的示例中,你可以假设有一些逻辑会消耗Bean定义,并设置一些使用所提供元数据的缓存基础架构。
10.2 XML模式编写
自2.0版起,Spring就提供了一种机制,可将基于模式的扩展添加到用于定义和配置Bean的基于Spring XML格式中。本节将介绍如何编写自己的自定义XMLBean 定义解析器,并将此类解析器集成到Spring IOC容器中。 为了便于使用模式感知XML编辑器编写配置文件,Spring的可扩展XML配置机制基于XML模式。如果你不熟悉Spring的标准发行版附带的Spring当前XML配置扩展,就先阅读前面有关XML模式的章节。 创建新的XML配置扩展:
创建XML模式来描述自定义元素。
编码自定义NamespaceHandler实现
编码一个或多个BeanDefinitionParser实现(这才是真正的工作)。
向Spring注册新的人工制品。
在一个统一的示例中,我们创建一个XML扩展(自定义XML元素),让我们可以配置SimpleDateFormat类型的对象(来自java.text包)。完成后,我们就可以定义SimpleDateFormat类型的Bean定义,如下所示:
(我们将在本附录的后面部分提供更详细的示例)。第一个简单示例的目的是指导你完成制作自定义扩展的基本步骤。
10.2.1 创建模式
创建与Spring的IOC容器配合使用的XML配置扩展首先要编写一个XML模式来描述扩展。在我们的示例中,我们使用以下模式来配置SimpleDateFormat对象:
如下面的示例所示,上述模式允许我们使用myns:dateformat/元素在XML应用程序上下文文件中直接陪吹SimpleDateFormat对象:
请注意,在我们创建了基础结构类之后,前面的XML代码段与下面的XML代码段基本相同:
前面两个代码段中的第二个代码段在容器中创建了一个bean(名称为dadteFormat,类型为SimpleDateFormat),并设置了几个属性。
10.2.2 对NamespaceHandler进行编码
出了模式之外,我们还需要NamespaceHandler来解析Spring在解析配置文件时遇到的该特定的命名空间的所有元素。在本例中,NamespaceHandler应负责解析myns: dateformat元素。 NamespaceHandler界面有三种方法:
init():允许对NamespaceHandler进行初始化,并在使用处理程序之前被Spring调用。
BeanDefinition parse(Element,ParserContext):当Spring遇到顶层元素(未嵌套在Bean定义或不同命名空间内)时调用。该方法本身可以注册Bean定义,也可以返回Bean定义,或两者兼而有之。
BeanDefinitionHolder decorate(Node,BeanDefinitionHolder,ParserContext): 当Spring遇到不同命名空间的属性或嵌元素时调用。一个或多个Bean定义的装饰(例如)用于Spring支持的作用域。首先,我们将重点介绍一个未使用装饰的简单示例,然后在一个更高级的示例中展示装饰。
虽然你可以为整个命名空间编写自己的NamespaceHandler(并因此提供解析命名空间中每个元素的代码),单通常情况下,Spring XML配置文件中的每个顶层XML元素都会导致单个Bean定义(就像我们的情况一样,单个myns:dateformat/元素会导致单个SimpleDateFormat Bean定义)。Spring提供了许多支持这种情况的便利类。在下面的示例中,我们使用了NamespaceHandlerSupport类:
你可能会注意到,该类中实际上并没有很多解析逻辑。事实上,NamespaceHandlerSupport类有一个内置的委托概念。它支持注册任意数量的BeanDefinitionParser示例,并在需要解析其命名空间中的元素时委托给这些实例。这种干净利落的分工使NamespaceHandler可以处理其命名空间中所有自定义元素的解析协调工作,同时委派BeanDefinitionParsers完成XML解析的繁重工作。这意味着每个BeanDefinitionParser只包含解析单个自定义元素的逻辑,我们可以在下一步中看到这一点。
10.2.3 使用BeanDefinitionParser
如果NamespaceHandler遇到已映射到特定Bean定义解析器(本例中为dateformat)的XMl元素类型,就会使用BeanDefinitionParser。换句话说,BeanDefinitionParser负责解析模式中定义的一个不同的顶级XML元素。在解析器中,我们可以访问XML元素(因此可以访问其子元素),这样就可以解析我们自定义的XML内容,如下例所示:
在这个简单的案例中,我们只需要做这些。单个BeanDefinition的创建由AbstractSingleBeanDefinitionParser超类处理,提取和设置bean定义的唯一标识符也是如此。
10.2.4 注册处理程序和模式
编码工作已经完成。剩下要做的就是让Spring XML解析基础架构知道我们的自定义元素。为此,我们需要在两个专用属性文件中注册自定义namespaceHandler和自定义XSD文件。这些属性文件都放置在应用程序的META-INF目录中,例如,可以与JAR文件中的二进制类一起发布。SpringXMl解析基础架构通过使用这些特殊属性文件自动获取新扩展,其格式将在接下来的两节中详细介绍。
Writing META-INF/spring.handlers
名为spring.handlers的属性文件包含XML Schema URI与命名空间处理程序类的映射。在我们的示例中,我们需要编写以下内容:
(在java属性格式中,:字符是一个有效的分隔符,因此URI中的:字符需要用反斜杠转义)。 键值对的第一部分(键)是与自定义命名空间扩展相关联的URI,需要与自定义XSD模式中指定的targetNamespace属性的值完全匹配。
Writing ’META-INF/spring.schemas‘
名为spring.schemas的属性文件包含XML模式位置(在使用模式作为xsi: schemaLocation属性一部分的XML文件中与模式声明一起被引用)到类路径资源的映射。需要使用该文件来防止Spring使用默认的EntityResolver来检索模式文件。如果在此属性文件中制定了映射,Spring就会在类路径中搜索模式(本利中为org.springframework.samples.xml包中的myns.xsd)。以下代码段显示了我们需要为自定义模式添加的行: http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(请记住,:字符必须转义) 我们鼓励你将XSD文件(或多个文件)与类路径上的NamespaceHandler和BeanDefinitionParser类一起部署。
10.2.5 在Spring XML配置中使用自定义扩展
使用自己实现的自定义扩展与使用Spring提供的“自定义”扩展并无不同。下面的示例在Spring XML配置文件中使用了前面步骤中开发的自定义 元素:
10.2.6 More Detailed Examples
本节将介绍一些更详细的自定义XML扩展实例。
Nesting Custom Elements within Custom Elements
本节提供的实例展示了如何编写满足一下配置的目标所需的各种工件:
上述配置将自定义扩展互相嵌套。 foo:component/ 元素实际配置的类是Component类(如下例所示)。请注意,Component类没有为components属性提供设置方法。这使得很难(或者说不可能)通过使用设置器注入为Component类配置bean定义。下面的列表显示了Component类:
解决这一问题的典型方法是创建一个自定义FactoryBean,为Components属性公开一个setter属性。下面的列表显示了这样一个自定义FactoryBean:
这样做的效果很好,但会向最终用户暴露大量Spring管线。我们要做的是编写一个自定义扩展,将所有Spring管道隐藏起来。如果按照前面描述的步骤,我们首先创建XSD模式来定义自定义标签的结构,如下表所示:
同样按照前面描述的流程,我们创建一个自定义NamespaceHandler文件:
接下来是自定义BeanDefinitionParser。请记住,我们正在创建一个描述ComponentFactoryBean的BeanDefinition。下面的列表显示了我们自定义BeanDefinitionParser的实现:
最后,需要修改META-INF/spring.handlers和META-INF/spring.schemas文件,在SpringXml基础架构中注册各种构件,具体如下:
Custom Attributes on “Normal” Elements
编写自己的自定义解析器和相关工件并不难。但是,有时这样做并不正确。例如,你需要为己有Bean定义添加元数据。在这种情况下线,你当然不希望编写自己整个自定义扩展。相反,你只想在现有的Bean定义元素中添加一个附加属性。 在举一个例子,假设你为一个服务对象定义了一个bean定义,该服务对象(自己也不知道)访问一个群集JCache,而你希望确保命名的JCache实例在周围的群集中急切启动。下面的列表显示了这样一个定义:
然后,我们可以解析'jcache: cache-name'属性时创建另一个BeanDefinition。然后,这个Bean将为我们初始化命名的JCache。我们还可以修改' checkingAccountService'的现有BeanDefinition,使其依赖于新的JCache初始化BeanDefinition。下面的列表显示了我们的JCacheInitializer:
现在我们可以开始自定义扩展了。首先,我们创建描述自定义属性的XSD模式,如下所示:
接下来,我们需要创建相关的NamespaceHandler,如下所示:
接下来,我们需要创建解析器。请注意,在本例中,由于我们要解析的是一个XML属性,所以我们写的BeanDefinitionDecorator而不是BeanDefinitionParser。下面的列表显示了我们的BeanDefinitionDecorator实现:
最后,我们需要通过修改META-INF/spring.handlers和META-INF/spring.schemas文件,将各种构件注册到Spring XML基础架构中,如下所示:
10.3 Application Startup Steps
附录的这一部分列出了核心容器使用的现有StartupSteps工具
name | description | tags |
---|---|---|
spring.beans.instantiate | 实例化bean及其依赖关系 | beanName Bean的名称,beanType注入点所需的类型 |
spring.beans.smart-initialize | 初始化SmartInitializingSingleton Bean | beanName Bean的名称 |
spring.context.annotated-bean-reader.create | 创建AnnotatedBeanDefinitionReader | |
spring.context.base-packages.scan | 扫描基础软件包 | packages用于扫描的基础软件包数组 |
spring.context.beans.post-process | bean后处理阶段 | |
spring.context.bean-factory.post-process | 调用BeanFactoryPostProcessor | postProcessor当前后处理器 |
spring.context.beandefregistry.post-process | 调用BeanDefinitionRegistryPostProcessor | postProcessor当前后处理器 |
spring.context.component-classes.register | 通过AnnotationConfigApplicationContext#register注册组件类 | class用于注册的给定类的数组。 |
spring.context.config-classes.enhance | 使用CGLIB代理增强配置类 | classCount增强类计数 |
spring.context.config-classes.parse | 配置类解析阶段使用ConfigurationClassPostProcessor | classCount |
spring.context.refresh | 应用程序上下文刷新阶段 |