核心技术
其中最重要的是Spring Framework的控制反转(IoC)容器。Spring框架的IoC容器的全面处理紧随其后,全面覆盖了Spring的面向方面编程(AOP)技术。Spring Framework有自己的AOP框架,它在概念上易于理解,并且成功地解决了Java企业编程中AOP要求的80%最佳点。
还提供了Spring与AspectJ集成的覆盖范围(目前最丰富的 - 在功能方面 - 当然也是Java企业领域中最成熟的AOP实现)。
1. IoC容器
本章介绍Spring的控制反转(IoC)容器。
1.1。Spring IoC容器和Bean简介
本章介绍了控制反转(IoC)原理的Spring Framework实现。(参见控制反转。)IoC也称为依赖注入(DI)。这是一个过程,通过这个过程,对象只能通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即,它们使用的其他对象)。 。然后容器在创建bean时注入这些依赖项。此过程基本上是bean本身的逆(因此名称,控制反转),通过使用类的直接构造或诸如服务定位器模式的机制来控制其依赖关系的实例化或位置。
在org.springframework.beans
和org.springframework.context
包是Spring框架的IoC容器的基础。该 BeanFactory
接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext
是一个子界面BeanFactory
。它补充说:
-
更容易与Spring的AOP功能集成
-
消息资源处理(用于国际化)
-
活动出版
-
特定
WebApplicationContext
于应用程序层的上下文,例如在Web应用程序中使用的上下文。
简而言之,它BeanFactory
提供了配置框架和基本功能,并ApplicationContext
添加了更多特定于企业的功能。它ApplicationContext
是完整的超集,BeanFactory
在本章中仅用于Spring的IoC容器的描述。有关使用BeanFactory
而不是ApplicationContext,
看到 的BeanFactory
更多信息。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化,组装和管理的对象。否则,bean只是应用程序中许多对象之一。Bean及其之间的依赖关系反映在容器使用的配置元数据中。
1.2。集装箱概览
该org.springframework.context.ApplicationContext
接口代表Spring IoC容器,负责实例化,配置和组装bean。容器通过读取配置元数据获取有关要实例化,配置和组装的对象的指令。配置元数据以XML,Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖性。
ApplicationContext
Spring提供了几种接口实现。在独立应用程序中,通常会创建一个ClassPathXmlApplicationContext
或的实例 FileSystemXmlApplicationContext
。虽然XML是定义配置元数据的传统格式,但您可以通过提供少量XML配置来声明容器使用Java注释或代码作为元数据格式,以声明方式启用对这些其他元数据格式的支持。
在大多数应用程序方案中,不需要显式用户代码来实例化Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序文件中的简单八行(左右)样板Web描述符XML web.xml
通常就足够了(请参阅Web应用程序的便捷ApplicationContext实例)。如果您使用 Spring Tool Suite(基于Eclipse的开发环境),只需点击几下鼠标或按键即可轻松创建此样板配置。
下图显示了Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,以便在ApplicationContext
创建和初始化之后,您拥有完全配置且可执行的系统或应用程序。
图1. Spring IoC容器
1.2.1。配置元数据
如上图所示,Spring IoC容器使用一种配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉Spring容器在应用程序中实例化,配置和组装对象。
传统上,配置元数据以简单直观的XML格式提供,本章大部分内容用于传达Spring IoC容器的关键概念和功能。
基于XML的元数据不是唯一允许的配置元数据形式。Spring IoC容器本身完全与实际编写此配置元数据的格式分离。目前,许多开发人员为其Spring应用程序选择 基于Java的配置。 | |
---|---|
有关在Spring容器中使用其他形式的元数据的信息,请参阅:
-
基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。
-
基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为核心Spring Framework的一部分。因此,您可以使用Java而不是XML文件在应用程序类外部定义bean。要使用这些新功能,请参阅
@Configuration
,@Bean
,@Import
,和@DependsOn
注释。
Spring配置包含容器必须管理的至少一个且通常不止一个bean定义。基于XML的配置元数据将这些bean配置为<bean/>
顶级元素内的<beans/>
元素。Java配置通常@Bean
在@Configuration
类中使用注释方法。
这些bean定义对应于构成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),表示对象(如Struts Action
实例),基础结构对象(如Hibernate SessionFactories
,JMS Queues
等)。通常,不会在容器中配置细粒度域对象,因为DAO和业务逻辑通常负责创建和加载域对象。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。请参阅使用AspectJ使用Spring依赖注入域对象。
以下示例显示了基于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 -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
该id 属性是一个标识单个bean定义的字符串。 | |
---|---|
该class 属性定义bean的类型并使用完全限定的类名。 |
id
属性的值指的是协作对象。在此示例中未显示用于引用协作对象的XML。有关更多信息,请参阅 依赖项。
1.2.2。实例化容器
提供给ApplicationContext
构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统,Java等)加载配置元数据CLASSPATH
。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了Spring的IoC容器之后,您可能想要了解有关Spring Resource 抽象的更多信息 (如参考资料中所述),它提供了一种从URI语法中定义的位置读取InputStream的便捷机制。特别是, Resource 路径用于构建应用程序上下文,如应用程序上下文和资源路径中所述。 | |
---|---|
以下示例显示了服务层对象(services.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">
<!-- 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>
以下示例显示了数据访问对象daos.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="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对象关系映射标准)。该property name
元素是指JavaBean属性的名称,以及ref
元素指的是另一个bean定义的名称。元素id
和ref
元素之间的这种联系表达了协作对象之间的依赖关系。有关配置对象的依赖关系的详细信息,请参阅 依赖关系。
编写基于XML的配置元数据
让bean定义跨越多个XML文件会很有用。通常,每个单独的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>
在前面的例子中,外部豆定义是从三个文件加载: services.xml
,messageSource.xml
,和themeSource.xml
。所有位置路径都与执行导入的定义文件相关,因此services.xml
必须与执行导入的文件位于相同的目录或类路径位置, messageSource.xml
而且themeSource.xml
必须位于resources
导入文件位置下方的位置。如您所见,忽略前导斜杠。但是,鉴于这些路径是相对的,最好不要使用斜杠。<beans/>
根据Spring Schema,正在导入的文件的内容(包括顶级元素)必须是有效的XML bean定义。
可以(但不建议)使用相对“../”路径引用父目录中的文件。这样做会对当前应用程序之外的文件创建依赖关系。特别是,不建议对classpath: URL(例如,classpath:../services.xml )使用此引用,其中运行时解析过程选择“最近的”类路径根,然后查看其父目录。类路径配置更改可能导致选择不同的,不正确的目录。您始终可以使用完全限定的资源位置而不是相对路径:例如,file:C:/config/services.xml 或classpath:/config/services.xml 。但是,请注意您将应用程序的配置与特定的绝对位置耦合。通常最好为这些绝对位置保持间接 - 例如,通过在运行时针对JVM系统属性解析的“$ {...}”占位符。 | |
---|---|
命名空间本身提供了import指令功能。Spring提供的一系列XML命名空间中提供了除普通bean定义之外的其他配置功能 - 例如,context
和util
名称空间。
Groovy Bean定义DSL
作为外化配置元数据的另一个示例,bean定义也可以在Spring的Groovy Bean定义DSL中表示,如Grails框架中所知。通常,此类配置位于“.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 bean定义,甚至支持Spring的XML配置命名空间。它还允许通过importBeans
指令导入XML bean定义文件。
1.2.3。使用容器
它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配置,bootstrapping看起来非常相似。它有一个不同的上下文实现类,它是Groovy-aware(但也理解XML bean定义)。以下示例显示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体GenericApplicationContext
与读者委托相结合 - 例如,XmlBeanDefinitionReader
对于XML文件,如以下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
您还可以使用GroovyBeanDefinitionReader
for Groovy文件,如以下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
您可以ApplicationContext
在不同的配置源中读取和匹配此类读取器委托,读取bean定义。
然后,您可以使用它getBean
来检索Bean的实例。该ApplicationContext
接口还有一些其他方法可以检索bean,但理想情况下,您的应用程序代码绝不应该使用它们。实际上,您的应用程序代码根本不应该调用该 getBean()
方法,因此根本不依赖于Spring API。例如,Spring与Web框架的集成为各种Web框架组件(如控制器和JSF托管bean)提供依赖注入,允许您通过元数据(例如自动装配注释)声明对特定bean的依赖性。
1.3。Bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>
定义的形式 )。
在容器本身内,这些bean定义表示为BeanDefinition
对象,其中包含(以及其他信息)以下元数据:
-
包限定的类名:通常是正在定义的bean的实际实现类。
-
Bean行为配置元素,说明bean在容器中的行为方式(范围,生命周期回调等)。
-
引用bean执行其工作所需的其他bean。这些引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置 - 例如,池的大小限制或在管理连接池的Bean中使用的连接数。
此元数据转换为构成每个bean定义的一组属性。下表描述了这些属性:
属性 | 解释在...... |
---|---|
类 | 实例化豆类 |
名称 | 命名豆 |
范围 | Bean范围 |
构造函数参数 | 依赖注入 |
属性 | 依赖注入 |
自动装配模式 | 自动化协作者 |
延迟初始化模式 | 懒惰初始化的豆类 |
初始化方法 | 初始化回调 |
破坏方法 | 毁灭回调 |
除了包含有关如何创建特定bean的信息的bean定义之外,这些ApplicationContext
实现还允许注册在容器外部(由用户)创建的现有对象。这是通过方法访问ApplicationContext的BeanFactory来完成的getBeanFactory()
,该方法返回BeanFactory DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过registerSingleton(..)
和registerBeanDefinition(..)
方法支持此注册。但是,典型的应用程序仅使用通过常规bean定义元数据定义的bean。
需要尽早注册Bean元数据和手动提供的单例实例,以便容器在自动装配和其他内省步骤期间正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但是在运行时注册新bean(与对工厂的实时访问同时)并未得到官方支持,并且可能导致并发访问异常,bean容器中的状态不一致,或者都。 | |
---|---|
1.3.1。命名豆
每个bean都有一个或多个标识符。这些标识符在托管bean的容器中必须是唯一的。bean通常只有一个标识符。但是,如果它需要多个,则额外的可以被视为别名。
在基于XML的配置元数据中,您可以使用id
属性,name
属性或两者来指定bean标识符。该id
属性允许您指定一个id。通常,这些名称是字母数字('myBean','someService'等),但它们也可以包含特殊字符。如果要为bean引入其他别名,还可以在name
属性中指定它们,用逗号(,
),分号(;
)或空格分隔。作为历史记录,在Spring 3.1之前的版本中,该id
属性被定义为一种xsd:ID
类型,它约束了可能的字符。从3.1开始,它被定义为一种xsd:string
类型。请注意,id
容器仍然强制实施bean 唯一性,但不再是XML解析器。
您不需要提供bean name
或id
bean。如果您不提供 name
或id
显式提供,则容器会为该bean生成唯一的名称。但是,如果要按名称引用该bean,则通过使用ref
元素或 Service Locator样式查找,必须提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关。
Bean命名约定
惯例是在命名bean时使用标准Java约定作为实例字段名称。也就是说,bean名称以小写字母开头,并从那里开始驼峰。这样的名字的例子包括accountManager
, accountService
,userDao
,loginController
,等等。
命名bean始终使您的配置更易于阅读和理解。此外,如果您使用Spring AOP,那么在将建议应用于与名称相关的一组bean时,它会有很大帮助。
通过类路径中的组件扫描,Spring按照前面描述的规则为未命名的组件生成bean名称:实质上,采用简单的类名并将其初始字符转换为小写。但是,在(不常见的)特殊情况下,当有多个字符且第一个和第二个字符都是大写字母时,原始外壳将被保留。这些规则与java.beans.Introspector.decapitalize (Spring在此处使用)定义的规则相同。 | |
---|---|
在Bean定义之外别名Bean
在bean定义本身中,您可以为bean提供多个名称,方法是使用id
属性指定的最多一个名称和属性中的任意数量的其他名称name
。这些名称可以是同一个bean的等效别名,对某些情况很有用,例如让应用程序中的每个组件通过使用特定于该组件本身的bean名称来引用公共依赖项。
但是,指定实际定义bean的所有别名并不总是足够的。有时需要为其他地方定义的bean引入别名。在大型系统中通常就是这种情况,其中配置在每个子系统之间分配,每个子系统具有其自己的一组对象定义。在基于XML的配置元数据中,您可以使用该<alias/>
元素来完成此任务。以下示例显示了如何执行此操作:
<alias name="fromName" alias="toName"/>
在这种情况下,fromName
在使用此别名定义之后,命名的bean(在同一容器中)也可以称为toName
。
例如,子系统A的配置元数据可以通过名称引用DataSource subsystemA-dataSource
。子系统B的配置元数据可以通过名称引用DataSource subsystemB-dataSource
。在编写同时使用这两个子系统的主应用程序时,主应用程序通过名称引用DataSource myApp-dataSource
。要使所有三个名称引用同一对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个唯一的名称引用dataSource,并保证不与任何其他定义冲突(有效地创建命名空间),但它们引用相同的bean。
Java的配置
如果使用Javaconfiguration,则@Bean
可以使用注释来提供别名。有关详细信息,请参阅使用@Bean
注释。
1.3.2。实例化豆类
bean定义本质上是用于创建一个或多个对象的配方。容器在被询问时查看命名bean的配方,并使用由该bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于XML的配置元数据,则指定要在元素的class
属性中实例化的对象的类型(或类)<bean/>
。此 class
属性(在内部,是实例Class
上的属性BeanDefinition
)通常是必需的。(有关例外,请参阅 使用实例工厂方法和Bean定义继承进行实例化。)您可以通过以下Class
两种方式之一使用该属性:
-
通常,在容器本身通过反向调用其构造函数直接创建bean的情况下指定要构造的bean类,稍微等同于使用
new
运算符的Java代码。 -
要指定包含
static
为创建对象而调用的工厂方法的实际类,在不太常见的情况下,容器static
在类上调用 工厂方法来创建bean。从调用static
工厂方法返回的对象类型可以完全是同一个类或另一个类。
内部类名
如果要为static
嵌套类配置bean定义,则必须使用嵌套类的二进制名称。
例如,如果您SomeThing
在com.example
包中调用了一个类,并且此类 SomeThing
具有一个static
被调用的嵌套类OtherThing
,则class
bean定义中的属性值将为com.example.SomeThing$OtherThing
。
请注意,使用$
名称中的字符将嵌套类名与外部类名分开。
使用构造函数实例化
当您通过构造方法创建bean时,所有普通类都可以使用并与Spring兼容。也就是说,正在开发的类不需要实现任何特定接口或以特定方式编码。简单地指定bean类就足够了。但是,根据您为该特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的JavaBeans。大多数Spring用户更喜欢实际的JavaBeans,只有一个默认(无参数)构造函数,并且在容器中的属性之后建模了适当的setter和getter。您还可以在容器中拥有更多异国情调的非bean样式类。例如,如果您需要使用绝对不符合JavaBean规范的旧连接池,那么Spring也可以对其进行管理。
使用基于XML的配置元数据,您可以按如下方式指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关为构造函数提供参数的机制(如果需要)以及在构造对象后设置对象实例属性的详细信息,请参阅 注入依赖项。
使用静态工厂方法实例化
定义使用静态工厂方法创建的bean时,请使用该class
属性指定包含static
工厂方法的类,并使用factory-method
名称的属性指定工厂方法本身的名称。您应该能够调用此方法(使用可选参数,如稍后所述)并返回一个活动对象,随后将其视为通过构造函数创建的对象。这种bean定义的一个用途是static
在遗留代码中调用工厂。
以下bean定义指定通过调用工厂方法来创建bean。该定义未指定返回对象的类型(类),仅指定包含工厂方法的类。在此示例中,该createInstance()
方法必须是静态方法。以下示例显示如何指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例显示了一个可以使用前面的bean定义的类:
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
属性设置工厂方法本身的名称。以下示例显示如何配置此类bean:
<!-- 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文档中,“工厂bean”是指在Spring容器中配置并通过实例或 静态工厂方法创建对象的bean 。相比之下,FactoryBean (注意大写)指的是特定于Spring的 FactoryBean 。 | |
---|---|
1.4。依赖
典型的企业应用程序不包含单个对象(或Spring用法中的bean)。即使是最简单的应用程序也有一些对象可以协同工作,以呈现最终用户所看到的连贯应用程序。下一节将介绍如何定义多个独立的bean定义,以及对象协作实现目标的完全实现的应用程序。
1.4.1。依赖注入
依赖注入(DI)是一个过程,通过这个过程,对象只能通过构造函数参数,工厂方法的参数或在构造对象实例后在对象实例上设置的属性来定义它们的依赖关系(即,它们使用的其他对象)。从工厂方法返回。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的反向(因此名称,控制反转),它通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。
使用DI原则的代码更清晰,当对象提供其依赖项时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI存在两个主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入
基于构造函数的DI由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。调用static
具有特定参数的工厂方法来构造bean几乎是等效的,本讨论同样处理构造函数和static
工厂方法的参数。以下示例显示了一个只能通过构造函数注入进行依赖注入的类:
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>
请记住,为了使这项工作开箱即用,必须在启用调试标志的情况下编译代码,以便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的依赖注入
在调用无参数构造函数或无参数static
工厂方法来实例化bean之后,基于setter的DI由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
支持它管理的bean的基于构造函数和基于setter的DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以a的形式配置依赖项,并将BeanDefinition
其与PropertyEditor
实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户不直接与这些类(即,编程),而是用XML bean
定义注释的组件(也就是带注释类@Component
, @Controller
等),或者@Bean
方法在基于Java的@Configuration
类。然后,这些源在内部转换为实例BeanDefinition
并用于加载整个Spring IoC容器实例。
基于构造函数或基于setter的DI?
由于您可以混合基于构造函数和基于setter的DI,因此将构造函数用于强制依赖项和setter方法或可选依赖项的配置方法是一个很好的经验法则。请注意, 在setter方法上使用@Required注释可用于使属性成为必需的依赖项; 但是,最好使用编程验证参数的构造函数注入。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null
。此外,构造函数注入的组件始终以完全初始化的状态返回到客户端(调用)代码。作为旁注,大量的构造函数参数是一个糟糕的代码气味,暗示该类可能有太多的责任,应该重构以更好地解决关注点的正确分离。
Setter注入应主要仅用于可在类中指定合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何位置执行非空检查。setter注入的一个好处是setter方法使该类的对象可以在以后重新配置或重新注入。因此,通过JMX MBean进行管理是二次注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源的第三方类时,会选择您。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是唯一可用的DI形式。
依赖性解决过程
容器执行bean依赖性解析,如下所示:
-
使用
ApplicationContext
描述所有bean的配置元数据创建和初始化。配置元数据可以由XML,Java代码或注释指定。 -
对于每个bean,它的依赖关系以属性,构造函数参数或static-factory方法的参数的形式表示(如果使用它而不是普通的构造函数)。实际创建bean时,会将这些依赖项提供给bean。
-
每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
-
作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够转换成字符串格式提供给所有内置类型的值,例如
int
,long
,String
,boolean
,等等。
Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。创建容器时会创建单例作用域并设置为预先实例化(默认值)的Bean。范围在Bean范围中定义。否则,仅在请求时才创建bean。创建bean可能会导致创建bean的图形,因为bean的依赖关系及其依赖关系(依此类推)被创建和分配。请注意,这些依赖项之间的解决方案不匹配可能会显示较晚 - 也就是说,首次创建受影响的bean时。
循环依赖
如果您主要使用构造函数注入,则可以创建无法解析的循环依赖关系场景。
例如:类A通过构造函数注入需要类B的实例,而类B通过构造函数注入需要类A的实例。如果将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并抛出a BeanCurrentlyInCreationException
。
一种可能的解决方案是编辑由setter而不是构造函数配置的某些类的源代码。或者,避免构造函数注入并仅使用setter注入。换句话说,尽管不推荐使用,但您可以使用setter注入配置循环依赖项。
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖强制其中一个bean在完全初始化之前被注入另一个bean(一个经典的鸡与鸡蛋场景)。
你通常可以相信Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的bean和循环依赖关系的引用。当实际创建bean时,Spring会尽可能晚地设置属性并解析依赖关系。这意味着,如果在创建该对象或其中一个依赖项时出现问题,则在请求对象时,正确加载的Spring容器可以在以后生成异常 - 例如,bean因缺失或无效而抛出异常属性。这可能会延迟一些配置问题的可见性ApplicationContext
默认情况下实现预实例化单例bean。以实际需要之前创建这些bean的一些前期时间和内存为代价,您ApplicationContext
会在创建时发现配置问题,而不是更晚。您仍然可以覆盖此默认行为,以便单例bean可以懒惰地初始化,而不是预先实例化。
如果不存在循环依赖关系,当一个或多个协作bean被注入依赖bean时,每个协作bean在被注入依赖bean之前完全配置。这意味着,如果bean A依赖于bean B,则Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果它不是预先实例化的单例),设置其依赖项,并调用相关的生命周期方法(如配置的init方法 或InitializingBean回调方法)。
依赖注入的示例
以下示例将基于XML的配置元数据用于基于setter的DI。Spring XML配置文件的一小部分指定了一些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文件中指定的属性匹配。以下示例使用基于构造函数的DI:
<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
。
现在考虑这个例子的变体,其中,不是使用构造函数,而是告诉Spring调用static
工厂方法来返回对象的实例:
<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; } }
static
工厂方法的参数由<constructor-arg/>
元素提供,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含static
工厂方法的类相同(尽管在本例中,它是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean
属性而不是class
属性),因此我们不在此讨论这些细节。
1.4.2。详细信息的依赖关系和配置
如上一节所述,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者作为内联定义的值。Spring的基于XML的配置元数据为此目的支持其元素<property/>
和<constructor-arg/>
元素中的子元素类型。
直值(基元,字符串等)
在value
所述的属性<property/>
元素指定属性或构造器参数的人类可读的字符串表示。Spring的 转换服务用于将这些值从a转换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容器通过使用JavaBeans 机制将<value/>
元素内的文本转换为 java.util.Properties
实例PropertyEditor
。这是一个很好的快捷方式,也是Spring团队支持<value/>
在value
属性样式上使用嵌套元素的少数几个地方之一。
该idref
元素
该idref
元素只是一种防错方法,可以将id
容器中另一个bean 的(字符串值 - 而不是引用)传递给<constructor-arg/>
or或<property/>
element。以下示例显示了如何使用它:
<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 的targetName
属性的值执行验证client
。只有在client
实际实例化bean 时才会发现错别字(很可能是致命的结果)。如果client
bean是原型 bean,则只能在部署容器后很长时间才能发现此错误和产生的异常。
4.0 beans XSD不再支持local 该idref 元素 的属性,因为它不再提供常规bean 引用的值。升级到4.0架构时,将现有idref local 引用更改idref bean 为。 | |
---|---|
其中一个共同的地方(至少在早期比Spring 2.0版本)<idref/>
元素带来的值在配置AOP拦截在 ProxyFactoryBean
bean定义。<idref/>
指定拦截器名称时使用元素可防止拼写错误的拦截器ID。
参考其他豆类(合作者)
所述ref
元件是内部的最终元件<constructor-arg/>
或<property/>
定义元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。引用的bean是要设置其属性的bean的依赖项,并且在设置该属性之前根据需要对其进行初始化。(如果协作者是单例bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。划定范围和有效性取决于是否通过指定其他对象的ID或名称bean
,local,
或parent
属性。
通过标记的bean
属性指定目标bean <ref/>
是最常用的形式,并允许创建对同一容器或父容器中的任何bean的引用,而不管它是否在同一XML文件中。bean
属性的值 可以id
与目标bean 的属性相同,或者与目标bean的name
属性中的值之一相同。以下示例显示如何使用ref
元素:
<ref bean="someBean"/>
通过该parent
属性指定目标bean 会创建对当前容器的父容器中的bean的引用。parent
属性的值可以id
与目标bean 的属性或目标bean的name
属性中的值之一相同。目标bean必须位于当前bean的父容器中。您应该使用此bean引用变体,主要是当您有容器层次结构并且希望将现有bean包装在父容器中时,该容器具有与父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不再支持local 该ref 元素 的属性,因为它不再提供常规bean 引用的值。升级到4.0架构时,将现有ref local 引用更改ref bean 为。 | |
---|---|
内豆
甲<bean/>
内部的元件<property/>
或<constructor-arg/>
元件限定内部豆,如下面的示例所示:
<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或名称。如果指定,则容器不使用此类值作为标识符。容器还会scope
在创建时忽略标志,因为内部bean始终是匿名的,并且始终使用外部bean创建。不可能独立访问内部bean或将它们注入协作bean而不是封闭bean。
作为一个极端情况,可以从自定义范围接收销毁回调 - 例如,对于包含在单例bean中的请求范围内部bean。内部bean实例的创建与其包含bean相关联,但是销毁回调允许它参与请求范围的生命周期。这不是常见的情况。内部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>
映射键或值的值或设置值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
合并合并
Spring容器还支持合并集合。应用程序开发人员可以定义父<list/>
,<map/>
,<set/>
或<props/>
元素,并有孩子<list/>
,<map/>
,<set/>
或<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
上属性<props/>
的元素 adminEmails
的财产child
bean定义。当child
容器解析并实例化bean时,生成的实例有一个adminEmails
Properties
集合,其中包含将子集合adminEmails
与父adminEmails
集合合并的结果 。以下清单显示了结果:
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
孩子Properties
集合的值设置继承父所有属性元素<props/>
,和孩子的为值support
值将覆盖父集合的价值。
这一合并行为同样适用于<list/>
,<map/>
和<set/>
集合类型。在<list/>
元素的特定情况下,保持与List
集合类型(即,ordered
值集合的概念)相关联的语义。父级的值位于所有子级列表的值之前。在的情况下Map
,Set
和Properties
集合类型,没有顺序存在。因此,没有排序的语义在背后的关联的集合类型的效果Map
,Set
以及Properties
该容器内部使用实现类型。
收集合并的局限性
您无法合并不同的集合类型(例如a Map
和a List
)。如果您尝试这样做,Exception
则会引发相应的操作。merge
必须在较低的继承子定义上指定该属性。merge
在父集合定义上指定属性是多余的,并且不会导致所需的合并。
强烈的收藏品
通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一种Collection
类型,使得它只能包含(例如)String
元素。如果使用Spring将强类型依赖注入Collection
到bean中,则可以利用Spring的类型转换支持,以便强类型Collection
实例的元素在添加到之前转换为适当的类型Collection
。以下Java类和bean定义显示了如何执行此操作:
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>
当为注入准备bean 的accounts
属性时,通过反射可获得something
关于强类型的元素类型的泛型信息Map<String, Float>
。因此,Spring的类型转换基础结构将各种值元素识别为类型Float
,并将字符串值(9.99, 2.75
,和 3.99
)转换为实际Float
类型。
空字符串值和空字符串值
Spring将属性等的空参数视为空Strings
。以下基于XML的配置元数据片段将email
属性设置为空 String
值(“”)。
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
上面的示例等效于以下Java代码:
exampleBean.setEmail("");
该<null/>
元素处理null
值。以下清单显示了一个示例:
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
上述配置等同于以下Java代码:
exampleBean.setEmail(null);
带有p命名空间的XML快捷方式
p-namespace允许您使用bean
元素的属性(而不是嵌套 <property/>
元素)来描述属性值协作bean,或两者。
Spring支持具有命名空间的可扩展配置格式,这些命名空间基于XML Schema定义。beans
本章中讨论的配置格式在XML Schema文档中定义。但是,p-namespace未在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>
该示例显示email
了bean定义中调用的p命名空间中的属性。这告诉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"/>
创建从bean john
到bean 的引用 jane
,而第二个bean定义p:spouse-ref="jane"
用作属性来执行完全相同的操作。在这种情况下,spouse
是属性名称,而该-ref
部分表示这不是直接值,而是对另一个bean的引用。
p命名空间不如标准XML格式灵活。例如,声明属性引用的格式与最终的属性冲突Ref ,而标准XML格式则不然。我们建议您仔细选择您的方法并将其传达给您的团队成员,以避免生成同时使用所有三种方法的XML文档。 | |
---|---|
带有c命名空间的XML快捷方式
与带有p-namespace的XML Shortcut类似,Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套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
的bean引用),供他们的名字设置构造函数的参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心内部)。
对于构造函数参数名称不可用的罕见情况(通常在没有调试信息的情况下编译字节码),您可以使用回退到参数索引,如下所示:
<!-- 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定义:
<bean id="something" class="things.ThingOne"> <property name="fred.bob.sammy" value="123" /> </bean>
该something
bean具有一个fred
属性,该属性具有属性,该bob
属性具有sammy
属性,并且最终sammy
属性的值设置为123
。为了使其工作,在构造bean之后,fred
属性something
和bob
属性fred
不得为null
。否则,NullPointerException
抛出一个。
1.4.3。运用depends-on
如果bean是另一个bean的依赖项,那通常意味着将一个bean设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的`` 元素来完成此操作。但是,有时bean之间的依赖关系不那么直接。例如,需要触发类中的静态初始化程序,例如数据库驱动程序注册。depends-on
在初始化使用此元素的bean之前,该属性可以显式强制初始化一个或多个bean。以下示例使用该depends-on
属性表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖关系,请提供bean名称列表作为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 的关系的从属bean 。这样,depends-on 也可以控制关机顺序。 | |
---|---|
1.4.4。懒惰初始化的豆类
默认情况下,ApplicationContext
实现会急切地创建和配置所有 单例 bean,作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后。当不希望出现这种情况时,可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,此行为由 元素lazy-init
上的属性控制<bean/>
,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被a消耗时ApplicationContext
,lazy
bean在ApplicationContext
启动时不会急切地预先实例化,而not.lazy
bean被急切地预先实例化。
但是,当延迟初始化的bean是未进行延迟初始化的单例bean的依赖项时,ApplicationContext
会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。惰性初始化的bean被注入到其他地方的单独的bean中,而这个bean并不是惰性初始化的。
您还可以通过使用元素default-lazy-init
上的属性来控制容器级别的延迟初始化, <beans/>
以下示例显示:
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
1.4.5。自动化协作者
Spring容器可以自动连接协作bean之间的关系。您可以让Spring通过检查bean的内容自动为您的bean解析协作者(其他bean)ApplicationContext
。自动装配具有以下优点:
-
自动装配可以显着减少指定属性或构造函数参数的需要。(在本章其他地方讨论的其他机制,如bean模板 ,在这方面也很有价值。)
-
自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发期间尤其有用,而不会在代码库变得更稳定时否定切换到显式布线的选项。
使用基于XML的配置元数据(请参阅依赖注入)时,可以使用元素的autowire
属性为 bean定义指定autowire模式<bean/>
。自动装配功能有四种模式。您指定每个bean的自动装配,因此可以选择要自动装配的那些。下表描述了四种自动装配模式:
模式 | 说明 |
---|---|
no | (默认)无自动装配。Bean引用必须由ref 元素定义。不建议对较大的部署更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 按属性名称自动装配。Spring查找与需要自动装配的属性同名的bean。例如,如果bean定义按名称设置为autowire并且它包含一个master 属性(即,它有一个 setMaster(..) 方法),则Spring会查找名为bean的定义master 并使用它来设置属性。 |
byType | 如果容器中只存在一个属性类型的bean,则允许属性自动装配。如果存在多个,则抛出致命异常,这表示您可能不会byType 对该bean 使用自动装配。如果没有匹配的bean,则不会发生任何事情(该属性未设置)。 |
constructor | 类似byType 但适用于构造函数参数。如果容器中没有构造函数参数类型的一个bean,则会引发致命错误。 |
使用byType
或constructor
自动装配模式,您可以连接阵列和键入的集合。在这种情况下,提供容器内与预期类型匹配的所有autowire候选者以满足依赖性。Map
如果预期的键类型是,则可以自动装配强类型实例String
。自动装配Map
实例的值由与预期类型匹配的所有bean实例组成, Map
实例的键包含相应的bean名称。
自动装配的局限和缺点
当在整个项目中一致地使用自动装配时,自动装配效果最佳。如果一般不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会让人感到困惑。
考虑自动装配的局限和缺点:
-
显式依赖项
property
和constructor-arg
设置始终覆盖自动装配。您不能自动装配简单属性,例如基元Strings
,和Classes
(以及此类简单属性的数组)。这种限制是按设计的。 -
自动装配不如显式布线精确。虽然,如前面的表中所述,Spring谨慎地避免在可能产生意外结果的模糊性的情况下进行猜测。您不再明确记录Spring管理对象之间的关系。
-
可能无法为可能从Spring容器生成文档的工具提供连线信息。
-
容器中的多个bean定义可以匹配setter方法或构造函数参数指定的类型以进行自动装配。对于数组,集合或
Map
实例,这不一定是个问题。但是,对于期望单个值的依赖关系,这种模糊性不是任意解决的。如果没有可用的唯一bean定义,则抛出异常。
在后一种情况下,您有几种选择:
-
放弃自动装配,支持显式布线。
-
通过将其
autowire-candidate
属性设置为bean,可以避免对bean定义进行自动装配false
,如下一节所述。 -
通过将
primary
其<bean/>
元素的属性设置为,将单个bean定义指定为主要候选者true
。 -
实现基于注释的配置可用的更细粒度的控件,如基于注释的容器配置中所述。
从自动装配中排除Bean
在每个bean的基础上,您可以从自动装配中排除bean。在Spring的XML格式中,将元素的autowire-candidate
属性设置<bean/>
为false
。容器使特定的bean定义对自动装配基础结构不可用(包括注释样式配置等@Autowired
)。
该autowire-candidate 属性旨在仅影响基于类型的自动装配。它不会影响名称的显式引用,即使指定的bean未标记为autowire候选,也会解析它。因此,如果名称匹配,则按名称自动装配会注入bean。 | |
---|---|
您还可以根据与bean名称的模式匹配来限制autowire候选者。顶级<beans/>
元素在其default-autowire-candidates
属性中接受一个或多个模式 。例如,要将autowire候选状态限制为名称以其结尾的任何bean Repository
,请提供值*Repository
。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的属性的显式值 true
或优先级始终优先。对于此类bean,模式匹配规则不适用。false``autowire-candidate
这些技术对于您永远不希望通过自动装配注入其他bean的bean非常有用。这并不意味着排除的bean本身不能使用自动装配进行配置。相反,bean本身不是自动装配其他bean的候选者。
1.4.6。方法注入
在大多数应用程序场景中,容器中的大多数bean都是 单例。当单例bean需要与另一个单例bean协作或非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。每次需要时,容器都不能为bean A提供bean B的新实例。
解决方案是放弃一些控制反转。你可以做一个豆意识到容器通过实现ApplicationContextAware
接口,并通过制作getBean("B")
到容器调用请求(典型新)bean B实例的实例每次豆A需要它。以下示例显示了此方法:
// 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 Framework。方法注入是Spring IoC容器的一个高级功能,可以让您干净地处理这个用例。
您可以在此博客条目中阅读有关方法注入动机的更多信息 。
查找方法注入
Lookup方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及原型bean,如上一节中描述的场景。Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。
要使这个动态子类工作,Spring 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(); }
在包含要注入的方法的客户端类中(CommandManager
在本例中),要注入的方法需要以下形式的签名:
<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>
只要需要bean 的新实例,标识为bean的bean 就会commandManager
调用自己的createCommand()
方法myCommand
。myCommand
如果实际需要,您必须小心将bean 部署为原型。如果它是单例,myCommand
则每次返回相同的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:
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类。
访问不同范围的目标bean的另一种方法是ObjectFactory / Provider 注入点。请参阅Scoped Beans作为依赖关系。您可能还会发现ServiceLocatorFactoryBean (在 org.springframework.beans.factory.config 包中)有用。 | |
---|---|
任意方法替换
与查找方法注入相比,一种不太有用的方法注入形式是能够使用另一个方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您确实需要此功能。
使用基于XML的配置元数据,您可以使用该replaced-method
元素将已存在的方法实现替换为已部署的bean。考虑以下类,它有一个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"/>
您可以使用<arg-type/>
元素中的一个或多个元素<replaced-method/>
来指示被覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String
:
java.lang.String String Str
因为参数的数量通常足以区分每个可能的选择,所以通过让您只键入与参数类型匹配的最短字符串,此快捷方式可以节省大量的输入。
1.5。Bean范围
创建bean定义时,可以创建用于创建由该bean定义定义的类的实际实例的配方。bean定义是一个配方的想法很重要,因为它意味着,与一个类一样,您可以从一个配方创建许多对象实例。
您不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别烘焙对象的范围。可以将Bean定义为部署在多个范围之一中。Spring Framework支持六个范围,其中四个范围仅在您使用Web感知时才可用ApplicationContext
。您还可以创建 自定义范围。
下表描述了支持的范围:
范围 | 描述 |
---|---|
独生子 | (默认)将单个bean定义范围限定为每个Spring IoC容器的单个对象实例。 |
原型 | 将单个bean定义范围限定为任意数量的对象实例。 |
请求 | 将单个bean定义范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在具有Web感知功能的Spring环境中有效ApplicationContext 。 |
会议 | 将单个bean定义范围限定为HTTP的生命周期Session 。仅在具有Web感知功能的Spring环境中有效ApplicationContext 。 |
应用 | 将单个bean定义范围限定为a的生命周期ServletContext 。仅在具有Web感知功能的Spring环境中有效ApplicationContext 。 |
的WebSocket | 将单个bean定义范围限定为a的生命周期WebSocket 。仅在具有Web感知功能的Spring环境中有效ApplicationContext 。 |
从Spring 3.0开始,线程范围可用,但默认情况下未注册:请参阅SimpleThreadScope 。从Spring 4.2开始,交易范围也可用: SimpleTransactionScope 。有关如何注册这些或任何其他自定义作用域的说明,请参阅 使用自定义作用域。 | |
---|---|
1.5.1。单身范围
只管理单个bean的一个共享实例,并且对具有与该bean定义匹配的ID或ID的bean的所有请求都会导致Spring容器返回一个特定的bean实例。
换句话说,当您定义bean定义并将其作为单一作用域时,Spring IoC容器只创建该bean定义定义的对象的一个实例。此单个实例存储在此类单例bean的缓存中,并且该命名Bean的所有后续请求和引用都将返回缓存对象。下图显示了单例范围的工作原理:
Spring的单例bean概念不同于Gang of Four(GoF)模式书中定义的单例模式。GoF单例对一个对象的范围进行硬编码,使得每个ClassLoader创建一个且只有一个特定类的实例。Spring单例的范围最好描述为每容器和每个bean。这意味着,如果在单个Spring容器中为特定类定义一个bean,则Spring容器将创建该bean定义所定义的类的一个且仅一个实例。单例范围是Spring中的默认范围。要将bean定义为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"/>
1.5.2。原型范围
bean部署的非单例原型范围导致每次发出对该特定bean的请求时都创建新的bean实例。也就是说,bean被注入另一个bean,或者通过getBean()
对容器的方法调用来请求它。通常,您应该对所有有状态bean使用原型范围,对无状态bean使用单例范围。
下图说明了Spring原型范围:
(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不会保持任何会话状态。我们更容易重用单例图的核心。)
以下示例将bean定义为XML中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他范围相比,Spring不管理原型bean的完整生命周期。容器实例化,配置和组装原型对象并将其交给客户端,而没有该原型实例的进一步记录。因此,尽管无论范围如何都在所有对象上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。客户端代码必须清理原型范围的对象并释放原型bean所拥有的昂贵资源。要使Spring容器释放原型范围的bean所拥有的资源,请尝试使用自定义bean后处理器,它包含对需要清理的bean的引用。
在某些方面,Spring容器关于原型范围bean的角色是Java new
运算符的替代品。超过该点的所有生命周期管理必须由客户端处理。(有关Spring容器中bean的生命周期的详细信息,请参阅Lifecycle Callbacks。)
1.5.3。具有原型bean依赖关系的单例Bean
当您使用具有依赖于原型bean的单例作用域bean时,请注意在实例化时解析依赖项。因此,如果依赖项将原型范围的bean注入到单例范围的bean中,则会实例化一个新的原型bean,然后将依赖注入到单例bean中。原型实例是唯一提供给单例范围bean的实例。
但是,假设您希望单例范围的bean在运行时重复获取原型范围的bean的新实例。您不能将原型范围的bean依赖注入到您的单例bean中,因为当Spring容器实例化单例bean并解析并注入其依赖项时,该注入只发生一次。如果您需要在运行时多次使用原型bean的新实例,请参阅方法注入
1.5.4。请求,会话,应用程序和WebSocket范围
在request
,session
,application
,和websocket
范围只有当你使用一个基于web的Spring可ApplicationContext
实现(例如XmlWebApplicationContext
)。如果将这些范围与常规的Spring IoC容器一起使用,例如ClassPathXmlApplicationContext
,IllegalStateException
则会引发抱怨未知Bean范围的问题。
初始Web配置
为了支持豆的范围界定在request
,session
,application
,和 websocket
(即具有web作用域bean),需要做少量的初始配置定义你的豆之前。(标准范围不需要此初始设置:singleton
和prototype
。)
如何完成此初始设置取决于您的特定Servlet环境。
如果您在Spring Web MVC中访问scoped bean,实际上是在Spring处理的请求中,则DispatcherServlet
无需进行特殊设置。DispatcherServlet
已暴露所有相关国家。
如果您使用Servlet 2.5 Web容器,并且在Spring之外处理请求 DispatcherServlet
(例如,使用JSF或Struts时),则需要注册org.springframework.web.context.request.RequestContextListener
ServletRequestListener
。对于Servlet 3.0+,可以使用该WebApplicationInitializer
接口以编程方式完成。或者,或者对于旧容器,将以下声明添加到Web应用程序的web.xml
文件中:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
或者,如果您的侦听器设置存在问题,请考虑使用Spring RequestContextFilter
。过滤器映射取决于周围的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可以在调用链中进一步使用。
请求范围
考虑bean定义的以下XML配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器LoginAction
通过loginAction
对每个HTTP请求使用bean定义来创建bean 的新实例。也就是说, loginAction
bean的范围是HTTP请求级别。您可以根据需要更改创建的实例的内部状态,因为从同一loginAction
bean定义创建的其他实例在状态中看不到这些更改。它们特别针对个人要求。当请求完成处理时,将放弃作用于请求的bean。
使用注释驱动的组件或Java配置时,@RequestScope
注释可用于将组件分配给request
范围。以下示例显示了如何执行此操作:
@RequestScope @Component public class LoginAction { // ... }
会话范围
考虑bean定义的以下XML配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器UserPreferences
通过在userPreferences
单个HTTP的生存期内使用bean定义来创建bean 的新实例Session
。换句话说,userPreferences
bean在HTTP Session
级别上有效地作用域。与请求范围的bean一样,您可以根据需要更改创建的实例的内部状态,因为知道Session
同样使用从同一userPreferences
bean定义创建的实例的其他HTTP 实例在状态中看不到这些更改,因为它们特定于单个HTTP Session
。当Session
最终丢弃HTTP时Session
,也将丢弃作用于该特定HTTP的bean 。
使用注释驱动的组件或Java配置时,可以使用 @SessionScope
注释将组件分配给session
范围。
@SessionScope @Component public class UserPreferences { // ... }
适用范围
考虑bean定义的以下XML配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring容器AppPreferences
通过appPreferences
对整个Web应用程序使用一次bean定义来创建bean 的新实例。也就是说,appPreferences
bean在该ServletContext
级别作用域并存储为常规 ServletContext
属性。这有点类似于Spring单例bean,但在两个重要方面有所不同:它是一个单独的ServletContext
,不是每个Spring的'ApplicationContext'(在任何给定的Web应用程序中可能有几个),它实际上是暴露的,因此是可见的作为一个ServletContext
属性。
使用注释驱动的组件或Java配置时,可以使用 @ApplicationScope
注释将组件分配给application
范围。以下示例显示了如何执行此操作:
@ApplicationScope @Component public class AppPreferences { // ... }
作为依赖性的Scoped Bean
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖关系)的连接。如果要将(例如)HTTP请求范围的bean注入到寿命较长范围的另一个bean中,您可以选择注入AOP代理来代替范围内的bean。也就是说,您需要注入一个代理对象,该对象公开与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索真实目标对象,并将方法调用委托给真实对象。
您还可以<aop:scoped-proxy/> 在作用域的bean之间使用singleton ,然后通过引用然后通过可序列化的中间代理,从而能够在反序列化时重新获取目标单例bean。当声明<aop:scoped-proxy/> 范围的bean时prototype ,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后转发该调用。此外,范围代理不是以生命周期安全的方式从较短范围访问bean的唯一方法。您还可以将注入点(即构造函数或setter参数或autowired字段)声明为ObjectFactory<MyTargetBean> 允许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
和自定义范围水平要求<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>
在前面的示例中,singleton bean(userManager
)注入了对HTTP Session
-scoped bean(userPreferences
)的引用。这里的重点是 userManager
bean是一个单例:它每个容器只实例化一次,它的依赖关系(在这种情况下只有一个,userPreferences
bean)也只注入一次。这意味着userManager
bean只在完全相同的userPreferences
对象(即最初注入它的对象)上运行。
当将一个寿命较短的scoped bean注入一个寿命较长的scoped bean时,这不是你想要的行为(例如,将一个HTTP Session
-scoped协作bean作为依赖注入singleton bean)。相反,您需要一个userManager
对象,并且,在HTTP的生命周期中Session
,您需要一个userPreferences
特定于HTTP 的对象Session
。因此,容器创建一个对象,该对象公开与UserPreferences
该类完全相同的公共接口(理想情况下是一个UserPreferences
实例的对象),该UserPreferences
对象可以从作用域机制(HTTP请求Session
等)中获取真实 对象。容器将此代理对象注入到userManager
bean中,该bean不知道此UserPreferences
引用是代理。在这个例子中,当一个UserManager
实例在依赖注入的UserPreferences
对象上调用一个方法,它实际上是在代理上调用一个方法。然后,代理UserPreferences
从(在这种情况下)HTTP中Session
获取真实UserPreferences
对象,并将方法调用委托给检索到的真实对象。
因此,在将bean request-
和session-scoped
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代理只拦截公共方法调用!不要在这样的代理上调用非公共方法。它们不会委托给实际的作用域目标对象。 | |
---|---|
或者,您可以通过指定元素属性false
的值,将Spring容器配置为为此类作用域bean创建基于JDK接口的标准代理。使用基于JDK接口的代理意味着您不需要在应用程序类路径中使用其他库来影响此类代理。但是,这也意味着作用域bean的类必须至少实现一个接口,并且注入了作用域bean的所有协作者必须通过其中一个接口引用bean。以下示例显示了基于接口的代理:proxy-target-class``<aop:scoped-proxy/>
<!-- 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>
有关选择基于类或基于接口的代理的更多详细信息,请参阅代理机制。
1.5.5。自定义范围
bean范围机制是可扩展的。您可以定义自己的范围,甚至可以重新定义现有范围,尽管后者被认为是不好的做法,您无法覆盖内置singleton
和prototype
范围。
创建自定义范围
要将自定义作用域集成到Spring容器中,需要实现org.springframework.beans.factory.config.Scope
本节中描述的 接口。有关如何实现自己的作用域的想法,请参阅Scope
Spring Framework本身和Scope
javadoc 提供的实现 ,它们解释了您需要更详细地实现的方法。
该Scope
接口有四种方法可以从作用域中获取对象,将其从作用域中删除,然后将其销毁。
例如,会话范围实现返回会话范围的bean(如果它不存在,则该方法在将其绑定到会话以供将来参考之后返回该bean的新实例)。以下方法从基础范围返回对象:
Object get(String name, ObjectFactory objectFactory)
例如,会话范围实现从基础会话中删除会话范围的bean。应返回该对象,但如果找不到具有指定名称的对象,则可以返回null。以下方法从基础范围中删除对象:
Object remove(String name)
以下方法记录范围在销毁时或范围中指定对象被销毁时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
有关 销毁回调的更多信息,请参阅javadoc或Spring作用域实现。
以下方法获取基础范围的对话标识符:
String getConversationId()
每个范围的标识符都不同。对于会话范围的实现,该标识符可以是会话标识符。
使用自定义范围
在编写并测试一个或多个自定义Scope
实现之后,需要让Spring容器知道您的新范围。以下方法是Scope
使用Spring容器注册new的核心方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory
接口上声明,该接口可通过 Spring随附的BeanFactory
大多数具体ApplicationContext
实现的属性获得。
该registerScope(..)
方法的第一个参数是与范围关联的唯一名称。Spring容器本身中的这些名称的示例是singleton
和prototype
。该registerScope(..)
方法的第二个参数是Scope
您希望注册和使用的自定义实现的实际实例。
假设您编写自定义Scope
实现,然后注册它,如下一个示例所示。
下一个示例使用SimpleThreadScope ,它包含在Spring中,但默认情况下未注册。您自己的自定义Scope 实现的说明是相同的。 | |
---|---|
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
然后,您可以创建符合自定义的作用域规则的bean定义, Scope
如下所示:
<bean id="..." class="..." scope="thread">
使用自定义Scope
实现,您不仅限于范围的编程注册。您还可以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>
放置<aop:scoped-proxy/> 在FactoryBean 实现中时,工厂bean本身是作用域的,而不是从中返回的对象getObject() 。 | |
---|---|
1.6。定制Bean的本质
Spring Framework提供了许多可用于自定义bean特性的接口。本节将它们分组如下:
1.6.1。生命周期回调
要与容器的bean生命周期管理进行交互,可以实现Spring InitializingBean
和DisposableBean
接口。容器调用afterPropertiesSet()
前者,destroy()
后者让bean在初始化和销毁bean时执行某些操作。
JSR-250 @PostConstruct 和@PreDestroy 注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean不会耦合到特定于Spring的接口。有关详细信息,请参阅使用@PostConstruct 和@PreDestroy 。如果您不想使用JSR-250注释但仍想删除耦合,请考虑init-method 和destroy-method bean定义元数据。 | |
---|---|
在内部,Spring Framework使用BeanPostProcessor
实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为Spring默认不提供,您可以BeanPostProcessor
自己实现。有关更多信息,请参阅 容器扩展点。
除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle
接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。
本节描述了生命周期回调接口。
初始化回调
该org.springframework.beans.factory.InitializingBean
接口允许在容器上设置bean的所有必要属性后,一个bean进行初始化工作。的InitializingBean
接口规定了一个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用该InitializingBean
接口,因为它会不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct
注释或指定POJO初始化方法。对于基于XML的配置元数据,您可以使用该init-method
属性指定具有void无参数签名的方法的名称。使用Java配置,您可以使用。的initMethod
属性 @Bean
。请参阅接收生命周期回调。请考虑以下示例:
<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的配置元数据,您可以使用该destroy-method
属性<bean/>
。使用Java配置,您可以使用。的destroyMethod
属性@Bean
。请参阅 接收生命周期回调。考虑以下定义:
<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。
您可以destroy-method 为<bean> 元素的属性指定一个特殊 (inferred) 值,该值指示Spring自动检测特定bean类的公共close 或 shutdown 方法。(任何实现 java.lang.AutoCloseable 或java.io.Closeable 因此匹配的类。)您还可以(inferred) 在元素的default-destroy-method 属性 上设置此特殊值,<beans> 以将此行为应用于整组bean(请参阅 默认初始化和销毁方法)。请注意,这是Java配置的默认行为。 | |
---|---|
默认初始化和销毁方法
当你写的初始化和销毁不使用Spring的具体方法回调InitializingBean
和DisposableBean
回调接口,你通常写有名字,如方法init()
,initialize()
,dispose()
,等等。理想情况下,此类生命周期回调方法的名称在项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。
您可以将Spring容器配置为“查找”命名初始化并销毁每个bean上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用调用的初始化回调 init()
,而无需为init-method="init"
每个bean定义配置属性。Spring IoC容器在创建bean时调用该方法(并且符合前面描述的标准生命周期回调协定)。此功能还强制执行初始化和销毁方法回调的一致命名约定。
假设您的初始化回调方法已命名,init()
并且您的destroy回调方法已命名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."); } } }
然后,您可以在类似于以下内容的bean中使用该类:
<beans default-init-method="init"> <bean id="blogService" class="com.something.DefaultBlogService"> <property name="blogDao" ref="blogDao" /> </bean> </beans>
default-init-method
顶级<beans/>
元素属性上存在属性会导致Spring IoC容器init
将bean类上调用的方法识别为初始化方法回调。当bean被创建和组装时,如果bean类具有这样的方法,则在适当的时候调用它。
您可以通过使用default-destroy-method
顶级<beans/>
元素上的属性来类似地配置destroy方法回调(在XML中) 。
如果现有的bean类已经具有与约定一致的回调方法,则可以通过使用 自身的init-method
和destroy-method
属性指定(在XML中,即方法名称)来覆盖默认值<bean/>
。
Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等尚未应用于bean。首先完全创建目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,那么您的代码甚至可以绕过代理与原始目标bean进行交互。因此,将拦截器应用于init
方法是不一致的,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5开始,您有三个控制bean生命周期行为的选项:
-
定制
init()
和destroy()
方法 -
在
@PostConstruct
和@PreDestroy
注释。您可以组合这些机制来控制给定的bean。
如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则每个配置的方法都按照此注释后列出的顺序执行。但是,如果init() 为多个这些生命周期机制配置了相同的方法名称(例如, 对于初始化方法),则该方法将执行一次,如上 一节中所述。 | |
---|---|
为同一个bean配置的多个生命周期机制具有不同的初始化方法,如下所示:
-
用注释方法注释
@PostConstruct
-
afterPropertiesSet()
由InitializingBean
回调接口定义 -
自定义配置的
init()
方法
Destroy方法以相同的顺序调用:
-
用注释方法注释
@PreDestroy
-
destroy()
由DisposableBean
回调接口定义 -
自定义配置的
destroy()
方法
启动和关闭回调
该Lifecycle
接口为任何具有自己的生命周期要求的对象(例如启动和停止某些后台进程)定义了基本方法:
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何Spring管理的对象都可以实现该Lifecycle
接口。然后,当它 ApplicationContext
自己接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它将这些调用级联到Lifecycle
该上下文中定义的所有实现。它通过委托给a来实现LifecycleProcessor
,如下面的清单所示:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
请注意,LifecycleProcessor
它本身是Lifecycle
接口的扩展。它还添加了另外两种方法来响应刷新和关闭的上下文。
请注意,常规org.springframework.context.Lifecycle 接口是显式启动和停止通知的简单合约,并不意味着在上下文刷新时自动启动。要对特定bean的自动启动(包括启动阶段)进行细粒度控制,请考虑实现org.springframework.context.SmartLifecycle 。此外,请注意,在销毁之前不保证停止通知。在常规关闭时,所有Lifecycle bean在传播一般销毁回调之前首先收到停止通知。但是,在上下文生命周期中的热刷新或中止刷新尝试时,仅调用destroy方法。 | |
---|---|
启动和关闭调用的顺序非常重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并且在其依赖之前停止。但是,有时,直接依赖性是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle
接口定义了另一个选项,即getPhase()
在其超级接口上定义的方法 Phased
。以下清单显示了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
。因此,任何负相位值都表示对象应该在这些标准组件之前启动(并在它们之后停止)。任何正相值都是相反的。
定义的stop方法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()
已经显式调用一样,但它在上下文关闭时发生。另一方面,'refresh'回调启用了SmartLifecycle
bean的另一个功能 。刷新上下文时(在实例化并初始化所有对象之后),将调用该回调。此时,默认生命周期处理器检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的布尔值 。如果true
,那个对象是在那个点开始的,而不是等待显式调用上下文或它自己的对象start()
方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。该phase
值与任何“依赖式”的关系确定为前面所述的启动顺序。
在非Web应用程序中优雅地关闭Spring IoC容器
本节仅适用于非Web应用程序。Spring的基于Web的 ApplicationContext 实现已经具有代码,可以在相关Web应用程序关闭时正常关闭Spring IoC容器。 | |
---|---|
如果在非Web应用程序环境中使用Spring的IoC容器(例如,在富客户机桌面环境中),请使用JVM注册关闭挂钩。这样做可确保正常关闭并在单例bean上调用相关的destroy方法,以便释放所有资源。您仍然必须正确配置和实现这些destroy回调。
要注册关闭挂钩,请调用接口registerShutdownHook()
上声明的方法ConfigurableApplicationContext
,如以下示例所示:
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... } }
1.6.2。ApplicationContextAware
和BeanNameAware
当ApplicationContext
创建实现org.springframework.context.ApplicationContextAware
接口的对象实例时,将 为该实例提供对该实例的引用ApplicationContext
。以下清单显示了ApplicationContextAware
界面的定义:
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
因此,bean可以ApplicationContext
通过ApplicationContext
接口或通过将引用转换为此接口的已知子类(例如ConfigurableApplicationContext
,公开其他功能)以编程方式操纵创建它们的方法。一种用途是对其他bean进行编程检索。有时这种能力很有用。但是,一般情况下,您应该避免使用它,因为它将代码耦合到Spring并且不遵循Inversion of Control样式,其中协作者作为属性提供给bean。其他方法 ApplicationContext
提供对文件资源的访问,发布应用程序事件和访问MessageSource
。这些附加功能在附加ApplicationContext
功能中描述 。
从Spring 2.5开始,自动装配是另一种获取参考的方法 ApplicationContext
。“传统” constructor
和byType
自动装配模式(如自动装配协作者中所述)可以分别为ApplicationContext
构造函数参数或setter方法参数提供类型的依赖性 。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注释的新自动装配功能。如果这样做,ApplicationContext
则自动装入一个字段,构造函数参数或方法参数,ApplicationContext
如果相关的字段,构造函数或方法带有@Autowired
注释,则该参数需要该类型。有关更多信息,请参阅 使用@Autowired
。
当ApplicationContext
创建实现org.springframework.beans.factory.BeanNameAware
接口的类时,将为 该类提供对其关联对象定义中定义的名称的引用。以下清单显示了BeanNameAware接口的定义:
public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
回调正常bean属性的人口之后,但在一个初始化回调诸如调用InitializingBean
,afterPropertiesSet
或自定义的初始化方法。
1.6.3。其他Aware
接口
除了ApplicationContextAware
和BeanNameAware
讨论( 早期),Spring提供了一系列Aware
可以让豆子指示,他们需要一定的基础设施的依赖容器接口。作为一般规则,名称是依赖类型的良好指示。下表总结了最重要的Aware
接口:
名称 | 注入依赖 | 解释在...... |
---|---|---|
ApplicationContextAware | 宣布ApplicationContext 。 | ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware | 封闭的事件发布者ApplicationContext 。 | 附加功能 ApplicationContext |
BeanClassLoaderAware | 用于加载bean类的类加载器。 | 实例化豆类 |
BeanFactoryAware | 宣布BeanFactory 。 | ApplicationContextAware 和 BeanNameAware |
BeanNameAware | 声明bean的名称。 | ApplicationContextAware 和 BeanNameAware |
BootstrapContextAware | BootstrapContext 容器运行的资源适配器。通常仅在JCA感知ApplicationContext 实例中可用。 | JCA CCI |
LoadTimeWeaverAware | 定义的weaver用于在加载时处理类定义。 | 在Spring框架中使用AspectJ进行加载时编织 |
MessageSourceAware | 用于解析消息的已配置策略(支持参数化和国际化)。 | 附加功能 ApplicationContext |
NotificationPublisherAware | Spring JMX通知发布者。 | 通知 |
ResourceLoaderAware | 配置的加载程序,用于对资源进行低级访问。 | 资源 |
ServletConfigAware | 当前ServletConfig 容器运行。仅在Web感知弹簧中有效 ApplicationContext 。 | Spring MVC |
ServletContextAware | 当前ServletContext 容器运行。仅在Web感知弹簧中有效ApplicationContext 。 | Spring MVC |
请再次注意,使用这些接口会将您的代码绑定到Spring API,而不会遵循Inversion of Control样式。因此,我们建议将它们用于需要以编程方式访问容器的基础架构bean。
1.7。Bean定义继承
bean定义可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。子bean定义从父定义继承配置数据。子定义可以覆盖某些值或根据需要添加其他值。使用父bean和子bean定义可以节省大量的输入。实际上,这是一种模板形式。
如果以ApplicationContext
编程方式使用接口,则子bean定义由ChildBeanDefinition
类表示。大多数用户不在此级别上使用它们。相反,它们在类中以声明方式配置bean定义ClassPathXmlApplicationContext
。使用基于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> <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>
注意parent 属性。 | |
---|---|
如果没有指定,则bean bean定义使用父定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容(即,它必须接受父类的属性值)。
子bean定义从父级继承范围,构造函数参数值,属性值和方法覆盖,并带有添加新值的选项。static
您指定的任何范围,初始化方法,销毁方法或工厂方法设置都会覆盖相应的父设置。
其余设置始终取自子定义:取决于,autowire模式,依赖性检查,单例和惰性初始化。
前面的示例通过使用该abstract
属性将父bean定义显式标记为abstract 。如果父定义未指定类,abstract
则根据需要显式标记父bean定义,如以下示例所示:
<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定义,用作子定义的父定义。尝试使用这样的abstract
父bean,通过将其称为另一个bean的ref属性或getBean()
使用父bean ID 进行显式调用,将返回错误。类似地,容器的内部 preInstantiateSingletons()
方法忽略定义为abstract的bean定义。
ApplicationContext 默认情况下预先实例化所有单例。因此,重要的是(至少对于单例bean),如果你有一个(父)bean定义,你只打算用作模板,并且这个定义指定了一个类,你必须确保将abstract属性设置为true否则应用程序上下文将实际(尝试)预先实例化abstract bean。 | |
---|---|
1.8。集装箱扩建点
通常,应用程序开发人员不需要子类化ApplicationContext
实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节将介绍这些集成接口。
1.8.1。使用a定制BeanBeanPostProcessor
该BeanPostProcessor
接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等。如果要在Spring容器完成实例化,配置和初始化bean之后实现某些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor
实现。
您可以配置多个BeanPostProcessor
实例,并且可以BeanPostProcessor
通过设置order
属性来控制这些实例的执行顺序。只有在BeanPostProcessor
实现Ordered
接口时才能设置此属性。如果你自己编写BeanPostProcessor
,你也应该考虑实现这个Ordered
接口。有关更多详细信息,请参阅BeanPostProcessor
和Ordered
接口的javadoc 。另见关于实例的程序化登记BeanPostProcessor
的说明。
BeanPostProcessor 实例在bean(或对象)实例上运行。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor 实例执行它们的工作。BeanPostProcessor 实例的范围是每个容器。仅当您使用容器层次结构时,这才是相关的。如果BeanPostProcessor 在一个容器中定义一个容器,它只会对该容器中的bean进行后处理。换句话说,BeanPostProcessor 即使两个容器都是同一层次结构的一部分,在一个容器中定义的bean也不会被另一个容器中定义的bean进行后处理。要更改实际的bean定义(即定义bean的蓝图),您需要使用a BeanFactoryPostProcessor ,如 使用a 定制配置元数据中所述BeanFactoryPostProcessor 。 | |
---|---|
该org.springframework.beans.factory.config.BeanPostProcessor
接口由两个回调方法组成。当这样的类被注册为具有容器的后处理器时,对于由容器创建的每个bean实例,后处理器在容器初始化方法(例如InitializingBean.afterPropertiesSet()
或任何声明的init
方法)之前都从容器获得回调。调用,并在任何bean初始化后回调。后处理器可以对bean实例执行任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者它可以用代理包装bean。一些Spring AOP基础结构类实现为bean后处理器,以便提供代理包装逻辑。
的ApplicationContext
自动检测中的实现的配置的元数据中定义的任何豆BeanPostProcessor
接口。将 ApplicationContext
这些bean注册为后处理器,以便在创建bean时可以稍后调用它们。Bean后处理器可以以与任何其他bean相同的方式部署在容器中。
请注意,在配置类上BeanPostProcessor
使用@Bean
工厂方法声明a时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚地表明该bean的后处理器性质。否则,ApplicationContext
在完全创建之前, 无法按类型自动检测它。由于BeanPostProcessor
需要尽早实例化以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。
以编程方式注册BeanPostProcessor 实例虽然推荐的BeanPostProcessor 注册方法是通过 ApplicationContext 自动检测(如前所述),但您可以ConfigurableBeanFactory 使用该addBeanPostProcessor 方法以编程方式对其进行注册。当您需要在注册前评估条件逻辑或甚至跨层次结构中的上下文复制Bean post处理器时,这非常有用。但请注意,以BeanPostProcessor 编程方式添加的实例不尊重Ordered 接口。这里,注册的顺序决定了执行的顺序。另请注意,以BeanPostProcessor 编程方式注册的实例始终在通过自动检测注册的实例之前处理,而不管任何显式排序。 | |
---|---|
BeanPostProcessor 实例和AOP自动代理实现BeanPostProcessor 接口的类是特殊的,容器会对它们进行不同的处理。BeanPostProcessor 他们直接引用的所有实例和bean都会在启动时实例化,作为特殊启动阶段的一部分ApplicationContext 。接下来,所有BeanPostProcessor 实例都以排序方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为一个BeanPostProcessor 自身实现的,所以BeanPostProcessor 实例和它们直接引用的bean都不符合自动代理的条件,因此没有编织方面。对于任何此类bean,您应该看到一条信息性日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying) 。如果您BeanPostProcessor 通过使用自动装配或@Resource (可能回退到自动装配)将bean连接到您的 ,Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,因此,使它们不符合自动代理或其他类型的bean post -处理。例如,如果您有一个依赖项,@Resource 其中字段或setter名称与bean的声明名称没有直接对应,并且没有使用name属性,则Spring会访问其他bean以按类型匹配它们。 | |
---|---|
以下示例显示如何在中编写,注册和使用BeanPostProcessor
实例ApplicationContext
。
示例:Hello World,BeanPostProcessor
-style
第一个例子说明了基本用法。该示例显示了一个自定义 BeanPostProcessor
实现,该实现调用toString()
容器创建的每个bean 的方法,并将生成的字符串输出到系统控制台。
以下清单显示了自定义BeanPostProcessor
实现类定义:
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一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。在动态语言支持一章中详细介绍了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'sensenger '创建:org.springframework.scripting.groovy.GroovyMessenger@272961 org.sprin gframework.scripting.groovy.GroovyMessenger@272961
示例: RequiredAnnotationBeanPostProcessor
将回调接口或注释与自定义BeanPostProcessor
实现结合使用 是扩展Spring IoC容器的常用方法。一个例子是Spring RequiredAnnotationBeanPostProcessor
- 一个 BeanPostProcessor
随Spring发行版一起提供的实现,它确保标记有(任意)注释的bean上的JavaBean属性实际上(配置为)依赖注入值。
1.8.2。使用a自定义配置元数据BeanFactoryPostProcessor
我们看到的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor
。这个接口的语义类似于BeanPostProcessor
它的一个主要区别:BeanFactoryPostProcessor
对bean配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor
读取配置元数据,并可能在容器实例化除实例之外的任何bean 之前更改它BeanFactoryPostProcessor
。
您可以配置多个BeanFactoryPostProcessor
实例,并且可以BeanFactoryPostProcessor
通过设置order
属性来控制这些实例的运行顺序。但是,如果BeanFactoryPostProcessor
实现 Ordered
接口,则只能设置此属性。如果你自己编写BeanFactoryPostProcessor
,你也应该考虑实现这个Ordered
接口。有关更多详细信息,请参阅BeanFactoryPostProcessor
和Ordered
接口的javadoc 。
如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用a BeanPostProcessor (前面在使用a定制Bean中进行了描述BeanPostProcessor )。虽然技术上可以在a中使用bean实例BeanFactoryPostProcessor (例如,通过使用BeanFactory.getBean() ),但这样做会导致过早的bean实例化,从而违反标准的容器生命周期。这可能会导致负面影响,例如绕过bean后期处理。此外,BeanFactoryPostProcessor 实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才有意义。如果BeanFactoryPostProcessor 在一个容器中定义一个容器,则它仅应用于该容器中的bean定义。BeanFactoryPostProcessor 即使两个容器都是同一层次结构的一部分,一个容器中的Bean定义也不会被另一个容器中的实例进行后处理。 | |
---|---|
Bean工厂后处理器在其内部声明时会自动执行 ApplicationContext
,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer
和 PropertyPlaceholderConfigurer
。您还可以使用自定义BeanFactoryPostProcessor
- 例如,注册自定义属性编辑器。
一个ApplicationContext
自动检测部署在它实现了任何豆BeanFactoryPostProcessor
接口。它在适当的时候使用这些bean作为bean工厂后处理器。您可以像处理任何其他bean一样部署这些后处理器bean。
与BeanPostProcessor s一样,您通常不希望BeanFactoryPostProcessor 为延迟初始化配置 s。如果没有其他bean引用aBean(Factory)PostProcessor ,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,Bean(Factory)PostProcessor 会急切地实例化,即使你设定的 default-lazy-init 属性true 对你的声明<beans /> 元素。 | |
---|---|
示例:类名替换 PropertyPlaceholderConfigurer
您可以使用PropertyPlaceholderConfigurer
标准Java Properties
格式在单独的文件中使用bean定义中的外部化属性值。这样做可以使部署应用程序的人员自定义特定于环境的属性,例如数据库URL和密码,而不会出现修改主XML定义文件或容器文件的复杂性或风险。
请考虑以下基于XML的配置元数据片段,其中DataSource
定义了占位符值:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <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
文件配置的属性。在运行时,a PropertyPlaceholderConfigurer
将应用于替换DataSource的某些属性的元数据。要替换的值被指定为表单的占位符${property-name}
,它遵循Ant和log4j以及JSP EL样式。
实际值来自标准Java Properties
格式的另一个文件:
jdbc.driverClassName = org.hsqldb.jdbcDriver jdbc.url = JDBC:HSQLDB:HSQL://生产:9002 jdbc.username = SA jdbc.password =根
因此,${jdbc.username}
在运行时使用值“sa”替换字符串,这同样适用于与属性文件中的键匹配的其他占位符值。在PropertyPlaceholderConfigurer
为大多数属性和bean定义的属性占位符检查。此外,您可以自定义占位符前缀和后缀。
使用context
Spring 2.5中引入的命名空间,您可以使用专用配置元素配置属性占位符。您可以在location
属性中提供一个或多个位置作为逗号分隔列表,如以下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
在PropertyPlaceholderConfigurer
不仅将查找在属性Properties
指定的文件。默认情况下,如果它在指定的属性文件中找不到属性,它还会检查Java System
属性。您可以通过systemPropertiesMode
使用以下三个受支持的整数值之一设置configurer 的属性来自定义此行为:
-
never
(0):从不检查系统属性。 -
fallback
(1):如果在指定的属性文件中无法解析,则检查系统属性。这是默认值。 -
override
(2):在尝试指定的属性文件之前,首先检查系统属性。这使系统属性可以覆盖任何其他属性源。
有关PropertyPlaceholderConfigurer
更多信息,请参阅javadoc。
您可以使用PropertyPlaceholderConfigurer 替换类名称,这在您必须在运行时选择特定实现类时有时很有用。以下示例显示了如何执行此操作:<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <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的分辨率,当它即将被创造,这是在失败preInstantiateSingletons() 的阶段ApplicationContext 对非延迟实例化的bean。 | |
---|---|
示例: PropertyOverrideConfigurer
在PropertyOverrideConfigurer
另一个bean工厂后置处理器,类似 PropertyPlaceholderConfigurer
,但不同的是后者,原来的定义可以有缺省值或者根本没有值的bean属性。如果覆盖 Properties
文件没有某个bean属性的条目,则使用默认上下文定义。
请注意,bean定义不知道被覆盖,因此从XML定义文件中可以立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer
实例为同一个bean属性定义了不同的值,则由于覆盖机制,最后一个实例会获胜。
属性文件配置行采用以下格式:
beanName.property =值
以下清单显示了格式的示例:
dataSource.driverClassName = com.mysql.jdbc.Driver dataSource.url = JDBC:MySQL的:MYDB
此示例文件可以与包含名为dataSource
has has driver
和url
properties 的bean的容器定义一起使用 。
也支持复合属性名称,只要路径的每个组件(重写的最终属性除外)都已经非空(可能由构造函数初始化)。在下面的例子中,sammy
所述的属性bob
的财产fred
的财产tom
豆被设置为标量值123
:
tom.fred.bob.sammy = 123
指定的覆盖值始终是文字值。它们不会被翻译成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。 | |
---|---|
使用context
Spring 2.5中引入的命名空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3。使用a自定义实例化逻辑FactoryBean
您可以org.springframework.beans.factory.FactoryBean
为自己工厂的对象实现接口。
该FactoryBean
接口是Spring IoC容器实例化逻辑的可插拔点。如果你有一个复杂的初始化代码,用Java表示,而不是(可能)冗长的XML,你可以创建自己的 FactoryBean
,在该类中编写复杂的初始化,然后将自定义FactoryBean
插入容器。
该FactoryBean
接口提供了三种方法:
-
Object getObject()
:返回此工厂创建的对象的实例。可以共享实例,具体取决于此工厂是返回单例还是原型。 -
boolean isSingleton()
:true
如果FactoryBean
返回单例或false
其他方式返回 。 -
Class getObjectType()
:返回getObject()
方法返回的对象类型,或者null
如果事先不知道类型。
该FactoryBean
概念和接口被一些Spring框架内的场所。超过50个FactoryBean
接口的实现随Spring一起提供。
当你需要向一个容器询问一个实际的FactoryBean
实例本身而不是它生成的bean 时,在调用the的方法时id
,用strersand符号(&
)作为前缀。因此,对于给定 与的,调用在容器上返回的产品,而调用返回的 实例本身。getBean()``ApplicationContext``FactoryBean``id``myBean``getBean("myBean")``FactoryBean``getBean("&myBean")``FactoryBean
1.9。基于注释的容器配置
注释是否比配置Spring的XML更好?
基于注释的配置的引入引发了这种方法是否比XML“更好”的问题。简短的回答是“它取决于。”长期的答案是每种方法都有其优点和缺点,通常,由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而导致更短更简洁的配置。但是,XML擅长在不触及源代码或重新编译它们的情况下连接组件。一些开发人员更喜欢将布线靠近源,而另一些开发人员则认为注释类不再是POJO,而且配置变得分散且难以控制。
无论选择如何,Spring都可以兼顾两种风格,甚至可以将它们混合在一起。值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注释,而无需触及目标组件源代码,并且在工具方面,Spring Tool Suite支持所有配置样式 。
基于注释的配置提供了XML设置的替代方案,该配置依赖于字节码元数据来连接组件而不是角括号声明。开发人员不是使用XML来描述bean连接,而是通过在相关的类,方法或字段声明上使用注释将配置移动到组件类本身。如示例中所述:RequiredAnnotationBeanPostProcessor
使用BeanPostProcessor
与注释结合使用是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required
注释强制执行所需属性的可能性。Spring 2.5使得有可能采用相同的通用方法来驱动Spring的依赖注入。基本上,@Autowired
注释提供与自动装配协作者中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,例如 @PostConstruct
和@PreDestroy
。Spring 3.0增加了对javax.inject
包中包含的JSR-330(Java的依赖注入)注释的支持,例如@Inject
和@Named
。有关这些注释的详细信息,请参阅 相关章节。
注释注入在XML注入之前执行。因此,XML配置会覆盖通过这两种方法连接的属性的注释。 | |
---|---|
与往常一样,您可以将它们注册为单独的bean定义,但也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(请注意包含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>
(在隐式注册后处理器包括 AutowiredAnnotationBeanPostProcessor
, CommonAnnotationBeanPostProcessor
,PersistenceAnnotationBeanPostProcessor
,和前面提到的 RequiredAnnotationBeanPostProcessor
。)
<context:annotation-config/> 仅查找在定义它的同一应用程序上下文中的bean上的注释。这意味着,如果你<context:annotation-config/> 输入一个WebApplicationContext for DispatcherServlet ,它只检查@Autowired 你的控制器中的bean,而不是你的服务。有关更多信息,请参阅 DispatcherServlet。 | |
---|---|
1.9.1。@需要
该@Required
注释适用于bean属性setter方法,如下面的例子:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
此批注指示必须在配置时通过bean定义中的显式属性值或通过自动装配填充受影响的bean属性。如果尚未填充受影响的bean属性,则容器将引发异常。这允许急切和明确的失败,以后避免NullPointerException
实例等。我们仍然建议您将断言放入bean类本身(例如,转换为init方法)。即使您在容器外部使用类,这样做也会强制执行那些必需的引用和值。
从@Required Spring Framework 5.1开始,注释正式被弃用,支持使用构造函数注入所需的设置(或者InitializingBean.afterPropertiesSet() bean属性setter方法的自定义实现 )。 | |
---|---|
1.9.2。运用@Autowired
在本节中包含的示例中,@Inject 可以使用JSR 330的注释代替Spring的@Autowired 注释。有关详细信息,请参见此处 | |
---|---|
您可以将@Autowired
注释应用于构造函数,如以下示例所示:
public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
从Spring Framework 4.3开始,@Autowired 如果目标bean只定义了一个开头的构造函数,则不再需要对这样的构造函数进行注释。但是,如果有几个构造器可用,则必须注释至少一个构造器以教导容器使用哪一个。 | |
---|---|
您还可以将@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或组件类,容器通常预先知道具体类型。但是,对于@Bean 工厂方法,您需要确保声明的返回类型具有足够的表现力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最具体的返回类型(至少与引用bean的注入点所要求的具体相同)。 | |
---|---|
您还可以ApplicationContext
通过将注释添加到需要该类型数组的字段或方法来提供特定类型的所有bean ,如以下示例所示:
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 值与@Primary 每种类型的单个bean 相结合来建模。 | |
---|---|
Map
只要预期的密钥类型是,即使是类型化的实例也可以自动装配String
。Map值包含所有期望类型的bean,并且键包含相应的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属性表示该属性不是自动装配所必需的。如果无法自动装配,则会忽略该属性。@Required 另一方面,它更强大,因为它强制执行由容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。 | |
---|---|
或者,您可以通过Java 8表达特定依赖关系的非必需特性java.util.Optional
,如以下示例所示:
public class SimpleMovieLister { @Autowired public void setMovieFinder(Optional<MovieFinder> movieFinder) { ... } }
从Spring Framework 5.0开始,您还可以使用@Nullable
注释(任何包中的任何类型的注释 - 例如,javax.annotation.Nullable
来自JSR-305):
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 方法显式地“连接”这些类型。 | |
---|---|
1.9.3。微调基于注释的自动装配@Primary
由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程进行更多控制。实现这一目标的一种方法是使用Spring的@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>
1.9.4。使用限定符微调基于注释的自动装配
@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"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
具有main 限定符值的bean与使用相同值限定的构造函数参数连接。 | |
---|---|
具有action 限定符值的bean与使用相同值限定的构造函数参数连接。 |
对于回退匹配,bean名称被视为默认限定符值。因此,可以用一个定义bean id
的main
代替嵌套限定符元素,导致相同的匹配结果。但是,虽然您可以使用此约定来按名称引用特定bean,但@Autowired
基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回退,限定符值在类型匹配集中也总是具有缩小的语义。它们在语义上不表示对唯一bean的引用id
。良好限定的值是main
或EMEA
或persistent
,表达独立于从所述豆的特定部件的特性id
,在匿名bean定义的情况下可以自动生成,例如前面例子中的定义。
限定符也适用于类型集合,如前所述 - 例如,to Set<MovieCatalog>
。在这种情况下,根据声明的限定符,所有匹配的bean都作为集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,您可以MovieCatalog
使用相同的限定符值“action” 定义多个bean,所有这些bean都注入带Set<MovieCatalog>
注释的注释中@Qualifier("action")
。
在类型匹配候选项中,根据目标bean名称选择限定符值,不需要@Qualifier 注入点处的注释。如果没有其他解析指示符(例如限定符或主要标记),则对于非唯一依赖性情况,Spring会将注入点名称(即字段名称或参数名称)与目标bean名称进行匹配,然后选择同名的候选人,如果有的话。也就是说,如果您打算按名称表达注释驱动的注入,请不要主要使用@Autowired ,即使它能够在类型匹配候选项中通过bean名称进行选择。相反,使用JSR-250 @Resource 注释,该注释在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。@Autowired 具有相当不同的语义:在按类型选择候选bean之后,String 仅在那些类型选择的候选中考虑指定的限定符值(例如,将account 限定符与标记有相同限定符标签的bean 匹配)。对于自身定义为集合Map 或数组类型的bean来说,这@Resource 是一个很好的解决方案,它通过唯一名称引用特定的集合或数组bean。也就是说,从4.3开始,只要在返回类型签名或集合继承层次结构中保留元素类型信息,就可以Map 通过Spring的@Autowired 类型匹配算法匹配和数组类型 @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中提供限定符元数据。具体来说,请参阅使用注释提供限定符元数据。
在某些情况下,使用没有值的注释可能就足够了。当注释用于更通用的目的并且可以应用于多种不同类型的依赖项时,这可能很有用。例如,您可以提供可在没有Internet连接时搜索的脱机目录。首先,定义简单注释,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
然后将注释添加到要自动装配的字段或属性中,如以下示例所示:
public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }
此行添加@Offline 注释。 | |
---|---|
现在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/>
如果不存在此类限定符,则自动装配机制将回退到标记内提供的值 ,如以下示例中的最后两个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 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>
1.9.5。使用泛型作为自动装配限定符
除了@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;
1.9.6。运用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候选人:
-
autowire-candidate
每个bean定义的值 -
元素
default-autowire-candidates
上可用的任何模式<beans/>
-
@Qualifier
注释的存在以及注册的任何自定义注释CustomAutowireConfigurer
当多个bean有资格作为autowire候选者时,“primary”的确定如下:如果候选者中只有一个bean定义具有primary
设置为的属性true
,则选择它。
1.9.7。注射用@Resource
Spring还通过在字段或bean属性setter方法上使用JSR-250 @Resource
annotation(javax.annotation.Resource
)来支持注入。这是Java EE中的常见模式:例如,在JSF管理的bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。
@Resource
采用名称属性。默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循按名称语义,如以下示例所示:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
这条线注入了一个@Resource 。 | |
---|---|
如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。如果是字段,则采用字段名称。在setter方法的情况下,它采用bean属性名称。下面的例子将把bean movieFinder
注入其setter方法:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
提供注解的名称解析由一个bean的名称 ApplicationContext ,其中的CommonAnnotationBeanPostProcessor 知道。如果您SimpleJndiBeanFactory 明确配置Spring,则可以通过JNDI解析名称 。但是,我们建议您依赖于默认行为并使用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; public MovieRecommender() { } // ... }
context 根据已知的可解析依赖类型注入该字段: ApplicationContext 。 | |
---|---|
1.9.8。使用@PostConstruct
和@PreDestroy
将CommonAnnotationBeanPostProcessor
不仅承认了@Resource
注解也是JSR-250的生命周期注解:javax.annotation.PostConstruct
和 javax.annotation.PreDestroy
。在Spring 2.5中引入,对这些注释的支持提供了初始化回调和 销毁回调中描述的生命周期回调机制的替代 方法。如果 CommonAnnotationBeanPostProcessor
在Spring中注册ApplicationContext
,则在生命周期的同一点调用带有这些注释之一的方法,作为相应的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 注释类型是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation 包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api 工件需要是现在通过Maven Central获得,只需像任何其他库一样添加到应用程序的类路径中。 | |
---|---|
1.10。类路径扫描和托管组件
本章中的大多数示例都使用XML来指定BeanDefinition
在Spring容器中生成每个元素的配置元数据。上一节(基于注释的容器配置)演示了如何通过源级注释提供大量配置元数据。但是,即使在这些示例中,“基本”bean定义也在XML文件中显式定义,而注释仅驱动依赖项注入。本节介绍通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件匹配的类,并且具有向容器注册的相应bean定义。这消除了使用XML执行bean注册的需要。相反,您可以使用注释(例如,@Component
),AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类具有向容器注册的bean定义。
从Spring 3.0开始,Spring JavaConfig项目提供的许多功能都是核心Spring Framework的一部分。这允许您使用Java而不是使用传统的XML文件来定义bean。看看的@Configuration ,@Bean , @Import ,和@DependsOn 注释有关如何使用这些新功能的例子。 | |
---|---|
1.10.1。@Component
和进一步的刻板印象注释
的@Repository
注释是针对满足的存储库(也被称为数据访问对象或DAO)的作用或者固定型的任何类的标记。此标记的用法之一是异常的自动转换,如 异常转换中所述。
Spring提供进一步典型化注解:@Component
,@Service
,和 @Controller
。@Component
是任何Spring管理组件的通用构造型。@Repository
,@Service
和,@Controller
是@Component
更具体的用例的专业化(分别在持久性,服务和表示层)。因此,您可以来注解你的组件类有 @Component
,但是,通过与注解它们@Repository
,@Service
或者@Controller
,你的类能更好地被工具处理,或与切面进行关联。例如,这些刻板印象注释成为切入点的理想目标。@Repository
,@Service
并且@Controller
还可以在Spring Framework的未来版本中携带其他语义。因此,如果您在使用之间进行选择@Component
或者@Service
对于您的服务层,@Service
显然是更好的选择。同样,如前所述,@Repository
已经支持将其作为持久层中自动异常转换的标记。
1.10.2。使用元注释和组合注释
Spring提供的许多注释都可以在您自己的代码中用作元注释。元注释是可以应用于另一个注释的注释。例如,@Service
提及的注释前面是间注释有 @Component
,如下面的示例所示:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { // .... }
要以同样的方式对待的Component 原因。@Service``@Component | |
---|---|
您还可以组合元注释来创建“组合注释”。例如,@RestController
Spring MVC 的注释由@Controller
和 组成@ResponseBody
。
此外,组合注释可以选择从元注释重新声明属性以允许自定义。当您只想公开元注释属性的子集时,这可能特别有用。例如,Spring的@SessionScope
注释将范围名称硬编码为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; }
然后您可以使用@SessionScope
而不声明proxyMode
如下:
@Service @SessionScope public class SessionScopedService { // ... }
您还可以覆盖该值proxyMode
,如以下示例所示:
@Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) public class SessionScopedUserService implements UserService { // ... }
有关更多详细信息,请参阅 Spring Annotation Programming Model wiki页面。
1.10.3。自动检测类和注册Bean定义
Spring可以自动检测构造型类并注册相应的 BeanDefinition
实例ApplicationContext
。例如,以下两个类符合此类自动检测的条件:
@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,您需要添加 @ComponentScan
到您的@Configuration
类,其中该basePackages
属性是两个类的公共父包。(或者,您可以指定包含每个类的父包的逗号或分号或空格分隔列表。)
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { ... }
为简洁起见,前面的示例可能使用value 了注释的属性(即@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:annotation-config> 使用时通常不需要包含 元素<context:component-scan> 。 | |
---|---|
扫描类路径包需要在类路径中存在相应的目录条目。使用Ant构建JAR时,请确保不要激活JAR任务的仅文件开关。此外,在某些环境中,可能不会基于安全策略公开类路径目录 - 例如,JDK 1.7.0_45及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library” - 请参阅 http://stackoverflow.com/ questions / 19394570 / java-jre-7u45-breaks-classloader-getresources)。在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在module-info 描述符中导出组件类。如果您希望Spring调用类的非公共成员,请确保它们已“打开”(即,它们 在描述符中使用opens 声明而不是exports 声明module-info )。 | |
---|---|
此外,当您使用component-scan元素时,隐式包含AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
。这意味着这两个组件是自动检测并连接在一起的 - 所有这些都没有在XML中提供任何bean配置元数据。
您可以禁用注册,AutowiredAnnotationBeanPostProcessor 并将 CommonAnnotationBeanPostProcessor annotation-config属性包含在值中false 。 | |
---|---|
1.10.4。使用过滤器自定义扫描
默认情况下,类注有@Component
,@Repository
,@Service
, @Controller
,或者本身都标注有一个自定义的注释@Component
是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。他们加为includeFilters
或excludeFilters
的参数@ComponentScan
注释(或include-filter
或exclude-filter
在的子元素component-scan
元素)。每个过滤器元素都需要type
和expression
属性。下表介绍了筛选选项:
过滤器类型 | 示例表达 | 描述 |
---|---|---|
注释(默认) | org.example.SomeAnnotation | 要在目标组件中的类型级别出现的注释。 |
分配 | org.example.SomeClass | 目标组件可分配给(扩展或实现)的类(或接口)。 |
AspectJ的 | org.example..*Service+ | 要由目标组件匹配的AspectJ类型表达式。 |
正则表达式 | org\.example\.Default.* | 要由目标组件类名匹配的正则表达式。 |
习惯 | org.example.MyTypeFilter | org.springframework.core.type .TypeFilter 接口的自定义实现。 |
以下示例显示忽略所有@Repository
注释并使用“存根”存储库的配置:
@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 注释或提供元素use-default-filters="false" 属性来禁用默认过滤器<component-scan/> 。此,实际上,禁用与注解的类自动检测@Component ,@Repository , @Service ,@Controller ,或@Configuration 。 | |
---|---|
1.10.5。在组件中定义Bean元数据
Spring组件还可以向容器提供bean定义元数据。您可以@Bean
使用用于在带@Configuration
注释的类中定义bean元数据的相同注释来执行此操作。以下示例显示了如何执行此操作:
@Component public class FactoryMethodComponent { @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } public void doWork() { // Component method implementation omitted } }
前面的类是一个Spring组件,在其doWork()
方法中具有特定于应用程序的代码 。但是,它还提供了一个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
method参数自动装配country
到age
另一个名为的bean 的属性值privateInstance
。Spring Expression Language元素通过符号定义属性的值#{ <expression> }
。对于@Value
注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,您还可以声明类型的工厂方法参数 InjectionPoint
(或其更具体的子类:),DependencyDescriptor
以访问触发创建当前bean的请求注入点。请注意,这仅适用于实例创建bean实例,而不适用于注入现有实例。因此,此功能对原型范围的bean最有意义。对于其他作用域,工厂方法只能看到触发在给定作用域中创建新Bean实例的注入点(例如,触发创建惰性单例bean的依赖项)。在这种情况下,您可以使用提供的注入点元数据和语义关注。以下示例显示了如何使用InjectionPoint
:
@Component public class FactoryMethodComponent { @Bean @Scope("prototype") public TestBean prototypeInstance(InjectionPoint injectionPoint) { return new TestBean("prototypeInstance for " + injectionPoint.getMember()); } }
将@Bean
在普通的Spring组件方法比春天里的同行处理方式不同@Configuration
类。不同之处在于@Component
,CGLIB不会增强类来拦截方法和字段的调用。CGLIB代理是调用类中@Bean
方法中的方法或字段@Configuration
创建对协作对象的bean元数据引用的方法。这些方法不是用普通的Java语义调用的,而是通过容器来提供通常的生命周期管理和Spring bean的代理,即使在通过对@Bean
方法的编程调用引用其他bean时也是如此。相反,@Bean
在plain 中的方法中调用方法或字段@Component
class具有标准的Java语义,没有特殊的CGLIB处理或其他约束应用。
您可以将@Bean 方法声明为static ,允许在不创建包含配置类作为实例的情况下调用它们。这在定义后处理器bean(例如,类型BeanFactoryPostProcessor 或 BeanPostProcessor )时特别有意义,因为这样的bean在容器生命周期的早期就会初始化,并且应该避免在那时触发配置的其他部分。由于技术限制,对静态@Bean 方法的调用永远不会被容器拦截,甚至在@Configuration 类中也不会被拦截(如本节前面所述):CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean 方法具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。方法的Java语言可见性@Bean 不会立即影响Spring容器中的结果bean定义。您可以根据自己的需要在非@Configuration 类中自由声明工厂方法,也可以在任何地方自由声明静态方法。但是,类中的常规@Bean 方法@Configuration 需要可以覆盖 - 也就是说,它们不能声明为private 或final 。@Bean 还可以在给定组件或配置类的基类上以及在由组件或配置类实现的接口中声明的Java 8缺省方法上发现方法。这使得在编写复杂的配置安排时具有很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8默认方法实现多重继承。最后,单个类可以@Bean 为同一个bean 保存多个方法,作为根据运行时可用依赖性使用多个工厂方法的安排。这与在其他配置方案中选择“最贪婪”构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变体,类似于容器在多个@Autowired 构造函数之间进行选择的方式。 | |
---|---|
1.10.6。命名自动检测的组件
当组件作为扫描过程的一部分自动检测时,其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>
作为一般规则,考虑在其他组件可能对其进行显式引用时使用注释指定名称。另一方面,只要容器负责接线,自动生成的名称就足够了。
1.10.7。为自动检测组件提供范围
与Spring管理的组件一样,自动检测组件的默认和最常见的范围是singleton
。但是,有时您需要一个可以由@Scope
注释指定的不同范围。您可以在注释中提供范围的名称,如以下示例所示:
@Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... }
@Scope 注释仅在具体bean类(对于带注释的组件)或工厂方法(对于@Bean 方法)上进行了内省。与XML bean定义相比,没有bean定义继承的概念,类级别的继承层次结构与元数据目的无关。 | |
---|---|
有关特定于Web的范围(如Spring上下文中的“request”或“session”)的详细信息,请参阅请求,会话,应用程序和WebSocket范围。与这些范围的预构建注释一样,您也可以使用Spring的元注释方法编写自己的范围注释:例如,自定义注释元注释@Scope("prototype")
,可能还声明自定义范围代理模式。
要为范围解析提供自定义策略而不是依赖基于注释的方法,您可以实现该 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>
使用某些非单例作用域时,可能需要为作用域对象生成代理。这种推理在Scoped Beans中描述为Dependencies。为此,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>
1.10.8。使用注释提供限定符元数据
在@Qualifier
注释中讨论与预选赛微调基于注解的自动连接。该部分中的示例演示了@Qualifier
在解析自动线候选时使用注释和自定义限定符注释来提供细粒度控制。因为这些示例基于XML bean定义,所以通过使用XML中 元素的qualifier
或meta
元素bean
元素在候选bean定义上提供限定符元数据。当依靠类路径扫描来自动检测组件时,可以在候选类上为类型级注释提供限定符元数据。以下三个示例演示了此技术:
@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在其限定符元数据中提供变体,因为每个元数据都是按照 - 实例而不是每班。 | |
---|---|
1.10.9。生成候选组件索引
虽然类路径扫描速度非常快,但可以通过在编译时创建候选的静态列表来提高大型应用程序的启动性能。在此模式下,所有作为组件扫描目标的模块都必须使用此机制。
您的现有@ComponentScan 或<context:component-scan 指令必须保持原样,以请求上下文扫描某些包中的候选项。当ApplicationContext 检测到这样的索引时,它会自动使用它而不是扫描类路径。 | |
---|---|
要生成索引,请为包含组件扫描指令目标的组件的每个模块添加其他依赖项。以下示例显示了如何使用Maven执行此操作:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-indexer</artifactId> <version>5.1.5.RELEASE</version> <optional>true</optional> </dependency> </dependencies>
对于Gradle 4.5及更早版本,应在compileOnly
配置中声明依赖项,如以下示例所示:
dependencies { compileOnly "org.springframework:spring-context-indexer:5.1.5.RELEASE" }
使用Gradle 4.6及更高版本时,应在annotationProcessor
配置中声明依赖项,如以下示例所示:
dependencies { annotationProcessor "org.springframework:spring-context-indexer:5.1.5.RELEASE" }
该进程生成一个META-INF/spring.components
包含在jar文件中的文件。
在IDE中使用此模式时,spring-context-indexer 必须将其注册为注释处理器,以确保在更新候选组件时索引是最新的。 | |
---|---|
META-INF/spring.components 在类路径中找到 a时,将自动启用索引。如果索引部分可用一些库(或用例),但整个应用程序无法建立,可以通过设置回退到普通类路径安排(好像没有索引存在的话)spring.index.ignore 来 true ,无论是作为一个系统属性或spring.properties 类路径根目录下的文件。 | |
---|---|
1.11。使用JSR 330标准注释
从Spring 3.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> | |
---|---|
1.11.1。用@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
在字段级别,方法级别和构造函数 - 参数级别使用。此外,您可以将注入点声明为aProvider
,允许按需访问较短范围的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) { ... } }
1.11.2。@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的构造型模型来构建自定义组件注释。 | |
---|---|
1.11.3。JSR-330标准注释的局限性
使用标准注释时,您应该知道某些重要功能不可用,如下表所示:
弹簧 | javax.inject。* | javax.inject限制/评论 |
---|---|---|
@Autowired | @注入 | @Inject 没有“必需”属性。可以与Java 8一起使用Optional 。 |
@零件 | @Named / @ManagedBean | JSR-330不提供可组合模型,只是一种识别命名组件的方法。 |
@Scope( “单”) | @辛格尔顿 | JSR-330的默认范围就像Spring一样prototype 。但是,为了使其与Spring的一般默认值保持一致,singleton 默认情况下在Spring容器中声明的JSR-330 bean是一个默认值。为了使用除以外的范围singleton ,您应该使用Spring的@Scope 注释。javax.inject 还提供了@Scope注释。然而,这个仅用于创建自己的注释。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier 只是构建自定义限定符的元注释。具体String 限定符(如@Qualifier 带有值的Spring )可以通过关联javax.inject.Named 。 |
@值 | - | 没有等价物 |
@需要 | - | 没有等价物 |
@懒 | - | 没有等价物 |
的ObjectFactory | 提供商 | javax.inject.Provider 是Spring的直接替代品ObjectFactory ,只有较短的get() 方法名称。它也可以与Spring @Autowired 或非注释构造函数和setter方法结合使用。 |
1.12。基于Java的容器配置
本节介绍如何在Java代码中使用注释来配置Spring容器。它包括以下主题:
1.12.1。基本概念:@Bean
和@Configuration
Spring的新Java配置支持中的中心工件是 @Configuration
注释类和@Bean
注释方法。
该@Bean
注释被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于那些熟悉Spring的<beans/>
XML配置的人来说,@Bean
注释与<bean/>
元素扮演的角色相同。你可以@Bean
在任何Spring中使用-annotated方法 @Component
。但是,它们最常用于@Configuration
豆类。
对类进行注释@Configuration
表明其主要目的是作为bean定义的来源。此外,@Configuration
类允许通过调用@Bean
同一类中的其他方法来定义bean间依赖关系。最简单的@Configuration
类如下:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
上面的AppConfig
类等效于以下Spring <beans/>
XML:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
完整@Configuration vs“lite”@Bean模式?
当@Bean
在未注释的类中声明方法时 @Configuration
,它们被称为以“精简”模式处理。在一个@Component
或甚至在一个普通的旧类中声明的Bean方法被认为是“精简”,包含类的主要目的不同,并且@Bean
方法在那里是一种奖励。例如,服务组件可以通过@Bean
每个适用组件类的附加方法将管理视图公开给容器。在这种情况下,@Bean
方法是通用的工厂方法机制。
与full不同@Configuration
,lite @Bean
方法不能声明bean间依赖关系。相反,它们对其包含组件的内部状态进行操作,并且可选地,对它们可以声明的参数进行操作。@Bean
因此,这种方法不应该引用其他 @Bean
方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是不必在运行时应用CGLIB子类,因此在类设计方面没有限制(也就是说,包含类可能是final
等等)。
在常见的场景中,@Bean
方法将在@Configuration
类中声明,确保始终使用“完整”模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止@Bean
通过常规Java调用意外地调用相同的 方法,这有助于减少在“精简”模式下操作时难以跟踪的细微错误。
的@Bean
和@Configuration
注解的深度在以下章节中讨论。首先,我们将介绍使用基于Java的配置创建弹簧容器的各种方法。
1.12.2。使用。实例化Spring容器AnnotationConfigApplicationContext
以下部分AnnotationConfigApplicationContext
介绍了在Spring 3.0中引入的Spring。这种通用ApplicationContext
实现不仅能够接受@Configuration
类作为输入,还能接受 @Component
使用JSR-330元数据注释的普通类和类。
当@Configuration
提供类作为输入时,@Configuration
类本身被注册为bean定义,并且@Bean
类中的所有声明的方法也被注册为bean定义。
当@Component
提供JSR-330类时,它们被注册为bean定义,并且假定DI元数据例如@Autowired
或@Inject
在必要时在这些类中使用。
简单的施工
与实例化a时Spring XML文件用作输入的方式大致相同 ClassPathXmlApplicationContext
,可以@Configuration
在实例化时使用类作为输入AnnotationConfigApplicationContext
。这允许完全无XML使用Spring容器,如以下示例所示:
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
使用Spring依赖注入注解,例如@Autowired
。
用编程方式构建容器 register(Class<?>…)
您可以AnnotationConfigApplicationContext
使用no-arg构造函数实例化一个,然后使用该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用户可能熟悉与Spring context: 命名空间等效的XML声明,如下例所示:<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 方法都被处理并在容器中注册为bean定义。 | |
---|---|
支持Web应用程序 AnnotationConfigWebApplicationContext
可用的WebApplicationContext
变体。在配置Spring servlet侦听器,Spring MVC等时 ,可以使用此实现。以下代码段配置典型的Spring MVC Web应用程序(请注意context-param和init-param的使用):AnnotationConfigApplicationContext``AnnotationConfigWebApplicationContext``ContextLoaderListener``DispatcherServlet``web.xml``contextClass
<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>
1.12.3。使用@Bean
注释
@Bean
是方法级注释和XML <bean/>
元素的直接模拟。注释支持一些提供的属性<bean/>
,例如:* init-method * destroy-method * autowiring * name
。
您可以在带@Bean
注释的类@Configuration
或带 注释的类中使用注释@Component
。
声明一个Bean
要声明bean,可以使用注释注释方法@Bean
。您可以使用此方法在ApplicationContext
指定为方法的返回值的类型中注册bean定义。默认情况下,bean名称与方法名称相同。以下示例显示了@Bean
方法声明:
@Configuration public class AppConfig { @Bean public TransferServiceImpl transferService() { return new TransferServiceImpl(); } }
上述配置与以下Spring XML完全等效:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
这两个声明都将一个名为transferService
available 的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依赖项
带@Bean
注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们TransferService
需要a 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的init-method
,并destroy-method
在属性上的bean
元素,如下例所示:
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配置定义的具有public close 或shutdown method的bean 会自动使用销毁回调登记。如果您有公共close 或shutdown 方法,并且您不希望在容器关闭时调用它,则可以添加@Bean(destroyMethod="") 到bean定义以禁用默认(inferred) 模式。对于使用JNDI获取的资源,您可能希望默认执行此操作,因为其生命周期在应用程序之外进行管理。特别是,确保始终为a执行此操作DataSource ,因为已知它在Java EE应用程序服务器上存在问题。以下示例显示如何防止自动销毁回调 DataSource :@Bean(destroyMethod="") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); } 此外,使用@Bean 方法,您通常使用编程JNDI查找,通过使用Spring JndiTemplate 或JndiLocatorDelegate 帮助程序或直接JNDI InitialContext 用法但不使用JndiObjectFactoryBean 变量(这将强制您将返回类型声明为FactoryBean 类型而不是实际目标类型,使得更难以用于其他@Bean 打算在此处引用所提供资源的方法中的交叉引用调用。 | |
---|---|
在BeanOne
前面注释中的示例的情况下,init()
在构造期间直接调用该方法同样有效,如以下示例所示:
@Configuration public class AppConfig { @Bean public BeanOne beanOne() { BeanOne beanOne = new BeanOne(); beanOne.init(); return beanOne; } // ... }
当您直接使用Java工作时,您可以使用对象执行任何您喜欢的操作,并且不必总是依赖于容器生命周期。 | |
---|---|
指定Bean范围
Spring包含@Scope
注释,以便您可以指定bean的范围。
使用@Scope
注释
您可以指定使用@Bean
注释定义的bean 应具有特定范围。您可以使用Bean Scopes部分中指定的任何标准作用域 。
默认范围是singleton
,但您可以使用@Scope
注释覆盖它,如以下示例所示:
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
@Scope
和 scoped-proxy
Spring提供了一种通过作用域代理处理作用域依赖项的便捷方法 。使用XML配置时创建此类代理的最简单方法是<aop:scoped-proxy/>
元素。使用@Scope
注释在Java中配置bean 提供了对该proxyMode
属性的等效支持。默认值为no proxy(ScopedProxyMode.NO
),但您可以指定ScopedProxyMode.TARGET_CLASS
或ScopedProxyMode.INTERFACES
。
如果将scoped代理示例从XML参考文档(请参阅范围代理)移植 到@Bean
使用Java,它类似于以下内容:
// 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
方法的名称作为结果bean的名称。但是,可以使用name
属性覆盖此功能,如以下示例所示:
@Configuration public class AppConfig { @Bean(name = "myThing") public Thing thing() { return new Thing(); } }
Bean Aliasing
正如Naming Beans中所讨论的,有时需要为单个bean提供多个名称,也称为bean别名。 为此目的name
,@Bean
注释的属性接受String数组。以下示例显示如何为bean设置多个别名:
@Configuration public class AppConfig { @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
Bean描述
有时,提供更详细的bean文本描述会很有帮助。当bean(可能通过JMX)进行监视时,这可能特别有用。
要向a添加描述@Bean
,可以使用 @Description
注释,如以下示例所示:
@Configuration public class AppConfig { @Bean @Description("Provides a basic example of a bean") public Thing thing() { return new Thing(); } }
1.12.4。使用@Configuration
注释
@Configuration
是一个类级别的注释,指示对象是bean定义的来源。@Configuration
classes通过公共@Bean
注释方法声明bean 。@Bean
对@Configuration
类上的方法的调用也可用于定义bean间依赖项。请参阅基本概念:@Bean
和@Configuration
一般性介绍。
注入bean间依赖关系
当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间依赖关系的@Bean 方法只有在@Configuration 类中声明方法时才有效。您不能使用普通@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(); } } }
有关基于Java的配置如何在内部工作的更多信息
请考虑以下示例,该示例显示了@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的范围,行为可能会有所不同。我们在这里谈论单身人士。 | |
---|---|
从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经重新打包org.springframework.cglib 并直接包含在spring-core JAR中。 | |
---|---|
由于CGLIB在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从4.3开始,配置类允许使用任何构造函数,包括使用 @Autowired 默认注入的单个非默认构造函数声明。如果您希望避免任何CGLIB强加的限制,请考虑@Bean 在非@Configuration 类上声明您的方法(例如,在普通@Component 类上)。@Bean 然后拦截方法之间的跨方法调用,因此您必须完全依赖于构造函数或方法级别的依赖注入。 | |
---|---|
1.12.5。编写基于Java的配置
Spring的基于Java的配置功能允许您撰写注释,这可以降低配置的复杂性。
使用@Import
注释
就像<import/>
在Spring XML文件中使用该元素来帮助模块化配置一样,@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
在构造期间记住可能大量的 类。
从Spring Framework 4.2开始,@Import 还支持引用常规组件类,类似于AnnotationConfigApplicationContext.register 方法。如果要避免组件扫描,这一点特别有用,可以使用一些配置类作为明确定义所有组件的入口点。 | |
---|---|
注入对导入@Bean
定义的依赖性
前面的例子有效,但很简单。在大多数实际情况中,bean跨配置类彼此依赖。使用XML时,这不是问题,因为不涉及编译器,并且您可以声明 ref="someBean"
并信任Spring在容器初始化期间解决它。使用@Configuration
类时,Java编译器会对配置模型施加约束,因为对其他bean的引用必须是有效的Java语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的,一个@Bean
方法可以有任意数量的参数来描述bean的依赖关系。考虑以下更多真实场景,其中包含几个@Configuration
类,每个类都依赖于其他类中声明的bean:
@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:这意味着它们可以利用@Autowired
和@Value
注入以及与任何其他bean相同的其他功能。
确保以这种方式注入的依赖项只是最简单的类型。@Configuration 在上下文初始化期间很早就处理了类,并且强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能采用基于参数的注入,如前面的示例所示。另外,要特别注意BeanPostProcessor 和BeanFactoryPostProcessor 定义@Bean 。这些通常应该声明为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 仅在Spring Framework 4.3中支持类中的 构造函数注入。另请注意,无需指定@Autowired 目标bean是否仅定义了一个构造函数。在前面的示例中,构造函数@Autowired 上没有必要RepositoryConfig 。 | |
---|---|
完全符合条件的进口豆类,便于导航
在前面的场景中,使用@Autowired
效果很好并提供了所需的模块性,但确定声明自动装配的bean定义的确切位置仍然有些模棱两可。例如,作为开发人员ServiceConfig
,您如何确切地知道@Autowired AccountRepository
bean的声明位置?它在代码中并不明确,这可能就好了。请记住, 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之前创建特定的其他bean,超出后者的直接依赖意味着什么)。 | |
---|---|
有条件地包括@Configuration
类或@Bean
方法
基于某些任意系统状态,有条件地启用或禁用完整@Configuration
类或甚至单个@Bean
方法通常很有用。一个常见的例子是@Profile
只有在Spring中启用了特定的配置文件时才使用注释来激活bean Environment
( 有关详细信息,请参阅Bean定义配置文件)。
该@Profile
注释是通过使用一种称为更灵活的注释实际执行@Conditional
。该@Conditional
注释指示特定org.springframework.context.annotation.Condition
前应谘询的实施@Bean
是注册。
Condition
接口的实现提供了一个matches(…)
返回true
或的方法false
。例如,以下清单显示了Condition
用于的实际 实现@Profile
:
@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
更多详细信息,请参阅javadoc。
结合Java和XML配置
Spring的@Configuration
类支持并非旨在成为Spring XML的100%完全替代品。某些工具(如Spring XML命名空间)仍然是配置容器的理想方法。在XML方便或必要的情况下,您可以选择:例如,通过使用“以XML为中心”的方式实例化容器ClassPathXmlApplicationContext
,或者通过使用AnnotationConfigApplicationContext
和@ImportResource
注释以“以Java为中心”的方式实例化它。 根据需要导入XML。
以XML为中心的@Configuration
类的使用
最好从XML引导Spring容器并@Configuration
以ad-hoc方式包含 类。例如,在使用Spring XML的大型现有代码库中,可以@Configuration
根据需要更轻松地创建类,并将其包含在现有XML文件中。在本节的后面部分,我们将介绍@Configuration
在这种“以XML为中心”的情况下使用类的选项。
将@Configuration
类声明为普通的Spring <bean/>
元素
请记住,@Configuration
类最终是容器中的bean定义。在本系列示例中,我们创建了一个@Configuration
名为的类,AppConfig
并将其system-test-config.xml
作为<bean/>
定义包含在其中。因为 <context:annotation-config/>
已打开,容器会识别@Configuration
注释并 正确处理@Bean
声明的方法AppConfig
。
以下示例显示了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://本地主机/ 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 不严格要求显式bean 。 | |
---|---|
使用<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>
@Configuration
以类为中心的XML使用 @ImportResource
在@Configuration
类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,您可以@ImportResource
根据需要使用和定义尽可能多的XML。这样做可以实现“以Java为中心”的方法来配置容器并将XML保持在最低限度。以下示例(包括配置类,定义bean的XML文件,属性文件和main
类)显示了如何使用@ImportResource
注释来实现根据需要使用XML的“以Java为中心”的配置:
@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://本地主机/ XDB jdbc.username = SA jdbc.password =
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }
1.13。环境抽象
该Environment
接口是集成在容器模型应用环境的两个关键方面的抽象:型材 和性能。
配置文件是仅在给定配置文件处于活动状态时才向容器注册的Bean定义的命名逻辑组。可以将Bean分配给配置文件,无论是以XML还是使用注释定义。Environment
与配置文件相关的对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。
属性在几乎所有应用程序中都发挥着重要作用,可能源自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc Properties
对象,Map
对象等。Environment
与属性相关的对象的作用是为用户提供方便的服务接口,用于配置属性源并从中解析属性。
1.13.1。Bean定义配置文件
Bean定义配置文件在核心容器中提供了一种机制,允许在不同环境中注册不同的bean。“环境”这个词对不同的用户来说意味着不同的东西,这个功能可以帮助解决许多用例,包括:
-
在QA或生产环境中,针对开发中的内存数据源而不是从JNDI查找相同的数据源。
-
仅在将应用程序部署到性能环境时注册监视基础结构。
-
为客户A和客户B部署注册bean的自定义实现。
考虑实际应用中的第一个用例,它需要一个 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用户已经设计了许多方法来完成这项工作,通常依赖于系统环境变量和<import/>
包含${placeholder}
令牌的XML 语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是核心容器功能,可为此问题提供解决方案。
如果我们概括了前面的特定于环境的bean定义示例中显示的用例,我们最终需要在某些上下文中注册某些bean定义,而在其他上下文中则不需要。您可以说您希望在情境A中注册特定的bean定义配置文件,在情况B中注册不同的配置文件。我们首先更新配置以反映此需求。
运用 @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 方法,您通常选择使用编程JNDI查找,使用Spring的JndiTemplate / JndiLocatorDelegate helper或InitialContext 前面显示的直接JNDI 用法,但不使用JndiObjectFactoryBean 变量,这会强制您将返回类型声明为FactoryBean 类型。 | |
---|---|
配置文件字符串可以包含简单的配置文件名称(例如production
)或配置文件表达式。概要表达式允许表达更复杂的概要逻辑(例如,production & us-east
)。配置文件表达式支持以下运算符:
-
!
:配置文件的逻辑“不” -
&
:配置文件的逻辑“和” -
|
:配置文件的逻辑“或”
不使用括号, 不能混合使用& 和| 运算符。例如, 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 注释。如果a @Component 或@Configuration class被标记@Profile({"p1", "p2"}) ,则除非已激活配置文件'p1'或'p2',否则不会注册或处理该类。如果给定的配置文件以NOT运算符(! )作为前缀,则仅在配置文件未激活时才注册带注释的元素。例如,@Profile({"p1", "!p2"}) 如果配置文件“p1”处于活动状态或配置文件“p2”未激活,则会发生注册。 | |
---|---|
@Profile
也可以在方法级别声明只包含配置类的一个特定bean(例如,对于特定bean的替代变体),如以下示例所示:
@Configuration public class AppConfig { @Bean("dataSource") @Profile("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") public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
该standaloneDataSource 方法仅在development 配置文件中可用。 | |
---|---|
该jndiDataSource 方法仅在production 配置文件中可用。 |
使用@Profile on @Bean 方法,可能会应用特殊方案:对于@Bean 相同Java方法名称的重载方法(类似于构造函数重载),@Profile 需要在所有重载方法上一致地声明条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,@Profile 不能用于选择具有特定参数签名的重载方法。在创建时,Spring的构造函数解析算法遵循同一bean的所有工厂方法之间的分辨率。如果要定义具有不同配置文件条件的备用Bean,请使用通过使用@Bean name属性指向相同bean名称的不同Java方法名称,如上例所示。如果参数签名都是相同的(例如,所有变体都具有no-arg工厂方法),那么这是首先在有效的Java类中表示这种排列的唯一方法(因为只有一个特定名称和参数签名的方法)。 | |
---|---|
XML Bean定义配置文件
XML对应物是元素的profile
属性<beans>
。我们之前的示例配置可以在两个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/>
在同一文件中使用split和nest 元素,如下例所示:
<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副本不支持前面描述的配置文件表达式。但是,可以通过使用! 运算符来否定轮廓。也可以通过嵌套配置文件来应用逻辑“和”,如以下示例所示:<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 如果两个production 和 us-east 配置文件都处于活动状态,则会公开Bean 。 | |
---|---|
激活个人资料
现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到NoSuchBeanDefinitionException
抛出,因为容器找不到名为的Spring bean dataSource
。
激活配置文件可以通过多种方式完成,但最直接的方法是以编程方式对Environment
可通过API提供的API进行操作ApplicationContext
。以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("development"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();
此外,您还可以通过spring.profiles.active
属性声明性地激活配置文件,该 属性可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml
或甚至作为JNDI中的条目来指定(请参阅PropertySource
抽象)。在集成测试中,可以使用模块中的@ActiveProfiles
注释声明活动配置文件spring-test
(请参阅使用环境配置文件的上下文配置)。
请注意,配置文件不是“任何 - 或”命题。您可以一次激活多个配置文件。以编程方式,您可以为setActiveProfiles()
方法提供多个配置文件名称,该 方法接受String…
varargs。以下示例激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明性地,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示默认启用的配置文件。请考虑以下示例:
@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(); } }
如果没有激活配置文件,dataSource
则创建该配置文件。您可以将此视为一种为一个或多个bean提供默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。
您可以通过更改默认的配置文件的名称setDefaultProfiles()
上Environment
,或者声明,通过使用spring.profiles.default
属性。
1.13.2。PropertySource
抽象化
Spring的Environment
抽象提供了对可配置的属性源层次结构的搜索操作。请考虑以下列表:
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
。A PropertySource
是对任何键值对源的简单抽象,Spring StandardEnvironment
配置有两个PropertySource对象 - 一个表示JVM系统属性集(System.getProperties()
),另一个表示系统环境变量集(System.getenv()
)。
这些默认属性源StandardEnvironment 适用于独立应用程序。StandardServletEnvironment 填充了其他默认属性源,包括servlet配置和servlet上下文参数。它可以选择启用a JndiPropertySource 。有关详细信息,请参阅javadoc。 | |
---|---|
具体来说,当您使用时StandardEnvironment
,env.containsProperty("my-property")
如果运行时存在my-property
系统属性或my-propertyi
环境变量,则调用返回true 。
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果my-property 在调用期间恰好在两个位置都设置了属性env.getProperty("my-property") ,则系统属性值“wins”并返回。请注意,属性值不会合并,而是由前面的条目完全覆盖。对于公共StandardServletEnvironment 层次结构,完整层次结构如下,最高优先级条目位于顶部:ServletConfig参数(如果适用 - 例如,在DispatcherServlet 上下文的情况下)ServletContext参数(web.xml context-param条目)JNDI环境变量(java:comp/env/ 条目)JVM系统属性(-D 命令行参数)JVM系统环境(操作系统环境变量) | |
---|---|
最重要的是,整个机制是可配置的。您可能希望将自定义的属性源集成到此搜索中。为此,请实现并实例化您自己的PropertySource
并将其添加到PropertySources
当前的集合中Environment
。以下示例显示了如何执行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());
在上面的代码中,MyPropertySource
在搜索中添加了最高优先级。如果它包含my-property
属性,则检测并返回该属性,以支持my-property
任何其他属性PropertySource
。所述 MutablePropertySources
API公开了大量的,其允许该组的属性源的精确操作方法。
1.13.3。运用@PropertySource
该@PropertySource
注解提供便利和声明的机制添加PropertySource
到Spring的Environment
。
给定一个名为app.properties
包含键值对的文件testbean.name=myTestBean
,以下@Configuration
类使用以下@PropertySource
方式调用testBean.getName()
return 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
则抛出a。
该@PropertySource 注释是可重复的,根据Java的8约定。但是,所有这些@PropertySource 注释都需要在同一级别声明,可以直接在配置类上声明,也可以在同一自定义注释中作为元注释声明。不建议混合直接注释和元注释,因为直接注释有效地覆盖了元注释。 | |
---|---|
1.13.4。占位符决议在声明中
从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。这已不再是这种情况。因为Environment
抽象集成在整个容器中,所以很容易通过它来解决占位符的分辨率。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,或完全删除它们。您也可以根据需要将自己的属性源添加到混合中。
具体而言,以下语句无论customer
属性的定义位置如何都可以使用,只要它在以下位置可用Environment
:
<beans> <import resource="com/bank/service/${customer}-config.xml"/> </beans>
1.14。注册一个LoadTimeWeaver
在LoadTimeWeaver
用于由Spring动态变换的类,因为它们被装载到Java虚拟机(JVM)。
要启用加载时编织,可以将其添加@EnableLoadTimeWeaving
到其中一个 @Configuration
类中,如以下示例所示:
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
或者,对于XML配置,您可以使用以下context:load-time-weaver
元素:
<beans> <context:load-time-weaver/> </beans>
一旦为其中的ApplicationContext
任何bean 配置,就ApplicationContext
可以实现LoadTimeWeaverAware
,从而接收对加载时weaver实例的引用。这与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 EE Web应用程序的正常启动过程的一部分。
为了BeanFactory
以更加面向框架的样式增强功能,上下文包还提供以下功能:
-
通过
MessageSource
界面访问i18n风格的消息。 -
通过
ResourceLoader
界面访问URL和文件等资源。 -
事件发布,即
ApplicationListener
通过使用接口实现接口的beanApplicationEventPublisher
。 -
加载多个(分层)上下文,让每个上下文通过
HierarchicalBeanFactory
界面聚焦在一个特定层上,例如应用程序的Web层 。
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
则抛出a。 -
String getMessage(MessageSourceResolvable resolvable, Locale locale)
:前面方法中使用的所有属性也包装在一个名为的类中MessageSourceResolvable
,您可以使用此方法。
当ApplicationContext
被加载时,它自动搜索MessageSource
在上下文中定义的bean。bean必须具有名称messageSource
。如果找到这样的bean,则对前面方法的所有调用都被委托给消息源。如果未找到任何消息源,则ApplicationContext
尝试查找包含具有相同名称的bean的父级。如果是,它使用该bean作为MessageSource
。如果 ApplicationContext
找不到任何消息源,DelegatingMessageSource
则实例化为空 以便能够接受对上面定义的方法的调用。
春天提供了两种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
,exceptions
并windows
在类路径中定义。解决消息的任何请求都以JDK标准的方式处理,通过ResourceBundle
对象解析消息。出于示例的目的,假设上述两个资源包文件的内容如下:
# 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); }
上述程序产生的结果如下:
鳄鱼摇滚!
总而言之,它MessageSource
是在一个名为的文件中定义的,该文件beans.xml
存在于类路径的根目录中。该messageSource
bean定义是指通过它的一些资源包的basenames
属性。这是在列表中传递的三个文件basenames
属性存在于你的classpath根目录的文件,被称为format.properties
,exceptions.properties
和 windows.properties
分别。
下一个示例显示传递给消息查找的参数。这些参数将转换为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()
方法得到的结果如下:
userDao参数是必需的。
关于国际化(“i18n”),Spring的各种MessageSource
实现遵循与标准JDK相同的区域设置解析和回退规则 ResourceBundle
。总之,和继续该示例messageSource
先前定义的,如果你想解析British(消息en-GB
)语言环境中,您将创建文件名为format_en_GB.properties
,exceptions_en_GB.properties
和 windows_en_GB.properties
分别。
通常,区域设置解析由应用程序的周围环境管理。在以下示例中,手动指定解析(英国)消息的区域设置:
#innex_en_GB.properties argument.required = Ebagum小伙子,我说,{0}参数是必需的。
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小伙子,我说,'userDao'论证是必需的。
您还可以使用该MessageSourceAware
界面获取对MessageSource
已定义的任何内容的引用 。在创建和配置bean时,将使用应用程序上下文注入ApplicationContext
实现MessageSourceAware
接口的任何bean MessageSource
。
作为替代ResourceBundleMessageSource ,Spring提供了一个 ReloadableResourceBundleMessageSource 类。此变体支持相同的捆绑文件格式,但比基于标准JDK的ResourceBundleMessageSource 实现更灵活 。特别是,它允许从任何Spring资源位置(不仅从类路径)读取文件,并支持bundle属性文件的热重新加载(同时有效地在它们之间缓存它们)。有关ReloadableResourceBundleMessageSource 详细信息,请参阅javadoc。 | |
---|---|
1.15.2。标准和自定义事件
ApplicationContext
通过ApplicationEvent
类和ApplicationListener
接口提供事件处理。如果将实现ApplicationListener
接口的bean 部署到上下文中,则每次 ApplicationEvent
将其发布到该ApplicationContext
bean时,都会通知该bean。从本质上讲,这是标准的Observer设计模式。
从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注释的模型以及发布任意事件(即不一定从中扩展的对象ApplicationEvent )的能力。当发布这样的对象时,我们将它包装在一个事件中。 | |
---|---|
下表描述了Spring提供的标准事件:
事件 | 说明 |
---|---|
ContextRefreshedEvent | ApplicationContext 初始化或刷新时发布(例如,通过refresh() 在ConfigurableApplicationContext 接口上使用该方法)。这里,“初始化”意味着加载所有bean,检测并激活后处理器bean,预先实例化单例,并且ApplicationContext 对象已准备好使用。只要上下文尚未关闭,只要所选择的ApplicationContext 实际支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext 支持热刷新,但GenericApplicationContext 不支持 。 |
ContextStartedEvent | ApplicationContext 通过start() 在ConfigurableApplicationContext 接口上使用该方法 启动时发布。这里,“已启动”意味着所有Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未为自动启动配置的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent | ApplicationContext 通过stop() 在ConfigurableApplicationContext 接口上使用方法 停止时发布。这里,“停止”意味着所有Lifecycle bean都会收到明确的停止信号。可以通过start() 呼叫重新启动已停止的上下文 。 |
ContextClosedEvent | ApplicationContext 通过close() 在ConfigurableApplicationContext 接口上使用方法 关闭时发布。这里,“关闭”意味着所有单例bean都被销毁。封闭的环境达到了生命的终点。它无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于Web的事件,告诉所有bean已经为HTTP请求提供服务。请求完成后发布此事件。此事件仅适用于使用Spring的Web应用程序DispatcherServlet 。 |
您还可以创建和发布自己的自定义事件。以下示例显示了一个扩展Spring 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
,请在publishEvent()
方法上调用该方法 ApplicationEventPublisher
。通常,这是通过创建一个实现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容器检测到EmailService
实现 ApplicationEventPublisherAware
并自动调用setApplicationEventPublisher()
。实际上,传入的参数是Spring容器本身。您正通过其ApplicationEventPublisher
界面与应用程序上下文进行 交互。
要接收自定义ApplicationEvent
,您可以创建一个实现 ApplicationListener
并将其注册为Spring 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
界面的javadoc 。
以下示例显示了用于注册和配置上述每个类的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>
总而言之,当调用bean 的sendEmail()
方法时emailService
,如果有任何应该列入黑名单的电子邮件消息,BlackListEvent
则会发布类型的自定义事件 。该blackListNotifier
bean被注册为 ApplicationListener
与接收BlackListEvent
,此时它可以通知有关各方。
Spring的事件机制是为在同一应用程序上下文中的Spring bean之间的简单通信而设计的。但是,对于更复杂的企业集成需求,单独维护的 Spring Integration项目为构建基于众所周知的Spring编程模型的轻量级,面向模式,事件驱动的体系结构提供了完整的支持 。 | |
---|---|
基于注释的事件监听器
从Spring 4.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
表达式的注释的属性来添加额外的运行时过滤,该属性应该匹配以实际调用特定事件的方法。
以下示例显示了仅当content
事件的属性等于时,才能重写我们的通知程序以进行调用my-event
:
@EventListener(condition = "#blEvent.content == 'my-event'") public void processBlackListEvent(BlackListEvent blEvent) { // notify appropriate parties via notificationAddress... }
每个SpEL
表达式都针对专用上下文进行评估。下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理:
名称 | 地点 | 描述 | 例 |
---|---|---|---|
事件 | 根对象 | 实际的ApplicationEvent 。 | #root.event |
参数数组 | 根对象 | 用于调用目标的参数(作为数组)。 | #root.args[0] |
参数名称 | 评估背景 | 任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为没有调试信息),参数名称也可以在#a<#arg> where #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... }
异步侦听 器不支持此功能。 | |
---|---|
这个新方法ListUpdateEvent
为BlackListEvent
上述方法处理的每个方法发布一个新的方法。如果您需要发布多个事件,则可以返回一个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>
where 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 您作为事件发送的任何对象。 | |
---|---|
1.15.3。方便地访问低级资源
为了最佳地使用和理解应用程序上下文,您应该熟悉Spring的Resource
抽象,如 参考资料中所述。
应用程序上下文是a ResourceLoader
,可用于加载Resource
对象。A Resource
本质上是JDK java.net.URL
类的功能更丰富的版本。实际上,在适当Resource
的情况下包装一个实例的实现java.net.URL
。A Resource
可以透明的方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,任何可用标准URL描述的位置,以及一些其他变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际应用程序上下文类型。
您可以配置部署到应用程序上下文中的bean来实现特殊的回调接口,ResourceLoaderAware
在初始化时自动回调,应用程序上下文本身作为传入 ResourceLoader
。您还可以公开Resource
要用于访问静态资源的类型属性。它们像任何其他属性一样被注入其中。您可以将这些Resource
属性指定为简单String
路径,并依赖特殊的JavaBean PropertyEditor
(由上下文自动注册),以便Resource
在部署Bean时将这些文本字符串转换为实际对象。
提供给ApplicationContext
构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现进行适当处理。例如,ClassPathXmlApplicationContext
将简单的位置路径视为类路径位置。您还可以使用具有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义,而不管实际的上下文类型如何。
1.15.4。方便的Web应用程序的ApplicationContext实例化
您可以ApplicationContext
使用例如a以声明方式创建实例 ContextLoader
。当然,您也可以ApplicationContext
使用其中一个ApplicationContext
实现以编程方式创建实例。
您可以ApplicationContext
使用ContextLoaderListener
,注册一个,如下例所示:
<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
默认使用。当参数确实存在时,侦听器String
使用预定义的分隔符(逗号,分号和空格)分隔,并将值用作搜索应用程序上下文的位置。还支持Ant样式的路径模式。示例是/WEB-INF/*Context.xml
(对于名称Context.xml
以WEB-INF
目录结尾 并位于目录/WEB-INF/**/*Context.xml
中的所有文件)和(对于任何子目录中的所有此类文件WEB-INF
)。
1.15.5。将Spring部署ApplicationContext
为Java EE RAR文件
可以将Spring部署ApplicationContext
为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于ApplicationContext
能够访问Java EE服务器工具的独立引导(仅在Java EE环境中托管)。RAR部署是部署无头WAR文件的一种更自然的替代方案 - 实际上是一个没有任何HTTP入口点的WAR文件,仅用于ApplicationContext
在Java EE环境中引导Spring 。
RAR部署非常适用于不需要HTTP入口点但仅包含消息端点和预定作业的应用程序上下文。在这样的上下文中的Bean可以使用应用程序服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSource
实例和JMS ConnectionFactory
实例,并且还可以通过Spring的标准事务管理以及JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以WorkManager
通过Spring的TaskExecutor
抽象与应用程序服务器的JCA交互。
有关SpringContextResourceAdapter
RAR部署中涉及的配置详细信息,请参阅该类的javadoc 。
对于将Spring ApplicationContext简单部署为Java EE RAR文件:
-
将所有应用程序类打包到一个RAR文件(这是一个具有不同文件扩展名的标准JAR文件)。。将所有必需的库JAR添加到RAR存档的根目录中。。添加
META-INF/ra.xml
部署描述符(如javadoc中SpringContextResourceAdapter
所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”)。 -
将生成的RAR文件放入应用程序服务器的部署目录中。
这种RAR部署单元通常是独立的。它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的交互ApplicationContext 通常通过与其他模块共享的JMS目标进行。ApplicationContext 例如,基于RAR的还可以调度一些作业或对文件系统(或类似物)中的新文件作出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这可以由同一台机器上的其他应用程序模块使用。 | |
---|---|
1.16。该BeanFactory
该BeanFactory
API提供了春天的IoC功能的基本依据。其特定合同主要用于与Spring的其他部分和相关的第三方框架集成,其DefaultListableBeanFactory
实现是更高级别GenericApplicationContext
容器中的关键委托。
BeanFactory
和相关接口(例如BeanFactoryAware
,InitializingBean
, DisposableBean
)对于其他框架组件的重要结合点。通过不需要任何注释或甚至反射,它们允许容器与其组件之间的非常有效的交互。应用程序级bean可以使用相同的回调接口,但通常更喜欢通过注释或通过编程配置进行声明性依赖注入。
请注意,核心BeanFactory
API级别及其DefaultListableBeanFactory
实现不会对配置格式或要使用的任何组件注释做出假设。所有这些风格都通过扩展(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)进行,并BeanDefinition
作为核心元数据表示在共享对象上运行。这是使Spring的容器如此灵活和可扩展的本质。
1.16.1。BeanFactory
还是ApplicationContext
?
本节介绍之间的差异BeanFactory
和 ApplicationContext
容器级别和引导的意义。
您应该使用a,ApplicationContext
除非您有充分的理由不这样做, GenericApplicationContext
并将其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现。这些是Spring用于所有常见目的的核心容器的主要入口点:加载配置文件,触发类路径扫描,以编程方式注册bean定义和带注释的类,以及(从5.0开始)注册功能bean定义。
因为a ApplicationContext
包含a的所有功能BeanFactory
,所以BeanFactory
除了需要完全控制bean处理的场景之外,通常建议使用它。在一个ApplicationContext
(例如 GenericApplicationContext
实现)中,按照约定(即通过bean名称或bean类型 - 特别是后处理器)检测到几种bean,而plain DefaultListableBeanFactory
对任何特殊bean都是不可知的。
对于许多扩展容器功能,例如注释处理和AOP代理,BeanPostProcessor
扩展点是必不可少的。如果仅使用普通DefaultListableBeanFactory
处理器,则默认情况下不会检测到并激活此类后处理器。这种情况可能令人困惑,因为您的bean配置实际上没有任何问题。相反,在这种情况下,容器需要通过额外的设置完全自举。
下表列出了提供的功能BeanFactory
和 ApplicationContext
接口和实现。
特征 | BeanFactory | ApplicationContext |
---|---|---|
Bean实例化/布线 | 是 | 是 |
集成的生命周期管理 | 没有 | 是 |
自动BeanPostProcessor 注册 | 没有 | 是 |
自动BeanFactoryPostProcessor 注册 | 没有 | 是 |
方便MessageSource 访问(内化) | 没有 | 是 |
内置ApplicationEvent 发布机制 | 没有 | 是 |
要使用a显式注册bean后处理器DefaultListableBeanFactory
,您需要以编程方式调用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
要应用于BeanFactoryPostProcessor
plain DefaultListableBeanFactory
,需要调用其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 PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都不方便,这就是为什么各种ApplicationContext
变体优于DefaultListableBeanFactory
Spring支持的应用程序中的普通模式 ,尤其是在典型企业设置中依赖于BeanFactoryPostProcessor
和BeanPostProcessor
扩展容器功能的实例时。
An AnnotationConfigApplicationContext 已经注册了所有常见的注释后处理器,并且可以通过配置注释在封面下引入额外的处理器,例如@EnableTransactionManagement 。在Spring的基于注释的配置模型的抽象级别,bean后处理器的概念变成仅仅是内部容器细节。 | |
---|---|
2.资源
本章介绍Spring如何处理资源以及如何在Spring中使用资源。它包括以下主题:
2.1。介绍
java.net.URL
遗憾的是,Java的各种URL前缀的标准类和标准处理程序不足以完全访问低级资源。例如,没有URL
可用于访问需要从类路径或相对于a获取的资源的标准化实现 ServletContext
。虽然可以为专用URL
前缀注册新的处理程序(类似于现有的前缀处理程序http:
),但这通常非常复杂,并且URL
接口仍然缺少一些理想的功能,例如检查资源是否存在的方法指着。
2.2。资源接口
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
界面。以下清单显示了InputStreamSource
界面的定义:
public interface InputStreamSource { InputStream getInputStream() throws IOException; }
Resource
界面中一些最重要的方法是:
-
getInputStream()
:找到并打开资源,返回InputStream
从资源中读取的内容。预计每次调用都会返回一个新的InputStream
。呼叫者有责任关闭流。 -
exists()
:返回boolean
指示此资源是否实际以物理形式存在的指示。 -
isOpen()
:返回一个boolean
指示此资源是否表示具有打开流的句柄的指示符。如果true
,InputStream
不能多次读取,必须只读一次然后关闭以避免资源泄漏。false
所有常规资源实现的返回值,但InputStreamResource
。 -
getDescription()
:返回此资源的描述,用于处理资源时的错误输出。这通常是完全限定的文件名或资源的实际URL。
其他方法允许您获取表示资源的实际URL
或File
对象(如果底层实现兼容并支持该功能)。
Resource
当需要资源时,Spring本身广泛使用抽象,作为许多方法签名中的参数类型。某些Spring API中的其他方法(例如各种ApplicationContext
实现的构造函数)采用以 String
简单或简单的形式创建Resource
适合于该上下文实现的方法,或者通过String
路径上的特殊前缀,让调用者指定特定的Resource
实现必须创建和使用。
虽然Resource
Spring和Spring都使用了很多接口,但实际上在自己的代码中使用它作为通用实用程序类非常有用,可以访问资源,即使你的代码不知道或不关心任何其他部分春天 虽然这会将您的代码耦合到Spring,但它实际上只将它耦合到这一小组实用程序类中,这些实用程序类可以作为一个更有能力的替代品,URL
并且可以被认为等同于您将用于此目的的任何其他库。
该Resource 抽象并没有改变功能。它尽可能地包裹它。例如,a UrlResource 包装URL并使用包装URL 来完成其工作。 | |
---|---|
2.3。内置资源实现
Spring包括以下Resource
实现:
2.3.1。 UrlResource
UrlResource
包装a java.net.URL
并可用于访问通常可通过URL访问的任何对象,例如文件,HTTP目标,FTP目标等。所有URL都具有标准化String
表示,以便使用适当的标准化前缀来指示另一个URL类型。这包括file:
访问文件系统路径,http:
通过HTTP协议ftp:
访问资源,通过FTP访问资源等。
A UrlResource
是由Java代码通过显式使用UrlResource
构造函数创建的,但是当您调用带有String
表示路径的参数的API方法时,通常会隐式创建。对于后一种情况,JavaBeans PropertyEditor
最终决定Resource
要创建哪种类型。如果路径字符串包含众所周知的(对于它,即)前缀(例如classpath:
),则会Resource
为该前缀创建一个专用的。但是,如果它不识别前缀,则假定该字符串是标准URL字符串并创建一个UrlResource
。
2.3.2。 ClassPathResource
此类表示应从类路径获取的资源。它使用线程上下文类加载器,给定的类加载器或给定的类来加载资源。
此Resource
实现支持解决方案,就java.io.File
好像类路径资源驻留在文件系统中,但不支持驻留在jar中的类路径资源,并且尚未(通过servlet引擎或任何环境)扩展到文件系统。为了解决这个问题,各种Resource
实现总是支持解决方案java.net.URL
。
A ClassPathResource
是由Java代码通过显式使用ClassPathResource
构造函数创建的,但是当您调用带有String
表示路径的参数的API方法时,通常会隐式创建 。对于后一种情况,JavaBeans 在字符串路径上PropertyEditor
识别特殊前缀,classpath:
并ClassPathResource
在此情况下创建。
2.3.3。 FileSystemResource
这是一个Resource
实现java.io.File
和java.nio.file.Path
处理。它支持分辨率作为File
和URL
。
2.3.4。 ServletContextResource
这是一个资源Resource
实现,用于ServletContext
解释相关Web应用程序根目录中的相对路径。
它始终支持流访问和URL访问,但java.io.File
仅在扩展Web应用程序存档且资源实际位于文件系统上时才允许访问。无论它是在文件系统上扩展还是直接从JAR或其他地方(如数据库)(可以想象)访问,实际上都依赖于Servlet容器。
2.3.5。 InputStreamResource
An InputStreamResource
是Resource
给定的实现InputStream
。只有在没有Resource
适用的具体实施时才应使用它。特别地,在可能ByteArrayResource
的Resource
情况下,优选 或任何基于文件的实现。
与其他Resource
实现相比,这是已打开资源的描述符。因此,它true
从isOpen()
。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。
2.3.6。 ByteArrayResource
这是Resource
给定字节数组的实现。它ByteArrayInputStream
为给定的字节数组创建一个 。
它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用InputStreamResource
。
2.4。该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");
反对a ClassPathXmlApplicationContext
,该代码返回一个ClassPathResource
。如果对一个FileSystemXmlApplicationContext
实例执行相同的方法,它将返回一个 FileSystemResource
。对于a WebApplicationContext
,它会返回一个 ServletContextResource
。它同样会为每个上下文返回适当的对象。
因此,您可以以适合特定应用程序上下文的方式加载资源。
另一方面,您也ClassPathResource
可以通过指定特殊classpath:
前缀强制使用,而不管应用程序上下文类型如何,如下例所示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
同样,您可以UrlResource
通过指定任何标准 java.net.URL
前缀来强制使用a 。以下一对示例使用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
对象的策略:
字首 | 例 | 说明 |
---|---|---|
类路径: | classpath:com/myapp/config.xml | 从类路径加载。 |
文件: | file:///data/config.xml | URL 从文件系统加载为。另见FileSystemResource 警告。 |
HTTP: | http://myserver/logo.png | 加载为URL 。 |
(没有) | /data/config.xml | 取决于潜在的ApplicationContext 。 |
2.5。该ResourceLoaderAware
接口
该ResourceLoaderAware
接口是一个特殊的标记接口,用于标识希望提供ResourceLoader
引用的对象。以下清单显示了ResourceLoaderAware
界面的定义:
public interface ResourceLoaderAware { void setResourceLoader(ResourceLoader resourceLoader); }
当一个类实现ResourceLoaderAware
并部署到应用程序上下文中时(作为Spring管理的bean),它被ResourceLoaderAware
应用程序上下文识别。然后应用程序上下文调用 setResourceLoader(ResourceLoader)
,将自身作为参数提供(请记住,Spring中的所有应用程序上下文都实现了ResourceLoader
接口)。
由于a ApplicationContext
是a ResourceLoader
,bean也可以实现ApplicationContextAware
接口并直接使用提供的应用程序上下文来加载资源。但是,一般情况下,ResourceLoader
如果您需要,最好使用专用 接口。代码只能耦合到资源加载接口(可以被认为是实用程序接口)而不是整个Spring ApplicationContext
接口。
从Spring 2.5开始,您可以依靠自动装配ResourceLoader
作为实现ResourceLoaderAware
接口的替代方案。“传统” constructor
和byType
自动装配模式(如自动装配协作者中所述)现在能够分别为ResourceLoader
构造函数参数或setter方法参数提供类型的依赖性。为了获得更大的灵活性(包括自动装配字段和多参数方法的能力),请考虑使用基于注释的自动装配功能。在这种情况下,只要有问题的字段,构造函数或方法带有 注释,ResourceLoader
就会自动装入一个字段,构造函数参数或方法参数,这些参数需要该ResourceLoader
类型@Autowired
。有关更多信息,请参阅使用@Autowired
。
2.6。资源作为依赖关系
如果bean本身将通过某种动态过程确定并提供资源路径,那么bean使用ResourceLoader
接口加载资源可能是有意义的。例如,考虑加载某种模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么ResourceLoader
完全消除接口的使用是有意义的,让bean暴露Resource
它需要的属性,并期望它们被注入其中。
然后注入这些属性变得微不足道的是,所有应用程序上下文都注册并使用特殊的JavaBeans PropertyEditor
,它可以将String
路径转换为Resource
对象。因此,如果myBean
具有类型的模板属性Resource
,则可以使用该资源的简单字符串进行配置,如以下示例所示:
<bean id="myBean" class="..."> <property name="template" value="some/resource/path/myTemplate.txt"/> </bean>
请注意,资源路径没有前缀。因此,因为应用程序上下文本身将被用作ResourceLoader
,所以资源本身通过a ClassPathResource
,a FileSystemResource
或a 加载 ServletContextResource
,具体取决于上下文的确切类型。
如果需要强制使用特定Resource
类型,则可以使用前缀。以下两个示例显示了如何强制a ClassPathResource
和a UrlResource
(后者用于访问文件系统文件):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
2.7。应用程序上下文和资源路径
本节介绍如何使用资源创建应用程序上下文,包括使用XML的快捷方式,如何使用通配符以及其他详细信息。
2.7.1。构建应用程序上下文
应用程序上下文构造函数(对于特定的应用程序上下文类型)通常将字符串或字符串数组作为资源的位置路径,例如构成上下文定义的XML文件。
当这样的位置路径没有前缀时,Resource
从该路径构建并用于加载bean定义的特定类型取决于并且适合于特定的应用程序上下文。例如,请考虑以下示例,该示例创建 ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
bean定义是从类路径加载的,因为使用了a ClassPathResource
。但是,请考虑以下示例,该示例创建FileSystemXmlApplicationContext
:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
现在,bean定义是从文件系统位置加载的(在这种情况下,相对于当前工作目录)。
请注意,在位置路径上使用特殊类路径前缀或标准URL前缀会覆盖Resource
为加载定义而创建的默认类型。请考虑以下示例:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
使用FileSystemXmlApplicationContext
从类路径加载bean定义。但是,它仍然是一个 FileSystemXmlApplicationContext
。如果它随后用作a ResourceLoader
,则任何未加前缀的路径仍被视为文件系统路径。
构造ClassPathXmlApplicationContext
实例 - 快捷方式
在ClassPathXmlApplicationContext
提供了多种构造方法以便于实例。基本思想是,您只能提供一个字符串数组,该数组只包含XML文件本身的文件名(没有前导路径信息),并且还提供了一个Class
。所述ClassPathXmlApplicationContext
然后导出从所提供的类的路径信息。
请考虑以下目录布局:
COM / 富/ 的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
各种构造函数的详细信息,请参阅javadoc。
2.7.2。应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如前所示),每个路径都与目标进行一对一映射Resource
,或者可以包含特殊的“classpath *:”前缀或内部Ant-样式正则表达式(使用Spring的PathMatcher
实用程序匹配)。后者都是有效的通配符。
此机制的一个用途是当您需要进行组件样式的应用程序组装时。所有组件都可以将上下文定义片段“发布”到一个众所周知的位置路径,并且当使用前缀相同的路径创建最终应用程序上下文时 classpath*:
,将自动拾取所有组件片段。
请注意,此通配符特定于在应用程序上下文构造函数中使用资源路径(或PathMatcher
直接使用实用程序类层次结构时),并在构造时解析。它与Resource
类型本身无关。您不能使用classpath*:
前缀来构造实际Resource
,因为资源一次只指向一个资源。
蚂蚁风格的图案
路径位置可以包含Ant样式模式,如以下示例所示:
/WEB-INF/*-context.xml COM / myCompany中/ ** / applicationContext.xml中 文件:C:/一些/路径/ * - context.xml中 类路径:COM / myCompany中/ ** / applicationContext.xml中
当路径位置包含Ant样式模式时,解析程序遵循更复杂的过程来尝试解析通配符。它Resource
为最后一个非通配符段生成一个路径,并从中获取一个URL。如果此URL不是jar:
URL或特定zip:
于容器的变体(例如在WebLogic中,wsjar
在WebSphere中等),java.io.File
则从中获取a 并通过遍历文件系统来解析通配符。对于jar URL,解析器要么从中获取java.net.JarURLConnection
,要么手动解析jar URL,然后遍历jar文件的内容以解析通配符。
对可移植性的影响
如果指定的路径已经是文件URL(隐式,因为基础 ResourceLoader
是文件系统或显式),则可以保证通配符以完全可移植的方式工作。
如果指定的路径是类路径位置,则解析程序必须通过Classloader.getResource()
调用获取最后一个非通配符路径段URL 。由于这只是路径的一个节点(不是最后的文件),因此实际上未定义(在 ClassLoader
javadoc中)在这种情况下返回的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(…)
),然后合并以形成最终的应用程序上下文定义。
通配符类路径依赖于getResources() 底层类加载器的方法。由于现在大多数应用程序服务器都提供了自己的类加载器实现,因此行为可能会有所不同,尤其是在处理jar文件时。检查是否classpath* 有效的简单测试是使用类加载器从类路径中的jar中加载文件: getClass().getClassLoader().getResources("<someFileInsideTheJar>") 。尝试使用具有相同名称但放在两个不同位置的文件进行此测试。如果返回了不适当的结果,请检查应用程序服务器文档以获取可能影响类加载器行为的设置。 | |
---|---|
您还可以将classpath*:
前缀与PathMatcher
位置路径的其余部分中的模式组合(例如,classpath*:META-INF/*-beans.xml
)。在这种情况下,解析策略非常简单:ClassLoader.getResources()
在最后一个非通配符路径段上使用调用来获取类加载器层次结构中的所有匹配资源,然后,在每个资源之外,PathMatcher
使用前面描述的相同解析策略通配符子路径。
关于通配符的其他说明
请注意classpath*:
,当与Ant样式模式结合使用时,除非实际目标文件驻留在文件系统中,否则只能在模式启动之前与至少一个根目录一起可靠地工作。这意味着一种模式,例如 classpath*:*.xml
可能无法从jar文件的根目录中检索文件,而只能从扩展目录的根目录中检索文件。
Spring检索类路径条目的能力来自JDK的 ClassLoader.getResources()
方法,该方法仅返回空字符串的文件系统位置(指示搜索的潜在根)。Spring 也会评估 URLClassLoader
运行时配置和java.class.path
jar文件中的清单,但不能保证这会导致可移植行为。
扫描类路径包需要在类路径中存在相应的目录条目。使用Ant构建JAR时,请不要激活JAR任务的仅文件开关。此外,在某些环境中,类路径目录可能不会基于安全策略公开 - 例如,JDK 1.7.0_45及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library”。请参阅 http:/ /stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。此处强烈建议将资源放入专用目录,避免上述搜索jar文件根级别的可移植性问题。 | |
---|---|
classpath:
如果要搜索的根包在多个类路径位置中可用,则不保证具有资源的Ant样式模式可以找到匹配的资源。请考虑以下资源位置示例:
COM / myCompany中/包1 /服务的context.xml
现在考虑一个人可能用来尝试查找该文件的Ant风格路径:
类路径:COM / myCompany中/ ** /服务的context.xml
这样的资源可能只在一个位置,但是当使用诸如前面示例的路径来尝试解析它时,解析器处理由返回的(第一个)URLgetResource("com/mycompany");
。如果此基本包节点存在于多个类加载器位置中,则实际的最终资源可能不存在。因此,在这种情况下,您应该更喜欢使用classpath*:
相同的Ant样式模式,该模式搜索包含根包的所有类路径位置。
2.7.3。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
与强制使用的UrlResource
使用file:
URL前缀。以下示例显示了如何执行此操作:
// 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");
3.验证,数据绑定和类型转换
将验证视为业务逻辑有利有弊,而Spring提供的验证(和数据绑定)设计不排除其中任何一个。具体来说,验证不应该与Web层相关联,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring提出了一个Validator
基本的界面,在应用程序的每一层都非常有用。
数据绑定对于让用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象)非常有用。Spring提供了恰如其分的命名DataBinder
。在Validator
和 DataBinder
补validation
包,它主要在使用,但不限于MVC框架。
这BeanWrapper
是Spring Framework中的一个基本概念,并且在很多地方使用。但是,您可能不需要BeanWrapper
直接使用。但是,由于这是参考文档,我们认为可能会有一些解释。我们BeanWrapper
在本章中解释,因为,如果您要使用它,那么在尝试将数据绑定到对象时,您很可能会这样做。
Spring DataBinder
和更低级别BeanWrapper
都使用PropertyEditorSupport
实现来解析和格式化属性值。该PropertyEditor
和PropertyEditorSupport
接口是JavaBean规范的一部分,这一章中也有介绍。Spring 3引入了一个 core.convert
包,它提供了一个通用类型转换工具,以及一个用于格式化UI字段值的更高级“格式”包。您可以使用这些包作为PropertyEditorSupport
实现的更简单的替代方案。它们也在本章中讨论。
JSR-303 / JSR-349 Bean验证
从版本4.0开始,Spring Framework支持Bean Validation 1.0(JSR-303)和Bean Validation 1.1(JSR-349),用于设置支持并使它们适应Spring的Validator
界面。
应用程序可以选择全局启用Bean Validation一次,如 Spring Validation中所述,并专门用于所有验证需求。
应用程序还可Validator
以为每个 DataBinder
实例注册其他Spring 实例,如配置a中所述DataBinder
。这可以用于在不使用注释的情况下插入验证逻辑。
3.1。使用Spring的Validator接口验证
Spring提供了一个Validator
可用于验证对象的界面。该 Validator
接口通过使用Errors
对象来工作,以便在验证时,验证器可以向Errors
对象报告验证失败。
考虑以下小数据对象的示例:
public class Person { private String name; private int age; // the usual getters and setters... }
下一个示例Person
通过实现org.springframework.validation.Validator
接口的以下两种方法为类提供验证行为:
-
supports(Class)
:这可以Validator
验证提供的实例Class
吗? -
validate(Object, org.springframework.validation.Errors)
:验证给定对象,如果验证错误,则注册具有给定Errors
对象的对象。
实现a Validator
非常简单,特别是当您知道ValidationUtils
Spring Framework提供的 帮助程序类时。下面的示例实现Validator
的Person
实例:
public class PersonValidator implements Validator { /** * This Validator validates *only* Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old"); } } }
类的static
rejectIfEmpty(..)
方法ValidationUtils
用于拒绝name
属性,如果它是null
空字符串。有一个看ValidationUtils
的javadoc看到它提供了除了前面显示的例子什么功能。
虽然可以实现单个Validator
类来验证富对象中的每个嵌套对象,但最好将每个嵌套对象类的验证逻辑封装在自己的Validator
实现中。“富”对象的一个简单示例Customer
是由两个String
属性(第一个和第二个名称)和一个复杂Address
对象组成。Address
对象可以独立于Customer
对象使用,因此AddressValidator
已经实现了不同的对象。如果您希望CustomerValidator
重用AddressValidator
类中包含的逻辑而不需要复制和粘贴,则可以AddressValidator
在您的内部依赖注入或实例化CustomerValidator
,如下例所示:
public class CustomerValidator implements Validator { private final Validator addressValidator; public CustomerValidator(Validator addressValidator) { if (addressValidator == null) { throw new IllegalArgumentException("The supplied [Validator] is " + "required and must not be null."); } if (!addressValidator.supports(Address.class)) { throw new IllegalArgumentException("The supplied [Validator] must " + "support the validation of [Address] instances."); } this.addressValidator = addressValidator; } /** * This Validator validates Customer instances, and any subclasses of Customer too */ public boolean supports(Class clazz) { return Customer.class.isAssignableFrom(clazz); } public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required"); Customer customer = (Customer) target; try { errors.pushNestedPath("address"); ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors); } finally { errors.popNestedPath(); } } }
验证错误将报告给Errors
传递给验证程序的对象。对于Spring Web MVC,您可以使用<spring:bind/>
标记来检查错误消息,但您也可以Errors
自己检查对象。有关它提供的方法的更多信息可以在javadoc中找到。
3.2。将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍输出与验证错误相对应的消息。在上一节所示的示例中,我们拒绝了name
和age
字段。如果我们想使用a输出错误消息MessageSource
,我们可以使用我们在拒绝字段时提供的错误代码(在这种情况下为'name'和'age')。当你从接口调用(直接或间接地,通过使用ValidationUtils
类)rejectValue
或其他reject
方法之一时Errors
,底层实现不仅注册你传入的代码,还注册了许多额外的错误代码。在MessageCodesResolver
确定该错误代码的Errors
接口寄存器。默认情况下DefaultMessageCodesResolver
使用(例如),它不仅使用您给出的代码注册消息,还会注册包含您传递给reject方法的字段名称的消息。因此,如果您通过使用拒绝字段 rejectValue("age", "too.darn.old")
,除了too.darn.old
代码之外,Spring还会注册too.darn.old.age
并且too.darn.old.age.int
(第一个包括字段名称,第二个包括字段的类型)。这样做是为了方便在定位错误消息时帮助开发人员。
关于更多信息MessageCodesResolver
以及默认的策略可以在javadoc的发现 MessageCodesResolver
和DefaultMessageCodesResolver
分别。
3.3。豆操作和BeanWrapper
该org.springframework.beans
软件包遵循JavaBeans标准。JavaBean是一个具有默认无参数构造函数的类,它遵循命名约定,其中(例如)名为的属性bingoMadness
将具有setter方法setBingoMadness(..)
和getter方法getBingoMadness()
。有关JavaBeans和规范的更多信息,请参阅 javabeans。
beans包中一个非常重要的类是BeanWrapper
接口及其相应的实现(BeanWrapperImpl
)。从javadoc引用, BeanWrapper
提供功能来设置和获取属性值(单独或批量),获取属性描述符和查询属性以确定它们是可读还是可写。此外,这些商品BeanWrapper
支持嵌套属性,可以将子属性的属性设置为无限深度。该 BeanWrapper
还支持标准JavaBean的能力PropertyChangeListeners
和VetoableChangeListeners
,而不需要在辅助代码。最后但并非最不重要的是,它BeanWrapper
提供了对设置索引属性的支持。在BeanWrapper
通常不使用直接应用的代码,但使用由 DataBinder
和所述BeanFactory
。
工作的方式BeanWrapper
部分由其名称表示:它包装bean以对该bean执行操作,例如设置和检索属性。
3.3.1。设置和获取基本和嵌套属性
设置和获取属性可以通过使用完成setPropertyValue
, setPropertyValues
,getPropertyValue
,和getPropertyValues
方法,并配备了一对夫妇超载变种。Springs javadoc更详细地描述了它们。JavaBeans规范具有指示对象属性的约定。下表显示了这些约定的一些示例:
表达 | 说明 |
---|---|
name | 指示与or 和方法name 对应的属性。getName()``isName()``setName(..) |
account.name | 指示与(例如)或方法对应name 的属性的嵌套属性。account``getAccount().setName()``getAccount().getName() |
account[2] | 指示索引属性的第三个元素account 。索引属性可能是类型的array ,list 或其它天然有序集合。 |
account[COMPANYNAME] | 指示由 属性的COMPANYNAME 键索引的映射条目的值account Map 。 |
(下一节如果你不打算与合作是不是对你非常重要的BeanWrapper
。如果您仅使用DataBinder
和BeanFactory
与他们的默认实现,你可以跳过至约部分 PropertyEditors
。)
以下两个示例类使用BeanWrapper
to get和set属性:
public class Company { private String name; private Employee managingDirector; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Employee getManagingDirector() { return this.managingDirector; } public void setManagingDirector(Employee managingDirector) { this.managingDirector = managingDirector; } }
public class Employee { private String name; private float salary; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
下面的代码片断展示了如何检索和操作的一些实例化属性的一些例子Companies
和Employees
:
BeanWrapper company = new BeanWrapperImpl(new Company()); // setting the company name.. company.setPropertyValue("name", "Some Company Inc."); // ... can also be done like this: PropertyValue value = new PropertyValue("name", "Some Company Inc."); company.setPropertyValue(value); // ok, let's create the director and tie it to the company: BeanWrapper jim = new BeanWrapperImpl(new Employee()); jim.setPropertyValue("name", "Jim Stravinsky"); company.setPropertyValue("managingDirector", jim.getWrappedInstance()); // retrieving the salary of the managingDirector through the company Float salary = (Float) company.getPropertyValue("managingDirector.salary");
3.3.2。内置PropertyEditor
实现
Spring使用a的概念来实现a 和a PropertyEditor
之间的转换 。以与对象本身不同的方式表示属性可能很方便。例如, 可以在人类可读的方式(如代表:),同时我们还可以将人类可读的形式返回到原来的日期(或者甚至更好,转换成一个人类可读的形式进入回任何日期的对象)。通过注册类型的自定义编辑器可以实现此行为 。在一个或另一个特定的IoC容器中注册自定义编辑器(如前一章所述),使其了解如何将属性转换为所需类型。有关更多信息 ,请参阅的javadocObject``String``Date``String``'2007-14-09'``Date``java.beans.PropertyEditor``BeanWrapper``PropertyEditor
java.beans
来自Oracle的包。
在Spring中使用属性编辑的几个示例:
-
通过使用
PropertyEditor
实现来设置bean的属性。当您使用String
在XML文件中声明的某个bean的属性值时,Spring(如果相应属性的setter具有Class
参数)用于ClassEditor
尝试将参数解析为Class
对象。 -
在Spring的MVC框架中解析HTTP请求参数是通过使用
PropertyEditor
可以在所有子类中手动绑定的各种实现来完成的CommandController
。
Spring有许多内置PropertyEditor
实现,可以让生活变得轻松。它们都位于org.springframework.beans.propertyeditors
包装中。大多数(但不是全部,如下表所示)默认情况下是由 BeanWrapperImpl
。如果属性编辑器可以某种方式配置,您仍然可以注册自己的变体来覆盖默认变体。下表描述了PropertyEditor
Spring提供的各种实现:
类 | 说明 |
---|---|
ByteArrayPropertyEditor | 字节数组的编辑器。将字符串转换为其对应的字节表示形式。默认注册BeanWrapperImpl 。 |
ClassEditor | 解析表示类到实际类的字符串,反之亦然。当找不到某个类时,IllegalArgumentException 会抛出一个类。默认情况下,注册者 BeanWrapperImpl 。 |
CustomBooleanEditor | 属性的可自定义属性编辑器Boolean 。默认情况下,注册 BeanWrapperImpl 但可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
CustomCollectionEditor | 集合的属性编辑器,将任何源Collection 转换为给定的目标 Collection 类型。 |
CustomDateEditor | 可自定义的属性编辑器java.util.Date ,支持自定义DateFormat 。没有默认注册。必须根据需要使用适当的格式进行用户注册。 |
CustomNumberEditor | 定制的属性编辑器Number 的子类,如Integer ,Long ,Float ,或 Double 。默认情况下,注册BeanWrapperImpl 但可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
FileEditor | 将字符串解析为java.io.File 对象。默认情况下,注册者 BeanWrapperImpl 。 |
InputStreamEditor | 单向属性编辑器,可以获取字符串并生成(通过中间ResourceEditor 和Resource ),InputStream 以便InputStream 属性可以直接设置为字符串。请注意,默认用法不会InputStream 为您关闭。默认情况下,注册者BeanWrapperImpl 。 |
LocaleEditor | 可以将字符串解析为Locale 对象,反之亦然(字符串格式 *[country]*[variant] 与toString() 方法 相同Locale )。默认情况下,注册者BeanWrapperImpl 。 |
PatternEditor | 可以将字符串解析为java.util.regex.Pattern 对象,反之亦然。 |
PropertiesEditor | 可以将字符串(使用java.util.Properties 类的javadoc中定义的格式进行格式化 )转换为Properties 对象。默认情况下,注册者BeanWrapperImpl 。 |
StringTrimmerEditor | 修剪字符串的属性编辑器。(可选)允许将空字符串转换为null 值。默认情况下未注册 - 必须是用户注册的。 |
URLEditor | 可以将URL的字符串表示形式解析为实际URL 对象。默认情况下,注册者BeanWrapperImpl 。 |
Spring使用它java.beans.PropertyEditorManager
来设置可能需要的属性编辑器的搜索路径。搜索路径还包括sun.bean.editors
,其包括PropertyEditor
实现为类型,例如Font
,Color
和最原始类型。另请注意,标准JavaBeans基础结构会自动发现PropertyEditor
类(无需显式注册它们),如果它们与它们处理的类位于同一个包中,并且与该类具有相同的名称,并Editor
附加。例如,可以使用以下类和包结构,这足以使SomethingEditor
类被识别并用作PropertyEditor
for Something
-typed属性。
COM CHANK 流行的 某物 SomethingEditor // Something类的PropertyEditor
请注意,您也可以使用标准的BeanInfo
JavaBeans的机制在这里也描述(在一定程度上 在这里)。以下示例使用该BeanInfo
机制使用PropertyEditor
关联类的属性显式注册一个或多个实例:
COM CHANK 流行的 某物 SomethingBeanInfo // Something类的BeanInfo
以下引用SomethingBeanInfo
类的Java源代码将a CustomNumberEditor
与该类的age
属性相关联Something
:
public class SomethingBeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors() { try { final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true); PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) { public PropertyEditor createPropertyEditor(Object bean) { return numberPE; }; }; return new PropertyDescriptor[] { ageDescriptor }; } catch (IntrospectionException ex) { throw new Error(ex.toString()); } } }
注册其他自定义PropertyEditor
实现
将bean属性设置为字符串值时,Spring IoC容器最终使用标准JavaBeans PropertyEditor
实现将这些字符串转换为属性的复杂类型。Spring预先注册了许多自定义PropertyEditor
实现(例如,将表示为字符串的类名转换为Class
对象)。此外,Java的标准JavaBeans PropertyEditor
查找机制允许一个PropertyEditor
类被适当地命名并放置在与它提供支持的类相同的包中,以便可以自动找到它。
如果需要注册其他自定义PropertyEditors
,可以使用多种机制。假设您有参考,最通常不方便或不推荐的手动方法是使用界面的registerCustomEditor()
方法 。另一种(稍微方便一点)机制是使用一个特殊的bean工厂后处理器调用。虽然你可以使用带有实现的bean工厂后处理器,但是它有一个嵌套的属性设置,所以我们强烈建议你使用它,在 那里你可以用类似的方式将它部署到任何其他bean,并且可以自动检测它和应用。ConfigurableBeanFactory``BeanFactory``CustomEditorConfigurer``BeanFactory``CustomEditorConfigurer``ApplicationContext
请注意,所有bean工厂和应用程序上下文都会自动使用许多内置属性编辑器,通过它们使用a BeanWrapper
来处理属性转换。BeanWrapper
寄存器在上一节中列出的标准属性编辑器。此外, ApplicationContexts
还可以覆盖或添加其他编辑器,以适合特定应用程序上下文类型的方式处理资源查找。
标准JavaBeans PropertyEditor
实例用于将表示为字符串的属性值转换为属性的实际复杂类型。您可以使用CustomEditorConfigurer
bean工厂后处理器方便地添加对其他PropertyEditor
实例的支持ApplicationContext
。
考虑以下示例,该示例定义了一个被调用的用户类ExoticType
和另一个被调用的类DependsOnExoticType
,需要ExoticType
将其 设置为属性:
package example; public class ExoticType { private String name; public ExoticType(String name) { this.name = name; } } public class DependsOnExoticType { private ExoticType type; public void setType(ExoticType type) { this.type = type; } }
正确设置后,我们希望能够将type属性指定为字符串,并将其PropertyEditor
转换为实际 ExoticType
实例。以下bean定义显示了如何设置此关系:
<bean id="sample" class="example.DependsOnExoticType"> <property name="type" value="aNameForExoticType"/> </bean>
在PropertyEditor
实现看上去类似以下内容:
// converts string representation to ExoticType object package example; public class ExoticTypeEditor extends PropertyEditorSupport { public void setAsText(String text) { setValue(new ExoticType(text.toUpperCase())); } }
最后,以下示例显示了如何使用CustomEditorConfigurer
注册new PropertyEditor
, ApplicationContext
然后可以根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="example.ExoticType" value="example.ExoticTypeEditor"/> </map> </property> </bean>
运用 PropertyEditorRegistrar
使用Spring容器注册属性编辑器的另一种机制是创建和使用a PropertyEditorRegistrar
。当您需要在几种不同情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册商,并在每种情况下重复使用它。PropertyEditorRegistrar
实例与一个名为PropertyEditorRegistry
的接口一起工作,该接口由Spring BeanWrapper
(和DataBinder
)实现。PropertyEditorRegistrar
当与CustomEditorConfigurer
(在此描述)一起使用时,实例特别方便,它暴露了一个名为的属性setPropertyEditorRegistrars(..)
。以这种方式PropertyEditorRegistrar
添加到a的实例 CustomEditorConfigurer
可以很容易地与之共享DataBinder
和Spring MVC控制器。此外,它避免了在自定义编辑器上进行同步的需要:A PropertyEditorRegistrar
期望PropertyEditor
为每个bean创建尝试创建新的实例。
以下示例显示如何创建自己的PropertyEditorRegistrar
实现:
package com.foo.editors.spring; public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { // it is expected that new PropertyEditor instances are created registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor()); // you could register as many custom property editors as are required here... } }
另请参阅org.springframework.beans.support.ResourceEditorRegistrar
示例 PropertyEditorRegistrar
实现。请注意,在实现该 registerCustomEditors(..)
方法时,它会创建每个属性编辑器的新实例。
下一个示例显示了如何配置CustomEditorConfigurer
和注入我们的实例 CustomPropertyEditorRegistrar
:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <ref bean="customPropertyEditorRegistrar"/> </list> </property> </bean> <bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(与本章的重点有所不同,对于那些使用Spring的MVC Web框架的人来说),PropertyEditorRegistrars
结合使用数据绑定Controllers
(例如SimpleFormController
)可以非常方便。以下示例PropertyEditorRegistrar
在initBinder(..)
方法的实现中使用a :
public final class RegisterUserController extends SimpleFormController { private final PropertyEditorRegistrar customPropertyEditorRegistrar; public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) { this.customPropertyEditorRegistrar = propertyEditorRegistrar; } protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { this.customPropertyEditorRegistrar.registerCustomEditors(binder); } // other methods to do with registering a User }
这种PropertyEditor
注册方式可以导致简洁的代码(实现initBinder(..)
只有一行),并允许将通用PropertyEditor
注册代码封装在一个类中,然后Controllers
根据需要共享 。
3.4。弹簧类型转换
Spring 3引入了一个core.convert
提供通用类型转换系统的包。系统定义了一个用于实现类型转换逻辑的SPI和一个用于在运行时执行类型转换的API。在Spring容器中,您可以使用此系统作为PropertyEditor
实现的替代方法,以将外部化的bean属性值字符串转换为必需的属性类型。您还可以在需要进行类型转换的应用程序中的任何位置使用公共API。
3.4.1。转换器SPI
用于实现类型转换逻辑的SPI简单且强类型化,如以下接口定义所示:
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
要创建自己的转换器,请将Converter
接口和参数化实现S
为要转换T
的类型以及要转换的类型。如果S
需要将集合或数组转换为数组或集合,也可以透明地应用这样的转换器T
,前提是已经注册了委托数组或集合转换器(DefaultConversionService
默认情况下也是如此)。
对于每次调用convert(S)
,源参数保证不为null。Converter
如果转换失败,您 可能会抛出任何未经检查的异常。具体来说,它应该抛出一个 IllegalArgumentException
报告无效的源值。注意确保您的Converter
实现是线程安全的。
core.convert.support
为方便起见,在包中提供了几种转换器实现。这些包括从字符串到数字的转换器以及其他常见类型。以下列表显示了StringToInteger
该类,这是一个典型的Converter
实现:
package org.springframework.core.convert.support; final class StringToInteger implements Converter<String, Integer> { public Integer convert(String source) { return Integer.valueOf(source); } }
3.4.2。运用ConverterFactory
当您需要集中整个类层次结构的转换逻辑时(例如,从转换String
为Enum
对象时),您可以实现 ConverterFactory
,如以下示例所示:
package org.springframework.core.convert.converter; public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
参数化S为要转换的类型,R为定义可转换为的类范围的基本类型。然后实现getConverter(Class<T>)
,其中T是R的子类。
考虑StringToEnumConverterFactory
作为一个例子:
package org.springframework.core.convert.support; final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> { public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) { return new StringToEnumConverter(targetType); } private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> { private Class<T> enumType; public StringToEnumConverter(Class<T> enumType) { this.enumType = enumType; } public T convert(String source) { return (T) Enum.valueOf(this.enumType, source.trim()); } } }
3.4.3。运用GenericConverter
当您需要复杂的Converter
实现时,请考虑使用该 GenericConverter
接口。使用更灵活但不太强类型的签名Converter
,GenericConverter
支持在多个源类型和目标类型之间进行转换。此外,GenericConverter
还可以在实现转换逻辑时使用可用的源和目标字段上下文。这样的上下文允许类型转换由字段注释或在字段签名上声明的通用信息驱动。以下清单显示了以下接口定义GenericConverter
:
package org.springframework.core.convert.converter; public interface GenericConverter { public Set<ConvertiblePair> getConvertibleTypes(); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
要实现a GenericConverter
,请getConvertibleTypes()
返回支持的源→目标类型对。然后实现convert(Object, TypeDescriptor, TypeDescriptor)
包含转换逻辑。源TypeDescriptor
提供对包含要转换的值的源字段的访问。目标TypeDescriptor
提供对要设置转换值的目标字段的访问。
a的一个很好的例子GenericConverter
是在Java数组和集合之间进行转换的转换器。这样一个ArrayToCollectionConverter
内省声明了目标集合类型来解析集合的元素类型。这样,在目标字段上设置集合之前,可以将源数组中的每个元素转换为集合元素类型。
因为GenericConverter 是一个更复杂的SPI接口,所以只有在需要时才应该使用它。支持Converter 或ConverterFactory 基本类型转换需求。 | |
---|---|
运用 ConditionalGenericConverter
有时,Converter
只有在特定条件成立时才需要运行。例如,您可能希望Converter
仅在目标字段上存在特定注释时运行,或者Converter
仅static valueOf
在目标类上定义特定方法(如方法)时才运行。 ConditionalGenericConverter
是GenericConverter
和 ConditionalConverter
接口的联合,可以让您定义这样的自定义匹配条件:
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); } public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { }
a的一个很好的例子ConditionalGenericConverter
是EntityConverter
在持久实体标识符和实体引用之间进行转换。EntityConverter
仅当目标实体类型声明静态查找器方法(例如,findAccount(Long)
)时,此类可能匹配 。您可以在执行中执行这样的finder方法检查 matches(TypeDescriptor, TypeDescriptor)
。
3.4.4。该ConversionService
API
ConversionService
定义用于在运行时执行类型转换逻辑的统一API。转换器通常在以下Facade接口后面执行:
package org.springframework.core.convert; public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); <T> T convert(Object source, Class<T> targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
大多数ConversionService
实现也实现ConverterRegistry
,它提供用于注册转换器的SPI。在内部,ConversionService
实现委托其注册的转换器执行类型转换逻辑。
包中ConversionService
提供了强大的实现core.convert.support
。GenericConversionService
是适用于大多数环境的通用实现。ConversionServiceFactory
提供了一个方便的工厂来创建常见ConversionService
配置。
3.4.5。配置一个ConversionService
A ConversionService
是一个无状态对象,旨在在应用程序启动时实例化,然后在多个线程之间共享。在Spring应用程序中,通常ConversionService
为每个Spring容器(或ApplicationContext
)配置一个实例。ConversionService
当需要框架执行类型转换时,Spring会选择并使用它。您也可以将其 ConversionService
注入任何bean并直接调用它。
如果没有ConversionService 向Spring注册,PropertyEditor 则使用原始系统。 | |
---|---|
要注册一个默认ConversionService
使用Spring,用添加以下bean定义id
的conversionService
:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认值ConversionService
可以在字符串,数字,枚举,集合,映射和其他常见类型之间进行转换。要使用您自己的自定义转换器补充或覆盖默认转换器,请设置该converters
属性。属性值可以实现任何的Converter
,ConverterFactory
或者GenericConverter
界面。
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="example.MyCustomConverter"/> </set> </property> </bean>
ConversionService
在Spring MVC应用程序中使用它也很常见。请参阅 Spring MVC章节中的转换和格式化。
在某些情况下,您可能希望在转换期间应用格式。有关使用FormatterRegistry
的详细信息, 请参见SPIFormattingConversionServiceFactoryBean
。
3.4.6。以ConversionService
编程方式使用
要以ConversionService
编程方式使用实例,您可以像对待任何其他bean一样注入对它的引用。以下示例显示了如何执行此操作:
@Service public class MyService { @Autowired public MyService(ConversionService conversionService) { this.conversionService = conversionService; } public void doIt() { this.conversionService.convert(...) } }
对于大多数用例,您可以使用convert
指定的方法targetType
,但它不适用于更复杂的类型,例如参数化元素的集合。例如,如果你想转换List
的Integer
到List
的String
程序,您需要提供的源和目标类型的正式定义。
幸运的是,TypeDescriptor
提供了各种选项来使这样做简单明了,如下例所示:
DefaultConversionService cs = new DefaultConversionService(); List<Integer> input = .... cs.convert(input, TypeDescriptor.forObject(input), // List<Integer> type descriptor TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
请注意,DefaultConversionService
自动注册适合大多数环境的转换器。这包括收集器,标转换器,以及基本的Object
-到- String
转换器。您可以ConverterRegistry
使用类addDefaultConverters
上的静态方法向任何注册器注册相同的转换器DefaultConversionService
。
值类型转换器重新用于数组和集合,所以没有必要创建一个特定的转换器从转换Collection
的S
到 Collection
的T
,假设标准收集处理是适当的。
3.5。Spring Field格式
如前一节所述,core.convert
是一种通用类型转换系统。它提供了统一的ConversionService
API以及强类型Converter
SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring容器使用此系统绑定bean属性值。另外,Spring Expression Language(SpEL)和DataBinder
使用此系统绑定字段值。例如,当SpEL需要强制a Short
到a Long
来完成expression.setValue(Object bean, Object value)
尝试时,core.convert
系统会执行强制。
现在考虑典型客户端环境的类型转换要求,例如Web或桌面应用程序。在这样的环境中,您通常转换String
为支持客户端回发过程,以及返回String
以支持视图呈现过程。此外,您经常需要本地化String
值。更通用的core.convert
Converter
SPI不直接解决此类格式化要求。为了直接解决这些问题,Spring 3引入了一个方便的Formatter
SPI,PropertyEditor
为客户端环境的实现提供了一种简单而强大的替代方案。
通常,您可以Converter
在需要实现通用类型转换逻辑时使用SPI - 例如,在a java.util.Date
和a 之间进行转换Long
。您可以Formatter
在客户端环境(例如Web应用程序)中工作时使用SPI,并且需要解析和打印本地化的字段值。在ConversionService
为双方提供的SPI统一类型转换API。
3.5.1。该Formatter
SPI
Formatter
用于实现字段格式化逻辑的SPI简单且强类型化。以下清单显示了Formatter
接口定义:
package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> { }
Formatter
从延伸Printer
和Parser
积木式接口。以下清单显示了这两个接口的定义:
public interface Printer<T> { String print(T fieldValue, Locale locale); }
import java.text.ParseException; public interface Parser<T> { T parse(String clientValue, Locale locale) throws ParseException; }
要创建自己的Formatter
,请实现Formatter
前面显示的界面。参数T
化为您要格式化的对象类型 - 例如, java.util.Date
。实现print()
操作以T
在客户端区域设置中打印要显示的实例。实现parse()
操作以T
从客户端语言环境返回的格式化表示中解析实例 。如果解析尝试失败,您Formatter
应该抛出一个ParseException
或一个IllegalArgumentException
。注意确保您的Formatter
实现是线程安全的。
在format
子包提供若干Formatter
实施方式中,为方便起见。该number
包提供NumberStyleFormatter
,CurrencyStyleFormatter
和 PercentStyleFormatter
格式化Number
使用a的对象java.text.NumberFormat
。该datetime
包提供了一个DateFormatter
格式化java.util.Date
对象的方法java.text.DateFormat
。该datetime.joda
软件包基于Joda-Time库提供全面的日期时间格式支持。
以下DateFormatter
是一个示例Formatter
实现:
package org.springframework.format.datetime; public final class DateFormatter implements Formatter<Date> { private String pattern; public DateFormatter(String pattern) { this.pattern = pattern; } public String print(Date date, Locale locale) { if (date == null) { return ""; } return getDateFormat(locale).format(date); } public Date parse(String formatted, Locale locale) throws ParseException { if (formatted.length() == 0) { return null; } return getDateFormat(locale).parse(formatted); } protected DateFormat getDateFormat(Locale locale) { DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); dateFormat.setLenient(false); return dateFormat; } }
Spring团队欢迎社区推动的Formatter
贡献。请参阅 GitHub要贡献的问题。
3.5.2。注释驱动的格式
可以按字段类型或注释配置字段格式。要将注释绑定到a Formatter
,请执行AnnotationFormatterFactory
。以下清单显示了AnnotationFormatterFactory
界面的定义:
package org.springframework.format; public interface AnnotationFormatterFactory<A extends Annotation> { Set<Class<?>> getFieldTypes(); Printer<?> getPrinter(A annotation, Class<?> fieldType); Parser<?> getParser(A annotation, Class<?> fieldType); }
创建实现:。参数化A是annotationType
您希望与格式化逻辑关联的字段- 例如org.springframework.format.annotation.DateTimeFormat
。。已getFieldTypes()
返回上可以使用注释字段的类型。。已经getPrinter()
返回Printer
到打印注释字段的值。。已经getParser()
返回Parser
到解析clientValue
为一个注释字段。
以下示例AnnotationFormatterFactory
实现将@NumberFormat
注释绑定到格式化程序,以指定数字样式或模式:
public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NumberFormat> { public Set<Class<?>> getFieldTypes() { return new HashSet<Class<?>>(asList(new Class<?>[] { Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class })); } public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) { if (!annotation.pattern().isEmpty()) { return new NumberStyleFormatter(annotation.pattern()); } else { Style style = annotation.style(); if (style == Style.PERCENT) { return new PercentStyleFormatter(); } else if (style == Style.CURRENCY) { return new CurrencyStyleFormatter(); } else { return new NumberStyleFormatter(); } } } }
要触发格式化,可以使用@NumberFormat注释字段,如以下示例所示:
public class MyModel { @NumberFormat(style=Style.CURRENCY) private BigDecimal decimal; }
格式注释API
org.springframework.format.annotation
包中存在便携式格式注释API 。您可以使用@NumberFormat
格式化Number
领域,如Double
和 Long
,并@DateTimeFormat
格式化java.util.Date
,java.util.Calendar
,Long
(为毫秒时间戳)以及JSR-310 java.time
和乔达时间值类型。
以下示例用于@DateTimeFormat
将a格式化java.util.Date
为ISO Date(yyyy-MM-dd):
public class MyModel { @DateTimeFormat(iso=ISO.DATE) private Date date; }
3.5.3。该FormatterRegistry
SPI
这FormatterRegistry
是一个用于注册格式化器和转换器的SPI。 FormattingConversionService
是FormatterRegistry
适合大多数环境的实现。您可以以编程方式或声明性地将此变体配置为Spring bean,例如使用FormattingConversionServiceFactoryBean
。由于此实现也实现了ConversionService
,您可以直接将其配置为与Spring DataBinder
和Spring表达式语言(SpEL)一起使用。
以下清单显示了FormatterRegistry
SPI:
package org.springframework.format; public interface FormatterRegistry extends ConverterRegistry { void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); void addFormatterForFieldType(Formatter<?> formatter); void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory); }
如上面的清单所示,您可以按字段类型或注释注册格式化程序。
该FormatterRegistry
SPI允许您配置集中格式规则,而不是在你的控制器重复这样的配置。例如,您可能希望强制所有日期字段以某种方式格式化,或者具有特定注释的字段以某种方式格式化。使用共享FormatterRegistry
,您可以定义一次这些规则,并在需要格式化时应用它们。
3.5.4。该FormatterRegistrar
SPI
FormatterRegistrar
是一个SPI,用于通过FormatterRegistry注册格式化程序和转换器。以下清单显示了其接口定义:
package org.springframework.format; public interface FormatterRegistrar { void registerFormatters(FormatterRegistry registry); }
FormatterRegistrar
注册给定格式类别的多个相关转换器和格式化程序时,A 非常有用,例如日期格式。它在声明性注册不足的情况下也很有用 - 例如,当格式化程序需要在与其自身不同的特定字段类型下<T>
或在注册Printer
/ / 时进行索引时Parser
。下一节提供有关转换器和格式化程序注册的更多信息。
3.5.5。在Spring MVC中配置格式化
请参阅Spring MVC章节中的转换和格式化。
3.6。配置全局日期和时间格式
默认情况下,未使用注释的日期和时间字段将@DateTimeFormat
使用DateFormat.SHORT
样式从字符串转换。如果您愿意,可以通过定义自己的全局格式来更改此设置。
为此,您需要确保Spring不注册默认格式化程序。相反,您应该手动注册所有格式化程序。使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
或org.springframework.format.datetime.DateFormatterRegistrar
类,具体取决于您是否使用Joda-Time库。
例如,以下Java配置注册全局yyyyMMdd
格式(此示例不依赖于Joda-Time库):
@Configuration public class AppConfig { @Bean public FormattingConversionService conversionService() { // Use the DefaultFormattingConversionService but do not register defaults DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false); // Ensure @NumberFormat is still supported conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); // Register date conversion with a specific global format DateFormatterRegistrar registrar = new DateFormatterRegistrar(); registrar.setFormatter(new DateFormatter("yyyyMMdd")); registrar.registerFormatters(conversionService); return conversionService; } }
如果您更喜欢基于XML的配置,则可以使用 FormattingConversionServiceFactoryBean
。以下示例显示了如何执行此操作(这次使用Joda Time):
<?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="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="registerDefaultFormatters" value="false" /> <property name="formatters"> <set> <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" /> </set> </property> <property name="formatterRegistrars"> <set> <bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar"> <property name="dateFormatter"> <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean"> <property name="pattern" value="yyyyMMdd"/> </bean> </property> </bean> </set> </property> </bean> </beans>
乔达时提供单独的不同类型来表示date ,time 和date-time 的值。的dateFormatter ,timeFormatter 和dateTimeFormatter 的性质 JodaTimeFormatterRegistrar ,应使用来配置不同的格式为每种类型。它DateTimeFormatterFactoryBean 提供了一种创建格式化程序的便捷方法。 | |
---|---|
如果您使用Spring MVC,请记住明确配置使用的转换服务。对于基于Java的@Configuration ,这意味着扩展WebMvcConfigurationSupport 类并重写mvcConversionService() 方法。对于XML,您应该使用元素的conversion-service 属性 mvc:annotation-driven 。有关详细信息,请参阅转换和格式。 | |
---|---|
3.7。弹簧验证
Spring 3为其验证支持引入了几项增强功能。首先,完全支持JSR-303 Bean Validation API。其次,当以编程方式使用时,SpringDataBinder
可以验证对象以及绑定它们。第三,Spring MVC支持声明性地验证@Controller
输入。
3.7.1。JSR-303 Bean Validation API概述
JSR-303标准化了Java平台的验证约束声明和元数据。通过使用此API,您可以使用声明性验证约束来注释域模型属性,并且运行时会强制执行它们。您可以使用许多内置约束。您还可以定义自己的自定义约束。
请考虑以下示例,该示例显示了PersonForm
具有两个属性的简单模型:
public class PersonForm { private String name; private int age; }
JSR-303允许您为这些属性定义声明性验证约束,如以下示例所示:
public class PersonForm { @NotNull @Size(max=64) private String name; @Min(0) private int age; }
当JSR-303 Validator验证此类的实例时,将强制执行这些约束。
有关JSR-303和JSR-349的一般信息,请参阅Bean Validation网站。有关默认参考实现的特定功能的信息,请参阅Hibernate Validator文档。要学习如何将bean验证提供程序设置为Spring bean,请继续阅读。
3.7.2。配置Bean验证提供程序
Spring提供对Bean Validation API的完全支持。这包括方便地支持将JSR-303或JSR-349 Bean Validation提供程序作为Spring bean引导。这使您 可以在应用程序中注入javax.validation.ValidatorFactory
或javax.validation.Validator
需要验证。
您可以使用LocalValidatorFactoryBean
它将默认Validator配置为Spring bean,如以下示例所示:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
前面示例中的基本配置通过使用其默认引导机制触发bean验证以进行初始化。JSR-303或JSR-349提供程序(例如Hibernate Validator)应该存在于类路径中并自动检测。
注入验证器
LocalValidatorFactoryBean
同时实现了javax.validation.ValidatorFactory
和 javax.validation.Validator
,以及Spring的 org.springframework.validation.Validator
。您可以将这些接口中的任何一个引用注入到需要调用验证逻辑的bean中。
javax.validation.Validator
如果您希望直接使用Bean Validation API,则可以注入引用,如以下示例所示:
import javax.validation.Validator; @Service public class MyService { @Autowired private Validator validator;
org.springframework.validation.Validator
如果您的bean需要Spring Validation API,您可以注入一个引用,如下例所示:
import org.springframework.validation.Validator; @Service public class MyService { @Autowired private Validator validator; }
配置自定义约束
每个bean验证约束由两部分组成:一个@Constraint
声明约束及其可配置属性的注释。实现javax.validation.ConstraintValidator
约束行为的接口的实现。
要将声明与实现相关联,每个@Constraint
注释都引用相应的ConstraintValidator
实现类。在运行时,ConstraintValidatorFactory
在域模型中遇到约束注释时, 实例化引用的实现。
默认情况下,LocalValidatorFactoryBean
配置SpringConstraintValidatorFactory
使用Spring创建ConstraintValidator
实例的a。这使得您的自定义 ConstraintValidators
可以像任何其他Spring bean一样受益于依赖注入。
以下示例显示了一个自定义@Constraint
声明,后跟一个ConstraintValidator
使用Spring进行依赖项注入的关联 实现:
@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=MyConstraintValidator.class) public @interface MyConstraint { }
import javax.validation.ConstraintValidator; public class MyConstraintValidator implements ConstraintValidator { @Autowired; private Foo aDependency; ... }
如前面的示例所示,ConstraintValidator
实现可以将其依赖项 @Autowired
与任何其他Spring bean一样。
弹簧驱动的方法验证
您可以通过bean定义将Bean Validation 1.1支持的方法验证功能(以及作为自定义扩展,也可以通过Hibernate Validator 4.3)集成到Spring上下文中MethodValidationPostProcessor
,如下所示:
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
要获得Spring驱动的方法验证资格,所有目标类都需要使用Spring的@Validated
注释进行注释。(或者,您也可以声明要使用的验证组。)请参阅MethodValidationPostProcessor
javadoc以获取有关Hibernate Validator和Bean Validation 1.1提供程序的设置详细信息。
其他配置选项
默认LocalValidatorFactoryBean
配置足以满足大多数情况。从消息插值到遍历解析,各种Bean Validation构造有许多配置选项。有关LocalValidatorFactoryBean
这些选项的更多信息,请参阅 javadoc。
3.7.3。配置一个DataBinder
从Spring 3开始,您可以使用a配置DataBinder
实例Validator
。配置完成后,您可以Validator
通过调用来调用binder.validate()
。任何验证都会 Errors
自动添加到活页夹中BindingResult
。
以下示例说明如何DataBinder
在绑定到目标对象后以编程方式使用调用验证逻辑:
Foo target = new Foo(); DataBinder binder = new DataBinder(target); binder.setValidator(new FooValidator()); // bind to the target object binder.bind(propertyValues); // validate the target object binder.validate(); // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult();
您还可以通过和配置DataBinder
多个Validator
实例 。将全局配置的bean验证与在DataBinder实例上本地配置的Spring组合时,这非常有用。请参阅[validation-mvc-configurations]。dataBinder.addValidators``dataBinder.replaceValidators``Validator
3.7.4。Spring MVC 3验证
请参阅Spring MVC章节中的验证。
4.春季表达语言(SpEL)
Spring Expression Language(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了其他功能,最值得注意的是方法调用和基本字符串模板功能。
虽然还有其他几种Java表达式语言--OGNL,MVEL和JBoss EL,仅举几例 - 创建Spring表达式语言是为Spring社区提供一种支持良好的表达式语言,可以在所有产品中使用春季组合。其语言特性受Spring组合项目要求的驱动,包括基于Eclipse的Spring Tool Suite中代码完成支持的工具要求。也就是说,SpEL基于技术无关的API,可以在需要时集成其他表达式语言实现。
虽然SpEL是Spring组合中表达式评估的基础,但它并不直接与Spring绑定,可以单独使用。要自包含,本章中的许多示例都使用SpEL,就像它是一种独立的表达式语言一样。这需要创建一些引导基础结构类,例如解析器。大多数Spring用户不需要处理这种基础结构,而只能编写用于评估的表达式字符串。这种典型用法的一个示例是将SpEL集成到创建基于XML或基于注释的bean定义中,如表达式支持中所示,用于定义bean定义。
本章介绍表达式语言的功能,API及其语言语法。在几个地方,Inventor
和Society
类为目标的表达评价对象使用。这些类声明和用于填充它们的数据列在本章末尾。
表达式语言支持以下功能:
-
文字表达
-
布尔和关系运算符
-
常用表达
-
类表达式
-
访问属性,数组,列表和映射
-
方法调用
-
关系运算符
-
分配
-
调用构造函数
-
Bean引用
-
阵列构造
-
内联列表
-
内联地图
-
三元运算符
-
变量
-
用户定义的函数
-
收集投影
-
收藏品选择
-
模板化的表达
4.1。评估
本节介绍SpEL接口及其表达式语言的简单使用。完整的语言参考可以在语言参考中找到 。
以下代码介绍了用于评估文字字符串表达式的SpEL API Hello World
。
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); String message = (String) exp.getValue();
消息变量的值是'Hello World' 。 | |
---|---|
您最有可能使用的SpEL类和接口位于 org.springframework.expression
包及其子包中,例如spel.support
。
该ExpressionParser
接口负责解析表达式字符串。在前面的示例中,表达式字符串是由周围的单引号表示的字符串文字。该Expression
接口是负责评估先前定义的表达式字符串。两个例外是可以扔掉,ParseException
而 EvaluationException
打电话时,parser.parseExpression
和exp.getValue
分别。
SpEL支持广泛的功能,例如调用方法,访问属性和调用构造函数。
在下面的方法调用示例中,我们concat
在字符串文字上调用该方法:
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); String message = (String) exp.getValue();
现在的价值message 是'Hello World!'。 | |
---|---|
以下调用JavaBean属性的示例调用该String
属性Bytes
:
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes()' Expression exp = parser.parseExpression("'Hello World'.bytes"); byte[] bytes = (byte[]) exp.getValue();
该行将文字转换为字节数组。 | |
---|---|
SpEL还通过使用标准点表示法(例如prop1.prop2.prop3
)和属性值的设置来支持嵌套属性 。也可以访问公共字段。以下示例显示如何使用点表示法来获取文字的长度:
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes().length' Expression exp = parser.parseExpression("'Hello World'.bytes.length"); int length = (Integer) exp.getValue();
'Hello World'.bytes.length 给出文字的长度。 | |
---|---|
可以调用String的构造函数而不是使用字符串文字,如以下示例所示:
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); String message = exp.getValue(String.class);
String 从文字构造一个新的并使它成为大写。 | |
---|---|
注意使用通用方法:public <T> T getValue(Class<T> desiredResultType)
。使用此方法无需将表达式的值强制转换为所需的结果类型。EvaluationException
如果无法将值强制转换为类型T
或使用已注册的类型转换器转换,则抛出An 。
SpEL的更常见用法是提供针对特定对象实例(称为根对象)计算的表达式字符串。以下示例说明如何name
从Inventor
类的实例检索属性或创建布尔条件:
// Create and set a calendar GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); // The constructor arguments are name, birthday, and nationality. Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("name"); String name = (String) exp.getValue(tesla); // name == "Nikola Tesla" exp = parser.parseExpression("name == 'Nikola Tesla'"); boolean result = exp.getValue(tesla, Boolean.class); // result == true
解析name 为表达式。 | |
---|---|
4.1.1。理解EvaluationContext
在计算EvaluationContext
表达式以解析属性,方法或字段以及帮助执行类型转换时,将使用该接口。Spring提供了两种实现。
-
SimpleEvaluationContext
:为不需要SpEL语言语法的完整范围的表达式类别公开一个基本SpEL语言功能和配置选项的子集,并且应该进行有意义的限制。示例包括但不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开全套SpEL语言功能和配置选项。您可以使用它来指定默认根对象并配置每个可用的与评估相关的策略。
SimpleEvaluationContext
旨在仅支持SpEL语言语法的子集。它排除了Java类型引用,构造函数和bean引用。它还要求您明确选择表达式中属性和方法的支持级别。默认情况下,create()
静态工厂方法仅启用对属性的读访问权限。您还可以获取构建器以配置所需的确切支持级别,定位以下一个或多个组合:
-
PropertyAccessor
仅限定制(无反射) -
只读访问的数据绑定属性
-
读写的数据绑定属性
类型转换
默认情况下,SpEL使用Spring core(org.springframework.core.convert.ConversionService
)中提供的转换服务。此转换服务附带了许多用于常见转换的内置转换器,但也是完全可扩展的,因此您可以在类型之间添加自定义转换。此外,它具有泛型意识。这意味着,当您在表达式中使用泛型类型时,SpEL会尝试转换以保持其遇到的任何对象的类型正确性。
这在实践中意味着什么?假设使用赋值setValue()
来设置List
属性。实际上是属性的类型List<Boolean>
。SpEL识别列表中的元素Boolean
在放入其中之前需要转换为。以下示例显示了如何执行此操作:
class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } Simple simple = new Simple(); simple.booleanList.add(true); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); // "false" is passed in here as a String. SpEL and the conversion service // will recognize that it needs to be a Boolean and convert it accordingly. parser.parseExpression("booleanList[0]").setValue(context, simple, "false"); // b is false Boolean b = simple.booleanList.get(0);
4.1.2。分析器配置
可以使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration
)配置SpEL表达式解析器。配置对象控制某些表达式组件的行为。例如,如果索引到数组或集合并且指定索引处的元素是null
,则可以自动创建该元素。当使用由一系列属性引用组成的表达式时,这很有用。如果索引到数组或列表并指定超出数组或列表当前大小末尾的索引,则可以自动增大数组或列表以适应该索引。以下示例演示如何自动增长列表:
class Demo { public List<String> list; } // Turn on: // - auto null reference initialization // - auto collection growing SpelParserConfiguration config = new SpelParserConfiguration(true,true); ExpressionParser parser = new SpelExpressionParser(config); Expression expression = parser.parseExpression("list[3]"); Demo demo = new Demo(); Object o = expression.getValue(demo); // demo.list will now be a real collection of 4 entries // Each entry is a new empty String
4.1.3。SpEL编译
Spring Framework 4.1包含一个基本的表达式编译器。表达式通常被解释,在评估期间提供了很多动态灵活性,但是没有提供最佳性能。对于偶尔的表达式使用,这很好,但是,当其他组件(如Spring Integration)使用时,性能可能非常重要,并且不需要动态。
SpEL编译器旨在满足此需求。在评估期间,编译器会生成一个真实的Java类,它体现了表达式行为,并使用它来实现更快的表达式评估。由于缺少表达式类型,编译器使用在执行编译时对表达式的解释评估期间收集的信息。例如,它不完全从表达式中知道属性引用的类型,但是,在第一次解释的求值期间,它会发现它是什么。当然,如果各种表达元素的类型随着时间的推移而变化,那么基于此信息的编译可能会引起麻烦。因此,编译最适合于类型信息在重复评估时不会改变的表达式。
请考虑以下基本表达式:
someArray [0] .someProperty.someOtherProperty <0.1
因为前面的表达式涉及数组访问,某些属性取消引用和数字操作,所以性能增益非常明显。在一个50000次迭代的微基准测试示例中,使用解释器进行评估需要75ms,使用表达式的编译版本只需要3ms。
编译器配置
默认情况下,编译器未打开,但您可以通过两种不同的方式打开它。您可以使用解析器配置过程(前面讨论过)或在将SpEL用法嵌入到另一个组件中时使用系统属性来打开它。本节讨论这两个选项。
编译器可以以三种模式之一操作,这些模式在org.springframework.expression.spel.SpelCompilerMode
枚举中捕获。模式如下:
-
OFF
(默认值):编译器已关闭。 -
IMMEDIATE
:在立即模式下,表达式会尽快编译。这通常是在第一次解释评估之后。如果编译的表达式失败(通常由于类型更改,如前所述),则表达式求值的调用者会收到异常。 -
MIXED
:在混合模式下,表达式会随着时间的推移在解释和编译模式之间静默切换。经过一定数量的解释运行后,它们会切换到编译形式,如果编译后的表单出现问题(如类型更改,如前所述),表达式会自动再次切换回解释形式。稍后,它可能会生成另一个编译的表单并切换到它。基本上,用户进入IMMEDIATE
模式的例外是在内部处理。
IMMEDIATE
模式存在,因为MIXED
模式可能会导致具有副作用的表达式出现问题。如果编译后的表达式在部分成功后爆炸,则可能已经完成了影响系统状态的事情。如果发生这种情况,调用者可能不希望它以解释模式静默重新运行,因为表达式的一部分可能正在运行两次。
选择模式后,使用SpelParserConfiguration
配置解析器。以下示例显示了如何执行此操作:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader()); SpelExpressionParser parser = new SpelExpressionParser(config); Expression expr = parser.parseExpression("payload"); MyMessage message = new MyMessage(); Object payload = expr.getValue(message);
指定编译器模式时,还可以指定类加载器(允许传递null)。编译表达式在任何提供的子类加载器中定义。重要的是要确保,如果指定了类加载器,它可以看到表达式评估过程中涉及的所有类型。如果未指定类加载器,则使用默认类加载器(通常是表达式求值期间运行的线程的上下文类加载器)。
配置编译器的第二种方法是在SpEL嵌入到某个其他组件中时使用,并且可能无法通过配置对象对其进行配置。在这些情况下,可以使用系统属性。您可以设置 spring.expression.compiler.mode
属性为一个SpelCompilerMode
枚举值(off
,immediate
,或mixed
)。
编译器限制
从Spring Framework 4.1开始,基本的编译框架已经到位。但是,该框架尚不支持编译各种表达式。最初的重点是可能在性能关键环境中使用的常用表达式。目前无法编译以下类型的表达式:
-
涉及转让的表达
-
表达式依赖于转换服务
-
表达式使用自定义解析器或访问器
-
使用选择或投影的表达式
将来可以编写更多类型的表达式。
4.2。Bean定义中的表达式
您可以将SpEL表达式与基于XML或基于注释的配置元数据一起用于定义BeanDefinition
实例。在这两种情况下,定义表达式的语法都是表单#{ <expression string> }
。
4.2.1。XML配置
可以使用表达式设置属性或构造函数参数值,如以下示例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean>
该systemProperties
变量是预定义的,所以你可以在你的表达式中使用它,如下例所示:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <!-- other properties --> </bean>
请注意,您不必#
在此上下文中使用符号为预定义变量添加前缀。
您还可以按名称引用其他bean属性,如以下示例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean> <bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> <!-- other properties --> </bean>
4.2.2。注释配置
要指定默认值,可以将@Value
注释放在字段,方法和方法或构造函数参数上。
以下示例设置字段变量的默认值:
public static class FieldValueTestBean @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
以下示例显示了等效但在属性setter方法上:
public static class PropertyValueTestBean private String defaultLocale; @Value("#{ systemProperties['user.region'] }") public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } }
自动化方法和构造函数也可以使用@Value
注释,如以下示例所示:
public class SimpleMovieLister { private MovieFinder movieFinder; private String defaultLocale; @Autowired public void configure(MovieFinder movieFinder, @Value("#{ systemProperties['user.region'] }") String defaultLocale) { this.movieFinder = movieFinder; this.defaultLocale = defaultLocale; } // ... }
public class MovieRecommender { private String defaultLocale; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, @Value("#{systemProperties['user.country']}") String defaultLocale) { this.customerPreferenceDao = customerPreferenceDao; this.defaultLocale = defaultLocale; } // ... }
4.3。语言参考
本节描述Spring表达式语言的工作原理。它包括以下主题:
4.3.1。文字表达
支持的文字表达式的类型是字符串,数值(int,real,hex),boolean和null。字符串由单引号分隔。要在字符串中放置单引号,请使用两个单引号字符。
以下清单显示了文字的简单用法。通常,它们不是像这样单独使用,而是作为更复杂表达式的一部分使用 - 例如,在逻辑比较运算符的一侧使用文字。
ExpressionParser parser = new SpelExpressionParser(); // evals to "Hello World" String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); // evals to 2147483647 int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); Object nullValue = parser.parseExpression("null").getValue();
数字支持使用负号,指数表示法和小数点。默认情况下,使用Double.parseDouble()解析实数。
4.3.2。属性,数组,列表,地图和索引器
使用属性引用进行导航很容易。为此,请使用句点指示嵌套属性值。Inventor
该类的实例pupin
和/ tesla
,填充了示例部分中使用的类中列出的数据。为了“向下”导航并获得特斯拉的出生年份和普平的出生城市,我们使用以下表达式:
// evals to 1856 int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
属性名称的第一个字母允许不区分大小写。数组和列表的内容是使用方括号表示法获得的,如下例所示:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); // Inventions Array // evaluates to "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue( context, tesla, String.class); // Members List // evaluates to "Nikola Tesla" String name = parser.parseExpression("Members[0].Name").getValue( context, ieee, String.class); // List and Array navigation // evaluates to "Wireless communication" String invention = parser.parseExpression("Members[0].Inventions[6]").getValue( context, ieee, String.class);
通过指定括号内的文字键值来获取映射的内容。在以下示例中,因为Officers
映射的键是字符串,所以我们可以指定字符串文字:
// Officer's Dictionary Inventor pupin = parser.parseExpression("Officers['president']").getValue( societyContext, Inventor.class); // evaluates to "Idvor" String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue( societyContext, String.class); // setting values parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue( societyContext, "Croatia");
4.3.3。内联列表
您可以使用{}
表示法直接在表达式中表达列表。
// evaluates to a Java list containing the four numbers List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
{}
本身就是一个空列表。出于性能原因,如果列表本身完全由固定文字组成,则会创建一个常量列表来表示表达式(而不是在每个评估中构建新列表)。
4.3.4。内联地图
您还可以使用{key:value}
表示法直接在表达式中表达地图。以下示例显示了如何执行此操作:
// evaluates to a Java map containing the two entries Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context); Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
{:}
本身就是一张空地图。出于性能原因,如果地图本身由固定文字或其他嵌套常量结构(列表或地图)组成,则会创建一个常量地图来表示表达式(而不是在每次评估时构建新地图)。引用地图键是可选的。上面的示例不使用带引号的键。
4.3.5。数组构造
您可以使用熟悉的Java语法构建数组,可选择提供初始化程序以在构造时填充数组。以下示例显示了如何执行此操作:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); // Array with initializer int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); // Multi dimensional array int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
在构造多维数组时,当前无法提供初始化程序。
4.3.6。方法
您可以使用典型的Java编程语法调用方法。您还可以在文字上调用方法。还支持变量参数。以下示例显示了如何调用方法:
// string literal, evaluates to "bc" String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); // evaluates to true boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue( societyContext, Boolean.class);
4.3.7。运营商
Spring Expression Language支持以下类型的运算符:
关系运算符
使用标准运算符表示法支持关系运算符(等于,不等于,小于,小于或等于,大于,等于或等于)。以下清单显示了一些运算符示例:
// evaluates to true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); // evaluates to false boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); // evaluates to true boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
大于和小于比较null 遵循一个简单的规则:null 被视为没有(不是零)。因此,任何其他值总是大于null (X > null 总是true ),并且没有其他值永远小于任何值(X < null 总是如此false )。如果您更喜欢数字比较,请避免基于数字的比较,null 以支持与零进行比较(例如,X > 0 或X < 0 )。 | |
---|---|
除了标准的关系运算符之外,SpEL还支持instanceof
基于正则表达式的matches
运算符。以下列表显示了两者的示例:
// evaluates to false boolean falseValue = parser.parseExpression( "'xyz' instanceof T(Integer)").getValue(Boolean.class); // evaluates to true boolean trueValue = parser.parseExpression( "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); //evaluates to false boolean falseValue = parser.parseExpression( "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
注意原始类型,因为它们立即被装箱到包装类型,因此在1 instanceof T(int) 评估false 时1 instanceof T(Integer) 评估为true ,如预期的那样。 | |
---|---|
每个符号运算符也可以指定为纯字母等价运算符。这避免了所使用的符号对于嵌入表达式的文档类型具有特殊含义的问题(例如在XML文档中)。文本等价物是:
-
lt
(<
) -
gt
(>
) -
le
(<=
) -
ge
(>=
) -
eq
(==
) -
ne
(!=
) -
div
(/
) -
mod
(%
) -
not
(!
)。
所有文本运算符都不区分大小写。
逻辑运算符
SpEL支持以下逻辑运算符:
-
and
-
or
-
not
以下示例显示如何使用逻辑运算符
// -- AND -- // evaluates to false boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); // evaluates to true String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- OR -- // evaluates to true boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); // evaluates to true String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- NOT -- // evaluates to false boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); // -- AND and NOT -- String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
数学运算符
您可以在数字和字符串上使用加法运算符。您只能对数字使用减法,乘法和除法运算符。您还可以使用模数(%)和指数幂(^)运算符。强制执行标准运算符优先级。以下示例显示了正在使用的数学运算符:
// Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression( "'test' + ' ' + 'string'").getValue(String.class); // 'test string' // Subtraction int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // Multiplication int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // Division int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // Modulus int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
任务操作员
要设置属性,请使用赋值运算符(=
)。这通常在调用期间完成,setValue
但也可以在调用内完成getValue
。以下清单显示了使用赋值运算符的两种方法:
Inventor inventor = new Inventor(); EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic"); // alternatively String aleks = parser.parseExpression( "Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
4.3.8。类型
您可以使用特殊T
运算符指定java.lang.Class
(类型)的实例。也可以使用此运算符调用静态方法。在StandardEvaluationContext
使用TypeLocator
查找类型以及 StandardTypeLocator
(可替换)是建立与所述的理解 java.lang
包。这意味着T()
对其中类型的引用java.lang
不需要完全限定,但所有其他类型引用必须是。以下示例显示如何使用T
运算符:
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); boolean trueValue = parser.parseExpression( "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") .getValue(Boolean.class);
4.3.9。构造函数
您可以使用new
运算符调用构造函数。除了基本类型(int
,float
等等)和String之外,您应该使用完全限定的类名。以下示例显示如何使用new
运算符来调用构造函数:
Inventor einstein = p.parseExpression( "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')") .getValue(Inventor.class); //create new inventor instance within add method of List p.parseExpression( "Members.add(new org.spring.samples.spel.inventor.Inventor( 'Albert Einstein', 'German'))").getValue(societyContext);
4.3.10。变量
您可以使用#variableName
语法引用表达式中的变量。通过setVariable
在EvaluationContext
实现上使用该方法来设置变量。以下示例显示如何使用变量:
Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); context.setVariable("newName", "Mike Tesla"); parser.parseExpression("Name = #newName").getValue(context, tesla); System.out.println(tesla.getName()) // "Mike Tesla"
在#this
与#root
变量
该#this
变量总是被定义并且是指当前的评价对象(针对其不合格的引用解析)。该#root
变量总是被定义,并且是指根上下文对象。尽管#this
可能会在评估表达式的组件时发生变化,但#root
始终引用根。以下示例显示如何使用#this
和#root
变量:
// create an array of integers List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); // create parser and set variable 'primes' as the array of integers ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess(); context.setVariable("primes", primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( "#primes.?[#this>10]").getValue(context);
4.3.11。功能
您可以通过注册可在表达式字符串中调用的用户定义函数来扩展SpEL。该功能通过注册EvaluationContext
。以下示例显示如何注册用户定义的函数:
Method method = ...; EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method);
例如,请考虑以下实用方法来反转字符串:
public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(input.length()); for (int i = 0; i < input.length(); i++) backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } }
然后,您可以注册并使用上述方法,如以下示例所示:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class);
4.3.12。Bean参考
如果已使用bean解析器配置了评估上下文,则可以使用该@
符号从表达式中查找bean 。以下示例显示了如何执行此操作:
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@something").getValue(context);
要访问工厂bean本身,您应该在bean名称前加上一个&
符号。以下示例显示了如何执行此操作:
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation Object bean = parser.parseExpression("&foo").getValue(context);
4.3.13。三元算子(If-Then-Else)
您可以使用三元运算符在表达式中执行if-then-else条件逻辑。以下清单显示了一个最小的示例:
String falseString = parser.parseExpression( "false ? 'trueExp' : 'falseExp'").getValue(String.class);
在这种情况下,布尔值false
会返回字符串值'falseExp'
。一个更现实的例子如下:
parser.parseExpression("Name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; String queryResultString = parser.parseExpression(expression) .getValue(societyContext, String.class); // queryResultString = "Nikola Tesla is a member of the IEEE Society"
有关三元运算符的更短语法,请参阅Elvis运算符的下一节。
4.3.14。猫王运营商
Elvis运算符是三元运算符语法的缩写,用于 Groovy语言。使用三元运算符语法,您通常必须重复两次变量,如以下示例所示:
String name = "Elvis Presley"; String displayName = (name != null ? name : "Unknown");
相反,您可以使用Elvis运算符(以与Elvis的发型相似的名称命名)。以下示例显示如何使用Elvis运算符:
ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(String.class); System.out.println(name); // 'Unknown'
以下列表显示了一个更复杂的示例:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Nikola Tesla tesla.setName(null); name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Elvis Presley
您可以使用Elvis运算符在表达式中应用默认值。下面的示例演示了如何在@Value 表达式中使用Elvis运算符:@Value("#{systemProperties['pop3.port'] ?: 25}")``pop3.port 如果已定义,则将注入系统属性,否则注入25。 | |
---|---|
4.3.15。安全导航操作员
航行安全的操作使用,以避免NullPointerException
与来自Groovy的 语言。通常,在引用对象时,可能需要在访问对象的方法或属性之前验证它是否为null。为避免这种情况,安全导航操作符返回null而不是抛出异常。以下示例显示如何使用安全导航运算符:
ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); System.out.println(city); // null - does not throw NullPointerException!!!
4.3.16。收藏选择
Selection是一种强大的表达式语言功能,允许您通过从其条目中进行选择将源集合转换为另一个集合。
选择使用的语法.?[selectionExpression]
。它过滤集合并返回包含原始元素子集的新集合。例如,选择让我们可以轻松获得塞尔维亚发明家的列表,如下例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression( "Members.?[Nationality == 'Serbian']").getValue(societyContext);
可以在列表和地图上进行选择。对于列表,将针对每个单独的列表元素评估选择标准。针对地图,针对每个映射条目(Java类型的对象)评估选择标准 Map.Entry
。每个映射条目都可以将其键和值作为选项中使用的属性进行访问。
以下表达式返回一个新映射,该映射由原始映射中条目值小于27的那些元素组成:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回所有选定元素外,您还可以只检索第一个或最后一个值。要获得与选择匹配的第一个条目,语法为.^[selectionExpression]
。要获得最后一个匹配选择,语法是 .$[selectionExpression]
。
4.3.17。收集投影
投影允许集合驱动子表达式的评估,结果是新集合。投影的语法是.![projectionExpression]
。例如,假设我们有一个发明者列表,但想要他们出生的城市列表。实际上,我们想要为发明人列表中的每个条目评估“placeOfBirth.city”。以下示例使用投影来执行此操作:
// returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
您还可以使用地图来驱动投影,在这种情况下,投影表达式将根据地图中的每个条目进行评估(表示为Java Map.Entry
)。跨地图投影的结果是一个列表,其中包含对每个地图条目的投影表达式的评估。
4.3.18。表达模板
表达式模板允许将文本文本与一个或多个评估块混合。每个评估块都使用您可以定义的前缀和后缀字符分隔。一个常见的选择是#{ }
用作分隔符,如下例所示:
String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // evaluates to "random number is 0.7038186818312008"
通过将文本文本'random number is '
与在#{ }
分隔符内部计算表达式的结果(在这种情况下,调用该random()
方法的结果)连接来计算字符串。该parseExpression()
方法的第二个参数是类型ParserContext
。该ParserContext
接口用于影响表达式的解析方式,以支持表达式模板功能。定义TemplateParserContext
如下:
public class TemplateParserContext implements ParserContext { public String getExpressionPrefix() { return "#{"; } public String getExpressionSuffix() { return "}"; } public boolean isTemplate() { return true; } }
4.4。示例中使用的类
本节列出了本章示例中使用的类。
示例1. Inventor.java
package org.spring.samples.spel.inventor; import java.util.Date; import java.util.GregorianCalendar; public class Inventor { private String name; private String nationality; private String[] inventions; private Date birthdate; private PlaceOfBirth placeOfBirth; public Inventor(String name, String nationality) { GregorianCalendar c= new GregorianCalendar(); this.name = name; this.nationality = nationality; this.birthdate = c.getTime(); } public Inventor(String name, Date birthdate, String nationality) { this.name = name; this.nationality = nationality; this.birthdate = birthdate; } public Inventor() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNationality() { return nationality; } public void setNationality(String nationality) { this.nationality = nationality; } public Date getBirthdate() { return birthdate; } public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } public PlaceOfBirth getPlaceOfBirth() { return placeOfBirth; } public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { this.placeOfBirth = placeOfBirth; } public void setInventions(String[] inventions) { this.inventions = inventions; } public String[] getInventions() { return inventions; } }
示例2. PlaceOfBirth.java
package org.spring.samples.spel.inventor; public class PlaceOfBirth { private String city; private String country; public PlaceOfBirth(String city) { this.city=city; } public PlaceOfBirth(String city, String country) { this(city); this.country = country; } public String getCity() { return city; } public void setCity(String s) { this.city = s; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } }
例3. Society.java
package org.spring.samples.spel.inventor; import java.util.*; public class Society { private String name; public static String Advisors = "advisors"; public static String President = "president"; private List<Inventor> members = new ArrayList<Inventor>(); private Map officers = new HashMap(); public List getMembers() { return members; } public Map getOfficers() { return officers; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isMember(String name) { for (Inventor inventor : members) { if (inventor.getName().equals(name)) { return true; } } return false; } }
5.使用Spring进行面向方面编程
面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。方面实现了跨越多种类型和对象的关注点(例如事务管理)的模块化。(这些担忧在AOP文献中通常被称为“横切”问题。)
Spring的一个关键组件是AOP框架。虽然Spring IoC容器不依赖于AOP(意味着您不需要使用AOP),但AOP补充了Spring IoC以提供非常强大的中间件解决方案。
Spring 2.0+ AOP
Spring 2.0引入了一种更简单,更强大的方法,通过使用基于模式的方法或@AspectJ注释样式来编写自定义方面。这两种样式都提供完全类型的建议和使用AspectJ切入点语言,同时仍然使用Spring AOP进行编织。
本章讨论Spring 2.0+模式和基于@ AspectJ的AOP支持。下一章将讨论较低级别的AOP支持,如Spring 1.2应用程序中常见的那样。
AOP在Spring Framework中用于:
-
提供声明性企业服务,尤其是作为EJB声明性服务的替代品。最重要的此类服务是 声明式事务管理。
-
让用户实现自定义方面,补充他们使用AOP的OOP。
如果您只对通用声明性服务或其他预先打包的声明性中间件服务(如池)感兴趣,则无需直接使用Spring AOP,并且可以跳过本章的大部分内容。 | |
---|---|
5.1。AOP概念
让我们首先定义一些中心AOP概念和术语。这些术语不是特定于Spring的。不幸的是,AOP术语不是特别直观。但是,如果Spring使用自己的术语,那将更加令人困惑。
-
方面:跨越多个类别的关注点的模块化。事务管理是企业Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,方面是通过使用常规类(基于模式的方法)或使用
@Aspect
注释(@AspectJ样式)注释的常规类来实现的 。 -
加入点:程序执行期间的一个点,例如执行方法或处理异常。在Spring AOP中,连接点始终表示方法执行。
-
建议:特定连接点的某个方面采取的操作。不同类型的建议包括“周围”,“之前”和“之后”建议。(建议类型将在后面讨论。)许多AOP框架(包括Spring)将建议建模为拦截器并在连接点周围维护一系列拦截器。
-
切入点:匹配连接点的谓词。建议与切入点表达式相关联,并在切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。
-
简介:代表类型声明其他方法或字段。Spring AOP允许您向任何建议的对象引入新接口(以及相应的实现)。例如,您可以使用简介使bean实现
IsModified
接口,以简化缓存。(介绍被称为AspectJ社区中的类型间声明。) -
目标对象:由一个或多个方面建议的对象。也称为“建议对象”。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
-
AOP代理:由AOP框架创建的对象,用于实现方面契约(建议方法执行等)。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。
-
编织:将方面与其他应用程序类型或对象链接以创建建议对象。这可以在编译时(例如,使用AspectJ编译器),加载时间或在运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。
Spring AOP包括以下类型的建议:
-
建议之前:在连接点之前运行但无法阻止执行流程进入连接点的建议(除非它抛出异常)。
-
返回建议后:在连接点正常完成后运行的建议(例如,如果方法返回而不抛出异常)。
-
抛出建议后:如果方法通过抛出异常退出,则执行建议。
-
在(最终)建议之后:无论连接点退出的方式(正常或异常返回),都要执行建议。
-
围绕建议:围绕连接点的建议,例如方法调用。这是最有力的建议。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
围绕建议是最普遍的建议。由于Spring AOP(如AspectJ)提供了全方位的建议类型,因此我们建议您使用可以实现所需行为的最不强大的建议类型。例如,如果您只需要使用方法的返回值更新缓存,那么最好实现返回后的建议而不是周围的建议,尽管周围的建议可以完成同样的事情。使用最具体的建议类型可以提供更简单的编程模型,减少错误的可能性。例如,您不需要proceed()
在JoinPoint
used for around advice 上调用该方法,因此,您无法调用它。
在Spring 2.0中,所有通知参数都是静态类型的,因此您可以使用相应类型的建议参数(例如,方法执行的返回值的类型)而不是Object
数组。
由切入点匹配的连接点的概念是AOP的关键,它将其与仅提供拦截的旧技术区分开来。切入点使得建议可以独立于面向对象的层次结构进行定向。例如,您可以将一个提供声明性事务管理的建议应用于跨多个对象的一组方法(例如服务层中的所有业务操作)。
5.2。Spring AOP功能和目标
Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类加载器层次结构,因此适合在servlet容器或应用程序服务器中使用。
Spring AOP目前仅支持方法执行连接点(建议在Spring bean上执行方法)。虽然可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但未实现字段拦截。如果您需要建议字段访问和更新连接点,请考虑使用AspectJ等语言。
Spring AOP的AOP方法与大多数其他AOP框架的方法不同。目的不是提供最完整的AOP实现(尽管Spring AOP非常强大)。相反,目标是在AOP实现和Spring IoC之间提供紧密集成,以帮助解决企业应用程序中的常见问题。
因此,例如,Spring Framework的AOP功能通常与Spring IoC容器一起使用。通过使用普通bean定义语法来配置方面(尽管这允许强大的“自动代理”功能)。这是与其他AOP实现的重要区别。使用Spring AOP无法轻松或高效地完成某些操作,例如建议非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。但是,我们的经验是Spring AOP为适合AOP的企业Java应用程序中的大多数问题提供了出色的解决方案。
Spring AOP从未努力与AspectJ竞争,以提供全面的AOP解决方案。我们认为,基于代理的框架(如Spring AOP)和完整的框架(如AspectJ)都很有价值,而且它们是互补的,而不是竞争。Spring将Spring AOP和IoC与AspectJ无缝集成,以在一致的基于Spring的应用程序架构中实现AOP的所有使用。此集成不会影响Spring AOP API或AOP Alliance API。Spring AOP仍然向后兼容。有关Spring AOP API的讨论,请参阅以下章节。
Spring框架的核心原则之一是非侵入性。这个想法是,您不应该被迫在您的业务或域模型中引入特定于框架的类和接口。但是,在某些地方,Spring Framework确实为您提供了将Spring Framework特定的依赖项引入代码库的选项。为您提供此类选项的基本原理是,在某些情况下,以这种方式阅读或编写某些特定功能可能更容易。但是,Spring Framework(几乎)总是为您提供选择:您可以自由决定哪种选项最适合您的特定用例或场景。与本章相关的一个选择是选择哪种AOP框架(以及哪种AOP样式)。您可以选择AspectJ,Spring AOP或两者。您还可以选择@AspectJ注释样式方法或Spring XML配置样式方法。本章选择首先介绍@ AspectJ风格的方法,这一事实不应被视为Spring团队倾向于采用Spring XML配置风格的@AspectJ注释风格方法。请参阅选择要使用的AOP声明样式,以更全面地讨论每种样式的“为什么和为何”。 | |
---|---|
5.3。AOP代理
Spring AOP默认使用AOP代理的标准JDK动态代理。这使得任何接口(或接口集)都可以被代理。
Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于优化的做法是编程接口而不是类,业务类通常实现一个或多个业务接口。可以 强制使用CGLIB,在那些需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法的情况下(希望很少见)。
掌握Spring AOP是基于代理的这一事实非常重要。请参阅 了解AOP代理,以全面了解此实现细节的实际含义。
5.4。@AspectJ支持
@AspectJ指的是将方面声明为使用注释注释的常规Java类的样式。作为AspectJ 5版本的一部分,AspectJ项目引入了@AspectJ样式 。Spring使用AspectJ提供的库解释与AspectJ 5相同的注释,用于切入点解析和匹配。但是,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或weaver。
使用AspectJ编译器和weaver可以使用完整的AspectJ语言,并在使用AspectJ和Spring Applications中进行了讨论。 | |
---|---|
5.4.1。启用@AspectJ支持
要在Spring配置中使用@AspectJ方面,您需要启用Spring支持,以基于@AspectJ方面配置Spring AOP,并根据这些方面是否建议自动代理bean。通过自动代理,我们的意思是,如果Spring确定bean被一个或多个方面建议,它会自动为该bean生成一个代理来拦截方法调用,并确保根据需要执行建议。
可以使用XML或Java样式配置启用@AspectJ支持。在任何一种情况下,您还需要确保AspectJ的aspectjweaver.jar
库位于应用程序的类路径中(版本1.8或更高版本)。此库位于 lib
AspectJ分发目录或Maven Central存储库中。
使用Java配置启用@AspectJ支持
要使用Java启用@AspectJ支持@Configuration
,请添加@EnableAspectJAutoProxy
注释,如以下示例所示:
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
使用XML配置启用@AspectJ支持
要使用基于XML的配置启用@AspectJ支持,请使用该aop:aspectj-autoproxy
元素,如以下示例所示:
<aop:aspectj-autoproxy/>
这假设您使用基于XML架构的配置中描述的架构支持 。有关如何在 命名空间中导入标记,请参阅 AOP架构aop
。
5.4.2。声明一个方面
在启用@AspectJ支持的情况下,在应用程序上下文中定义的任何bean都具有@AspectJ方面的类(具有@Aspect
注释),Spring会自动检测并用于配置Spring AOP。接下来的两个示例显示了非常有用的方面所需的最小定义。
这两个示例中的第一个示例在应用程序上下文中显示了一个常规bean定义,该定义指向具有@Aspect
注释的bean类:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> <!-- configure properties of the aspect here --> </bean>
两个示例中的第二个示出了NotVeryUsefulAspect
类定义,该注释使用org.aspectj.lang.annotation.Aspect
注释进行注释;
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
方面(带有注释的类@Aspect
)可以有方法和字段,与任何其他类相同。它们还可以包含切入点,建议和引入(类型间)声明。
通过组件扫描自动检测方面您可以在Spring XML配置中将方面类注册为常规bean,或者通过类路径扫描自动检测它们 - 与任何其他Spring管理的bean相同。但请注意,@Aspect 注释不足以在类路径中进行自动检测。为此,您需要添加单独的@Component 注释(或者,根据Spring的组件扫描程序的规则,可以添加符合条件的自定义构造型注释)。 | |
---|---|
与其他方面的方面建议?在Spring AOP中,方面本身不能成为其他方面建议的目标。@Aspect 类上的注释将其标记为方面,因此将其从自动代理中排除。 | |
---|---|
5.4.3。声明切入点
切入点确定感兴趣的连接点,从而使我们能够控制建议何时执行。Spring AOP仅支持Spring bean的方法执行连接点,因此您可以将切入点视为匹配Spring bean上方法的执行。切入点声明有两个部分:一个包含名称和任何参数的签名,以及一个精确确定我们感兴趣的方法执行的切入点表达式。在AOP的@AspectJ注释样式中,切入点签名由常规方法定义提供,并使用@Pointcut
注释指示切入点表达式(用作切入点签名的方法必须具有void
返回类型)。
一个示例可以帮助区分切入点签名和切入点表达式。以下示例定义了一个名为的切入点anyOldTransfer
,该切入点与任何名为的方法的执行匹配transfer
:
@Pointcut("execution(* transfer(..))")// the pointcut expression private void anyOldTransfer() {}// the pointcut signature
形成@Pointcut
注释值的切入点表达式是常规的AspectJ 5切入点表达式。有关AspectJ的切入点语言的完整讨论,请参阅AspectJ编程指南(以及,对于扩展, AspectJ 5开发人员的笔记本)或AspectJ上的一本书(例如Eclipse AspectJ,Colyer等人,或AspectJ in Action),作者:Ramnivas Laddad)。
支持的切入点指示符
Spring AOP支持以下AspectJ切入点指示符(PCD)用于切入点表达式:
-
execution
:用于匹配方法执行连接点。这是使用Spring AOP时使用的主要切入点指示符。 -
within
:限制匹配某些类型中的连接点(使用Spring AOP时在匹配类型中声明的方法的执行)。 -
this
:限制与连接点的匹配(使用Spring AOP时执行方法),其中bean引用(Spring AOP代理)是给定类型的实例。 -
target
:限制与连接点的匹配(使用Spring AOP时执行方法),其中目标对象(被代理的应用程序对象)是给定类型的实例。 -
args
:限制与连接点的匹配(使用Spring AOP时执行方法),其中参数是给定类型的实例。 -
@target
:限制与连接点的匹配(使用Spring AOP时执行方法),其中执行对象的类具有给定类型的注释。 -
@args
:限制与连接点的匹配(使用Spring AOP时执行方法),其中传递的实际参数的运行时类型具有给定类型的注释。 -
@within
:限制匹配到具有给定注释的类型中的连接点(使用Spring AOP时在具有给定注释的类型中声明的方法的执行)。 -
@annotation
:限制连接点的匹配,其中连接点的主题(在Spring AOP中执行的方法)具有给定的注释。
其他切入点类型
完整的AspectJ切入点语言支持未在Spring支持额外的切入点指示符:call
,get
,set
,preinitialization
,staticinitialization
,initialization
,handler
,adviceexecution
,withincode
,cflow
, cflowbelow
,if
,@this
,和@withincode
。在Spring AOP解释的切入点表达式中使用这些切入点指示符会导致IllegalArgumentException
被抛出。
Spring AOP支持的切入点指示符集可以在将来的版本中进行扩展,以支持更多的AspectJ切入点指示符。
由于Spring AOP仅限制与方法执行连接点的匹配,因此前面对切入点指示符的讨论给出了比在AspectJ编程指南中找到的更窄的定义。除此之外,AspectJ本身具有基于类型的语义和,在执行的连接点,无论是this
和target
指的是相同的对象:对象执行方法。Spring AOP是一个基于代理的系统,它区分代理对象本身(绑定到this
)和代理后面的目标对象(绑定到target
)。
由于Spring的AOP框架基于代理的特性,根据定义,目标对象内的调用不会被截获。对于JDK代理,只能拦截代理上的公共接口方法调用。使用CGLIB,代理上的公共和受保护方法调用被截获(如果需要,甚至是包可见的方法)。但是,通过代理进行的常见交互应始终通过公共签名进行设计。请注意,切入点定义通常与任何截获的方法匹配。如果切入点严格意义上是公开的,即使在通过代理进行潜在非公共交互的CGLIB代理方案中,也需要相应地定义切入点。如果你的拦截需要包括方法调用甚至是目标类中的构造函数,那么考虑使用Spring驱动的原生AspectJ编织而不是Spring的基于代理的AOP框架。这构成了具有不同特征的不同AOP使用模式,因此在做出决定之前一定要熟悉编织。 | |
---|---|
Spring AOP还支持另一个名为的PCD bean
。此PCD允许您将连接点的匹配限制为特定的命名Spring bean或一组命名的Spring bean(使用通配符时)。该bean
PCD具有下列形式:
bean(idOrNameOfBean)
该idOrNameOfBean
令牌可以是任何Spring bean的名字。提供了使用该*
字符的有限通配符支持,因此,如果为Spring bean建立了一些命名约定,则可以编写bean
PCD表达式来选择它们。与其他切入点指示符的情况一样,bean
PCD也可以与&&
(和),||
(或)和!
(否定)运算符一起使用。
该bean PCD在本机AspectJ织只在Spring AOP和不支持。它是AspectJ定义的标准PCD的Spring特定扩展,因此不适用于@Aspect 模型中声明的方面。该bean PCD在实例级别(建设于Spring bean的概念)进行操作,而不是在类型级别(到织造为主AOP是有限的)。基于实例的切入点指示符是Spring基于代理的AOP框架的一种特殊功能,它与Spring bean工厂紧密集成,通过名称可以自然而直接地识别特定的bean。 | |
---|---|
结合Pointcut表达式
您可以组合切入点表达式,可以使用&&,
||
和组合!
。您还可以按名称引用切入点表达式。以下示例显示了三个切入点表达式:
@Pointcut("execution(public * *(..))") private void anyPublicOperation() {} @Pointcut("within(com.xyz.someapp.trading..*)") private void inTrading() {} @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {}
anyPublicOperation 如果方法执行连接点表示任何公共方法的执行,则匹配。 | |
---|---|
inTrading 如果方法执行在交易模块中,则匹配。 | |
tradingOperation 如果方法执行表示交易模块中的任何公共方法,则匹配。 |
如前所示,最好从较小的命名组件构建更复杂的切入点表达式。当按名称引用切入点时,将应用常规Java可见性规则(您可以看到相同类型的私有切入点,层次结构中的受保护切入点,任何位置的公共切入点等)。可见性不会影响切入点匹配。
共享公共切入点定义
在使用企业应用程序时,开发人员通常希望从几个方面引用应用程序的模块和特定的操作集。我们建议定义一个“SystemArchitecture”方面,为此目的捕获常见的切入点表达式。这样的方面通常类似于以下示例:
package com.xyz.someapp; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SystemArchitecture { /** * A join point is in the web layer if the method is defined * in a type in the com.xyz.someapp.web package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} /** * A join point is in the service layer if the method is defined * in a type in the com.xyz.someapp.service package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} /** * A join point is in the data access layer if the method is defined * in a type in the com.xyz.someapp.dao package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} /** * A business service is the execution of any method defined on a service * interface. This definition assumes that interfaces are placed in the * "service" package, and that implementation types are in sub-packages. * * If you group service interfaces by functional area (for example, * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))" * could be used instead. * * Alternatively, you can write the expression using the 'bean' * PCD, like so "bean(*Service)". (This assumes that you have * named your Spring service beans in a consistent fashion.) */ @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") public void businessService() {} /** * A data access operation is the execution of any method defined on a * dao interface. This definition assumes that interfaces are placed in the * "dao" package, and that implementation types are in sub-packages. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
您可以在需要切入点表达式的任何位置引用此类方面中定义的切入点。例如,要使服务层成为事务性的,您可以编写以下内容:
<aop:config> <aop:advisor pointcut="com.xyz.someapp.SystemArchitecture.businessService()" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
的<aop:config>
和<aop:advisor>
元件在讨论基于Schema的AOP支持。事务管理中讨论了事务元素。
例子
Spring AOP用户可能execution
最常使用切入点指示符。执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除返回类型模式(ret-type-pattern
在前面的代码片段中),名称模式和参数模式之外的所有部分都是可选的。返回类型模式确定方法的返回类型必须是什么才能匹配连接点。 *
最常用作返回类型模式。它匹配任何返回类型。仅当方法返回给定类型时,完全限定类型名称才匹配。名称模式与方法名称匹配。您可以将*
通配符用作名称模式的全部或部分。如果指定声明类型模式,则包括尾随.
以将其连接到名称模式组件。参数模式稍微复杂一些:()
匹配不带参数的方法,而(..)
匹配任何数量(零个或多个)参数。该(*)
模式匹配采用任何类型的一个参数的方法。 (*,String)
匹配一个带有两个参数的方法。第一个可以是任何类型,而第二个必须是a String
。有关更多信息,请参阅AspectJ编程指南的 语言语义部分。
以下示例显示了一些常见的切入点表达式:
-
执行任何公共方法:
execution(public * *(..))
-
执行名称以以下开头的任何方法
set
:execution(* set*(..))
-
执行
AccountService
接口定义的任何方法:execution(* com.xyz.service.AccountService.*(..))
-
执行
service
包中定义的任何方法:execution(* com.xyz.service.*.*(..))
-
执行服务包或其子包中定义的任何方法:
execution(* com.xyz.service..*.*(..))
-
服务包中的任何连接点(仅在Spring AOP中执行方法):
within(com.xyz.service.*)
-
服务包或其子包中的任何连接点(仅在Spring AOP中执行方法):
within(com.xyz.service..*)
-
代理实现
AccountService
接口的任何连接点(仅在Spring AOP中执行方法) :this(com.xyz.service.AccountService)
'this'更常用于绑定形式。请参阅有关 如何在建议正文中提供代理对象的声明建议部分。 -
目标对象实现
AccountService
接口的任何连接点(仅在Spring AOP中执行方法):target(com.xyz.service.AccountService)
'target'更常用于绑定形式。有关如何在建议体中提供目标对象的信息,请参阅“ 声明建议”部分。 -
采用单个参数的任何连接点(仅在Spring AOP中执行的方法)以及在运行时传递的参数是
Serializable
:args(java.io.Serializable)
'args'更常用于绑定形式。请参阅声明建议部分,了解如何在建议体中提供方法参数。 请注意,此示例中给出的切入点不同于
execution(* *(java.io.Serializable))
。如果参数在运行时传递,则args版本匹配Serializable
,如果方法签名声明了单个参数类型,则执行版本匹配Serializable
。 -
目标对象具有
@Transactional
注释的任何连接点(仅在Spring AOP中执行方法) :@target(org.springframework.transaction.annotation.Transactional)
您还可以在绑定表单中使用“@target”。有关如何在建议体中提供注释对象的信息,请参阅“ 声明建议”部分。 -
任何连接点(仅在Spring AOP中执行方法),其中目标对象的声明类型具有
@Transactional
注释:@within(org.springframework.transaction.annotation.Transactional)
您也可以在绑定表单中使用“@within”。有关如何在建议体中提供注释对象的信息,请参阅“ 声明建议”部分。 -
任何连接点(仅在Spring AOP中执行方法),其中执行方法具有
@Transactional
注释:@annotation(org.springframework.transaction.annotation.Transactional)
您还可以在绑定表单中使用“@annotation”。有关如何在建议体中提供注释对象的信息,请参阅“ 声明建议”部分。 -
任何连接点(仅在Spring AOP中执行的方法),它接受一个参数,并且传递的参数的运行时类型具有
@Classified
注释:@args(com.xyz.security.Classified)
您也可以在绑定表单中使用“@args”。请参阅“ 声明建议”部分,了解如何在建议体中提供注释对象。 -
名为的Spring bean上的任何连接点(仅在Spring AOP中执行方法)
tradeService
:bean(tradeService)
-
具有与通配符表达式匹配的名称的Spring bean上的任何连接点(仅在Spring AOP中执行方法)
*Service
:bean(*Service)
写好切入点
在编译期间,AspectJ处理切入点以优化匹配性能。检查代码并确定每个连接点是否(静态地或动态地)匹配给定切入点是一个代价高昂的过程。(动态匹配意味着无法通过静态分析完全确定匹配,并且在代码中放置测试以确定代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ会将其重写为匹配过程的最佳形式。这是什么意思?基本上,切入点在DNF(析取范式)中重写,并且切入点的组件被排序,以便首先检查那些评估更便宜的组件。
但是,AspectJ只能使用它所说的内容。为了获得最佳匹配性能,您应该考虑他们要实现的目标,并在定义中尽可能缩小匹配的搜索空间。现有的指示符自然分为三组:kinded,scoping和contextual:
-
Kinded代号选择特定类型的连接点的:
execution
,get
,set
,call
,和handler
。 -
范围界定指示符选择一组感兴趣的连接点(可能是多种类型):
within
和withincode
-
基于上下文语境指示符匹配(和任选的绑定): ,
this
,target
和@annotation
一个写得很好的切入点应至少包括前两种类型(kinded和scoping)。您可以包含上下文指示符以基于连接点上下文进行匹配,或者绑定该上下文以在建议中使用。由于额外的处理和分析,仅提供一个kinded指示符或仅提供上下文指示符,但可能会影响编织性能(使用的时间和内存)。范围界定指示符非常快速匹配,使用它们意味着AspectJ可以非常快速地解除不应该进一步处理的连接点组。如果可能,一个好的切入点应该总是包含一个。
5.4.4。宣布建议
建议与切入点表达式相关联,并在切入点匹配的方法执行之前,之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是在适当位置声明的切入点表达式。
在建议之前
您可以使用@Before
注释在方面中的建议之前声明:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
如果我们使用就地切入点表达式,我们可以重写前面的示例,如下例所示:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("execution(* com.xyz.myapp.dao.*.*(..))") public void doAccessCheck() { // ... } }
返回建议后
返回建议后,匹配的方法执行正常返回。您可以使用@AfterReturning
注释声明它:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
您可以在同一方面拥有多个建议声明(以及其他成员)。我们在这些示例中仅显示一个建议声明,以集中每个声明的效果。 | |
---|---|
有时,您需要在建议体中访问返回的实际值。您可以使用@AfterReturning
绑定返回值的形式来获取该访问权限,如以下示例所示:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... } }
returning
属性中使用的名称必须与advice方法中的参数名称相对应。当方法执行返回时,返回值作为相应的参数值传递给advice方法。甲returning
子句也限制了只能匹配到返回指定类型的值(在这种情况下,那些方法执行Object
,它匹配任何返回值)。
请注意,在返回建议后使用时,无法返回完全不同的参考。
投掷建议后
抛出建议运行时,匹配的方法执行通过抛出异常退出。您可以使用@AfterThrowing
注释声明它,如以下示例所示:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... } }
通常,您希望建议仅在抛出给定类型的异常时运行,并且您还经常需要访问建议体中的抛出异常。您可以使用该 throwing
属性来限制匹配(如果需要,Throwable
否则使用 - 作为异常类型)并将抛出的异常绑定到advice参数。以下示例显示了如何执行此操作:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } }
throwing
属性中使用的名称必须与advice方法中的参数名称相对应。当通过抛出异常退出方法时,异常将作为相应的参数值传递给advice方法。甲throwing
子句也限制了只能匹配到抛出指定类型的异常(那些方法执行DataAccessException
,在这种情况下)。
之后(最后)建议
在匹配的方法执行退出之后(最终)建议运行之后。它是使用@After
注释声明的。在建议必须准备好处理正常和异常返回条件。它通常用于释放资源和类似目的。以下示例显示了在finally建议之后如何使用:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @Aspect public class AfterFinallyExample { @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // ... } }
围绕建议
最后一种建议是建议。周围的建议围绕匹配方法的执行运行。它有机会在方法执行之前和之后完成工作,并确定何时,如何,甚至方法实际上都可以执行。如果您需要以线程安全的方式(例如,启动和停止计时器)在方法执行之前和之后共享状态,则通常会使用around建议。始终使用符合您要求的最不强大的建议形式(也就是说,如果建议之前不要使用建议)。
使用@Around
注释声明around建议。advice方法的第一个参数必须是type ProceedingJoinPoint
。在建议的主体内,调用导致底层方法执行proceed()
的ProceedingJoinPoint
原因。该proceed
方法也可以传入Object[]
。数组中的值在进行时用作方法执行的参数。
使用a proceed 调用时Object[] 的行为proceed 与AspectJ编译器编译的for advice 的行为略有不同。对于使用传统AspectJ语言编写的周围建议,传递给的参数的数量 proceed 必须与传递给around建议的参数的数量(不是底层连接点所采用的参数的数量)相匹配,并且传递给的值在给定的情况下继续参数位置取代了值绑定到的实体的连接点的原始值(如果现在没有意义,请不要担心)。Spring采用的方法更简单,并且更好地匹配其基于代理的仅执行语义。如果编译为Spring编写的@AspectJ方面并使用,则只需要了解这种差异proceed 与使用AspectJ编译器和weaver的参数。有一种方法可以编写在Spring AOP和AspectJ上100%兼容的方面,这将在下面的建议参数部分中讨论。 | |
---|---|
以下示例显示如何使用around建议:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
around通知返回的值是方法调用者看到的返回值。例如,一个简单的缓存方面可以从缓存中返回一个值(如果有的话),proceed()
如果没有则调用。请注意,proceed
可以在around建议的正文中调用一次,多次或根本不调用。所有这些都是合法的。
建议参数
Spring提供完全类型的建议,这意味着您在建议签名中声明了所需的参数(正如我们之前看到的返回和抛出示例),而不是一直使用Object[]
数组。我们将在本节后面的内容中看到如何使建议和其他上下文值可用。首先,我们来看看如何编写通用建议,以便了解建议目前建议的方法。
访问当前 JoinPoint
任何通知方法都可以声明一个类型的参数作为其第一个参数 org.aspectj.lang.JoinPoint
(注意,需要在通知周围声明类型的第一个参数ProceedingJoinPoint
,它是一个子类JoinPoint
。 JoinPoint
接口提供了许多有用的方法:
-
getArgs()
:返回方法参数。 -
getThis()
:返回代理对象。 -
getTarget()
:返回目标对象。 -
getSignature()
:返回正在建议的方法的描述。 -
toString()
:打印建议方法的有用说明。
有关更多详细信息,请参阅javadoc。
将参数传递给建议
我们已经看到了如何绑定返回的值或异常值(在返回之后和抛出建议之后使用)。要使参数值可用于建议体,您可以使用绑定形式args
。如果在args表达式中使用参数名称代替类型名称,则在调用通知时,相应参数的值将作为参数值传递。一个例子应该使这更清楚。假设您要建议执行以Account
对象作为第一个参数的DAO操作,并且您需要访问建议体中的帐户。你可以写下面的内容:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount(Account account) { // ... }
args(account,..)
切入点表达式的一部分有两个目的。首先,它将匹配仅限于那些方法至少接受一个参数的方法执行,并且传递给该参数的参数是一个实例Account
。其次,它Account
通过account
参数使实际对象可用于建议。
另一种编写方法是声明一个切入点,Account
当它与连接点匹配时“提供” 对象值,然后从建议中引用指定的切入点。这看起来如下:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... }
有关更多详细信息,请参阅AspectJ编程指南。
代理对象(this
),目标对象(target
),和说明(@within
, @target
,@annotation
,和@args
)都可以以类似的方式结合。接下来的两个示例显示如何匹配带@Auditable
注释的注释方法的执行 并提取审计代码:
这两个示例中的第一个显示了@Auditable
注释的定义:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); }
这两个示例中的第二个显示了与@Auditable
方法执行相匹配的建议:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
建议参数和泛型
Spring AOP可以处理类声明和方法参数中使用的泛型。假设您有一个如下所示的泛型类型:
public interface Sample<T> { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection<T> param); }
您可以通过在要拦截方法的参数类型中键入advice参数,将方法类型的拦截限制为某些参数类型:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation }
此方法不适用于通用集合。因此,您无法按如下方式定义切入点:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection<MyType> param) { // Advice implementation }
为了使这项工作,我们必须检查集合中的每个元素,这是不合理的,因为我们也无法决定如何处理null
一般的值。要实现与此类似的操作,您必须键入参数Collection<?>
并手动检查元素的类型。
确定参数名称
通知调用中的参数绑定依赖于切入点表达式中使用的名称与通知和切入点方法签名中声明的参数名称匹配。参数名称不能通过Java反射获得,因此Spring AOP使用以下策略来确定参数名称:
-
如果用户已明确指定参数名称,则使用指定的参数名称。通知和切入点注释都有一个可选
argNames
属性,您可以使用该属性指定带注释方法的参数名称。这些参数名称在运行时可用。以下示例显示如何使用该argNames
属性:@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code and bean }
如果第一个参数是的
JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
类型,CA从价值离开了参数的名称argNames
属性。例如,如果修改前面的建议以接收连接点对象,则该argNames
属性不需要包含它:@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(JoinPoint jp, Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code, bean, and jp }
给出的第一个参数的特殊待遇
JoinPoint
,ProceedingJoinPoint
和JoinPoint.StaticPart
类型是不收取任何其它连接上下文的通知情况下,特别方便。在这种情况下,您可以省略该argNames
属性。例如,以下建议无需声明argNames
属性:@Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
-
使用该
'argNames'
属性有点笨拙,因此如果'argNames'
未指定该属性,Spring AOP会查看该类的调试信息,并尝试从局部变量表中确定参数名称。只要使用调试信息('-g:vars'
至少)编译了类,就会出现此信息。使用此标志进行编译的后果是:(1)您的代码稍微容易理解(逆向工程),(2)类文件大小略大(通常无关紧要),(3)优化删除未使用的本地变量未由编译器应用。换句话说,通过使用此标志构建,您应该不会遇到任何困难。如果没有调试信息,AspectJ编译器(ajc)编译了@AspectJ方面,则无需添加 argNames
属性,因为编译器会保留所需的信息。 -
如果代码编译时没有必要的调试信息,Spring AOP会尝试推断绑定变量与参数的配对(例如,如果只有一个变量绑定在切入点表达式中,并且advice方法只接受一个参数,那么配对很明显)。如果给定可用信息,变量的绑定是不明确的,
AmbiguousBindingException
则抛出a。 -
如果以上所有策略都失败了,那就
IllegalArgumentException
抛出了。
继续论证
我们之前评论过,我们将描述如何proceed
使用在Spring AOP和AspectJ中一致工作的参数编写调用。解决方案是确保建议签名按顺序绑定每个方法参数。以下示例显示了如何执行此操作:
@Around("execution(List<Account> find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
在许多情况下,无论如何都要执行此绑定(如前面的示例所示)。
建议订购
当多条建议都想在同一个连接点运行时会发生什么?Spring AOP遵循与AspectJ相同的优先级规则来确定建议执行的顺序。最高优先级的建议首先“在路上”(因此,给出两条之前的建议,优先级最高的建议首先运行)。从连接点“出路”,最高优先级建议最后运行(因此,给定两条后建议,具有最高优先级的建议将运行第二)。
当在不同方面定义的两条建议都需要在同一个连接点上运行时,除非另行指定,否则执行顺序是不确定的。您可以通过指定优先级来控制执行顺序。这是通过在方法类中实现org.springframework.core.Ordered
接口或使用注释对其进行Order
注释来以常规Spring方式完成的。给定两个方面,从Ordered.getValue()
(或注释值)返回较低值的方面具有较高的优先级。
当在同一方面中定义的两条建议都需要在同一个连接点上运行时,排序是未定义的(因为无法通过反射为javac编译的类检索声明顺序)。考虑将这些建议方法折叠到每个方面类中每个连接点的一个建议方法中,或者将这些建议重构为可以在方面级别订购的单独方面类。
5.4.5。简介
简介(在AspectJ中称为类型间声明)使方面能够声明建议对象实现给定接口,并代表这些对象提供该接口的实现。
您可以使用@DeclareParents
注释进行介绍。此批注用于声明匹配类型具有新父级(因此名称)。例如,给定一个名为interface的接口UsageTracked
和该接口的实现DefaultUsageTracked
,以下方面声明服务接口的所有实现者也实现了UsageTracked
接口(例如,通过JMX公开统计信息):
@Aspect public class UsageTracking { @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } }
要实现的接口由注释字段的类型确定。注释的 value
属性@DeclareParents
是AspectJ类型模式。任何匹配类型的bean都实现了该UsageTracked
接口。请注意,在前面示例的before advice中,服务bean可以直接用作UsageTracked
接口的实现。如果以编程方式访问bean,您将编写以下内容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
5.4.6。Aspect实例化模型
这是一个高级主题。如果您刚刚开始使用AOP,您可以安全地跳过它直到稍后。 | |
---|---|
默认情况下,应用程序上下文中的每个方面都有一个实例。AspectJ将其称为单例实例化模型。可以使用备用生命周期定义方面。Spring支持AspectJ的perthis
和pertarget
实例化模型(percflow, percflowbelow,
和pertypewithin
目前不支持)。
您可以perthis
通过perthis
在@Aspect
注释中指定子句来声明方面。请考虑以下示例:
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { private int someState; @Before(com.xyz.myapp.SystemArchitecture.businessService()) public void recordServiceUsage() { // ... } }
在前面的示例中,该'perthis'
子句的作用是为执行业务服务的每个唯一服务对象创建一个方面实例(每个唯一对象在由切入点表达式匹配的连接点处绑定到'this')。方法实例是在第一次在服务对象上调用方法时创建的。当服务对象超出范围时,该方面超出范围。在创建方面实例之前,其中没有任何建议执行。一旦创建了方面实例,在其中声明的通知就会在匹配的连接点执行,但仅在服务对象与此方面关联的服务对象时执行。有关per
子句的更多信息,请参阅AspectJ编程指南。
该pertarget
实例化样板工程完全相同的方式perthis
,但在匹配的连接点,每个独立目标对象创建一个切面实例。
5.4.7。AOP示例
现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起做一些有用的事情。
由于并发问题(例如,死锁失败者),业务服务的执行有时可能会失败。如果重试该操作,则可能在下一次尝试时成功。对于适合在这种情况下重试的业务服务(不需要回到用户以进行冲突解决的幂等操作),我们希望透明地重试操作以避免客户端看到PessimisticLockingFailureException
。这是明确跨越服务层中的多个服务的要求,因此是通过一个方面实现的理想选择。
因为我们想要重试操作,所以我们需要使用around建议,以便我们可以proceed
多次调用。以下清单显示了基本方面的实现:
@Aspect public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(PessimisticLockingFailureException ex) { lockFailureException = ex; } } while(numAttempts <= this.maxRetries); throw lockFailureException; } }
请注意,该方面实现了Ordered
接口,以便我们可以将方面的优先级设置为高于事务通知(我们每次重试时都需要一个新的事务)。该maxRetries
和order
性能都Spring配置。主要行动发生在doConcurrentOperation
周围的建议中。请注意,目前我们将重试逻辑应用于每个businessService()
。我们试图继续,如果我们失败了PessimisticLockingFailureException
,我们再试一次,除非我们已经用尽所有的重试尝试。
相应的Spring配置如下:
<aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
为了优化方面以便它只重试幂等操作,我们可以定义以下 Idempotent
注释:
@Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation }
然后,我们可以使用注释来注释服务操作的实现。对方面进行更改以仅重试幂等操作涉及改进切入点表达式,以便只有@Idempotent
操作匹配,如下所示:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { ... }
5.5。基于模式的AOP支持
如果您更喜欢基于XML的格式,Spring还支持使用新的aop
命名空间标记定义方面。支持与使用@AspectJ样式时完全相同的切入点表达式和建议种类。因此,在本节中,我们将重点放在新语法上,并将读者引用到上一节(@AspectJ支持)中的讨论,以了解编写切入点表达式和建议参数的绑定。
要使用本节中描述的aop命名空间标记,您需要导入 spring-aop
模式,如基于XML模式的配置中所述。有关 如何在命名空间中导入标记,请参阅AOP架构aop
。
在Spring配置中,所有aspect和advisor元素必须放在一个<aop:config>
元素中(<aop:config>
在应用程序上下文配置中可以有多个元素)。一个<aop:config>
元素可以包含切入点,顾问和纵横元件(注意,这些必须以这个顺序进行声明)。
该<aop:config> 风格的配置使得大量使用Spring的 自动代理机制。如果您已经通过使用BeanNameAutoProxyCreator 或类似的东西使用显式自动代理,这可能会导致问题(例如建议不被编织) 。建议的使用模式是仅使用<aop:config> 样式或仅使用AutoProxyCreator 样式,并且永远不要混用它们。 | |
---|---|
5.5.1。声明一个方面
使用模式支持时,方面是在Spring应用程序上下文中定义为bean的常规Java对象。状态和行为在对象的字段和方法中捕获,切入点和建议信息在XML中捕获。
您可以使用<aop:aspect>元素声明方面,并使用该ref
属性引用支持bean ,如以下示例所示:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
支持方面的bean(aBean
在这种情况下)当然可以配置和依赖注入,就像任何其他Spring bean一样。
5.5.2。声明切入点
您可以在<aop:config>
元素内声明一个命名切入点,让切入点定义在多个方面和顾问之间共享。
表示服务层中任何业务服务执行的切入点可以定义如下:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> </aop:config>
请注意,切入点表达式本身使用与@AspectJ支持中描述的相同的AspectJ切入点表达式语言。如果使用基于模式的声明样式,则可以引用切入点表达式中类型(@Aspects)中定义的命名切入点。定义上述切入点的另一种方法如下:
<aop:config> <aop:pointcut id="businessService" expression="com.xyz.myapp.SystemArchitecture.businessService()"/> </aop:config>
假设您具有共享公共切入点定义中SystemArchitecture
描述的方面。
然后在方面内部声明切入点与声明顶级切入点非常相似,如下例所示:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> ... </aop:aspect> </aop:config>
与@AspectJ方面大致相同,使用基于模式的定义样式声明的切入点可以收集连接点上下文。例如,以下切入点将this
对象收集为连接点上下文并将其传递给建议:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/> <aop:before pointcut-ref="businessService" method="monitor"/> ... </aop:aspect> </aop:config>
必须通过包含匹配名称的参数来声明建议以接收收集的连接点上下文,如下所示:
public void monitor(Object service) { ... }
当需要连接子表达式,&&
是一个XML文档中的尴尬,这样你就可以使用and
,or
以及not
到位的关键字&&
,||
和!
分别。例如,以前的切入点可以更好地编写如下:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service..(..)) and this(service)"/> <aop:before pointcut-ref="businessService" method="monitor"/> ... </aop:aspect> </aop:config>
请注意,以这种方式定义的切入点由其XML引用,id
不能用作命名切入点来形成复合切入点。因此,基于模式的定义样式中的命名切入点支持比@AspectJ样式提供的更有限。
5.5.3。宣布建议
基于模式的AOP支持使用与@AspectJ样式相同的五种建议,并且它们具有完全相同的语义。
在建议之前
在匹配的方法执行之前运行建议之前。它<aop:aspect>
通过使用<aop:before>元素在一个内部声明 ,如下例所示:
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
这里dataAccessOperation
是id
top(<aop:config>
)级别定义的切入点。要改为内联切入点,请使用pointcut-ref
属性替换pointcut
属性,如下所示:
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut="execution(* com.xyz.myapp.dao.*.*(..))" method="doAccessCheck"/> ... </aop:aspect>
正如我们在讨论@AspectJ样式时所提到的,使用命名切入点可以显着提高代码的可读性。
该method
属性标识doAccessCheck
提供建议正文的method()。必须为包含建议的aspect元素引用的bean定义此方法。在执行数据访问操作(由切入点表达式匹配的方法执行连接点)之前,将doAccessCheck
调用方面bean上的方法。
返回建议后
在匹配的方法执行正常完成后返回通知运行。它<aop:aspect>
以与建议之前相同的方式在内部声明。以下示例显示了如何声明它:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
与@AspectJ样式一样,您可以在建议体内获取返回值。为此,请使用returns属性指定应将返回值传递到的参数的名称,如以下示例所示:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> ... </aop:aspect>
该doAccessCheck
方法必须声明一个名为的参数retVal
。此参数的类型以与描述相同的方式约束匹配@AfterReturning
。例如,您可以按如下方式声明方法签名:
public void doAccessCheck(Object retVal) {...
投掷建议后
抛出建议执行时,匹配的方法执行通过抛出异常退出。它<aop:aspect>
通过使用投掷后元素在一个内部声明,如下例所示:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" method="doRecoveryActions"/> ... </aop:aspect>
与@AspectJ样式一样,您可以在建议体中获取抛出的异常。为此,请使用throwing属性指定应将异常传递到的参数的名称,如以下示例所示:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" throwing="dataAccessEx" method="doRecoveryActions"/> ... </aop:aspect>
该doRecoveryActions
方法必须声明一个名为的参数dataAccessEx
。此参数的类型以与描述相同的方式约束匹配@AfterThrowing
。例如,方法签名可以声明如下:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
之后(最后)建议
在(最终)建议运行之后,无论匹配的方法执行如何退出。您可以使用该after
元素声明它,如以下示例所示:
<aop:aspect id="afterFinallyExample" ref="aBean"> <aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/> ... </aop:aspect>
围绕建议
最后一种建议是建议。周围的建议围绕匹配的方法执行运行。它有机会在方法执行之前和之后完成工作,并确定何时,如何,甚至方法实际上都可以执行。around建议通常用于以线程安全的方式(例如,启动和停止计时器)在方法执行之前和之后共享状态。始终使用符合您要求的最不强大的建议形式。如果在建议可以完成工作之前,请不要使用周围的建议。
您可以使用该aop:around
元素声明周围的建议。advice方法的第一个参数必须是type ProceedingJoinPoint
。在建议的主体内,调用导致底层方法执行proceed()
的ProceedingJoinPoint
原因。proceed
也可以用a来调用该方法Object[]
。数组中的值在进行时用作方法执行的参数。见 around通知的注意事项调用proceed
有Object[]
。以下示例显示如何在XML中声明建议:
<aop:aspect id="aroundExample" ref="aBean"> <aop:around pointcut-ref="businessService" method="doBasicProfiling"/> ... </aop:aspect>
doBasicProfiling
建议的实现可以与@AspectJ示例中的完全相同(当然,减去注释),如以下示例所示:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
建议参数
基于模式的声明样式支持完全类型化的建议,方法与@AspectJ支持描述的方式相同 - 通过名称匹配建议方法参数的切入点参数。有关详细信息,请参阅建议参数 如果您希望显式指定通知方法的参数名称(不依赖于前面描述的检测策略),可以使用arg-names
通知元素的属性来实现,该属性的处理方式与argNames
通知注释中的属性相同。(如确定参数名称中所述)。以下示例显示如何在XML中指定参数名称:
<aop:before pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)" method="audit" arg-names="auditable"/>
该arg-names
属性接受以逗号分隔的参数名称列表。
以下稍微涉及的基于XSD的方法示例显示了一些与大量强类型参数一起使用的建议:
package x.y.service; public interface PersonService { Person getPerson(String personName, int age); } public class DefaultFooService implements FooService { public Person getPerson(String name, int age) { return new Person(name, age); } }
接下来是方面。请注意,该profile(..)
方法接受许多强类型参数,第一个参数恰好是用于继续方法调用的连接点。此参数的存在表示profile(..)
将用作around
建议,如以下示例所示:
package x.y; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; public class SimpleProfiler { public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable { StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'"); try { clock.start(call.toShortString()); return call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } } }
最后,以下示例XML配置会影响特定连接点的前一个建议的执行:
<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"> <!-- this is the object that will be proxied by Spring's AOP infrastructure --> <bean id="personService" class="x.y.service.DefaultPersonService"/> <!-- this is the actual advice itself --> <bean id="profiler" class="x.y.SimpleProfiler"/> <aop:config> <aop:aspect ref="profiler"> <aop:pointcut id="theExecutionOfSomePersonServiceMethod" expression="execution(* x.y.service.PersonService.getPerson(String,int)) and args(name, age)"/> <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod" method="profile"/> </aop:aspect> </aop:config> </beans>
请考虑以下驱动程序脚本:
import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import x.y.service.PersonService; public final class Boot { public static void main(final String[] args) throws Exception { BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml"); PersonService person = (PersonService) ctx.getBean("personService"); person.getPerson("Pengo", 12); } }
使用这样的Boot类,我们将在标准输出上获得类似于以下内容的输出:
StopWatch'Pengo'和'12''的分析:运行时间(毫秒)= 0 ----------------------------------------- ms%任务名称 ----------------------------------------- 00000?执行(的getFoo)
建议订购
当多个建议需要在同一个连接点(执行方法)执行时,排序规则如建议排序中所述。方面之间的优先级是通过将Order
注释添加到支持方面的bean或通过让bean实现Ordered
接口来确定的。
5.5.4。简介
介绍(在AspectJ中称为类型间声明)让方面声明建议对象实现给定接口并代表这些对象提供该接口的实现。
您可以使用aop:declare-parents
内部的元素进行介绍aop:aspect
。您可以使用该aop:declare-parents
元素声明匹配类型具有新父级(因此名称)。例如,给定一个名为的接口UsageTracked
和该接口的实现名称 DefaultUsageTracked
,以下方面声明服务接口的所有实现者也实现该UsageTracked
接口。(例如,通过JMX公开统计信息。)
<aop:aspect id="usageTrackerAspect" ref="usageTracking"> <aop:declare-parents types-matching="com.xzy.myapp.service.*+" implement-interface="com.xyz.myapp.service.tracking.UsageTracked" default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/> <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)" method="recordUsage"/> </aop:aspect>
然后,支持usageTracking
bean 的类将包含以下方法:
public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); }
要实现的接口由implement-interface
属性确定。types-matching
属性的值是AspectJ类型模式。任何匹配类型的bean都实现了该UsageTracked
接口。请注意,在前面示例的before advice中,服务bean可以直接用作UsageTracked
接口的实现。要以编程方式访问bean,您可以编写以下内容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
5.5.5。Aspect实例化模型
模式定义方面唯一支持的实例化模型是单例模型。未来的版本可能支持其他实例化模型。
5.5.6。顾问
“顾问”的概念来自Spring中定义的AOP支持,并且在AspectJ中没有直接的等价物。顾问就像一个小小的自足方面,只有一条建议。建议本身由bean表示,并且必须实现Spring中的建议类型中描述的建议接口 之一。顾问可以利用AspectJ切入点表达式。
Spring支持使用<aop:advisor>
元素的顾问概念。您最常见的是它与事务性建议一起使用,它在Spring中也有自己的命名空间支持。以下示例显示了一个顾问:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
除了pointcut-ref
前面示例中使用的pointcut
属性之外,您还可以使用该 属性来内联定义切入点表达式。
要定义顾问程序的优先级以便建议可以参与排序,请使用该order
属性来定义Ordered
顾问程序的值。
5.5.7。AOP架构示例
本节说明如何使用架构支持重写An AOP示例中的并发锁定失败重试示例 。
由于并发问题(例如,死锁失败者),业务服务的执行有时可能会失败。如果重试该操作,则可能在下一次尝试时成功。对于适合在这种情况下重试的业务服务(不需要回到用户以进行冲突解决的幂等操作),我们希望透明地重试操作以避免客户端看到PessimisticLockingFailureException
。这是明确跨越服务层中的多个服务的要求,因此是通过一个方面实现的理想选择。
因为我们想要重试操作,所以我们需要使用around建议,以便我们可以proceed
多次调用。以下清单显示了基本方面实现(使用模式支持的常规Java类):
public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(PessimisticLockingFailureException ex) { lockFailureException = ex; } } while(numAttempts <= this.maxRetries); throw lockFailureException; } }
请注意,该方面实现了Ordered
接口,以便我们可以将方面的优先级设置为高于事务通知(我们每次重试时都需要一个新的事务)。该maxRetries
和order
性能都Spring配置。主要操作发生在doConcurrentOperation
around advice方法中。我们试着继续。如果我们失败了PessimisticLockingFailureException
,我们再试一次,除非我们已经用尽所有的重试尝试。
此类与@AspectJ示例中使用的类相同,但删除了注释。 | |
---|---|
相应的Spring配置如下:
<aop:config> <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor"> <aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:around pointcut-ref="idempotentOperation" method="doConcurrentOperation"/> </aop:aspect> </aop:config> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
请注意,在当时,我们假设所有业务服务都是幂等的。如果不是这种情况,我们可以通过引入Idempotent
注释并使用注释来注释服务操作的实现来优化方面,使其仅重试真正的幂等操作,如以下示例所示:
@Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation }
对方面进行更改以仅重试幂等操作涉及改进切入点表达式,以便只有@Idempotent
操作匹配,如下所示:
<aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..)) and @annotation(com.xyz.myapp.service.Idempotent)"/>
5.6。选择要使用的AOP声明样式
一旦确定某个方面是实现给定需求的最佳方法,您如何决定使用Spring AOP或AspectJ以及Aspect语言(代码)样式,@ AspectJ注释样式还是Spring XML样式?这些决策受到许多因素的影响,包括应用程序要求,开发工具和团队对AOP的熟悉程度。
5.6.1。Spring AOP还是Full AspectJ?
使用最简单的方法。Spring AOP比使用完整的AspectJ更简单,因为不需要将AspectJ编译器/ weaver引入开发和构建过程。如果您只需要建议在Spring bean上执行操作,那么Spring AOP是正确的选择。如果需要建议不受Spring容器管理的对象(例如域对象),则需要使用AspectJ。如果您希望建议除简单方法执行之外的连接点(例如,字段获取或设置连接点等),则还需要使用AspectJ。
使用AspectJ时,您可以选择AspectJ语言语法(也称为“代码样式”)或@AspectJ注释样式。显然,如果您不使用Java 5+,则可以选择:使用代码样式。如果方面在您的设计中发挥重要作用,并且您能够使用Eclipse 的AspectJ开发工具(AJDT)插件,则AspectJ语言语法是首选选项。它更清晰,更简单,因为该语言是专门为写作方面而设计的。如果您不使用Eclipse或只有几个方面在您的应用程序中不起主要作用,您可能需要考虑使用@AspectJ样式,在IDE中坚持使用常规Java编译,并添加一个方面编织阶段到你的构建脚本。
5.6.2。@AspectJ或Spring for AOP的XML?
如果您选择使用Spring AOP,则可以选择@AspectJ或XML样式。需要考虑各种权衡。
XML样式可能是现有Spring用户最熟悉的,并且由真正的POJO支持。当使用AOP作为配置企业服务的工具时,XML可能是一个不错的选择(一个好的测试是你是否认为切入点表达式是你可能想要独立改变的配置的一部分)。使用XML样式,从您的配置可以更清楚地了解系统中存在哪些方面。
XML风格有两个缺点。首先,它没有完全封装它在一个地方解决的要求的实现。DRY原则指出,系统中的任何知识都应该有单一,明确,权威的表示。使用XML样式时,有关如何实现需求的知识将分支到支持bean类的声明和配置文件中的XML。使用@AspectJ样式时,此信息封装在单个模块中:方面。其次,XML样式在它可以表达的内容方面比@AspectJ样式稍微受限:仅支持“单例”方面实例化模型,并且不可能组合在XML中声明的命名切入点。例如,在@AspectJ样式中,您可以编写如下内容:
@Pointcut("execution(* get*())") public void propertyAccess() {} @Pointcut("execution(org.xyz.Account+ *(..))") public void operationReturningAnAccount() {} @Pointcut("propertyAccess() && operationReturningAnAccount()") public void accountPropertyAccess() {}
在XML样式中,您可以声明前两个切入点:
<aop:pointcut id="propertyAccess" expression="execution(* get*())"/> <aop:pointcut id="operationReturningAnAccount" expression="execution(org.xyz.Account+ *(..))"/>
XML方法的缺点是您无法 accountPropertyAccess
通过组合这些定义来定义切入点。
@AspectJ样式支持额外的实例化模型和更丰富的切入点组合。它具有将方面保持为模块化单元的优点。它还具有以下优点:Spring AOP和AspectJ都可以理解(并因此消耗)@AspectJ方面。因此,如果您以后决定需要AspectJ的功能来实现其他要求,则可以轻松迁移到基于AspectJ的方法。总而言之,只要您的方面不仅仅是简单的企业服务配置,Spring团队更喜欢@AspectJ风格。
5.7。混合方面类型
通过使用自动代理支持,模式定义的<aop:aspect>
方面,<aop:advisor>
声明的顾问程序,甚至在相同配置中使用Spring 1.2样式定义的代理和拦截器,完全可以混合@AspectJ样式方面。所有这些都是通过使用相同的底层支持机制实现的,并且可以毫无困难地共存。
5.8。代理机制
Spring AOP使用JDK动态代理或CGLIB为给定目标对象创建代理。(只要有选择,JDK动态代理就是首选)。
如果要代理的目标对象实现至少一个接口,则使用JDK动态代理。目标类型实现的所有接口都是代理的。如果目标对象未实现任何接口,则会创建CGLIB代理。
如果要强制使用CGLIB代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),您可以这样做。但是,您应该考虑以下问题:
-
final
方法无法建议,因为它们无法被覆盖。 -
从Spring 3.2开始,不再需要将CGLIB添加到项目类路径中,因为CGLIB类被重新打包
org.springframework
并直接包含在spring-core JAR中。这意味着基于CGLIB的代理支持“正常工作”,与JDK动态代理始终具有相同的方式。 -
从Spring 4.0开始,代理对象的构造函数不再被调用两次,因为CGLIB代理实例是通过Objenesis创建的。只有当您的JVM不允许构造函数绕过时,您才会看到Spring的AOP支持中的双重调用和相应的调试日志条目。
要强制使用CGLIB代理,请将元素proxy-target-class
属性的值设置<aop:config>
为true,如下所示:
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
要在使用@AspectJ自动代理支持时强制CGLIB代理,请将元素的proxy-target-class
属性设置 <aop:aspectj-autoproxy>
为true
,如下所示:
<aop:aspectj-autoproxy proxy-target-class="true"/>
多个<aop:config/> 部分在运行时折叠为单个统一的自动代理创建器,它应用指定的任何部分(通常来自不同的XML bean定义文件)的最强代理设置 <aop:config/> 。这也适用于<tx:annotation-driven/> 和<aop:aspectj-autoproxy/> 元素。为了清楚起见,使用proxy-target-class="true" on <tx:annotation-driven/> , <aop:aspectj-autoproxy/> 或者<aop:config/> 元素会强制使用CGLIB代理来处理所有这三个代理。 | |
---|---|
5.8.1。了解AOP代理
Spring AOP是基于代理的。在编写自己的方面或使用Spring Framework提供的任何基于Spring AOP的方面之前,掌握最后一个语句实际意味着什么的语义是非常重要的。
首先考虑一下你有一个普通的,无代理的,没有特别关于它的直接对象引用的场景,如下面的代码片段所示:
public class SimplePojo implements Pojo { public void foo() { // this next method invocation is a direct call on the 'this' reference this.bar(); } public void bar() { // some logic... } }
如果在对象引用上调用方法,则直接在该对象引用上调用该方法,如下图所示:
public class Main { public static void main(String[] args) { Pojo pojo = new SimplePojo(); // this is a direct method call on the 'pojo' reference pojo.foo(); } }
当客户端代码具有的引用是代理时,事情会稍微改变。请考虑以下图表和代码段:
public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.addInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); } }
这里要理解的关键是该类main(..)
方法中的 客户端代码Main
具有对代理的引用。这意味着对该对象引用的方法调用是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)。但是,一旦调用最终到达目标对象(SimplePojo
在这种情况下为引用),任何方法调用它可以对其自身进行调用,例如, this.bar()
或者this.foo()
将针对this
引用而不是代理调用。这具有重要意义。这意味着自我调用不会导致与方法调用相关的建议有机会执行。
好的,那么该怎么办呢?最好的方法(术语“最好”,在这里松散地使用)是重构你的代码,这样就不会发生自我调用。这确实需要您做一些工作,但这是最好的,最少侵入性的方法。接下来的方法是绝对可怕的,我们毫不犹豫地指出它,正是因为它是如此可怕。您可以(对我们来说很痛苦)将类中的逻辑完全绑定到Spring AOP,如下例所示:
public class SimplePojo implements Pojo { public void foo() { // this works, but... gah! ((Pojo) AopContext.currentProxy()).bar(); } public void bar() { // some logic... } }
这完全将您的代码耦合到Spring AOP,它使类本身意识到它正在AOP上下文中使用,它在AOP面前飞行。在创建代理时,还需要一些其他配置,如以下示例所示:
public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.adddInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); factory.setExposeProxy(true); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); } }
最后,必须注意的是AspectJ没有这种自调用问题,因为它不是基于代理的AOP框架。
5.9。程序化创建@AspectJ代理
除了通过使用宣布在配置方面<aop:config>
还是 <aop:aspectj-autoproxy>
,还可以以编程方式创建通知目标对象的代理。有关Spring的AOP API的完整详细信息,请参阅下一章。在这里,我们希望关注使用@AspectJ方面自动创建代理的能力。
您可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory
该类为一个或多个@AspectJ方面建议的目标对象创建代理。此类的基本用法非常简单,如以下示例所示:
// create a factory that can generate a proxy for the given target object AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // add an aspect, the class must be an @AspectJ aspect // you can call this as many times as you need with different aspects factory.addAspect(SecurityManager.class); // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect factory.addAspect(usageTracker); // now get the proxy object... MyInterfaceType proxy = factory.getProxy();
有关更多信息,请参阅javadoc。
5.10。在Spring应用程序中使用AspectJ
到目前为止,我们在本章中介绍的所有内容都是纯粹的Spring AOP。在本节中,我们将介绍如何使用AspectJ编译器或weaver代替Spring AOP或者除了Spring AOP之外,如果您的需求超出了Spring AOP提供的功能。
Spring附带了一个小的AspectJ方面库,它可以在您的发行版中独立使用spring-aspects.jar
。您需要将其添加到类路径中才能使用其中的方面。使用AspectJ向依赖项使用Spring和其他Spring方面注入域对象AspectJ讨论此库的内容以及如何使用它。使用Spring IoC配置AspectJ方面讨论如何依赖注入使用AspectJ编译器编织的AspectJ方面。最后, Spring Framework中使用AspectJ进行的加载时编织为使用AspectJ的Spring应用程序提供了加载时编织的介绍。
5.10.1。使用AspectJ依赖于使用Spring注入域对象
Spring容器实例化和配置在应用程序上下文中定义的bean。在给定包含要应用的配置的bean定义的名称的情况下,还可以要求bean工厂配置预先存在的对象。 spring-aspects.jar
包含一个注释驱动的方面,利用此功能允许依赖注入任何对象。该支持旨在用于在任何容器控制之外创建的对象。域对象通常属于此类别,因为它们通常由new
运算符以编程方式创建,或者由于数据库查询而由ORM工具创建。
该@Configurable
注解标记了一个类为通过Spring驱动的配置。在最简单的情况下,您可以纯粹使用它作为标记注释,如下例所示:
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation.Configurable; @Configurable public class Account { // ... }
当以这种方式用作标记接口时,Spring Account
通过使用与完全限定类型名称(com.xyz.myapp.domain.Account
)具有相同名称的bean定义(通常为prototype-scoped)来配置带注释类型的新实例(在本例中)。由于bean的默认名称是其类型的完全限定名称,因此声明原型定义的便捷方法是省略该id
属性,如以下示例所示:
<bean class="com.xyz.myapp.domain.Account" scope="prototype"> <property name="fundsTransferService" ref="fundsTransferService"/> </bean>
如果要显式指定要使用的原型bean定义的名称,可以直接在注释中执行此操作,如以下示例所示:
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation.Configurable; @Configurable("account") public class Account { // ... }
Spring现在查找名为bean的定义account
,并将其用作配置新Account
实例的定义。
您还可以使用自动装配来避免必须指定专用的bean定义。要让Spring应用自动装配,请使用注释的autowire
属性 @Configurable
。您可以分别按类型或名称指定@Configurable(autowire=Autowire.BY_TYPE)
或 @Configurable(autowire=Autowire.BY_NAME
自动装配。作为替代方案,从Spring 2.5开始,最好@Configurable
通过使用@Autowired
或@Inject
在字段或方法级别为bean 指定显式的,注释驱动的依赖注入( 有关更多详细信息,请参阅基于注释的容器配置)。
最后,您可以使用该dependencyCheck
属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
)为新创建和配置的对象中的对象引用启用Spring依赖项检查。如果将此属性设置为true
,则Spring在配置后验证是否已设置所有属性(不是基元或集合)。
请注意,使用注释本身不会做任何事情。它是 AnnotationBeanConfigurerAspect
在spring-aspects.jar
作用于注释的存在。本质上,方面说,“在从注释类型的新对象的初始化返回之后@Configurable
,根据注释的属性使用Spring配置新创建的对象”。在此上下文中,“初始化”是指新实例化的对象(例如,使用new
运算符实例化的对象)以及Serializable
正在进行反序列化的对象(例如,通过readResolve())。
上段中的一个关键短语是“实质上”。对于大多数情况,“从新对象初始化返回后”的确切语义很好。在此上下文中,“初始化之后”意味着在构造对象之后注入依赖项。这意味着依赖项不可用于类的构造函数体。如果希望在构造函数体执行之前注入依赖项,从而可以在构造函数体中使用,则需要在@Configurable 声明中定义它 ,如下所示:@Configurable(preConstruction=true) 您可以在AspectJ编程指南的本附录中找到有关AspectJ 中各种切入点类型的语言语义的更多信息 。 | |
---|---|
为此,必须使用AspectJ编织器编写带注释的类型。您可以使用构建时Ant或Maven任务来执行此操作(例如,参见 AspectJ开发环境指南)或加载时编织(请参阅Spring Framework中使用AspectJ的加载时编织)。它 AnnotationBeanConfigurerAspect
本身需要由Spring配置(以获取对用于配置新对象的bean工厂的引用)。如果使用基于Java的配置,则可以添加@EnableSpringConfigured
到任何 @Configuration
类,如下所示:
@Configuration @EnableSpringConfigured public class AppConfig { }
如果您更喜欢基于XML的配置,Spring context
命名空间 定义了一个方便的context:spring-configured
元素,您可以按如下方式使用它:
<context:spring-configured/>
@Configurable
在配置方面之前创建的对象实例会导致向调试日志发出消息,并且不会发生对象的配置。一个示例可能是Spring配置中的bean,它在Spring初始化时创建域对象。在这种情况下,您可以使用 depends-on
bean属性手动指定bean依赖于配置方面。以下示例显示如何使用该depends-on
属性:
<bean id="myService" class="com.xzy.myapp.service.MyService" depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"> <!-- ... --> </bean>
不要@Configurable 通过bean配置器方面激活处理,除非你真的想在运行时依赖它的语义。特别是,请确保不要使用@Configurable 在容器上注册为常规Spring bean的bean类。这样做会导致双重初始化,一次通过容器,一次通过方面。 | |
---|---|
单元测试@Configurable
对象
@Configurable
支持的目标之一是实现域对象的独立单元测试,而没有与硬编码查找相关的困难。如果@Configurable
类型尚未由AspectJ编织,则注释在单元测试期间不会产生任何影响。您可以在测试对象中设置模拟或存根属性引用,并照常进行。如果@Configurable
类型已由AspectJ编织,您仍然可以正常地在容器外部进行单元测试,但每次构造一个@Configurable
指示尚未由Spring配置的对象时,您会看到一条警告消息。
使用多个应用程序上下文
的AnnotationBeanConfigurerAspect
是,用于实现@Configurable
支持是一个AspectJ singleton切面。单例方面的范围与static
成员的范围相同:每个类加载器有一个方面实例,用于定义类型。这意味着,如果在同一个类加载器层次结构中定义多个应用程序上下文,则需要考虑在何处定义@EnableSpringConfigured
bean以及在spring-aspects.jar
类路径上放置的位置。
考虑一个典型的Spring Web应用程序配置,它具有共享的父应用程序上下文,它定义了公共业务服务,支持这些服务所需的一切,以及每个servlet的一个子应用程序上下文(包含特定于该servlet的定义)。所有这些上下文共存于同一个类加载器层次结构中,因此它AnnotationBeanConfigurerAspect
只能保存对其中一个的引用。在这种情况下,我们建议@EnableSpringConfigured
在共享(父)应用程序上下文中定义bean。这定义了您可能希望注入域对象的服务。结果是,您无法使用@Configurable机制(可能不是您想要执行的操作)来配置域对象,并引用对子(特定于servlet)上下文中定义的bean的引用。
当部署在同一个容器内的多个web应用程序,确保每一个web应用程序加载类型spring-aspects.jar
通过使用其自己的类加载器(例如,通过放置spring-aspects.jar
在'WEB-INF/lib'
)。如果spring-aspects.jar
仅添加到容器范围的类路径(因此由共享父类加载器加载),则所有Web应用程序共享相同的方面实例(可能不是您想要的)。
5.10.2。AspectJ的其他Spring方面
除了@Configurable
方面之外,还spring-aspects.jar
包含一个AspectJ方面,您可以使用它来为使用注释注释的类型和方法驱动Spring的事务管理@Transactional
。这主要适用于希望在Spring容器之外使用Spring Framework的事务支持的用户。
解释@Transactional
注释的方面是 AnnotationTransactionAspect
。使用此方面时,必须注释实现类(或该类中的方法或两者),而不是类实现的接口(如果有)。AspectJ遵循Java的规则,即接口上的注释不会被继承。
一@Transactional
类上注解指定任何公开操作的类执行默认事务语义。
@Transactional
类中方法的注释会覆盖类注释(如果存在)给出的默认事务语义。可以注释任何可见性的方法,包括私有方法。直接注释非公共方法是获得执行此类方法的事务划分的唯一方法。
从Spring Framework 4.2开始,spring-aspects 提供了一个类似的方面,为标准javax.transaction.Transactional 注释提供完全相同的功能。查看 JtaAnnotationTransactionAspect 更多详细信息。 | |
---|---|
对于想要使用Spring配置和事务管理支持但不想(或不能)使用注释的AspectJ程序员,spring-aspects.jar
还包含abstract
可以扩展以提供自己的切入点定义的方面。有关更多信息,请参阅AbstractBeanConfigurerAspect
和 AbstractTransactionAspect
方面的来源。作为示例,以下摘录显示了如何编写方面来配置域模型中定义的所有对象实例,方法是使用与完全限定类名匹配的原型bean定义:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect { public DomainObjectConfiguration() { setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver()); } // the creation of a new bean (any object in the domain model) protected pointcut beanCreation(Object beanInstance) : initialization(new(..)) && SystemArchitecture.inDomainModel() && this(beanInstance); }
5.10.3。使用Spring IoC配置AspectJ方面
当您在Spring应用程序中使用AspectJ方面时,很自然地希望并期望能够使用Spring配置这些方面。AspectJ运行时本身负责方面创建,通过Spring配置AspectJ创建方面的方法取决于方面使用的AspectJ实例化模型(per-xxx
子句)。
AspectJ的大多数方面都是单例方面。这些方面的配置很容易。您可以创建一个正常引用方面类型的bean定义,并包含factory-method="aspectOf"
bean属性。这可以确保Spring通过向AspectJ请求它来获取方面实例,而不是尝试创建实例本身。以下示例显示如何使用该factory-method="aspectOf"
属性:
<bean id="profiler" class="com.xyz.profiler.Profiler" factory-method="aspectOf"> <property name="profilingStrategy" ref="jamonProfilingStrategy"/> </bean>
注意factory-method="aspectOf" 属性 | |
---|---|
非单例方面更难配置。但是,可以通过创建原型bean定义并使用@Configurable
支持 spring-aspects.jar
来配置方面实例(一旦它们具有由AspectJ运行时创建的bean)来实现。
如果您想要使用AspectJ编写一些@AspectJ方面(例如,对域模型类型使用加载时编织)以及您希望与Spring AOP一起使用的其他@AspectJ方面,并且这些方面都在Spring中配置,您需要告诉Spring AOP @AspectJ自动代理支持,配置中定义的@AspectJ方面的确切子集应该用于自动代理。您可以通过<include/>
在<aop:aspectj-autoproxy/>
声明中使用一个或多个元素来完成此操作。每个<include/>
元素都指定一个名称模式,并且只有名称与至少一个模式匹配的bean才会用于Spring AOP自动代理配置。以下示例显示如何使用<include/>
元素:
<aop:aspectj-autoproxy> <aop:include name="thisBean"/> <aop:include name="thatBean"/> </aop:aspectj-autoproxy>
不要被<aop:aspectj-autoproxy/> 元素的名称误导。使用它会导致创建Spring AOP代理。这里使用@AspectJ样式的方面声明,但不涉及AspectJ运行时。 | |
---|---|
5.10.4。在Spring框架中使用AspectJ进行加载时编织
加载时编织(LTW)是指在将AspectJ方面加载到Java虚拟机(JVM)中时将其编织到应用程序的类文件中的过程。本节的重点是在Spring Framework的特定上下文中配置和使用LTW。本节不是LTW的一般介绍。有关LTW细节的详细信息以及仅使用AspectJ配置LTW(完全不涉及Spring),请参阅AspectJ开发环境指南的 LTW部分。
Spring Framework为AspectJ LTW带来的价值在于对编织过程进行更精细的控制。“Vanilla”AspectJ LTW通过使用Java(5+)代理来实现,该代理通过在启动JVM时指定VM参数来启用。因此,它是一个JVM范围的设置,在某些情况下可能很好,但通常有点过于粗糙。支持Spring的LTW允许您在每个ClassLoader
基础上打开LTW ,这更精细,并且在“单JVM多应用程序”环境中更有意义(例如在典型的应用程序服务器环境中找到的) )。
此外,在某些环境中,此支持可实现加载时编织,而无需对添加所需的应用程序服务器的启动脚本进行任何修改-javaagent:path/to/aspectjweaver.jar
或(如本节后面部分所述) -javaagent:path/to/org.springframework.instrument-{version}.jar
(以前称为 spring-agent.jar
)。开发人员修改构成应用程序上下文的一个或多个文件以启用加载时编织,而不是依赖通常负责部署配置的管理员,例如启动脚本。
现在销售宣传已经结束,让我们首先介绍使用Spring的AspectJ LTW的快速示例,然后详细介绍示例中介绍的元素。有关完整示例,请参阅 Petclinic示例应用程序。
第一个例子
假设您是一名应用程序开发人员,负责诊断系统中某些性能问题的原因。我们将打开一个简单的分析方面,让我们快速获得一些性能指标,而不是打破分析工具。然后,我们可以立即将精细的分析工具应用于该特定区域。
此处提供的示例使用XML配置。您还可以使用Java配置配置和使用@AspectJ 。具体来说,您可以使用@EnableLoadTimeWeaving 注释作为替代 <context:load-time-weaver/> (请参阅下面的详细信息)。 | |
---|---|
以下示例显示了分析方面,这不是花哨 - 它是一个基于时间的分析器,它使用@ AspectJ样式的方面声明:
package foo; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Pointcut; import org.springframework.util.StopWatch; import org.springframework.core.annotation.Order; @Aspect public class ProfilingAspect { @Around("methodsToBeProfiled()") public Object profile(ProceedingJoinPoint pjp) throws Throwable { StopWatch sw = new StopWatch(getClass().getSimpleName()); try { sw.start(pjp.getSignature().getName()); return pjp.proceed(); } finally { sw.stop(); System.out.println(sw.prettyPrint()); } } @Pointcut("execution(public * foo..*.*(..))") public void methodsToBeProfiled(){} }
我们还需要创建一个META-INF/aop.xml
文件,以通知AspectJ weaver我们想要将我们ProfilingAspect
编入我们的类中。此文件约定,即所调用的Java类路径上的文件(或多个文件)的存在 META-INF/aop.xml
是标准AspectJ。以下示例显示了该aop.xml
文件:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver> <!-- only weave classes in our application-specific packages --> <include within="foo.*"/> </weaver> <aspects> <!-- weave in just this aspect --> <aspect name="foo.ProfilingAspect"/> </aspects> </aspectj>
现在我们可以继续进行配置的Spring特定部分。我们需要配置一个 LoadTimeWeaver
(稍后解释)。此加载时weaver是负责将一个或多个META-INF/aop.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"> <!-- a service object; we will be profiling its methods --> <bean id="entitlementCalculationService" class="foo.StubEntitlementCalculationService"/> <!-- this switches on the load-time weaving --> <context:load-time-weaver/> </beans>
现在所有必需的工件(方面,META-INF/aop.xml
文件和Spring配置)都已到位,我们可以使用一种main(..)
方法创建以下驱动程序类, 以演示LTW的运行情况:
package foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class); EntitlementCalculationService entitlementCalculationService = (EntitlementCalculationService) ctx.getBean("entitlementCalculationService"); // the profiling aspect is 'woven' around this method execution entitlementCalculationService.calculateEntitlement(); } }
我们还有最后一件事要做。本节的介绍确实说可以选择在ClassLoader
Spring 上选择性地打开LTW ,这是事实。但是,对于此示例,我们使用Java代理(随Spring提供)来打开LTW。我们使用以下命令来运行Main
前面显示的类:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
这-javaagent
是一个标志,用于指定和启用 代理程序来检测在JVM上运行的程序。Spring Framework附带了一个代理程序,InstrumentationSavingAgent
它包含 在前面示例spring-instrument.jar
中作为-javaagent
参数值提供的代理程序中。
执行Main
程序的输出看起来像下一个例子。(我Thread.sleep(..)
在calculateEntitlement()
实现中引入了一个声明,以便探查器实际捕获0毫秒以外的东西(01234
毫秒不是AOP引入的开销)。下面的清单显示了运行探查器时得到的输出:
计算权利 StopWatch'ProfilingAspect':运行时间(毫秒)= 1234 ------ ----- ---------------------------- ms%任务名称 ------ ----- ---------------------------- 01234 100%calculateEntitlement
由于LTW是通过使用成熟的AspectJ实现的,因此我们不仅限于为Spring bean提供建议。程序的以下细微变化Main
产生相同的结果:
package foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Main { public static void main(String[] args) { new ClassPathXmlApplicationContext("beans.xml", Main.class); EntitlementCalculationService entitlementCalculationService = new StubEntitlementCalculationService(); // the profiling aspect will be 'woven' around this method execution entitlementCalculationService.calculateEntitlement(); } }
请注意,在前面的程序中,我们如何引导Spring容器,然后创建一个StubEntitlementCalculationService
完全在Spring上下文之外的新实例。剖析建议仍在编织中。
不可否认,这个例子很简单。但是,Spring中LTW支持的基础知识已在前面的示例中引入,本节的其余部分详细说明了每个配置和使用位置背后的“原因”。
在ProfilingAspect 本例中使用可能是基本的,但它是非常有用的。这是开发人员在开发期间可以使用的开发时间方面的一个很好的示例,然后可以轻松地从部署到UAT或生产中的应用程序的构建中排除。 | |
---|---|
方面
您在LTW中使用的方面必须是AspectJ方面。您可以使用AspectJ语言本身编写它们,也可以使用@ AspectJ样式编写方面。那么你的方面都是有效的AspectJ和Spring AOP方面。此外,编译的方面类需要在类路径上可用。
'META-INF / aop.xml文件'
AspectJ LTW基础结构是使用META-INF/aop.xml
Java类路径上的一个或多个文件(直接或更常见地,在jar文件中)配置的。
LTW部分AspectJ参考文档中详细介绍了此文件的结构和内容。因为aop.xml文件是100%AspectJ,所以我们在此不再进一步描述。
必需的库(JARS)
至少,您需要以下库来使用Spring Framework对AspectJ LTW的支持:
-
spring-aop.jar
(版本2.5或更高版本,加上所有必需的依赖项) -
aspectjweaver.jar
(1.6.8或更高版本)
如果使用Spring提供的代理来启用检测,则还需要:
-
spring-instrument.jar
弹簧配置
Spring的LTW支持的关键组件是LoadTimeWeaver
接口(在 org.springframework.instrument.classloading
包中),以及随Spring发行版一起提供的众多实现。一个LoadTimeWeaver
是负责添加一个或一个以上java.lang.instrument.ClassFileTransformers
的ClassLoader
在运行时,这将打开大门,各种各样有趣的应用方式,其中一个正好是各方面的LTW。
如果您不熟悉运行时类文件转换的概念,请java.lang.instrument 在继续之前查看该包的javadoc API文档。虽然该文档并不全面,但至少可以看到关键接口和类(供您阅读本节时参考)。 | |
---|---|
LoadTimeWeaver
为特定项目配置a ApplicationContext
可以像添加一行一样简单。(请注意,您几乎肯定需要使用一个ApplicationContext
作为您的Spring容器 - 通常,这BeanFactory
是不够的,因为LTW支持使用BeanFactoryPostProcessors
。)
要启用Spring Framework的LTW支持,您需要配置a LoadTimeWeaver
,通常使用@EnableLoadTimeWeaving
注释来完成,如下所示:
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
或者,如果您更喜欢基于XML的配置,请使用该 <context:load-time-weaver/>
元素。请注意,该元素是在context
命名空间中定义的 。以下示例显示如何使用<context:load-time-weaver/>
:
<?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:load-time-weaver/> </beans>
上述配置会自动为您定义和注册许多LTW特定的基础结构bean,例如a LoadTimeWeaver
和an AspectJWeavingEnabler
。默认LoadTimeWeaver
是DefaultContextLoadTimeWeaver
类,它尝试装饰自动检测到的LoadTimeWeaver
。LoadTimeWeaver
“自动检测” 的确切类型 取决于您的运行时环境。下表总结了各种LoadTimeWeaver
实现:
运行环境 | LoadTimeWeaver 履行 |
---|---|
在Oracle的WebLogic中运行 | WebLogicLoadTimeWeaver |
在Oracle的GlassFish中运行 | GlassFishLoadTimeWeaver |
在Apache Tomcat中运行 | TomcatLoadTimeWeaver |
在Red Hat的JBoss AS或WildFly中运行 | JBossLoadTimeWeaver |
在IBM的WebSphere中运行 | WebSphereLoadTimeWeaver |
JVM以Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar )开头 | InstrumentationLoadTimeWeaver |
后备,期望底层的ClassLoader遵循常见的约定(例如适用于TomcatInstrumentableClassLoader 和Resin) | ReflectiveLoadTimeWeaver |
请注意,该表仅列出了LoadTimeWeavers
使用时自动检测到的 DefaultContextLoadTimeWeaver
。您可以准确指定 LoadTimeWeaver
要使用的实现。
要LoadTimeWeaver
使用Java配置指定特定的,请实现该 LoadTimeWeavingConfigurer
接口并覆盖该getLoadTimeWeaver()
方法。以下示例指定ReflectiveLoadTimeWeaver
:
@Configuration @EnableLoadTimeWeaving public class AppConfig implements LoadTimeWeavingConfigurer { @Override public LoadTimeWeaver getLoadTimeWeaver() { return new ReflectiveLoadTimeWeaver(); } }
如果使用基于XML的配置,则可以将完全限定的类名指定为 元素weaver-class
上属性的值<context:load-time-weaver/>
。同样,以下示例指定了ReflectiveLoadTimeWeaver
:
<?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:load-time-weaver weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/> </beans>
在LoadTimeWeaver
由配置定义和注册可从Spring容器通过使用公知的名称以后检索loadTimeWeaver
。请记住,LoadTimeWeaver
仅存在作为Spring的LTW基础结构添加一个或多个的机制ClassFileTransformers
。ClassFileTransformer
LTW 的实际 情况是ClassPreProcessorAgentAdapter
(来自org.aspectj.weaver.loadtime
包)类。有关ClassPreProcessorAgentAdapter
更多详细信息,请参阅类的类级别javadoc ,因为编织实际如何实现的细节超出了本文档的范围。
剩下要讨论的配置有一个最终属性: aspectjWeaving
属性(或者aspectj-weaving
如果使用XML)。此属性控制是否启用LTW。它接受三个可能值中的一个,autodetect
如果该属性不存在,则默认值为 。下表总结了三个可能的值:
注释值 | XML值 | 说明 |
---|---|---|
ENABLED | on | AspectJ编织已打开,并且方面在加载时编织。 |
DISABLED | off | LTW已关闭。在加载时没有编织任何方面。 |
AUTODETECT | autodetect | 如果Spring LTW基础结构可以找到至少一个META-INF/aop.xml 文件,那么AspectJ编织就会打开。否则,它关闭。这是默认值。 |
特定于环境的配置
最后一节包含在应用程序服务器和Web容器等环境中使用Spring的LTW支持时所需的任何其他设置和配置。
Tomcat的
从历史上看,Apache Tomcat的默认类加载器不支持类转换,这就是Spring提供满足此需求的增强实现的原因。命名TomcatInstrumentableClassLoader
,加载程序适用于Tomcat 6.0及更高版本。
不要TomcatInstrumentableClassLoader 在Tomcat 8.0及更高版本上定义。相反,让SpringInstrumentableClassLoader 通过TomcatLoadTimeWeaver 策略自动使用Tomcat的新本机设施。 | |
---|---|
如果仍需要使用TomcatInstrumentableClassLoader
,可以按如下方式为每个Web应用程序单独注册:
-
复制
org.springframework.instrument.tomcat.jar
到$CATALINA_HOME/lib
,$CATALINA_HOME
表示Tomcat安装的根目录 -
通过编辑Web应用程序上下文文件,指示Tomcat使用自定义类加载器(而不是默认值),如以下示例所示:
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
Apache Tomcat 6.0+支持多个上下文位置:
-
服务器配置文件:
$CATALINA_HOME/conf/server.xml
-
默认上下文配置:
$CATALINA_HOME/conf/context.xml
,它影响所有已部署的Web应用程序 -
每个Web应用程序配置,可以在服务器端部署,也可以部署
$CATALINA_HOME/conf/[enginename]/[hostname]/[webapp]-context.xml
在Web应用程序存档中META-INF/context.xml
为了提高效率,我们建议使用嵌入式每Web应用程序配置样式,因为它仅影响使用自定义类加载器的应用程序,并且不需要对服务器配置进行任何更改。有关可用上下文位置的更多详细信息,请参阅Tomcat 6.0.x 文档。
或者,考虑使用Spring提供的通用VM代理,在Tomcat的启动脚本中指定(在本节前面描述)。这使得所有已部署的Web应用程序都可以使用检测,无论ClassLoader
它们运行的是什么。
WebLogic,WebSphere,Resin,GlassFish和JBoss
最新版本的WebLogic Server(版本10及更高版本),IBM WebSphere Application Server(版本7及更高版本),Resin(版本3.1及更高版本)和JBoss(版本6.x或更高版本)提供了 ClassLoader
能够进行本地检测的版本。Spring的原生LTW利用这种ClassLoader实现来实现AspectJ编织。如前所述,您可以通过激活加载时编织来启用LTW 。具体而言,您无需修改要添加的启动脚本-javaagent:path/to/spring-instrument.jar
。
请注意,GlassFish检测功能ClassLoader
仅在其EAR环境中可用。对于GlassFish Web应用程序,请遵循前面概述的Tomcat设置说明。
请注意,在JBoss 6.x上,您需要禁用应用服务器扫描,以防止它在应用程序实际启动之前加载类。一个快速的解决方法是向您的工件添加一个名为WEB-INF/jboss-scanning.xml
以下内容的文件:
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用Java应用程序
如果在不支持或不支持现有LoadTimeWeaver
实现的环境中需要类检测,则JDK代理可以是唯一的解决方案。对于这种情况,Spring提供了InstrumentationLoadTimeWeaver
,它需要一个特定于Spring的(但非常通用的)VM代理 org.springframework.instrument-{version}.jar
(以前称为spring-agent.jar
)。
要使用它,必须通过提供以下JVM选项来启动具有Spring代理的虚拟机:
-javaagent:/path/to/org.springframework.instrument- {}版本的.jar
请注意,这需要修改VM启动脚本,这可能会阻止您在应用程序服务器环境中使用它(取决于您的操作策略)。此外,JDK代理可以检测整个VM,这可能很昂贵。
出于性能原因,我们建议您仅在目标环境(例如Jetty)没有(或不支持)专用LTW 时才使用此配置。
5.11。更多资源
有关AspectJ的更多信息,请访问AspectJ网站。
Eclipse AspectJ由Adrian Colyer等人。人。(Addison-Wesley,2005)为AspectJ语言提供了全面的介绍和参考。
AspectJ in Action,第二版由Ramnivas Laddad(Manning,2009)强烈推荐。本书的重点是AspectJ,但是很多一般的AOP主题都在探索中(在某种程度上)。
6. Spring AOP API
前一章描述了Spring使用@AspectJ和基于模式的方面定义对AOP的支持。在本章中,我们将讨论较低级别的Spring AOP API以及Spring 1.2应用程序中常用的AOP支持。对于新应用程序,我们建议使用前一章中描述的Spring 2.0及更高版本的AOP支持。但是,当您使用现有应用程序时(或者当您阅读书籍和文章时),您可能会遇到Spring 1.2风格的示例。Spring 5仍然向后兼容Spring 1.2,本章中描述的所有内容在Spring 5中都得到了完全支持。
6.1。Spring中的Pointcut API
本节描述了Spring如何处理关键的切入点概念。
6.1.1。概念
Spring的切入点模型使切入点重用独立于建议类型。您可以使用相同的切入点来定位不同的建议。
该org.springframework.aop.Pointcut
接口是中央接口,用来将通知到特定的类和方法。完整的界面如下:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
将Pointcut
接口拆分为两部分允许重用类和方法匹配部分以及细粒度合成操作(例如与另一个方法匹配器执行“联合”)。
该ClassFilter
接口用于将切入点限制为给定的一组目标类。如果matches()
方法始终返回true,则匹配所有目标类。以下清单显示了ClassFilter
接口定义:
public interface ClassFilter { boolean matches(Class clazz); }
该MethodMatcher
接口通常更重要。完整的界面如下:
public interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); }
该matches(Method, Class)
方法用于测试此切入点是否与目标类上的给定方法匹配。可以在创建AOP代理时执行此评估,以避免对每个方法调用进行测试。如果为给定matches
方法返回双参数方法true
,并且isRuntime()
MethodMatcher 的方法返回true
,则在每次方法调用时都会调用三参数匹配方法。这使得切入点可以在执行目标通知之前立即查看传递给方法调用的参数。
大多数MethodMatcher
实现是静态的,这意味着它们的isRuntime()
方法返回false
。在这种情况下,matches
永远不会调用三参数方法。
如果可能,尝试使切入点成为静态,允许AOP框架在创建AOP代理时缓存切入点评估的结果。 | |
---|---|
6.1.2。切入点的操作
Spring支持切入点上的操作(特别是联合和交集)。
Union表示切入点匹配的方法。交叉表示两个切入点匹配的方法。联盟通常更有用。您可以通过使用类中的静态方法org.springframework.aop.support.Pointcuts
或使用ComposablePointcut
同一包中的类 来组合切入点 。但是,使用AspectJ切入点表达式通常是一种更简单的方法。
6.1.3。AspectJ表达式切入点
从2.0开始,Spring使用的最重要的切入点类型是 org.springframework.aop.aspectj.AspectJExpressionPointcut
。这是一个切入点,它使用AspectJ提供的库来解析AspectJ切入点表达式字符串。
有关受支持的AspectJ切入点基元的讨论,请参见上一章。
6.1.4。便利切入点实现
Spring提供了几种方便的切入点实现。您可以直接使用其中一些。其他用于在特定于应用程序的切入点中进行子类化。
静态切入点
静态切入点基于方法和目标类,不能考虑方法的参数。对于大多数用途,静态切入点足够 - 并且最好。当首次调用方法时,Spring只能评估一次静态切入点。之后,无需再次使用每个方法调用来评估切入点。
本节的其余部分描述了Spring中包含的一些静态切入点实现。
正则表达式切入点
指定静态切入点的一种显而易见的方法是正则表达式。除Spring之外的几个AOP框架使这成为可能。org.springframework.aop.support.JdkRegexpMethodPointcut
是一个通用的正则表达式切入点,它使用JDK中的正则表达式支持。
使用JdkRegexpMethodPointcut
该类,您可以提供模式字符串列表。如果其中任何一个匹配,则切入点评估为true
。(因此,结果实际上是这些切入点的结合。)
以下示例显示如何使用JdkRegexpMethodPointcut
:
<bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
Spring提供了一个名为的便利类RegexpMethodPointcutAdvisor
,它允许我们引用一个Advice
(记住一个Advice
可以是一个拦截器,在建议之前,抛出建议,以及其他)。在幕后,Spring使用了JdkRegexpMethodPointcut
。使用RegexpMethodPointcutAdvisor
简化了布线,因为一个bean封装了切入点和建议,如下例所示:
<bean id="settersAndAbsquatulateAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref bean="beanNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
您可以使用RegexpMethodPointcutAdvisor
任何Advice
类型。
属性驱动的切入点
一种重要的静态切入点是元数据驱动的切入点。这使用元数据属性的值(通常是源级元数据)。
动态切入点
与静态切入点相比,动态切入点的评估成本更高。它们考虑了方法参数以及静态信息。这意味着必须使用每个方法调用来评估它们,并且不能缓存结果,因为参数会有所不同。
主要的例子是control flow
切入点。
控制流切入点
Spring控制流切入点在概念上类似于AspectJ cflow
切入点,虽然功能较弱。(目前无法指定切入点在另一个切入点匹配的连接点下执行。)控制流切入点与当前调用堆栈匹配。例如,如果连接点由com.mycompany.web
包中的方法或SomeCaller
类调用,则可能会触发。使用org.springframework.aop.support.ControlFlowPointcut
该类指定控制流切入点。
在运行时评估控制流切入点的成本远远高于其他动态切入点。在Java 1.4中,成本大约是其他动态切入点的五倍。 | |
---|---|
6.1.5。切入点超级课程
Spring提供了有用的切入点超类来帮助您实现自己的切入点。
因为静态切入点最有用,所以您应该是子类 StaticMethodMatcherPointcut
。这需要只实现一个抽象方法(尽管您可以覆盖其他方法来自定义行为)。以下示例显示了如何子类化StaticMethodMatcherPointcut
:
class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } }
还有动态切入点的超类。
您可以在Spring 1.0 RC2及更高版本中使用任何建议类型的自定义切入点。
6.1.6。自定义切入点
因为Spring AOP中的切入点是Java类而不是语言功能(如在AspectJ中),所以您可以声明自定义切入点,无论是静态还是动态。Spring中的自定义切入点可以是任意复杂的。但是,如果可以,我们建议使用AspectJ切入点表达式语言。
更高版本的Spring可能会为JAC提供的“语义切入点”提供支持 - 例如,“所有改变目标对象中实例变量的方法”。 | |
---|---|
6.2。Spring中的建议API
现在我们可以研究Spring AOP如何处理建议。
6.2.1。建议生命周期
每个建议都是一个Spring bean。建议实例可以在所有建议对象之间共享,也可以对每个建议对象唯一。这对应于每个类或每个实例的建议。
每类建议最常使用。它适用于通用建议,例如交易顾问。这些不依赖于代理对象的状态或添加新状态。他们只是根据方法和论点采取行动。
每个实例的建议适用于介绍,以支持mixin。在这种情况下,建议将状态添加到代理对象。
您可以在同一个AOP代理中混合使用共享和每个实例的建议。
6.2.2。Spring中的建议类型
Spring提供了几种建议类型,并且可以扩展以支持任意建议类型。本节介绍基本概念和标准建议类型。
拦截建议
Spring中最基本的建议类型是拦截建议。
Spring符合AOP Alliance
接口,用于使用方法拦截的周围建议。实现MethodInterceptor
和实现建议的类也应该实现以下接口:
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
该方法的MethodInvocation
参数invoke()
公开了被调用的方法,目标连接点,AOP代理和方法的参数。该 invoke()
方法应该返回调用的结果:连接点的返回值。
以下示例显示了一个简单的MethodInterceptor
实现:
public class DebugInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("Before: invocation=[" + invocation + "]"); Object rval = invocation.proceed(); System.out.println("Invocation returned"); return rval; } }
注意调用proceed()
方法MethodInvocation
。这沿拦截器链向下进入连接点。大多数拦截器调用此方法并返回其返回值。但是,MethodInterceptor
与任何around建议一样,a 可以返回不同的值或抛出异常而不是调用proceed方法。但是,如果没有充分的理由,您不希望这样做。
MethodInterceptor 实现提供与其他符合AOP Alliance标准的AOP实现的互操作性。本节其余部分讨论的其他建议类型实现了常见的AOP概念,但是采用Spring特定的方式。虽然使用最具体的建议类型有一个优势,但MethodInterceptor 如果您可能希望在另一个AOP框架中运行该方面,请坚持使用建议。请注意,切入点目前在框架之间不可互操作,AOP联盟目前不定义切入点接口。 | |
---|---|
在建议之前
更简单的建议类型是之前的建议。这不需要MethodInvocation
对象,因为只在进入方法之前调用它。
之前建议的主要优点是不需要调用该proceed()
方法,因此不会无意中无法继续拦截链。
以下清单显示了MethodBeforeAdvice
界面:
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; }
(Spring的API设计允许在建议之前提供字段,尽管通常的对象适用于字段拦截,但Spring不太可能实现它。)
请注意,返回类型是void
。在通知可以在连接点执行之前插入自定义行为但不能更改返回值之前。如果before advice抛出异常,则会中止拦截器链的进一步执行。异常传播回拦截器链。如果未选中或在调用方法的签名上,则将其直接传递给客户端。否则,它由AOP代理包装在未经检查的异常中。
以下示例显示了Spring中的before建议,该建议计算所有方法调用:
public class CountingBeforeAdvice implements MethodBeforeAdvice { private int count; public void before(Method m, Object[] args, Object target) throws Throwable { ++count; } public int getCount() { return count; } }
之前建议可以与任何切入点一起使用。 | |
---|---|
抛出建议
如果连接点引发异常,则在返回连接点后调用抛出建议。Spring提供类型投掷建议。请注意,这意味着该org.springframework.aop.ThrowsAdvice
接口不包含任何方法。它是一个标记接口,用于标识给定对象实现一个或多个类型化throws建议方法。这些应该是以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只需要最后一个参数。方法签名可以有一个或四个参数,具体取决于通知方法是否对方法和参数感兴趣。接下来的两个列表显示了作为throws建议示例的类。
如果RemoteException
抛出a(包括子类),则调用以下建议:
public class RemoteThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // Do something with remote exception } }
与前面的建议不同,下一个示例声明了四个参数,以便它可以访问调用的方法,方法参数和目标对象。如果ServletException
抛出a,则调用以下建议:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something with all arguments } }
最后一个例子说明如何这两种方法可能会在处理两者的单一类中使用RemoteException
和ServletException
。可以在单个类中组合任意数量的throws建议方法。以下清单显示了最后一个示例:
public static class CombinedThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // Do something with remote exception } public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something with all arguments } }
如果throws-advice方法本身抛出异常,它会覆盖原始异常(也就是说,它会更改抛出给用户的异常)。覆盖异常通常是RuntimeException,它与任何方法签名兼容。但是,如果throws-advice方法抛出已检查的异常,则它必须与目标方法的声明的异常匹配,因此在某种程度上耦合到特定的目标方法签名。不要抛出与目标方法签名不兼容的未声明的已检查异常! | |
---|---|
抛出建议可以与任何切入点一起使用。 | |
---|---|
返回建议后
在Spring中返回后的建议必须实现 org.springframework.aop.AfterReturningAdvice
接口,如下所示:
public interface AfterReturningAdvice extends Advice { void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; }
返回后的建议可以访问返回值(它无法修改),调用的方法,方法的参数和目标。
返回通知后的以下内容计算所有未抛出异常的成功方法调用:
public class CountingAfterReturningAdvice implements AfterReturningAdvice { private int count; public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable { ++count; } public int getCount() { return count; } }
此建议不会更改执行路径。如果它抛出异常,则抛出拦截器链而不是返回值。
返回建议后可以使用任何切入点。 | |
---|---|
简介建议
Spring将介绍建议视为一种特殊的拦截建议。
简介需要一个IntroductionAdvisor
和一个IntroductionInterceptor
实现以下接口:
public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); }
invoke()
从AOP Alliance MethodInterceptor
接口继承的方法必须实现介绍。也就是说,如果调用的方法在引入的接口上,则引入拦截器负责处理方法调用 - 它无法调用proceed()
。
引言建议不能与任何切入点一起使用,因为它仅适用于类,而不是方法,级别。您只能使用带有IntroductionAdvisor
以下方法的介绍建议 :
public interface IntroductionAdvisor extends Advisor, IntroductionInfo { ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo { Class[] getInterfaces(); }
没有MethodMatcher
,因此没有Pointcut
与介绍建议相关联。只有类过滤是合乎逻辑的。
该getInterfaces()
方法返回此顾问程序引入的接口。
该validateInterfaces()
方法在内部用于查看引入的接口是否可以由已配置的接口实现IntroductionInterceptor
。
考虑Spring测试套件中的一个示例,假设我们要将以下接口引入一个或多个对象:
public interface Lockable { void lock(); void unlock(); boolean locked(); }
这说明了一个混合。我们希望能够将建议的对象转换Lockable
为其类型,并调用锁定和解锁方法。如果我们调用该lock()
方法,我们希望所有setter方法都抛出一个LockedException
。因此,我们可以添加一个方面,该方面提供了在不知道对象的情况下使对象不可变的能力:AOP的一个很好的例子。
首先,我们需要一个IntroductionInterceptor
能够解决繁重问题的工作。在这种情况下,我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor
便利类。我们可以IntroductionInterceptor
直接实现,但DelegatingIntroductionInterceptor
在大多数情况下使用 最佳。
该DelegatingIntroductionInterceptor
设计将导入委托到真正实现导入接口,隐藏拦截的使用来做到这一点。您可以使用构造函数参数将委托设置为任何对象。默认委托(使用无参数构造函数时)是this
。因此,在下一个示例中,委托是LockMixin
子类DelegatingIntroductionInterceptor
。给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor
实例查找委托实现的所有接口(除了IntroductionInterceptor
)并支持对其中任何接口的 介绍。子类如LockMixin
可以调用该suppressInterface(Class intf)
方法来抑制不应该公开的接口。但是,无论IntroductionInterceptor
准备支持多少接口,都可以 IntroductionAdvisor
用于控制哪些接口实际暴露。引入的接口隐藏了目标对同一接口的任何实现。
因此,LockMixin
扩展DelegatingIntroductionInterceptor
和实现Lockable
自己。超类自动获取Lockable
可以支持引入的超类,因此我们不需要指定它。我们可以用这种方式引入任意数量的接口。
请注意locked
实例变量的使用。这有效地将附加状态添加到目标对象中保存的状态。
以下示例显示了示例LockMixin
类:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable { private boolean locked; public void lock() { this.locked = true; } public void unlock() { this.locked = false; } public boolean locked() { return this.locked; } public Object invoke(MethodInvocation invocation) throws Throwable { if (locked() && invocation.getMethod().getName().indexOf("set") == 0) { throw new LockedException(); } return super.invoke(invocation); } }
通常,您无需覆盖该invoke()
方法。的 DelegatingIntroductionInterceptor
实现(它调用delegate
如果引入的方法的方法,否则指向连接点前进)通常就足够了。在本例中,我们需要添加一个检查:如果处于锁定模式,则不能调用setter方法。
所需的介绍只需要保存一个不同的 LockMixin
实例并指定引入的接口(在这种情况下,仅 Lockable
)。一个更复杂的例子可能会引用引入拦截器(它将被定义为原型)。在这种情况下,没有与a相关的配置LockMixin
,因此我们通过使用创建它new
。以下示例显示了我们的LockMixinAdvisor
类:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } }
我们可以非常简单地应用这个顾问,因为它不需要配置。(然而,这是不可能使用IntroductionInterceptor
没有IntroductionAdvisor
)。由于通常的引入,所述顾问必须是每个实例,因为它是有状态的。我们需要一个不同的实例LockMixinAdvisor
,因此 LockMixin
需要每个建议的对象。顾问包括建议对象的状态的一部分。
我们可以Advised.addAdvisor()
像在任何其他顾问中一样,使用XML配置中的方法或(推荐方式)以编程方式应用此顾问程序。下面讨论的所有代理创建选项,包括“自动代理创建器”,正确处理引入和有状态混合。
6.3。Spring中的Advisor API
在Spring中,Advisor是一个方面,它只包含与切入点表达式关联的单个建议对象。
除了介绍的特殊情况,任何顾问都可以使用任何建议。 org.springframework.aop.support.DefaultPointcutAdvisor
是最常用的顾问类。它可以与使用MethodInterceptor
,BeforeAdvice
或 ThrowsAdvice
。
可以在同一个AOP代理中混合Spring中的顾问程序和通知类型。例如,您可以在一个代理配置中使用拦截建议,抛出建议和建议之前。Spring自动创建必要的拦截链。
6.4。使用ProxyFactoryBean
创建AOP代理
如果您为业务对象使用Spring IoC容器(ApplicationContext
或者BeanFactory
)(并且您应该!),那么您希望使用Spring的AOPFactoryBean
实现之一。(请记住,工厂bean引入了一个间接层,让它创建一个不同类型的对象。)
Spring AOP支持还使用了工厂bean。 | |
---|---|
在Spring中创建AOP代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean
。这样可以完全控制切入点,任何适用的建议以及它们的顺序。但是,如果您不需要此类控件,则可以使用更简单的选项。
6.4.1。基本
的ProxyFactoryBean
,像其它的FactoryBean
实现中,引入了一个间接的水平。如果定义了一个ProxyFactoryBean
named foo
,那么引用的对象foo
看不到ProxyFactoryBean
实例本身,而是通过该getObject()
方法的实现创建的对象ProxyFactoryBean
。此方法创建一个包装目标对象的AOP代理。
使用一个ProxyFactoryBean
或另一个IoC感知类来创建AOP代理的最重要的好处之一是IoC也可以管理建议和切入点。这是一个强大的功能,可以实现其他AOP框架难以实现的某些方法。例如,一个建议本身可以引用应用程序对象(除了目标,它应该在任何AOP框架中可用),受益于依赖注入提供的所有可插入性。
6.4.2。JavaBean属性
FactoryBean
与Spring提供的大多数实现一样, ProxyFactoryBean
该类本身就是一个JavaBean。其属性用于:
-
指定要代理的目标。
-
指定是否使用CGLIB(稍后描述并参见基于JDK和CGLIB的代理)。
一些关键属性继承自org.springframework.aop.framework.ProxyConfig
(Spring中所有AOP代理工厂的超类)。这些关键属性包括以下内容:
-
proxyTargetClass
:true
如果要代理目标类,而不是目标类的接口。如果此属性值设置为true
,则创建CGLIB代理(但另请参阅基于JDK和CGLIB的代理)。 -
optimize
:控制是否将积极优化应用于通过CGLIB创建的代理。除非您完全了解相关AOP代理如何处理优化,否则您不应轻易使用此设置。目前仅用于CGLIB代理。它对JDK动态代理没有影响。 -
frozen
:如果是代理配置frozen
,则不再允许更改配置。这既可以作为轻微优化,也可以用于Advised
在创建代理后不希望调用者能够操作代理(通过接口)的情况。此属性的默认值为false
,因此允许更改(例如添加其他建议)。 -
exposeProxy
:确定是否应将当前代理公开在一个ThreadLocal
目标中,以便目标可以访问它。如果目标需要获取代理并且exposeProxy
属性设置为true
,则目标可以使用该AopContext.currentProxy()
方法。
其他特定属性ProxyFactoryBean
包括以下内容:
-
proxyInterfaces
:一组String
接口名称。如果未提供,则使用目标类的CGLIB代理(但另请参阅基于JDK和CGLIB的代理)。 -
interceptorNames
:要应用的String
数组Advisor
,拦截器或其他建议名称。以先到先得的方式订购非常重要。也就是说,列表中的第一个拦截器是第一个能够拦截调用的拦截器。名称是当前工厂中的bean名称,包括来自祖先工厂的bean名称。你不能在这里提到bean引用,因为这样做会导致
ProxyFactoryBean
忽略通知的单例设置。您可以使用星号(
*
)附加拦截器名称。这样做会导致应用所有顾问程序bean,其名称以要应用星号之前的部分开头。您可以在使用“全局”顾问中找到使用此功能的示例。 -
singleton:无论
getObject()
调用方法的频率如何,工厂是否应该返回单个对象。一些FactoryBean
实现提供了这样的方法。默认值为true
。如果您想使用有状态建议 - 例如,对于有状态的mixins - 使用原型建议以及单例值false
。
6.4.3。基于JDK和CGLIB的代理
本节作为关于如何ProxyFactoryBean
选择为特定目标对象(将被代理)创建基于JDK的代理或基于CGLIB的代理的权威文档。
ProxyFactoryBean 关于创建基于JDK或CGLIB的代理 的行为在Spring的1.2.x和2.0版本之间发生了变化。在ProxyFactoryBean 现在表现关于与上述的自动检测接口相似的语义 TransactionProxyFactoryBean 类。 | |
---|---|
如果要代理的目标对象的类(以下简称为目标类)未实现任何接口,则创建基于CGLIB的代理。这是最简单的方案,因为JDK代理是基于接口的,没有接口意味着甚至不可能进行JDK代理。您可以通过设置interceptorNames
属性来插入目标bean并指定拦截器列表。请注意,即使已将proxyTargetClass
属性 ProxyFactoryBean
设置为,也会创建基于CGLIB的代理false
。(这样做没有意义,最好从bean定义中删除,因为它最多是多余的,并且最糟糕的是混淆。)
如果目标类实现一个(或多个)接口,则创建的代理类型取决于接口的配置ProxyFactoryBean
。
如果已将proxyTargetClass
属性ProxyFactoryBean
设置为true
,则会创建基于CGLIB的代理。这是有道理的,并且符合最少惊喜的原则。即使已将proxyInterfaces
属性 ProxyFactoryBean
设置为一个或多个完全限定的接口名称,该proxyTargetClass
属性的设置也会true
导致基于CGLIB的代理生效。
如果已将proxyInterfaces
属性ProxyFactoryBean
设置为一个或多个完全限定的接口名称,则会创建基于JDK的代理。创建的代理实现了proxyInterfaces
属性中指定的所有接口。如果目标类碰巧实现了比proxyInterfaces
属性中指定的接口多得多的接口,那么这一切都很好,但返回的代理不会实现这些额外的接口。
如果尚未设置proxyInterfaces
属性ProxyFactoryBean
,但目标类确实实现了一个(或多个)接口,则 ProxyFactoryBean
自动检测目标类确实实现至少一个接口的事实,并创建基于JDK的代理。实际代理的接口是目标类实现的所有接口。实际上,这与提供目标类为proxyInterfaces
属性实现的每个接口的列表相同。但是,它的工作量明显减少,并且不易出现印刷错误。
6.4.4。代理接口
考虑一个简单的实例ProxyFactoryBean
。这个例子涉及:
-
代理的目标bean。这是
personTarget
示例中的bean定义。 -
An
Advisor
和anInterceptor
用于提供建议。 -
AOP代理bean定义,用于指定目标对象(
personTarget
bean),代理接口和要应用的建议。
以下清单显示了示例:
<bean id="personTarget" class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom string property value"/> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <property name="target" ref="personTarget"/> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
请注意,该interceptorNames
属性采用一个列表String
,该列表包含当前工厂中拦截器或顾问程序的bean名称。您可以在返回之前,之后使用顾问程序,拦截器,并抛出建议对象。顾问的排序很重要。
您可能想知道为什么列表不包含bean引用。这样做的原因是,如果将singleton属性ProxyFactoryBean 设置为false ,则必须能够返回独立的代理实例。如果任何顾问本身就是原型,则需要返回一个独立的实例,因此必须能够从工厂获得原型的实例。持有参考是不够的。 | |
---|---|
person
前面显示的bean定义可用于代替Person
实现,如下所示:
Person person = (Person) factory.getBean("person");
与普通Java对象一样,同一IoC上下文中的其他bean可以表达对它的强类型依赖。以下示例显示了如何执行此操作:
<bean id="personUser" class="com.mycompany.PersonUser"> <property name="person"><ref bean="person"/></property> </bean>
PersonUser
此示例中的类公开了类型的属性Person
。就其而言,可以透明地使用AOP代理来代替“真实”的人实现。但是,它的类将是一个动态代理类。可以将其转换为Advised
界面(稍后讨论)。
您可以使用匿名内部bean隐藏目标和代理之间的区别。只有ProxyFactoryBean
定义不同。该建议仅用于完整性。以下示例显示如何使用匿名内部bean:
<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom string property value"/> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <!-- Use inner bean, not local reference to target --> <property name="target"> <bean class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> </property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
使用匿名内部bean的优点是只有一个类型的对象Person
。如果我们想要阻止应用程序上下文的用户获取对未建议对象的引用或者需要避免Spring IoC自动装配的任何歧义,这将非常有用。可以说,有一个优点是ProxyFactoryBean
定义是独立的。但是,有时能够从工厂获得未建议的目标实际上可能是一个优势(例如,在某些测试场景中)。
6.4.5。代理类
如果您需要代理一个类而不是一个或多个接口,该怎么办?
想象一下,在我们之前的例子中,没有Person
接口。我们需要建议一个Person
没有实现任何业务接口的类。在这种情况下,您可以将Spring配置为使用CGLIB代理而不是动态代理。为此,请将前面显示的proxyTargetClass
属性设置 ProxyFactoryBean
为true
。虽然最好是编程到接口而不是类,但在使用遗留代码时,建议不实现接口的类的能力会很有用。(一般来说,Spring不是规定性的。虽然它可以很容易地应用好的实践,但它避免强制使用特定的方法。)
如果您愿意,即使您有接口,也可以在任何情况下强制使用CGLIB。
CGLIB代理通过在运行时生成目标类的子类来工作。Spring配置此生成的子类以将方法调用委托给原始目标。子类用于实现Decorator模式,在通知中编织。
CGLIB代理通常应对用户透明。但是,有一些问题需要考虑:
-
Final
方法无法建议,因为它们无法被覆盖。 -
无需将CGLIB添加到类路径中。从Spring 3.2开始,CGLIB被重新打包并包含在spring-core JAR中。换句话说,基于CGLIB的AOP“开箱即用”,JDK动态代理也是如此。
CGLIB代理和动态代理之间几乎没有性能差异。从Spring 1.0开始,动态代理略快一些。但是,这可能会在未来发生变化。在这种情况下,绩效不应该是决定性的考虑因素。
6.4.6。使用“全球”顾问
通过在拦截器名称后附加星号,将所有与星号前面的部分匹配的bean名称的顾问程序添加到顾问程序链中。如果您需要添加一组标准的“全局”顾问,这可以派上用场。以下示例定义了两个全局顾问程序:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>global*</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
6.5。简明的代理定义
特别是在定义事务代理时,最终可能会有许多类似的代理定义。使用父bean和子bean定义以及内部bean定义可以产生更清晰,更简洁的代理定义。
首先,我们为代理创建父,模板,bean定义,如下所示:
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
这从未实例化,因此它实际上可能是不完整的。然后,需要创建的每个代理都是子bean定义,它将代理的目标包装为内部bean定义,因为目标永远不会单独使用。以下示例显示了这样的子bean:
<bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean>
您可以覆盖父模板中的属性。在以下示例中,我们覆盖了事务传播设置:
<bean id="mySpecialService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MySpecialServiceImpl"> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="store*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
请注意,在父bean示例中,我们通过将abstract
属性设置为true
,将父bean定义显式标记为抽象, 如前所述,以便实际上不会实例化它。默认情况下,应用程序上下文(但不是简单的bean工厂)预先实例化所有单例。因此,重要的是(至少对于单例bean),如果您有一个(父)bean定义,您打算仅将其用作模板,并且此定义指定了一个类,则必须确保将该abstract
属性设置为true
。否则,应用程序上下文实际上会尝试预先实例化它。
6.6。用编程方式创建AOP代理ProxyFactory
使用Spring以编程方式创建AOP代理很容易。这使您可以使用Spring AOP而不依赖于Spring IoC。
目标对象实现的接口将自动代理。以下清单显示了为目标对象创建代理,其中包含一个拦截器和一个顾问程序:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addAdvice(myMethodInterceptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
第一步是构造一个类型的对象 org.springframework.aop.framework.ProxyFactory
。您可以使用目标对象创建它,如前面的示例所示,或者指定要在备用构造函数中代理的接口。
您可以添加建议(使用拦截器作为一种特殊的建议),顾问或两者,并在生命周期中操纵它们ProxyFactory
。如果添加IntroductionInterceptionAroundAdvisor
,则可以使代理实现其他接口。
还有一些便利方法ProxyFactory
(继承自AdvisedSupport
)可以添加其他通知类型,例如before和throws通知。 AdvisedSupport
既是超ProxyFactory
和ProxyFactoryBean
。
在大多数应用程序中,将AOP代理创建与IoC框架集成是最佳实践。我们建议您通常使用AOP从Java代码外部化配置。 | |
---|---|
6.7。操纵建议的对象
但是,您创建AOP代理,您可以使用该org.springframework.aop.framework.Advised
界面操作它们 。无论它实现哪个其他接口,任何AOP代理都可以转换为此接口。该界面包括以下方法:
Advisor[] getAdvisors(); void addAdvice(Advice advice) throws AopConfigException; void addAdvice(int pos, Advice advice) throws AopConfigException; void addAdvisor(Advisor advisor) throws AopConfigException; void addAdvisor(int pos, Advisor advisor) throws AopConfigException; int indexOf(Advisor advisor); boolean removeAdvisor(Advisor advisor) throws AopConfigException; void removeAdvisor(int index) throws AopConfigException; boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; boolean isFrozen();
该getAdvisors()
方法Advisor
为已添加到工厂的每个顾问程序,拦截器或其他建议类型返回一个。如果添加了an Advisor
,则此索引处返回的顾问程序是您添加的对象。如果您添加了拦截器或其他建议类型,Spring会将其包含在一个始终返回的切入点的顾问程序中true
。因此,如果添加了a MethodInterceptor
,则为此索引返回的顾问DefaultPointcutAdvisor
程序将返回MethodInterceptor
一个与所有类和方法匹配的切入点。
这些addAdvisor()
方法可用于添加任何方法Advisor
。通常,持有切入点和建议的顾问程序是通用的DefaultPointcutAdvisor
,您可以将其用于任何建议或切入点(但不适用于介绍)。
默认情况下,即使创建了代理,也可以添加或删除顾问程序或拦截器。唯一的限制是不可能添加或删除介绍顾问,因为工厂的现有代理不显示接口更改。(您可以从工厂获取新代理以避免此问题。)
以下示例显示将AOP代理转换为Advised
接口并检查和操作其建议:
Advised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); int oldAdvisorCount = advisors.length; System.out.println(oldAdvisorCount + " advisors"); // Add an advice like an interceptor without a pointcut // Will match all proxied methods // Can use for interceptors, before, after returning or throws advice advised.addAdvice(new DebugInterceptor()); // Add selective advice using a pointcut advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
尽管存在合法的使用案例,但是否有必要修改生产中的业务对象的建议是值得怀疑的(没有双关语意)。但是,它在开发中非常有用(例如,在测试中)。我们有时发现能够以拦截器或其他建议的形式添加测试代码,进入我们想要测试的方法调用是非常有用的。(例如,在为回滚标记事务之前,建议可以进入为该方法创建的事务内部,可能运行SQL以检查数据库是否已正确更新。) | |
---|---|
根据您创建代理的方式,通常可以设置frozen
标志。在这种情况下,该Advised
isFrozen()
方法返回true
,并且任何通过添加或删除修改建议的尝试都会导致AopConfigException
。在某些情况下,冻结建议对象状态的能力很有用(例如,防止调用代码删除安全拦截器)。如果已知不需要运行时建议修改,它也可以在Spring 1.1中使用以允许积极优化。
6.8。使用“自动代理”功能
到目前为止,我们已经考虑通过使用ProxyFactoryBean
或类似的工厂bean 来显式创建AOP代理。
Spring还允许我们使用“自动代理”bean定义,它可以自动代理选定的bean定义。这是基于Spring的“bean post processor”基础结构构建的,它可以在容器加载时修改任何bean定义。
在此模型中,您在XML bean定义文件中设置了一些特殊的bean定义来配置自动代理基础结构。这使您可以声明符合自动代理条件的目标。你不需要使用ProxyFactoryBean
。
有两种方法可以做到这一点:
-
通过使用引用当前上下文中的特定bean的自动代理创建器。
-
自动代理创建的一个特例值得单独考虑:由源级元数据属性驱动的自动代理创建。
6.8.1。自动代理Bean定义
本节介绍org.springframework.aop.framework.autoproxy
程序包提供的自动代理创建 程序。
BeanNameAutoProxyCreator
BeanNameAutoProxyCreator
该类是一个BeanPostProcessor
自动为名称与文字值或通配符匹配的bean创建AOP代理的类。以下示例显示了如何创建BeanNameAutoProxyCreator
bean:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="jdk*,onlyJdk"/> <property name="interceptorNames"> <list> <value>myInterceptor</value> </list> </property> </bean>
与此同时ProxyFactoryBean
,有一个interceptorNames
属性而不是拦截器列表,以允许原型顾问的正确行为。命名为“拦截器”可以是顾问或任何建议类型。
与通常的自动代理一样,使用的主要目的BeanNameAutoProxyCreator
是将相同的配置一致地应用于多个对象,并且配置量最小。将声明性事务应用于多个对象是一种流行的选择。
名称匹配的Bean定义(例如jdkMyBean
,onlyJdk
在前面的示例中)是具有目标类的普通旧bean定义。AOP代理由BeanNameAutoProxyCreator
。自动创建。相同的建议适用于所有匹配的bean。请注意,如果使用顾问程序(而不是前面示例中的拦截器),则切入点可能会以不同方式应用于不同的bean。
DefaultAdvisorAutoProxyCreator
一个更通用,功能更强大的自动代理创建者 DefaultAdvisorAutoProxyCreator
。这会在当前上下文中自动应用符合条件的顾问程序,而无需在auto-proxy advisor的bean定义中包含特定的bean名称。它提供了一致配置和避免重复的相同优点BeanNameAutoProxyCreator
。
使用此机制涉及:
-
指定
DefaultAdvisorAutoProxyCreator
bean定义。 -
在相同或相关的上下文中指定任意数量的顾问程序。请注意,这些必须是顾问,而不是拦截器或其他建议。这是必要的,因为必须有一个切入点来评估,以检查每个建议对候选bean定义的合格性。
该DefaultAdvisorAutoProxyCreator
自动评估包括在每个advisor中的切入点,看看有什么(如果有的话)的建议,应该适用于每个业务对象(比如businessObject1
和businessObject2
在本例中)。
这意味着可以自动将任意数量的顾问程序应用于每个业务对象。如果任何顾问程序中的切入点与业务对象中的任何方法都不匹配,则不会代理该对象。随着为新业务对象添加bean定义,必要时会自动代理它们。
自动代理通常具有使调用者或依赖者无法获得未建议的对象的优点。调用getBean("businessObject1")
此方法 ApplicationContext
将返回AOP代理,而不是目标业务对象。(前面所示的“内豆”成语也提供了这种好处。)
以下示例创建一个DefaultAdvisorAutoProxyCreator
bean以及本节中讨论的其他元素:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> <bean id="businessObject1" class="com.mycompany.BusinessObject1"> <!-- Properties omitted --> </bean> <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
DefaultAdvisorAutoProxyCreator
如果要将相同的建议一致地应用于许多业务对象,则非常有用。基础结构定义到位后,您可以添加新的业务对象,而不包括特定的代理配置。您还可以轻松地删除其他方面(例如,跟踪或性能监视方面),只需对配置进行最小的更改。
该DefaultAdvisorAutoProxyCreator
支持过滤(通过使用一种命名约定,使得只有特定的顾问进行评估,其允许使用多个不同配置,在同一个工厂AdvisorAutoProxyCreators)和订货。org.springframework.core.Ordered
如果这是一个问题,顾问可以实施界面以确保正确的订购。在TransactionAttributeSourceAdvisor
前述实施例中使用具有可配置的顺序值。默认设置是无序的。
6.9。运用TargetSource
实现
Spring提供了a的概念TargetSource
,在org.springframework.aop.TargetSource
界面中表达 。此接口负责返回实现连接点的“目标对象”。在TargetSource
每一个AOP代理处理一个方法调用时实现请求一个目标实例。
使用Spring AOP的开发人员通常不需要直接使用TargetSource
实现,但这提供了支持池,热插拔和其他复杂目标的强大方法。例如,TargetSource
通过使用池来管理实例,池可以为每个调用返回不同的目标实例。
如果未指定a TargetSource
,则使用默认实现来包装本地对象。每次调用都会返回相同的目标(正如您所期望的那样)。
本节的其余部分描述了Spring提供的标准目标源以及如何使用它们。
使用自定义目标源时,目标通常需要是原型而不是单例bean定义。这允许Spring在需要时创建新的目标实例。 | |
---|---|
6.9.1。热插拔目标源
的org.springframework.aop.target.HotSwappableTargetSource
存在让一个AOP代理的目标进行切换,同时让来电者保持自己对它的引用。
更改目标源的目标会立即生效。这 HotSwappableTargetSource
是线程安全的。
您可以使用swap()
HotSwappableTargetSource上的方法更改目标,如以下示例所示:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget);
以下示例显示了所需的XML定义:
<bean id="initialTarget" class="mycompany.OldTarget"/> <bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource"> <constructor-arg ref="initialTarget"/> </bean> <bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="swapper"/> </bean>
前面的swap()
调用更改了可交换bean的目标。持有对该bean的引用的客户端不知道该更改但立即开始命中新目标。
虽然这个例子没有添加任何建议(没有必要添加建议来使用a TargetSource
),但任何建议TargetSource
都可以与任意建议一起使用。
6.9.2。汇集目标来源
使用池化目标源为无状态会话EJB提供了类似的编程模型,其中维护了相同实例的池,方法调用将释放池中的空闲对象。
Spring池和SLSB池之间的一个重要区别是Spring池可以应用于任何POJO。与Spring一样,此服务可以以非侵入方式应用。
Spring为Commons Pool 2.2提供支持,它提供了一个相当有效的池实现。您需要commons-pool
应用程序类路径上的Jar才能使用此功能。您还可以子类化 org.springframework.aop.target.AbstractPoolingTargetSource
以支持任何其他池API。
Commons Pool 1.5+也受支持,但自Spring Framework 4.2起不推荐使用。 | |
---|---|
以下清单显示了一个示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype"> ... properties omitted </bean> <bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> <property name="maxSize" value="25"/> </bean> <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="poolTargetSource"/> <property name="interceptorNames" value="myInterceptor"/> </bean>
请注意,目标对象(businessObjectTarget
在前面的示例中)必须是原型。这使PoolingTargetSource
实现可以创建目标的新实例,以根据需要增大池。有关其属性的信息,请参阅您希望用于的javadoc AbstractPoolingTargetSource
和具体子类。maxSize
是最基本的,并始终保证在场。
在这种情况下,myInterceptor
是需要在同一IoC上下文中定义的拦截器的名称。但是,您无需指定拦截器来使用池。如果您只想要汇集而没有其他建议,请不要设置该 interceptorNames
属性。
您可以将Spring配置为能够将任何池化对象转换为 org.springframework.aop.target.PoolingConfig
接口,从而通过简介公开有关池的配置和当前大小的信息。您需要定义类似于以下内容的顾问程序:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="poolTargetSource"/> <property name="targetMethod" value="getPoolingConfigMixin"/> </bean>
这个顾问是通过在AbstractPoolingTargetSource
类上调用一个方便的方法获得的 ,因此使用了MethodInvokingFactoryBean
。此顾问程序的名称(poolConfigAdvisor
,此处)必须位于ProxyFactoryBean
公开池化对象的拦截器名称列表中。
演员阵容定义如下:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize());
通常不需要池化无状态服务对象。我们不认为它应该是默认选择,因为大多数无状态对象自然是线程安全的,并且如果缓存资源,实例池是有问题的。 | |
---|---|
通过使用自动代理可以实现更简单的池化。您可以设置TargetSource
任何自动代理创建者使用的实现。
6.9.3。原型目标来源
设置“原型”目标源类似于设置池TargetSource
。在这种情况下,将在每次方法调用时创建目标的新实例。虽然在现代JVM中创建新对象的成本并不高,但连接新对象(满足其IoC依赖性)的成本可能更高。因此,如果没有充分的理由,就不应该使用这种方法。
为此,您可以修改poolTargetSource
前面显示的定义,如下所示(为清晰起见,我们还更改了名称):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean>
唯一的属性是目标bean的名称。在TargetSource
实现中使用继承 来确保一致的命名。与池化目标源一样,目标bean必须是原型bean定义。
6.9.4。ThreadLocal
目标来源
ThreadLocal
如果需要为每个传入请求创建一个对象(每个线程),目标源很有用。a的概念ThreadLocal
提供了一个JDK范围的工具,可以在线程旁边透明地存储资源。设置a ThreadLocalTargetSource
与其他类型的目标源所解释的几乎相同,如下例所示:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> </bean>
ThreadLocal 在多线程和多类加载器环境中错误地使用实例时,实例会出现严重问题(可能导致内存泄漏)。你应该总是考虑将threadlocal包装在其他类中,而不是直接使用它ThreadLocal 自己(在包装类中除外)。此外,您应该始终记住正确设置和取消设置(后者只需要调用 ThreadLocal.set(null) )线程本地的资源。在任何情况下都应该进行取消,因为不取消它可能会导致有问题的行为。Spring的 ThreadLocal 支持为您完成此操作,应始终考虑使用 ThreadLocal 没有其他正确处理代码的实例。 | |
---|---|
6.10。定义新的建议类型
Spring AOP旨在可扩展。虽然拦截实现策略目前在内部使用,但除了围绕建议的拦截,之前,抛出建议以及返回建议之后,还可以支持任意建议类型。
该org.springframework.aop.framework.adapter
软件包是一个SPI软件包,可以在不更改核心框架的情况下添加对新自定义建议类型的支持。自定义Advice
类型的唯一约束是它必须实现 org.aopalliance.aop.Advice
标记接口。
有关org.springframework.aop.framework.adapter
详细信息,请参阅javadoc。
7.无安全性
尽管Java不允许您使用其类型系统表示null安全性,但Spring Framework现在在org.springframework.lang
包中提供以下注释,以便您声明API和字段的可空性:
-
@NonNull
:注释表示不能指定特定参数,返回值或字段null
(参数和返回值不需要where@NonNullApi
和@NonNullFields
apply)。 -
@Nullable
:注释,指示特定参数,返回值或字段可以null
。 -
@NonNullApi
:包级别的注释,声明非null作为参数和返回值的默认行为。 -
@NonNullFields
:包级别的注释,声明非null作为字段的默认行为。
Spring Framework利用这些注释,但它们也可以在任何基于Spring的Java项目中使用,以声明空安全API和可选的空安全字段。尚未支持泛型类型参数,varargs和数组元素可空性,但应在即将发布的版本中,请参阅SPR-15942以获取最新信息。预计可以在Spring Framework版本(包括次要版本)之间对可空性声明进行微调。方法体内使用的类型的可为空性超出了此功能的范围。
Reactor或Spring Data等库提供使用此功能的空安全API。 | |
---|---|
7.1。用例
除了提供Spring Framework API可空性的显式声明之外,IDE(例如IDEA或Eclipse)可以使用这些注释来提供与null安全相关的有用警告,以避免NullPointerException
在运行时。
它们还用于在Kotlin项目中使Spring API为null安全,因为Kotlin本身支持null安全性。有关更多详细信息,请参阅Kotlin支持文档。
7.2。JSR 305元注释
Spring注释是使用JSR 305注释进行元 注释的(一种休眠但广泛传播的JSR)。JSR 305元注释允许像IDEA或Kotlin这样的工具供应商以通用方式提供空安全支持,而无需对Spring注释进行硬编码支持。
没有必要也不建议在项目类路径中添加JSR 305依赖项以利用Spring null-safe API。只有项目,如使用空安全注解在他们的代码库中应增加基于Spring的库 com.google.code.findbugs:jsr305:3.0.2
与compileOnly
摇篮配置或Maven provided
范围,以避免编译警告。
8.数据缓冲区和编解码器
Java NIO提供ByteBuffer
但许多库在顶层构建自己的字节缓冲区API,尤其是对于重用缓冲区和/或使用直接缓冲区有利于性能的网络操作。例如,Netty具有ByteBuf
层次结构,Undertow使用XNIO,Jetty使用池化字节缓冲区以及要释放的回调,依此类推。该spring-core
模块提供了一组抽象以使用各种字节缓冲区API,如下所示:
-
DataBufferFactory
抽象创建数据缓冲区。 -
DataBuffer
表示可以合并的字节缓冲区 。 -
DataBufferUtils
提供数据缓冲的实用方法。
8.1。 DataBufferFactory
DataBufferFactory
用于以两种方式之一创建数据缓冲区:
-
分配一个新的数据缓冲区,可选择预先指定容量(如果已知),即使
DataBuffer
可以根据需要增长和缩小实现,这也会更有效。 -
包装现有的
byte[]
或者java.nio.ByteBuffer
用DataBuffer
实现来装饰给定的数据,并且不涉及分配。
请注意,WebFlux应用程序不DataBufferFactory
直接创建,而是通过客户端ServerHttpResponse
或ClientHttpRequest
客户端访问它。工厂的类型取决于底层客户端或服务器,例如NettyDataBufferFactory
Reactor Netty,DefaultDataBufferFactory
以及其他客户端或服务器 。
8.2。 DataBuffer
该DataBuffer
接口提供了同样的动作java.nio.ByteBuffer
,但也带来了一些额外的好处其中一些由Netty的启发ByteBuf
。以下是部分福利清单:
-
使用独立位置进行读写,即不需要
flip()
在读写之间切换。 -
按需扩大产能
java.lang.StringBuilder
。 -
汇集缓冲区和引用计数通过
PooledDataBuffer
。 -
查看缓冲区
java.nio.ByteBuffer
,InputStream
或OutputStream
。 -
确定给定字节的索引或最后一个索引。
8.3。 PooledDataBuffer
正如Javadoc for ByteBuffer中所解释的 ,字节缓冲区可以是直接缓冲区,也可以是非直接缓冲区。直接缓冲区可以驻留在Java堆之外,这样就无需复制本机I / O操作。这使得直接缓冲区对于通过套接字接收和发送数据特别有用,但是创建和释放它们也更加昂贵,这导致了池化缓冲区的想法。
PooledDataBuffer
是一个扩展,DataBuffer
它有助于引用计数,这对字节缓冲池是必不可少的。它是如何工作的?当a PooledDataBuffer
被分配时,引用计数为1.调用retain()
增加计数,同时调用release()
减少它。只要计数大于0,就保证缓冲区不被释放。当计数减少到0时,可以释放池化缓冲区,这实际上可能意味着缓冲区的保留内存返回到内存池。
请注意PooledDataBuffer
,在大多数情况下,不是直接操作,而是最好在DataBufferUtils
应用版本中使用便捷方法,或者DataBuffer
仅在它是实例时才保留 PooledDataBuffer
。
8.4。 DataBufferUtils
DataBufferUtils
提供了许多实用方法来操作数据缓冲区:
-
如果底层字节缓冲区API支持,则将数据缓冲区流加入单个缓冲区,可能只有零拷贝,例如通过复合缓冲区。
-
打开
InputStream
或NIOChannel
成Flux<DataBuffer>
,反之亦然Publisher<DataBuffer>
进入OutputStream
或NIOChannel
。 -
释放或保留
DataBuffer
缓冲区的实例的方法PooledDataBuffer
。 -
从字节流中跳过或取出,直到特定的字节数。
8.5。编解码器
该org.springframework.core.codec
软件包提供以下策略接口:
-
Encoder
编码Publisher<T>
成数据缓冲区流。 -
Decoder
解码Publisher<DataBuffer>
成更高级别的对象流。
该spring-core
模块提供byte[]
,ByteBuffer
,DataBuffer
,Resource
,和 String
编码器和解码器实现。该spring-web
模块增加了Jackson JSON,Jackson Smile,JAXB2,Protocol Buffers和其他编码器和解码器。请参阅 WebFlux部分中的编解码器。
8.6。运用DataBuffer
使用数据缓冲区时,必须特别注意确保缓冲区被释放,因为它们可能被合并。我们将使用编解码器来说明它是如何工作的,但概念更普遍适用。让我们看看内部编解码器必须在内部管理数据缓冲区。
A Decoder
是在创建更高级别对象之前读取输入数据缓冲区的最后一个,因此必须按如下方式释放它们:
-
如果
Decoder
只是简单地读取每个输入缓冲区并准备立即释放它,它可以通过DataBufferUtils.release(dataBuffer)
。 -
如果
Decoder
是使用Flux
或Mono
运营商,如flatMap
,reduce
,并在内部其他人预取和高速缓存的数据项,或者是使用运算符,如filter
,skip
,和其他人离开了项目,那么doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
必须添加到组合物中链以确保这些缓冲器之前被释放丢弃,也可能因此导致错误或取消信号。 -
如果
Decoder
以任何其他方式保持一个或多个数据缓冲区,则必须确保在完全读取时释放它们,或者在读取和释放高速缓存数据缓冲区之前发生错误或取消信号。
请注意,它DataBufferUtils#join
提供了一种将数据缓冲区流聚合到单个数据缓冲区的安全有效方法。同样skipUntilByteCount
,takeUntilByteCount
也是解码器使用的其他安全方法。
一个Encoder
别人必须读取(和释放)分配数据缓冲区。所以Encoder
没什么可做的。但是,Encoder
如果在使用数据填充缓冲区时发生序列化错误,则必须注意释放数据缓冲区。例如:
DataBuffer buffer = factory.allocateBuffer(); boolean release = true; try { // serialize and populate buffer.. release = false; } finally { if (release) { DataBufferUtils.release(buffer); } } return buffer;
a的使用者Encoder
负责释放它接收的数据缓冲区。在WebFlux应用程序中,它的输出Encoder
用于写入HTTP服务器响应或客户端HTTP请求,在这种情况下,释放数据缓冲区是代码写入服务器响应或客户端请求的责任。 。
请注意,在Netty上运行时,可以使用调试选项来 排除缓冲区泄漏。
9.附录
9.1。XML模式
附录的这一部分列出了与核心容器相关的XML模式。
9.1.1。该util
模式
顾名思义,util
标签处理常见的实用程序配置问题,例如配置集合,引用常量等。要在util
模式中使用标记,您需要在Spring XML配置文件的顶部包含以下前导码(片段中的文本引用正确的模式,以便util
命名空间中的标记可供您使用):
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- bean definitions here --> </beans>
运用 <util:constant/>
考虑以下bean定义:
<bean id="..." class="..."> <property name="isolation"> <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" /> </property> </bean>
前面的配置使用Spring FactoryBean
实现( FieldRetrievingFactoryBean
)将isolation
bean 的属性值设置为java.sql.Connection.TRANSACTION_SERIALIZABLE
常量的值。这一切都很好,但它很冗长,并且(不必要地)将Spring的内部管道暴露给最终用户。
以下基于XML Schema的版本更简洁,清楚地表达了开发人员的意图(“注入此常量值”),并且它读得更好:
<bean id="..." class="..."> <property name="isolation"> <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/> </property> </bean>
从字段值设置Bean属性或构造函数参数
FieldRetrievingFactoryBean
是一个FactoryBean
检索static
或非静态字段值的。它通常用于检索public
static
final
常量,然后可以使用它来为另一个bean设置属性值或构造函数参数。
以下示例static
通过使用staticField
属性显示字段的公开方式 :
<bean id="myField" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/> </bean>
还有一个便利用法表单,其中static
字段被指定为bean名称,如以下示例所示:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
这确实意味着bean不再有任何选择id
(因此引用它的任何其他bean也必须使用这个更长的名称),但这种形式定义非常简洁,非常方便用作内部bean因为id
不必为bean引用指定,如下例所示:
<bean id="..." class="..."> <property name="isolation"> <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" /> </property> </bean>
您还可以访问另一个bean的非静态(实例)字段,如FieldRetrievingFactoryBean
该类的API文档中所述 。
在Spring中,将枚举值作为属性或构造函数参数注入bean中很容易。你实际上不需要做任何事情或者对Spring内部有任何了解(或者甚至是关于类的类 FieldRetrievingFactoryBean
)。以下示例枚举显示了注入枚举值的容易程度:
package javax.persistence; public enum PersistenceContextType { TRANSACTION, EXTENDED }
现在考虑以下类型的setter PersistenceContextType
和相应的bean定义:
package example; public class Client { private PersistenceContextType persistenceContextType; public void setPersistenceContextType(PersistenceContextType type) { this.persistenceContextType = type; } }
<bean class="example.Client"> <property name="persistenceContextType" value="TRANSACTION"/> </bean>
运用 <util:property-path/>
请考虑以下示例:
<!-- target bean to be referenced by name --> <bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype"> <property name="age" value="10"/> <property name="spouse"> <bean class="org.springframework.beans.TestBean"> <property name="age" value="11"/> </bean> </property> </bean> <!-- results in 10, which is the value of property 'age' of bean 'testBean' --> <bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
前面的配置使用Spring FactoryBean
实现( PropertyPathFactoryBean
)来创建一个int
名为bean的bean(类型)testBean.age
,其值等于bean 的age
属性testBean
。
现在考虑以下示例,它添加了一个<util:property-path/>
元素:
<!-- target bean to be referenced by name --> <bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype"> <property name="age" value="10"/> <property name="spouse"> <bean class="org.springframework.beans.TestBean"> <property name="age" value="11"/> </bean> </property> </bean> <!-- results in 10, which is the value of property 'age' of bean 'testBean' --> <util:property-path id="name" path="testBean.age"/>
元素path
属性的值<property-path/>
遵循以下形式 beanName.beanProperty
。在这种情况下,它会获取age
名为bean 的属性testBean
。该age
属性的值是10
。
使用<util:property-path/>
设置bean属性或构造器参数
PropertyPathFactoryBean
是一个FactoryBean
评估给定目标对象的属性路径的。目标对象可以直接指定,也可以通过bean名称指定。然后,您可以在另一个bean定义中将此值用作属性值或构造函数参数。
以下示例按名称显示了针对另一个bean使用的路径:
// target bean to be referenced by name <bean id="person" class="org.springframework.beans.TestBean" scope="prototype"> <property name="age" value="10"/> <property name="spouse"> <bean class="org.springframework.beans.TestBean"> <property name="age" value="11"/> </bean> </property> </bean> // results in 11, which is the value of property 'spouse.age' of bean 'person' <bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> <property name="targetBeanName" value="person"/> <property name="propertyPath" value="spouse.age"/> </bean>
在以下示例中,将针对内部bean评估路径:
<!-- results in 12, which is the value of property 'age' of the inner bean --> <bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> <property name="targetObject"> <bean class="org.springframework.beans.TestBean"> <property name="age" value="12"/> </bean> </property> <property name="propertyPath" value="age"/> </bean>
还有一个快捷方式表单,其中bean名称是属性路径。以下示例显示了快捷方式窗体:
<!-- results in 10, which is the value of property 'age' of bean 'person' --> <bean id="person.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
这个表单确实意味着bean的名称没有选择。对它的任何引用也必须使用相同的id
,即路径。如果用作内部bean,则根本不需要引用它,如下例所示:
<bean id="..." class="..."> <property name="age"> <bean id="person.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/> </property> </bean>
您可以在实际定义中专门设置结果类型。对于大多数用例来说,这不是必需的,但它有时可能很有用。有关此功能的更多信息,请参阅javadoc。
运用 <util:properties/>
请考虑以下示例:
<!-- creates a java.util.Properties instance with values loaded from the supplied location --> <bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="location" value="classpath:com/foo/jdbc-production.properties"/> </bean>
前面的配置使用Spring FactoryBean
实现( PropertiesFactoryBean
)来实例化一个java.util.Properties
实例,其中包含从提供的Resource
位置加载的值。
以下示例使用util:properties
元素进行更简洁的表示:
<!-- creates a java.util.Properties instance with values loaded from the supplied location --> <util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
运用 <util:list/>
请考虑以下示例:
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' --> <bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </list> </property> </bean>
前面的配置使用Spring FactoryBean
实现( ListFactoryBean
)来创建一个java.util.List
实例,并使用从提供的值中初始化它sourceList
。
以下示例使用<util:list/>
元素进行更简洁的表示:
<!-- creates a java.util.List instance with the supplied values --> <util:list id="emails"> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </util:list>
您还可以List
使用元素list-class
上的属性显式控制实例化和填充的确切类型<util:list/>
。例如,如果我们确实需要java.util.LinkedList
实例化,我们可以使用以下配置:
<util:list id="emails" list-class="java.util.LinkedList"> <value>jackshaftoe@vagabond.org</value> <value>eliza@thinkingmanscrumpet.org</value> <value>vanhoek@pirate.org</value> <value>d'Arcachon@nemesis.org</value> </util:list>
如果未list-class
提供任何属性,容器将选择List
实现。
运用 <util:map/>
请考虑以下示例:
<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' --> <bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean"> <property name="sourceMap"> <map> <entry key="pechorin" value="pechorin@hero.org"/> <entry key="raskolnikov" value="raskolnikov@slums.org"/> <entry key="stavrogin" value="stavrogin@gov.org"/> <entry key="porfiry" value="porfiry@gov.org"/> </map> </property> </bean>
上面的配置使用Spring FactoryBean
实现( MapFactoryBean
)来创建一个java.util.Map
使用提供的键值对初始化的实例'sourceMap'
。
以下示例使用<util:map/>
元素进行更简洁的表示:
<!-- creates a java.util.Map instance with the supplied key-value pairs --> <util:map id="emails"> <entry key="pechorin" value="pechorin@hero.org"/> <entry key="raskolnikov" value="raskolnikov@slums.org"/> <entry key="stavrogin" value="stavrogin@gov.org"/> <entry key="porfiry" value="porfiry@gov.org"/> </util:map>
您还可以Map
使用元素'map-class'
上的属性显式控制实例化和填充的确切类型<util:map/>
。例如,如果我们确实需要java.util.TreeMap
实例化,我们可以使用以下配置:
<util:map id="emails" map-class="java.util.TreeMap"> <entry key="pechorin" value="pechorin@hero.org"/> <entry key="raskolnikov" value="raskolnikov@slums.org"/> <entry key="stavrogin" value="stavrogin@gov.org"/> <entry key="porfiry" value="porfiry@gov.org"/> </util:map>
如果未'map-class'
提供任何属性,容器将选择Map
实现。
运用 <util:set/>
请考虑以下示例:
<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' --> <bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean"> <property name="sourceSet"> <set> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </set> </property> </bean>
前面的配置使用Spring FactoryBean
实现( SetFactoryBean
)来创建一个java.util.Set
使用提供的值初始化的实例sourceSet
。
以下示例使用<util:set/>
元素进行更简洁的表示:
<!-- creates a java.util.Set instance with the supplied values --> <util:set id="emails"> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </util:set>
您还可以Set
使用元素set-class
上的属性显式控制实例化和填充的确切类型<util:set/>
。例如,如果我们确实需要java.util.TreeSet
实例化,我们可以使用以下配置:
<util:set id="emails" set-class="java.util.TreeSet"> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </util:set>
如果未set-class
提供任何属性,容器将选择Set
实现。
9.1.2。该aop
模式
这些aop
标签用于配置Spring中的所有AOP,包括Spring自己的基于代理的AOP框架和Spring与AspectJ AOP框架的集成。这些标签在题为“ 面向方面编程与Spring”的章节中进行了全面介绍。
为了完整性,要在aop
模式中使用标记,您需要在Spring XML配置文件的顶部有以下前导码(片段中的文本引用正确的模式,以便aop
命名空间中的标记可用于您):
<?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 definitions here --> </beans>
9.1.3。该context
模式
该context
标签处理ApplicationContext
,涉及到管道配置-也就是通常不是做了很多在Spring的“咕噜”的工作,如豆类是重要的终端用户,而是豆类BeanfactoryPostProcessors
。以下代码段引用了正确的架构,以便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"> <!-- bean definitions here --> </beans>
运用 <property-placeholder/>
此元素激活${…}
占位符的替换,占位符将根据指定的属性文件(作为Spring资源位置)进行解析。这个元素是一个PropertyPlaceholderConfigurer
为您设置的便利机制。如果您需要更多控制 PropertyPlaceholderConfigurer
,您可以自己明确定义一个。
运用 <annotation-config/>
此元素激活Spring基础结构以检测bean类中的注释:
-
JSR 250
@PostConstruct
,@PreDestroy
和@Resource
(如果有的话) -
JPA
@PersistenceContext
和@PersistenceUnit
(如果有的话)。
或者,您可以选择显式激活BeanPostProcessors
这些注释的个人。
此元素不会激活Spring @Transactional 注释的处理 。您可以将该 ``元素用于此目的。 | |
---|---|
运用 <component-scan/>
此元素在基于注释的容器配置中进行了详细说明。
运用 <load-time-weaver/>
在Spring Framework中使用AspectJ进行加载时编织中详细介绍了此元素。
运用 <spring-configured/>
在使用AspectJ依赖注入域对象与Spring中详细说明了这个元素。
运用 <mbean-export/>
配置基于MBean导出的注释中详细介绍了此元素。
9.1.4。豆类架构
最后但同样重要的是,我们在beans
架构中有元素。自框架诞生以来,这些元素一直存在于Spring中。beans
这里没有显示模式中各种元素的示例,因为它们在依赖关系和配置中得到了相当全面的介绍 (实际上,在整个章节中)。
请注意,您可以向<bean/>
XML定义添加零个或多个键值对。如果有的话,使用这些额外的元数据完成的工作完全取决于您自己的自定义逻辑(因此,如果您按照标题为XML Schema Authoring的附录中所述编写自己的自定义元素,通常只会使用它。
下面的示例显示<meta/>
了周围环境中的元素<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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="foo" class="x.y.Foo"> <meta key="cacheName" value="foo"/> <property name="name" value="Rick"/> </bean> </beans>
这是示例meta 元素 | |
---|---|
在前面的示例中,您可以假设有一些逻辑使用bean定义并设置一些使用提供的元数据的缓存基础结构。
9.2。XML Schema Authoring
从版本2.0开始,Spring提供了一种机制,用于为基本的Spring XML格式添加基于模式的扩展,以定义和配置bean。本节介绍如何编写自己的自定义XML bean定义解析器并将这些解析器集成到Spring IoC容器中。
为了便于创作使用模式感知XML编辑器的配置文件,Spring的可扩展XML配置机制基于XML Schema。如果您不熟悉Spring标准Spring发行版附带的当前XML配置扩展,则应首先阅读标题为[xsd-config]的附录。
要创建新的XML配置扩展:
-
编写 XML模式来描述您的自定义元素。
-
编写自定义
NamespaceHandler
实现。 -
代码一个或多个
BeanDefinitionParser
实现(这是完成实际工作的地方)。 -
使用Spring 注册新工件。
对于统一的示例,我们创建了一个XML扩展(一个自定义XML元素),它允许我们配置该类型的对象 SimpleDateFormat
(从java.text
包中)。完成后,我们将能够定义类型的bean定义,SimpleDateFormat
如下所示:
<myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
(我们在本附录后面会包含更详细的示例。第一个简单示例的目的是引导您完成制作自定义扩展的基本步骤。)
9.2.1。编写架构
创建用于Spring的IoC容器的XML配置扩展,首先要创建一个XML Schema来描述扩展。对于我们的示例,我们使用以下模式来配置SimpleDateFormat
对象:
<!-- myns.xsd (inside package org/springframework/samples/xml) --> <?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.mycompany.com/schema/myns" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.mycompany.com/schema/myns" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:element name="dateformat"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="lenient" type="xsd:boolean"/> <xsd:attribute name="pattern" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
指示的行包含所有可识别标记的扩展名基础(意味着它们具有id 我们可以用作容器中的bean标识符的属性)。我们可以使用此属性,因为我们导入了Spring提供的 beans 命名空间。 | |
---|---|
前面的模式允许我们SimpleDateFormat
使用<myns:dateformat/>
元素直接在XML应用程序上下文文件中配置对象,如以下示例所示:
<myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
请注意,在创建基础结构类之后,前面的XML代码段与以下XML代码段基本相同:
<bean id="dateFormat" class="java.text.SimpleDateFormat"> <constructor-arg value="yyyy-HH-dd HH:mm"/> <property name="lenient" value="true"/> </bean>
前两个片段中的第二个在容器中创建一个bean(由dateFormat
类型 名称标识SimpleDateFormat
),并设置了几个属性。
基于模式的创建配置格式的方法允许与具有模式感知XML编辑器的IDE紧密集成。通过使用正确创作的架构,您可以使用自动完成功能让用户在枚举中定义的几个配置选项之间进行选择。 | |
---|---|
9.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
类:
package org.springframework.samples.xml; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class MyNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); } }
你可能会注意到这个类中实际上并没有很多解析逻辑。实际上,该NamespaceHandlerSupport
课程具有内置的授权概念。它支持在BeanDefinitionParser
需要解析其名称空间中的元素时向其委派的任意数量的实例的注册。这种干净的关注分离让我们可以NamespaceHandler
处理在其命名空间中解析所有自定义元素的编排,同时委托BeanDefinitionParsers
执行XML解析的繁琐工作。这意味着每个BeanDefinitionParser
都只包含解析单个自定义元素的逻辑,我们可以在下一步中看到。
9.2.3。运用BeanDefinitionParser
BeanDefinitionParser
如果NamespaceHandler
遇到已映射到特定bean定义解析器的类型的XML元素(dateformat
在本例中),则使用A. 换句话说,BeanDefinitionParser
它负责解析模式中定义的一个不同的顶级XML元素。在解析器中,我们可以访问XML元素(以及它的子元素),以便我们可以解析自定义XML内容,如下例所示:
package org.springframework.samples.xml; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.util.StringUtils; import org.w3c.dom.Element; import java.text.SimpleDateFormat; public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { protected Class getBeanClass(Element element) { return SimpleDateFormat.class; } protected void doParse(Element element, BeanDefinitionBuilder bean) { // this will never be null since the schema explicitly requires that a value be supplied String pattern = element.getAttribute("pattern"); bean.addConstructorArg(pattern); // this however is an optional property String lenient = element.getAttribute("lenient"); if (StringUtils.hasText(lenient)) { bean.addPropertyValue("lenient", Boolean.valueOf(lenient)); } } }
我们使用Spring提供的方法AbstractSingleBeanDefinitionParser 来处理很多创建单个的基本grunt工作BeanDefinition 。 | |
---|---|
我们为AbstractSingleBeanDefinitionParser 超类提供单个BeanDefinition 代表的类型。 |
在这个简单的例子中,这就是我们需要做的一切。我们的单元的创建 BeanDefinition
由AbstractSingleBeanDefinitionParser
超类处理,bean定义的唯一标识符的提取和设置也是如此。
9.2.4。注册处理程序和架构
编码完成。剩下要做的就是让Spring XML解析基础架构了解我们的自定义元素。我们通过namespaceHandler
在两个专用属性文件中注册我们的自定义 和自定义XSD文件来实现此目的。这些属性文件都放在META-INF
应用程序的目录中,例如,可以与JAR文件中的二进制类一起分发。Spring XML解析基础结构通过使用这些特殊属性文件自动获取新扩展,其格式将在接下来的两节中详细介绍。
写作 META-INF/spring.handlers
调用的属性文件spring.handlers
包含XML Schema URI到命名空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:
HTTP \://www.mycompany.com/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(该:
字符是Java属性格式的有效分隔符,因此 :
URI中的字符需要使用反斜杠进行转义。)
键值对的第一部分(键)是与自定义命名空间扩展关联的URI,需要与targetNamespace
自定义XSD架构中指定的属性值完全匹配。
写'META-INF / spring.schemas'
调用的属性文件spring.schemas
包含XML模式位置的映射(与模式声明一起使用,在使用模式作为xsi:schemaLocation
属性的一部分的XML文件中)到类路径资源。需要此文件来防止Spring绝对必须使用EntityResolver
需要Internet访问权限的默认值来检索模式文件。如果在此属性文件中指定映射,Spring将在类路径中搜索模式(在本例 myns.xsd
中为org.springframework.samples.xml
包中)。以下代码段显示了我们需要为自定义架构添加的行:
HTTP \://www.mycompany.com/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(请记住,:
角色必须被转义。)
建议您将XSD文件(或多个文件)与类路径上的NamespaceHandler
和BeanDefinitionParser
类一起部署。
9.2.5。在Spring XML配置中使用自定义扩展
使用自己实现的自定义扩展与使用Spring提供的“自定义”扩展之一没有什么不同。以下示例使用<dateformat/>
Spring 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:myns="http://www.mycompany.com/schema/myns" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd"> <!-- as a top-level bean --> <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> <bean id="jobDetailTemplate" abstract="true"> <property name="dateFormat"> <!-- as an inner bean --> <myns:dateformat pattern="HH:mm MM-dd-yyyy"/> </property> </bean> </beans>
我们的自定义bean。 | |
---|---|
9.2.6。更详细的例子
本节介绍自定义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:foo="http://www.foo.com/schema/component" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.foo.com/schema/component http://www.foo.com/schema/component/component.xsd"> <foo:component id="bionic-family" name="Bionic-1"> <foo:component name="Mother-1"> <foo:component name="Karate-1"/> <foo:component name="Sport-1"/> </foo:component> <foo:component name="Rock-1"/> </foo:component> </beans>
上述配置将自定义扩展嵌套在彼此之内。该<foo:component/>
元素实际配置的Component
类是类(在下一个示例中显示)。注意Component
该类如何不公开components
属性的setter方法。这使得Component
通过使用setter注入为类配置bean定义变得困难(或者更不可能)。以下列表显示了Component
该类:
package com.foo; import java.util.ArrayList; import java.util.List; public class Component { private String name; private List<Component> components = new ArrayList<Component> (); // mmm, there is no setter method for the 'components' public void addComponent(Component component) { this.components.add(component); } public List<Component> getComponents() { return components; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
此问题的典型解决方案是创建一个FactoryBean
公开属性的setter属性的自定义components
。以下清单显示了这样的自定义FactoryBean
:
package com.foo; import org.springframework.beans.factory.FactoryBean; import java.util.List; public class ComponentFactoryBean implements FactoryBean<Component> { private Component parent; private List<Component> children; public void setParent(Component parent) { this.parent = parent; } public void setChildren(List<Component> children) { this.children = children; } public Component getObject() throws Exception { if (this.children != null && this.children.size() > 0) { for (Component child : children) { this.parent.addComponent(child); } } return this.parent; } public Class<Component> getObjectType() { return Component.class; } public boolean isSingleton() { return true; } }
这很好用,但它向最终用户公开了很多Spring管道。我们要做的是编写一个隐藏所有Spring管道的自定义扩展。如果我们坚持前面描述的步骤,我们首先创建XSD架构来定义自定义标签的结构,如下面的清单所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.foo.com/schema/component" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.foo.com/schema/component" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:element name="component"> <xsd:complexType> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element ref="component"/> </xsd:choice> <xsd:attribute name="id" type="xsd:ID"/> <xsd:attribute name="name" use="required" type="xsd:string"/> </xsd:complexType> </xsd:element> </xsd:schema>
再次按照前面描述的过程,我们再创建一个自定义NamespaceHandler
:
package com.foo; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class ComponentNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser()); } }
接下来是自定义BeanDefinitionParser
。请记住,我们正在创建 BeanDefinition
描述a ComponentFactoryBean
。以下清单显示了我们的自定义BeanDefinitionParser
:
package com.foo; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; import java.util.List; public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser { protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { return parseComponentElement(element); } private static AbstractBeanDefinition parseComponentElement(Element element) { BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class); factory.addPropertyValue("parent", parseComponent(element)); List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component"); if (childElements != null && childElements.size() > 0) { parseChildComponents(childElements, factory); } return factory.getBeanDefinition(); } private static BeanDefinition parseComponent(Element element) { BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class); component.addPropertyValue("name", element.getAttribute("name")); return component.getBeanDefinition(); } private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) { ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size()); for (Element element : childElements) { children.add(parseComponentElement(element)); } factory.addPropertyValue("children", children); } }
最后,需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件向Spring XML基础结构注册各种工件,如下所示:
#in'META-INF / spring.handlers' HTTP \://www.foo.com/schema/component=com.foo.ComponentNamespaceHandler
#in'META-INF / spring.schemas' HTTP \://www.foo.com/schema/component/component.xsd=com/foo/component.xsd
“普通”元素的自定义属性
编写自己的自定义解析器和相关工件并不难。但是,有时这不是正确的做法。考虑需要向现有bean定义添加元数据的场景。在这种情况下,您当然不希望编写自己的整个自定义扩展。相反,您只想在现有bean定义元素中添加其他属性。
通过另一个示例,假设您为服务对象(未知)访问集群JCache定义了bean定义 ,并且您希望确保在周围集群中急切地启动指定的JCache实例。以下清单显示了这样一个定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService" jcache:cache-name="checking.account"> <!-- other dependencies here... --> </bean>
然后我们可以BeanDefinition
在'jcache:cache-name'
解析属性时 创建另一个。BeanDefinition
然后,这为我们初始化命名的JCache。我们也可以修改现有BeanDefinition
的, 'checkingAccountService'
以便它依赖于这个新的JCache初始化BeanDefinition
。以下列表显示了我们的JCacheInitializer
:
package com.foo; public class JCacheInitializer { private String name; public JCacheInitializer(String name) { this.name = name; } public void initialize() { // lots of JCache API calls to initialize the named cache... } }
现在我们可以转到自定义扩展。首先,我们需要编写描述自定义属性的XSD架构,如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.foo.com/schema/jcache" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.foo.com/schema/jcache" elementFormDefault="qualified"> <xsd:attribute name="cache-name" type="xsd:string"/> </xsd:schema>
接下来,我们需要创建关联NamespaceHandler
,如下所示:
package com.foo; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class JCacheNamespaceHandler extends NamespaceHandlerSupport { public void init() { super.registerBeanDefinitionDecoratorForAttribute("cache-name", new JCacheInitializingBeanDefinitionDecorator()); } }
接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析XML属性,所以我们写一个BeanDefinitionDecorator
而不是一个BeanDefinitionParser
。以下列表显示了我们的BeanDefinitionDecorator
:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
ParserContext ctx) {
String initializerBeanName = registerJCacheInitializer(source, ctx);
createDependencyOnJCacheInitializer(holder, initializerBeanName);
return holder;
}
private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
String initializerBeanName) {
AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
String[] dependsOn = definition.getDependsOn();
if (dependsOn == null) {
dependsOn = new String[]{initializerBeanName};
} else {
List dependencies = new ArrayList(Arrays.asList(dependsOn));
dependencies.add(initializerBeanName);
dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
}
definition.setDependsOn(dependsOn);
}
private String registerJCacheInitializer(Node source, ParserContext ctx) {
String cacheName = ((Attr) source).getValue();
String beanName = cacheName + "-initializer";
if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
initializer.addConstructorArg(cacheName);
ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
}
return beanName;
}
}
最后,我们需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件来注册Spring XML基础结构中的各种工件,如下所示:
#in'META-INF / spring.handlers' HTTP \://www.foo.com/schema/jcache=com.foo.JCacheNamespaceHandler #in'META-INF / spring.schemas' HTTP \://www.foo.com/schema/jcache/jcache.xsd=com/foo/jcache.xsd
版本5.1.5.RELEASE 上次更新时间2019-02-13 05:31:19 UTC