首页 > 其他分享 >Spring 框架的核心技术(四)

Spring 框架的核心技术(四)

时间:2022-11-16 14:31:28浏览次数:70  
标签:String 框架 示例 核心技术 Spring AOP public 表达式

Spring 框架的核心技术(四)_spring

3.7. Java Bean 验证

Spring 框架提供了对Java Bean ValidationAPI 的支持。

3.7.1. Bean 验证概述

Bean 验证通过约束声明和 Java 应用程序的元数据。若要使用它,请使用 然后由运行时强制执行的声明性验证约束。有 内置约束,您还可以定义自己的自定义约束。

请考虑以下示例,该示例显示了具有两个属性的简单模型:​​PersonForm​

public class PersonForm {
private String name;
private int age;
}

Bean 验证允许您声明约束,如以下示例所示:

public class PersonForm {

@NotNull
@Size(max=64)
private String name;

@Min(0)
private int age;
}

然后,Bean 验证器根据声明的 约束。有关以下内容的一般信息,请参阅Bean 验证 接口。请参阅Hibernate 验证程序文档 具体约束。了解如何将 Bean 验证提供程序设置为 Spring 豆豆,继续阅读。

3.7.2. 配置 Bean 验证提供程序

Spring 提供对 Bean Validation API 的全面支持,包括引导 Bean 验证提供程序作为 Spring Bean。这使您可以在验证所在的任何地方注入aor。 在您的应用程序中需要。​​javax.validation.ValidatorFactory​​​​javax.validation.Validator​

您可以使用将默认验证器配置为 Spring Bean,如以下示例所示:​​LocalValidatorFactoryBean​

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}

前面示例中的基本配置触发 Bean 验证以初始化 使用其默认引导机制。Bean 验证提供程序,例如 Hibernate 验证器,应存在于类路径中并被自动检测。

注入验证器

​LocalValidatorFactoryBean​​实施两者,以及春天的。 您可以将对这些接口之一的引用注入到需要调用的 Bean 中 验证逻辑。​​javax.validation.ValidatorFactory​​​​javax.validation.Validator​​​​org.springframework.validation.Validator​

如果您更喜欢使用 Bean,可以注入对的引用 直接验证 API,如以下示例所示:​​javax.validation.Validator​

import javax.validation.Validator;

@Service
public class MyService {

@Autowired
private Validator validator;
}

你可以注入一个引用如果你的豆子 需要 Spring 验证 API,如以下示例所示:​​org.springframework.validation.Validator​

import org.springframework.validation.Validator;

@Service
public class MyService {

@Autowired
private Validator validator;
}
配置自定义约束

每个 Bean 验证约束由两部分组成:

  • 声明约束及其可配置属性的注释。@Constraint
  • 实现 约束的行为。javax.validation.ConstraintValidator

要将声明与实现相关联,eachannotation 引用相应的实现类。在运行时,在 在域模型中遇到约束注释。​​@Constraint​​​​ConstraintValidator​​​​ConstraintValidatorFactory​

默认情况下,配置 a 使用 Spring 创建实例。这使您的自定义像任何其他 Spring Bean 一样从依赖注入中受益。​​LocalValidatorFactoryBean​​​​SpringConstraintValidatorFactory​​​​ConstraintValidator​​​​ConstraintValidators​

下面的示例展示了一个自定义声明,后跟一个使用 Spring 进行依赖注入的关联实现:​​@Constraint​​​​ConstraintValidator​

@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;

// ...
}

如前面的示例所示,实现可以像任何其他 Spring Bean 一样具有其依赖项。​​ConstraintValidator​​​​@Autowired​

弹簧驱动方法验证

您可以集成 Bean 验证 1.1 支持的方法验证功能(并且,如 一个自定义扩展,也由Hibernate验证器4.3)通过abean定义进入Spring上下文:​​MethodValidationPostProcessor​

import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}

为了符合弹簧驱动方法验证的资格,需要注释所有目标类别 使用Spring'sannotation,它也可以选择声明验证 要使用的组。请参阅MethodValidationPostProcessor,了解 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息。​​@Validated​

其他配置选项

默认配置足以满足大多数人的需求 例。各种 Bean 验证有许多配置选项 构造,从消息插值到遍历解析。请参阅LocalValidatorFactoryBeanjavadoc 以获取有关这些选项的更多信息。​​LocalValidatorFactoryBean​

3.7.3. 配置​​DataBinder​

从 Spring 3 开始,您可以使用 a 配置实例。一次 配置后,您可以调用 Theby 调用。任何验证都会自动添加到活页夹中。​​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();

您还可以通过和配置多个实例。这在以下情况下很有用 将全局配置的 Bean 验证与 Springconfigure 相结合 本地在数据绑定程序实例上。请参阅Spring MVC 验证配置。​​DataBinder​​​​Validator​​​​dataBinder.addValidators​​​​dataBinder.replaceValidators​​​​Validator​

3.7.4. 弹簧 MVC 3 验证

请参阅Spring MVC章节中的验证。

4. 弹簧表达式语言

Spring 表达式语言(简称“SpEL”)是一种功能强大的表达式语言,它 支持在运行时查询和操作对象图。语言语法是 类似于统一 EL,但提供了额外的功能,最值得注意的是方法调用和 基本的字符串模板化功能。

虽然还有其他几种可用的Java表达式语言 - OGNL,MVEL和JBoss EL,仅举几例 — Spring 表达式语言的创建是为了提供 Spring 具有单一良好支持的表达式语言的社区,可以在所有语言中使用 春季产品组合中的产品。它的语言功能由 Spring产品组合中项目的要求,包括工具要求 以获取Spring Tools for Eclipse 中的代码完成支持。 也就是说,SpEL基于一个与技术无关的API,允许其他表达式语言。 如果需要,实施应集成。

而SpEL是春季表达评估的基础 投资组合,它不直接绑定到弹簧,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是一个 独立的表达语言。这需要创建一些引导 基础结构类,例如分析器。大多数 Spring 用户不需要处理 此基础结构,只能创作用于计算的表达式字符串。 这种典型用途的一个例子是将 SpEL 集成到创建 XML 或 基于注释的 Bean 定义,如用于定义 Bean 定义的表达式支持中所示。

本章介绍表达式语言、其 API 及其语言的功能 语法。在几个地方,和类被用作目标 表达式计算的对象。这些类声明和用于 填充它们列在本章末尾。​​Inventor​​​​Society​

表达式语言支持以下功能:

  • 文字表达式
  • 布尔运算符和关系运算符
  • 正则表达式
  • 类表达式
  • 访问属性、数组、列表和映射
  • 方法调用
  • 关系运算符
  • 分配
  • 调用构造函数
  • 豆类引用
  • 阵列结构
  • 内联列表
  • 内联地图
  • 三元运算符
  • 变量
  • 用户定义的函数
  • 馆藏投影
  • 产品系列选择
  • 模板化表达式

4.1. 评估

本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在语言参考中找到。

以下代码介绍了用于计算文本字符串表达式的 SpEL API。​​Hello World​

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

您最有可能使用的 SpEL 类和接口位于包及其子包中,例如。​​org.springframework.expression​​​​spel.support​

接口负责解析表达式字符串。在 在前面的示例中,表达式 String 是由周围的单 引号。接口负责评估先前定义的 表达式字符串。可以抛出的两个异常,并且,当调用和, 分别。​​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();

以下调用 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();

可以调用 String 的构造函数,而不是使用字符串文本,如下所示 示例显示:

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);

请注意泛型方法的使用:。 使用此方法无需将表达式的值强制转换为所需的 结果类型。如果无法将值转换为 类型或使用注册类型转换器转换的类型。​​public <T> T getValue(Class<T> desiredResultType)​​​​EvaluationException​​​​T​

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"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true

4.1.1. 理解​​EvaluationContext​

在计算要解析的表达式时使用接口 属性、方法或字段,以帮助执行类型转换。弹簧提供两个 实现。​​EvaluationContext​

  • ​SimpleEvaluationContext​​:公开基本 SpEL 语言功能的子集和 配置选项,适用于不需要全图范围的表达式类别 的 SpEL 语言语法,应该受到有意义的限制。示例包括 不限于数据绑定表达式和基于属性的筛选器。
  • ​StandardEvaluationContext​​:公开全套 SpEL 语言功能和 配置选项。您可以使用它来指定默认根对象并配置 每一项可用的与评价相关的战略。

​SimpleEvaluationContext​​旨在仅支持 SpEL 语言语法的一个子集。 它不包括 Java 类型引用、构造函数和 Bean 引用。它还需要 显式选择表达式中属性和方法的支持级别。 默认情况下,静态工厂方法仅允许对属性进行读取访问。 您还可以获取构建器来配置所需的确切支持级别,目标 以下一种或某种组合:​​create()​

  • 仅自定义(无反射)PropertyAccessor
  • 只读访问的数据绑定属性
  • 用于读取和写入的数据绑定属性
类型转换

默认情况下,SpEL 使用 Spring 核心中可用的转换服务 ().此转换服务来了 具有许多用于常见转换的内置转换器,但也完全可扩展,因此 您可以在类型之间添加自定义转化。此外,它是 泛型感知。这意味着,当您在 表达式,SpEL 尝试转换以保持任何对象的类型正确性 它遇到了。​​org.springframework.core.convert.ConversionService​

这在实践中意味着什么?假设赋值、使用、正在使用 以设置属性。属性的类型实际上是。斯佩尔 认识到列表的元素需要转换为之前 被放置在其中。以下示例演示如何执行此操作:​​setValue()​​​​List​​​​List<Boolean>​​​​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. 解析器配置

可以使用解析器配置来配置 SpEL 表达式解析器 对象 ()。配置情况 对象控制某些表达式组件的行为。例如,如果您 索引到数组或集合中,并且指定索引处的元素为 SpEL 可以自动创建元素。这在使用由 属性引用链。如果索引到数组或列表并指定索引 即超出数组或列表当前大小的末尾,SpEL 可以自动 增大数组或列表以容纳该索引。为了在 指定的索引,SpEL 将尝试使用元素类型的默认值创建元素 设置指定值之前的构造函数。如果元素类型没有 默认构造函数,将被添加到数组或列表中。如果没有内置 或知道如何设置值的自定义转换器,将保留在数组中或 在指定索引处列出。以下示例演示如何自动增长 该列表:​​org.springframework.expression.spel.SpelParserConfiguration​​​​null​​​​null​​​​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 包含一个基本的表达式编译器。表达式通常是 解释,这在评估过程中提供了很大的动态灵活性,但 不提供最佳性能。对于偶尔的表达式用法, 这很好,但是,当被其他组件(如弹簧集成)使用时, 性能可能非常重要,并且不需要真正的活力。

SpEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为并使用该类 类以实现更快的表达式计算。由于周围缺乏打字 表达式,编译器使用在解释计算期间收集的信息 执行编译时的表达式。例如,它不知道类型 属性引用纯粹来自表达式,但在第一次解释期间 评估,它发现它是什么。当然,基于这种派生的编译 如果各种表达式元素的类型,信息可能会在以后引起麻烦 随时间变化。因此,编译最适合于以下表达式: 类型信息不会在重复评估时更改。

请考虑以下基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问、某些属性取消引用、 和数值运算,性能提升可能非常明显。在示例中 微基准测试运行 50000 次迭代,使用 解释器,并且仅使用表达式的编译版本3ms。

编译器配置

默认情况下,编译器未打开,但您可以通过以下两个选项之一将其打开 不同的方式。您可以使用分析器配置过程将其打开 (前面讨论过)或使用 Spring 属性 当 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)。 编译的表达式在提供的 any 下创建的子类装入器中定义。 重要的是要确保,如果指定了类装入器,它可以看到 表达式计算过程。如果未指定类装入器,则使用缺省类装入器 (通常是表达式计算期间运行的线程的上下文类加载器)。

配置编译器的第二种方法是在 SpEL 嵌入到某些 其他组件,可能无法通过配置进行配置 对象。在这些情况下,可以通过JVM系统属性(或通过SpringProperties机制)将属性设置为枚举值之一(,,或)。​​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>

应用程序上下文中的所有 bean 都可以作为预定义变量使用,其 常见的豆名。这包括用于访问运行时环境的标准上下文 bean,例如 (类型) 和 and(类型)。​​environment​​​​org.springframework.core.env.Environment​​​​systemProperties​​​​systemEnvironment​​​​Map<String, Object>​

以下示例显示了对 bean 作为 SpEL 变量的访问:​​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 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 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;

public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}

// ...
}

4.3. 语言参考

本节介绍 Spring 表达式语言的工作原理。它涵盖以下内容 主题:

  • 文字表达式
  • 属性、数组、列表、映射和索引器
  • 内联列表
  • 内联地图
  • 阵列结构
  • 方法
  • 运营商
  • 类型
  • 构造 函数
  • 变量
  • 功能
  • 豆类参考资料
  • 三元运算符(如果-然后-否则)
  • 猫王操作员
  • 安全导航操作员

4.3.1. 文字表达式

支持的文字表达式类型包括字符串、数值(整数、实数、十六进制)、 布尔值和空值。字符串由单引号分隔。放置单引号本身 在字符串中,使用两个单引号字符。

下面的清单显示了文本的简单用法。通常,不使用它们 像这样孤立地,而是作为更复杂的表达的一部分——例如, 在逻辑比较运算符的一侧使用文本。

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);

映射的内容是通过在 括弧。在下面的示例中,因为 map 的键是字符串,我们可以指定 字符串:​​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 表达式语言支持以下类型的运算符:

  • 关系运算符
  • 逻辑运算符
  • 数学运算符
  • 赋值运算符
关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)通过使用标准运算符表示法来支持。这 以下清单显示了运算符的几个示例:

// 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);

除了标准的关系运算符外,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
赋值运算符

若要设置属性,请使用赋值运算符 ()。这通常在 呼叫 To,但也可以在 Call To 中完成。以下 列表显示了使用赋值运算符的两种方法:​​=​​​​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. 类型

您可以使用特殊运算符指定 ( 类型)。静态方法也使用此运算符调用。Theuse ato 查找类型,并且(可以替换)是在了解包的情况下构建的。这意味着对包中类型的引用不需要完全限定,但所有其他类型引用都必须是完全限定的。这 以下示例演示如何使用运算符:​​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​​​​java.lang​​​​Integer​​​​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 the 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​

变量始终被定义并引用当前评估对象 (针对其解析非限定引用)。变量始终 定义并引用根上下文对象。虽然可能会因 一个表达式被计算,总是引用根。以下示例 展示如何使用 theandvariables:​​#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. 函数

您可以通过注册可在 表达式字符串。该函数是通过 注册的。这 以下示例演示如何注册用户定义函数:​​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 名称前面加上 ansymbol。 以下示例演示如何执行此操作:​​&​

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 条件逻辑 表达式。下面的清单显示了一个最小示例:

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"

See the next section on the Elvis operator for an even shorter syntax for the ternary operator.

4.3.14. The Elvis Operator

猫王运算符是三元运算符语法的缩写,用于Groovy语言。 使用三元运算符语法,通常必须将变量重复两次,如 以下示例显示:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,您可以使用猫王运算符(以与猫王的发型相似而命名)。 下面的示例演示如何使用 Elvis 运算符:

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), 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

4.3.15. 安全导航操作员

安全导航运算符用于避免 aand 来自 时髦语言。通常,当您具有对对象的引用时,可能需要验证 在访问对象的方法或属性之前,它不为 null。为避免这种情况, 安全导航运算符返回 null 而不是引发异常。以下 示例演示如何使用安全导航运算符:​​NullPointerException​

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. 集合选择

选择是一项强大的表达式语言功能,可让您转换 通过从其条目中进行选择,将集合源到另一个集合中。

选择使用语法。它过滤集合和 返回包含原始元素子集的新集合。例如 选择让我们可以轻松获得塞尔维亚发明家的列表,如以下示例所示:​​.?[selectionExpression]​

List<Inventor> list = (List<Inventor>) parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext);

数组和任何实现或的东西都支持选择。对于列表或数组,将根据每个列表或数组评估选择标准 单个元素。根据地图,根据每个地图评估选择标准 条目(Java 类型的对象)。每个地图条目都具有 itsandaccess 作为属性,以便在选择中使用。​​java.lang.Iterable​​​​java.util.Map​​​​Map.Entry​​​​key​​​​value​

以下表达式返回一个新映射,该映射由 条目值小于 27 的原始地图:

爪哇岛

科特林

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

除了返回所有选定的元素外,您只能检索第一个或 最后一个元素。若要获取与所选内容匹配的第一个元素,语法是。若要获取最后一个匹配的选择,语法是。​​.^[selectionExpression]​​​​.$[selectionExpression]​

4.3.17. 集合投影

投影让集合驱动子表达式的计算,结果是 新系列。投影的语法是。例如 假设我们有一个发明家名单,但想要他们出生的城市名单。 实际上,我们希望评估发明人中每个条目的“placeOfBirth.city” 列表。以下示例使用投影来执行此操作:​​.![projectionExpression]​

// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");

数组和任何实现者的东西都支持投影。使用地图驱动投影时,投影表达式为 根据映射中的每个条目(表示为 Java)进行评估。结果 地图上的投影是一个列表,其中包含投影的评估 针对每个映射条目的表达式。​​java.lang.Iterable​​​​java.util.Map​​​​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. 示例中使用的类

本节列出了本章中的示例中使用的类。

发明家.爪哇

发明家.kt

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;
}
}

出生地.java

出生地.kt

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;
}
}

社会.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. 面向方面的编程与弹簧

面向方面编程(AOP)补充了面向对象编程(OOP) 提供了另一种思考程序结构的方式。模块化的关键单元 在OOP中是类,而在AOP中,模块化的单位是方面。方面 实现跨越问题(如事务管理)的模块化 多种类型和对象。(这种关切通常被称为“跨领域”关切。 在AOP文献中。

Spring 的关键组件之一是 AOP 框架。虽然春季国际奥委会 容器不依赖于AOP(这意味着如果您不需要,则不需要使用AOP ),AOP补充了Spring IoC,提供了一个非常有能力的中间件解决方案。

带AspectJ切入点的弹簧AOP

Spring 提供了简单而强大的方法来编写自定义方面,方法是使用基于模式的方法或@AspectJ注释样式。 这两种风格都提供了完全键入的建议和使用AspectJ切入点语言 同时仍然使用春季AOP进行编织。

本章讨论基于架构和@AspectJ的 AOP 支持。 下一章将讨论较低级别的 AOP 支持。

AOP在Spring Framework中用于:

  • 提供声明式企业服务。此类服务最重要的服务是声明式事务管理。
  • 让用户实现自定义方面,通过 AOP 补充他们对 OOP 的使用。

5.1. AOP 概念

让我们从定义一些核心的AOP概念和术语开始。这些术语不是 弹簧专用。不幸的是,AOP术语并不是特别直观。 但是,如果Spring使用自己的术语,那就更令人困惑了。

  • 方面:跨越多个类的关注点的模块化。 事务管理是企业Java中横切关注的一个很好的例子 应用。在Spring AOP中,通过使用常规类实现方面 (基于模式的方法)或用注释(@AspectJ样式)注释的常规类。@Aspect
  • 加入点:程序执行期间的点,例如执行 方法或异常的处理。在春季AOP中,连接点始终 表示方法执行。
  • 建议:某个方面在特定连接点执行的操作。不同类型的 建议包括“周围”、“之前”和“之后”建议。(建议类型讨论 后来。许多AOP框架,包括Spring,将建议建模为拦截器和 在连接点周围维护拦截器链。
  • 切入点:与连接点匹配的谓词。建议与 切入点表达式,并在与切入点匹配的任何连接点处运行(例如, 执行具有特定名称的方法)。连接点匹配的概念 by pointcut 表达式是 AOP 的核心,Spring 使用 AspectJ 切入点 默认情况下为表达式语言。
  • 简介:代表类型声明其他方法或字段。春天 AOP 允许您将新接口(和相应的实现)引入任何 建议对象。例如,您可以使用介绍来使 Bean 实现接口,以简化缓存。(简介称为 AspectJ 社区中的类型间声明。IsModified
  • 目标对象:由一个或多个方面建议的对象。也称为 “建议对象”。由于 Spring AOP 是使用运行时代理实现的,因此这 对象始终是代理对象。
  • AOP代理:由AOP框架创建的对象,以实现方面 合约(建议方法执行等)。在 Spring 框架中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。
  • 编织:将方面与其他应用程序类型或对象链接以创建 建议对象。这可以在编译时完成(使用 AspectJ 编译器,用于 示例)、加载时间或运行时。Spring AOP,像其他纯Java AOP框架一样, 在运行时执行编织。

Spring AOP 包括以下类型的建议:

  • 建议之前:在加入点之前运行但没有的建议 能够防止执行流继续到连接点(除非它抛出 一个例外)。
  • 返回建议后:在加入点完成后运行的建议 通常(例如,如果方法返回而不引发异常)。
  • 抛出建议后:如果方法通过抛出 例外。
  • 之后(最后)建议:无论通过何种方式运行的建议 加入点出口(正常或异常返回)。
  • 围绕建议:围绕连接点(如方法调用)的建议。 这是最有力的建议。围绕建议可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续到连接点或通过返回其来快捷方式建议的方法执行 自己的返回值或引发异常。

围绕建议是最普遍的建议。从春天开始,像AspectJ一样, 提供全方位的建议类型,我们建议您使用功能最弱的 可以实现所需行为的建议类型。例如,如果您只需要 使用方法的返回值更新缓存,最好实现 在返回建议后比周围的建议,尽管周围的建议可以完成 同样的事情。使用最具体的建议类型提供更简单的编程模型 出错的可能性更小。例如,您不需要调用该方法用于周围的建议,因此,您不能不调用它。​​proceed()​​​​JoinPoint​

所有建议参数都是静态类型的,以便您使用 适当的类型(例如,方法执行的返回值的类型)而不是 塔拉。​​Object​

与切入点匹配的连接点的概念是AOP的关键,它区分 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象的层次结构进行定位。例如,您可以应用 围绕建议,为一组跨越的方法提供声明式事务管理 多个对象(例如服务层中的所有业务操作)。

5.2. 春季AOP能力和目标

Spring AOP是用纯Java实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适用于 Servlet 容器或应用程序服务器。

Spring AOP 目前仅支持方法执行连接点(建议执行 春豆的方法)。未实现字段拦截,尽管支持 可以在不破坏核心 Spring AOP API 的情况下添加字段拦截。如果您需要 若要建议字段访问和更新连接点,请考虑使用诸如 AspectJ 之类的语言。

Spring AOP的AOP方法与大多数其他AOP框架不同。目标是 不提供最完整的AOP实现(尽管Spring AOP相当 有能力)。相反,其目的是在AOP实现和 Spring IoC,帮助解决企业应用中的常见问题。

因此,例如,Spring 框架的 AOP 功能通常用于 与 Spring IoC 容器结合使用。使用普通 Bean 配置方面 定义语法(尽管这允许强大的“自动代理”功能)。这是一个 与其他 AOP 实现的关键区别。你不能做某些事情 使用Spring AOP轻松或有效地,例如建议非常细粒度的对象(通常, 域对象)。在这种情况下,AspectJ是最佳选择。然而,我们的 经验是,Spring AOP为大多数问题提供了出色的解决方案 适用于 AOP 的企业 Java 应用程序。

Spring AOP从不努力与AspectJ竞争,以提供全面的AOP 溶液。我们相信基于代理的框架,如Spring AOP和成熟的框架。 像AspectJ这样的框架是有价值的,它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP在一致的基于Spring的应用程序中的所有用途 建筑。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关春季AOP API的讨论,请参阅下一章。

5.3. AOP 代理

Spring AOP 默认对 AOP 代理使用标准 JDK 动态代理。这 允许代理任何接口(或接口集)。

Spring AOP也可以使用CGLIB代理。这对于代理类而不是 接口。默认情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见的)情况下,可以强制使用 CGLIB,在这种情况下 需要建议未在接口上声明的方法或需要的地方 将代理对象作为具体类型传递给方法。

重要的是要掌握Spring AOP是基于代理的事实。请参阅了解 AOP 代理以彻底检查这到底是什么 实现细节实际上意味着。

5.4. @AspectJ支持

@AspectJ是指将方面声明为常规 Java 类的样式,该类使用 附注。@AspectJ风格是由AspectJ项目作为AspectJ 5版本的一部分引入的。春天 使用AspectJ提供的库解释与AspectJ 5相同的注释 用于切入点解析和匹配。不过,AOP 运行时仍然是纯粹的 Spring AOP,并且 不依赖于AspectJ编译器或Weaver。

5.4.1. 启用@AspectJ支持

要在 Spring 配置中使用@AspectJ方面,您需要启用 Spring 对 基于 @AspectJ 方面配置 Spring AOP 和基于 他们是否受到这些方面的建议。通过自动代理,我们的意思是,如果春天 确定 Bean 由一个或多个方面建议,它会自动生成 该 Bean 的代理,用于拦截方法调用并确保运行建议 根据需要。

可以使用 XML 或 Java 样式的配置启用@AspectJ支持。在任一 在这种情况下,您还需要确保 AspectJ 的库位于 应用程序的类路径(版本 1.8 或更高版本)。该库可在AspectJ发行版的目录中或Maven Central存储库中找到。​​aspectjweaver.jar​​​​lib​

使用 Java 配置启用@AspectJ支持

若要启用 Java @AspectJ支持,请添加注释,如以下示例所示:​​@Configuration​​​​@EnableAspectJAutoProxy​

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
通过 XML 配置启用@AspectJ支持

若要通过基于 XML 的配置启用@AspectJ支持,请使用 element,如以下示例所示:​​aop:aspectj-autoproxy​

<aop:aspectj-autoproxy/>

这假定您使用基于XML 架构的配置中所述的架构支持。 请参阅AOP 架构,了解如何 导入命名空间中的标签。​​aop​

5.4.2. 声明一个方面

启用@AspectJ支持后,在应用程序上下文中定义的任何 Bean 作为@AspectJ方面(具有注释)的类会自动 由 Spring 检测到并用于配置 Spring AOP。接下来的两个示例显示了 不太有用的方面所需的最小定义。​​@Aspect​

两个示例中的第一个显示了应用程序中的常规 Bean 定义 指向具有注释的 Bean 类的上下文:​​@Aspect​

<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​

5.4.3. 声明切入点

切入点确定连接兴趣点,从而使我们能够控制 当建议运行时。Spring AOP 仅支持春季的方法执行连接点 bean,所以你可以认为切入点与 Spring 上的方法执行相匹配 豆。切入点声明分为两部分:包含姓名的签名和任何 参数和切入点表达式,用于准确确定哪种方法 我们感兴趣的执行。在AOP的@AspectJ注释样式中,切入点 签名由常规方法定义提供,切入点表达式为 通过使用注释(用作切入点签名的方法)表示 必须具有返回类型)。​​@Pointcut​​​​void​

一个例子可能有助于区分切入点签名和切入点 表情清晰。下面的示例定义一个名为 匹配名为以下任何方法的执行:​​anyOldTransfer​​​​transfer​

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

构成注释值的切入点表达式是正则表达式 AspectJ 切入点表达。有关AspectJ的切入点语言的完整讨论,请参阅 方面J 编程指南(对于扩展,AspectJ 5 Developer's Notebook)或AspectJ上的一本书(如Eclipse AspectJ,作者:Colyer) 等人,或AspectJ in Action,作者:Ramnivas Laddad)。​​@Pointcut​

支持的切入点指示符

Spring AOP 支持以下 AspectJ 切点指示符 (PCD) 用于切中 表达 式:

  • ​execution​​:用于匹配方法执行连接点。这是主要的 使用弹簧 AOP 时要使用的切入点指示符。
  • ​within​​:限制匹配以某些类型中的连接点(执行 使用 Spring AOP 时在匹配类型中声明的方法)。
  • ​this​​:限制与连接点的匹配(使用 Spring 时执行方法 AOP),其中 Bean 引用(Spring AOP 代理)是给定类型的实例。
  • ​target​​:限制与连接点的匹配(使用时执行方法 Spring AOP),其中目标对象(被代理的应用程序对象)是一个实例 给定类型。
  • ​args​​:限制与连接点的匹配(使用 Spring 时执行方法 AOP),其中参数是给定类型的实例。
  • ​@target​​:限制与连接点的匹配(使用时执行方法 Spring AOP),其中执行对象的类具有给定类型的注释。
  • ​@args​​:限制与连接点的匹配(使用 Spring 时执行方法 AOP),其中传递的实际参数的运行时类型具有 给定类型。
  • ​@within​​:限制匹配以在具有给定的类型中的连接点 注释(在以下情况下执行在具有给定注释的类型中声明的方法 使用弹簧AOP)。
  • ​@annotation​​:限制与连接点的主题所在的连接点的匹配 (在 Spring AOP 中运行的方法)具有给定的注释。

其他切入点类型

完整的 AspectJ 切入点语言支持其他不是 在春季支持:,,,,,,,,,,,,,和。在切入点中使用这些切点指示符 由 Spring AOP 解释的表达式会导致 anbe 扔。​​call​​​​get​​​​set​​​​preinitialization​​​​staticinitialization​​​​initialization​​​​handler​​​​adviceexecution​​​​withincode​​​​cflow​​​​cflowbelow​​​​if​​​​@this​​​​@withincode​​​​IllegalArgumentException​

Spring AOP支持的切入点指示符集将来可能会扩展 版本以支持更多 AspectJ 切入点指示符。

由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论 的切入点指示符给出的定义比您在 AspectJ编程指南。此外,AspectJ本身具有基于类型的语义,并且在 一个执行连接点,两者和引用同一对象: 执行方法的对象。Spring AOP 是一个基于代理的系统,具有差异化 在代理对象本身(绑定到)和 代理(绑定到)。​​this​​​​target​​​​this​​​​target​

Spring AOP还支持一个名为的附加PCD。此 PCD 可让您限制 将连接点与特定命名的 Spring Bean 或一组命名的 Spring Bean 匹配 春豆(使用通配符时)。PCD具有以下形式:​​bean​​​​bean​

bean(idOrNameOfBean)

令牌可以是任何春豆的名称。有限的通配符 提供使用字符的支持,因此,如果您建立一些命名 Spring bean 的约定,你可以编写 aPCD 表达式 以选择它们。与其他切点指示器一样,PCD 可以 也可以与(和)、(或)和(否定)运算符一起使用。​​idOrNameOfBean​​​​*​​​​bean​​​​bean​​​​&&​​​​||​​​​!​

组合切入点表达式

您可以通过使用和组合切入点表达式。您也可以参考 按名称划分的切入点表达式。以下示例显示了三个切入点的表达式:​​&&,​​​​||​​​​!​

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

最佳做法是从较小的命名中构建更复杂的切入点表达式 组件,如前所示。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以在 层次结构,任何地方的公共切入点,等等)。可见性不影响切入点 匹配。

共享常见的切入点定义

在使用企业应用程序时,开发人员通常希望参考 从几个方面进行应用和特定的操作集。我们 建议定义捕获常见切入点表达式的方面 为此。此类方面通常类似于以下示例:​​CommonPointcuts​

package com.xyz.myapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.myapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.web..*)")
public void inWebLayer() {}

/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.myapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.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.myapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.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.myapp.abc.service and com.xyz.myapp.def.service) then
* the pointcut expression "execution(* com.xyz.myapp..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.myapp..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.myapp.dao.*.*(..))")
public void dataAccessOperation() {}

}

您可以在任何需要的地方参考此类方面定义的切入点 切入点的表达。例如,要使服务层具有事务性,您可以 编写以下内容:

<aop:config>
<aop:advisor
pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

基于架构的 AOP 支持中讨论了这些元素。这 事务管理中讨论了事务元素。​​<aop:config>​​​​<aop:advisor>​

例子

Spring AOP 用户可能最常使用点切指示符。 执行表达式的格式如下:​​execution​

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)

除返回类型模式(在前面的代码段中)之外的所有部分, 名称模式和参数模式是可选的。返回类型模式确定 方法的返回类型必须是什么,才能使联接点 matched.is 最常用作返回类型模式。它与任何回报相匹配 类型。仅当方法返回给定的 类型。名称模式与方法名称匹配。您可以将通配符用作全部或 名称模式的一部分。如果指定声明类型模式, 包括尾随以将其连接到名称模式组件。 参数模式稍微复杂一些:匹配 不带参数的方法,其中匹配任意数量的参数(零个或多个)。 模式匹配采用任意类型一个参数的方法。匹配采用两个参数的方法。第一个可以是任何类型的,而 第二个必须是 A。咨询语言 AspectJ 编程指南的语义部分了解更多信息。​​ret-type-pattern​​​​*​​​​*​​​​.​​​​()​​​​(..)​​​​(*)​​​​(*,String)​​​​String​

以下示例显示了一些常见的切入点表达式:

  • 任何公共方法的执行:
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..*)
  • 代理实现接口的任何连接点(仅在 Spring AOP 中执行方法):AccountService
this(com.xyz.service.AccountService)

目标对象的任何连接点(仅在 Spring AOP 中执行方法) 实现接口:​​AccountService​

target(com.xyz.service.AccountService)

任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 在运行时传递的参数是:​​Serializable​

args(java.io.Serializable)
  • 请注意,此示例中给出的切入点与 不同。如果运行时传递的参数匹配,则 args 版本匹配,如果方法签名声明单个,则执行版本匹配 类型的参数。execution(* *(java.io.Serializable))SerializableSerializable
  • 目标对象具有注释的任何连接点(仅在Spring AOP中执行方法):@Transactional
@target(org.springframework.transaction.annotation.Transactional)

任何连接点(仅在 Spring AOP 中执行方法),其中声明的类型为 目标对象具有注释:​​@Transactional​

@within(org.springframework.transaction.annotation.Transactional)

执行方法具有注释的任何连接点(仅在 Spring AOP 中执行方法):​​@Transactional​

@annotation(org.springframework.transaction.annotation.Transactional)

任何采用单个参数的连接点(仅在Spring AOP中执行方法), 其中传递的参数的运行时类型具有注释:​​@Classified​

@args(com.xyz.security.Classified)
  • Spring Bean 上的任何连接点(仅在 Spring AOP 中执行方法),名为:tradeService
bean(tradeService)
  • Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法),其名称为 匹配通配符表达式:*Service
bean(*Service)
写好的切入点

在编译过程中,AspectJ处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态)给定的切入点是一个昂贵的过程。(动态匹配表示匹配 无法从静态分析中完全确定,并且测试被放置在代码中 确定代码运行时是否存在实际匹配项)。在第一次遇到 切入点声明,AspectJ将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用DNF(析取)重写的 范式)和切入点的组件进行排序,以便这些组件 首先检查评估成本较低的内容。这意味着您不必担心 关于了解各种切入点指示器的性能并可能提供它们 在切入点声明中的任何顺序。

但是,AspectJ只能处理它被告知的内容。为了获得最佳性能 匹配,您应该考虑他们试图实现的目标并缩小搜索范围 定义中尽可能多地匹配空间。现有指示符 自然分为三组:善良、范围界定和上下文:

  • 种类指示符选择特定类型的连接点:,,,,和。executiongetsetcallhandler
  • 范围指示符选择一组感兴趣的连接点 (可能有很多种):和withinwithincode
  • 上下文指示符根据上下文匹配(并选择性绑定):,和thistarget@annotation

一个写得很好的切入点应该至少包括前两种类型(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.CommonPointcuts.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.CommonPointcuts.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.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}

属性中使用的名称必须与参数的名称相对应 在建议方法中。当方法执行返回时,返回值将传递给 建议方法作为相应的参数值。阿clause也 将匹配限制为仅那些返回值 指定类型(在本例中为,与任何返回值匹配)。​​returning​​​​returning​​​​Object​

请注意,在以下情况下无法返回完全不同的引用 返回建议后使用。

抛出建议后

抛出建议后,当匹配的方法执行退出时,通过抛出 例外。您可以使用注释声明它,因为 以下示例显示:​​@AfterThrowing​

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}

通常,您希望建议仅在引发给定类型的异常时才运行, 而且您还经常需要访问建议正文中抛出的异常。您可以 使用 theattribute 来限制匹配(如果需要 — 否则用作异常类型)并将抛出的异常绑定到 advice参数。 以下示例演示如何执行此操作:​​throwing​​​​Throwable​

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}

属性中使用的名称必须与 中的参数名称相对应 建议方法。当方法执行通过引发异常退出时,异常 作为相应的参数值传递给 advice。阿clause 还将匹配限制为仅那些引发异常的方法执行 指定类型(在本例中为 )。​​throwing​​​​throwing​​​​DataAccessException​

之后(最终)建议

之后(最后)建议在匹配的方法执行退出时运行。它由以下声明 使用注释。建议后必须准备好处理正常和 异常返回条件。它通常用于释放资源和类似资源 目的。以下示例显示了 after final 建议后如何使用:​​@After​

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
周围建议

最后一种建议是围绕建议围绕建议运行“围绕”匹配 方法的执行。它有机会在方法之前和之后做工作 运行并确定方法何时、如何运行,甚至是否实际运行。 如果需要在方法之前和之后共享状态,通常会使用“围绕建议” 以线程安全的方式执行 - 例如,启动和停止计时器。

围绕建议是通过用注释注释来声明方法的。这 方法应标记其返回类型和方法的第一个参数 必须是类型。在建议方法的正文中,您必须 调用 thein 以便底层方法 跑。调用无参数将导致调用方的原始 调用基础方法时提供给该方法的参数。对于高级使用 在这种情况下,该方法有一个重载变体,它接受一个数组 参数 ()。数组中的值将用作 调用时的基础方法。​@Around​​​​Object​​​​ProceedingJoinPoint​​​​proceed()​​​​ProceedingJoinPoint​​​​proceed()​​​​proceed()​​​​Object[]​

周围建议返回的值是 方法。例如,一个简单的缓存方面可以从缓存中返回一个值,如果它有 一个或调用(并返回该值)如果没有。请注意,可以在周围建议的正文中调用一次、多次或根本不调用。都 其中是合法的。​proceed()​​​​proceed​

以下示例演示如何使用绕过建议:

爪哇岛

科特林

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.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
建议参数

Spring 提供了完全类型的建议,这意味着您可以在 建议签名(正如我们之前在返回和抛出示例中看到的那样)而不是 始终使用阵列。我们看看如何进行论证和其他上下文 本节后面的建议机构可用的值。首先,我们来看看如何 撰写通用建议,以了解建议当前建议的方法。​​Object[]​

访问当前​​JoinPoint​

任何建议方法都可以声明一个类型的参数作为其第一个参数。请注意,需要围绕建议声明第一个 类型的参数,它是 的子类。​​org.aspectj.lang.JoinPoint​​​​ProceedingJoinPoint​​​​JoinPoint​

该接口提供了许多有用的方法:​​JoinPoint​

  • ​getArgs()​​:返回方法参数。
  • ​getThis()​​:返回代理对象。
  • ​getTarget()​​:返回目标对象。
  • ​getSignature()​​:返回建议的方法的说明。
  • ​toString()​​:打印建议方法的有用说明。

有关更多详细信息,请参阅​​javadoc​​。

将参数传递给建议

我们已经看到了如何绑定返回值或异常值(使用 after 返回并抛出建议后)。使参数值可用于建议 正文,可以使用绑定形式。如果使用参数名称代替 类型名称 在表达式中,相应参数的值作为 调用建议时的参数值。一个例子应该更清楚地说明这一点。 假设您想建议执行以对象为第一个参数的 DAO 操作,并且您需要访问建议正文中的帐户。 您可以编写以下内容:​​args​​​​args​​​​Account​

爪哇岛

科特林

@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}

切入点表达式的一部分有两个用途。首先,它 将匹配限制为仅那些方法执行,其中该方法至少采用一个 参数,并且传递给该参数的参数是 的实例。 其次,它通过参数使实际对象可用于建议。​​args(account,..)​​​​Account​​​​Account​​​​account​

另一种编写方法是声明一个切入点,当它与连接点匹配时“提供”对象值,然后引用命名的切入点 从建议。这将如下所示:​​Account​

爪哇岛

科特林

@Pointcut("com.xyz.myapp.CommonPointcuts.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);
}

You can restrict interception of method types to certain parameter types by tying the advice parameter to the parameter type for which you want to intercept the method:

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}

This approach does not work for generic collections. So you cannot define a pointcut as follows:

@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​​​​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
}
  • 使用属性有点笨拙,所以如果属性 尚未指定,Spring AOP 查看 类,并尝试从局部变量表中确定参数名称。这 只要类已使用 debug 编译,信息就存在 信息(至少)。使用此标志编译的后果 (1)你的代码稍微更容易理解(逆向工程),(2) 类文件大小略大(通常无关紧要),(3) 编译器不会应用优化以删除未使用的局部变量。在 换句话说,通过使用此标志进行构建,您应该不会遇到任何困难。argNamesargNames-g:vars
  • 如果代码在编译时没有必要的调试信息,Spring AOP 尝试推断绑定变量与参数的配对(例如,如果 切入点表达式中仅绑定一个变量,并且建议方法 只取一个参数,配对很明显)。如果变量的绑定是 鉴于现有信息,阿尼斯模棱两可 扔。AmbiguousBindingException
  • 如果上述所有策略都失败了,Anis 就会抛出。IllegalArgumentException
继续处理参数

我们之前说过,我们将描述如何用 在Spring AOP和AspectJ中一致工作的参数。解决方案是 以确保建议签名按顺序绑定每个方法参数。 以下示例演示如何执行此操作:​​proceed​

@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.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​​​​Ordered.getOrder()​

5.4.5. 简介

引入(在 AspectJ 中称为类型间声明)使方面能够声明 建议对象实现给定接口,并提供 该接口代表这些对象。

您可以使用注释进行介绍。此注释 用于声明匹配类型具有新的父级(因此得名)。例如 给定一个名为的接口和命名的该接口的实现,以下方面声明服务的所有实现者 接口还实现了接口(例如,通过JMX进行统计):​​@DeclareParents​​​​UsageTracked​​​​DefaultUsageTracked​​​​UsageTracked​

@Aspect
public class UsageTracking {

@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;

@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}

}

要实现的接口由带注释字段的类型确定。注释的属性是AspectJ类型模式。任何 匹配类型的 Bean 实现接口。请注意,在 在前面示例的建议之前,服务Bean可以直接用作 接口的实现。如果以编程方式访问 Bean, 您将编写以下内容:​​value​​​​@DeclareParents​​​​UsageTracked​​​​UsageTracked​

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

5.4.6. 方面实例化模型

默认情况下,应用程序中每个方面都有一个实例 上下文。AspectJ称之为单例实例化模型。可以定义 具有替代生命周期的方面。Spring 支持 AspectJ'sand实例化模型;,,并且目前不支持 支持。​​perthis​​​​pertarget​​​​percflow​​​​percflowbelow​​​​pertypewithin​

您可以通过在注释中指定 aclause 来声明 aaspect 。请考虑以下示例:​​perthis​​​​perthis​​​​@Aspect​

爪哇岛

科特林

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {

private int someState;

@Before("com.xyz.myapp.CommonPointcuts.businessService()")
public void recordServiceUsage() {
// ...
}
}

在前面的示例中,子句的效果是一方面实例 为执行业务服务的每个唯一服务对象创建(每个唯一 对象绑定的 toat 连接点与切入点表达式匹配)。方面 实例是在第一次对服务对象调用方法时创建的。这 当服务对象超出范围时,方面将超出范围。在方面之前 实例已创建,其中的任何建议都不会运行。一旦方面实例 已创建,其中声明的建议在匹配的加入点运行,但仅 当服务对象是与此方面关联的对象时。查看方面J 有关子句的更多信息,请参阅编程指南。​​perthis​​​​this​​​​per​

实例化模型的工作方式与 完全相同,但它 在匹配的连接点处为每个唯一的目标对象创建一个方面实例。​​pertarget​​​​perthis​

5.4.7. AOP 示例

现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起来做 有用的东西。

业务服务的执行有时可能会由于并发问题而失败(对于 例如,死锁失败者)。如果重试该操作,则很可能成功 在下一次尝试中。对于适合重试的业务服务 条件(不需要返回给用户进行冲突的幂等操作 resolution),我们希望透明地重试操作以避免客户端看到 a。这是一个明显跨越的要求 服务层中的多个服务,因此非常适合通过 方面。​​PessimisticLockingFailureException​

因为我们想重试操作,所以我们需要使用周围的建议,以便我们可以 多次调用。以下清单显示了基本方面实现:​​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.CommonPointcuts.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;
}
}

请注意,方面实现了接口,以便我们可以设置 高于交易建议的方面(我们每次都想要一笔新的交易 重试)。属性都是由 Spring 配置的。这 主要行动发生在周围的建议中。请注意,对于 时刻,我们将重试逻辑应用于每个。我们尝试继续, 如果我们以 A 失败,我们会再试一次,除非 我们已经用尽了所有的重试尝试。​​Ordered​​​​maxRetries​​​​order​​​​doConcurrentOperation​​​​businessService()​​​​PessimisticLockingFailureException​

相应的弹簧配置如下:

<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.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}

5.5. 基于模式的AOP支持

如果你更喜欢基于XML的格式,Spring还提供了对定义方面的支持。 使用命名空间标记。完全相同的切入点表达和建议种类 与使用@AspectJ样式时一样。因此,在本节中,我们重点介绍 该语法,并让读者参考上一节中的讨论 (@AspectJ支持)用于理解编写切入点表达式和绑定 的建议参数。​​aop​

要使用本节中描述的 aop 命名空间标记,您需要导入架构,如基于 XML 架构的配置中所述。有关如何在命名空间中导入标签的信息,请参阅AOP 架构。​​spring-aop​​​​aop​

在您的 Spring 配置中,所有方面和顾问元素都必须放置在 一个元素(您可以在一个 应用程序上下文配置)。元素可以包含切入点, 顾问和方面元素(请注意,这些元素必须按该顺序声明)。​​<aop:config>​​​​<aop:config>​​​​<aop:config>​

5.5.1. 声明一个方面

使用模式支持时,方面是定义为 Bean 的常规 Java 对象 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 对象的方法以及切入点和建议信息在 XML 中捕获。

您可以使用元素声明一个方面,并引用支持Bean 通过使用属性,如以下示例所示:​​<aop:aspect>​​​​ref​

<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
...
</bean>

当然,可以配置支持方面(在这种情况下)的 bean,并且 依赖注入就像任何其他春豆一样。​​aBean​

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.CommonPointcuts.businessService()"/>

</aop:config>

假设您具有共享常见切入点定义中所述的方面。​​CommonPointcuts​

那么在一个方面内声明一个切入点与声明一个顶级切入点非常相似, 如以下示例所示:

<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中很尴尬 文档,因此您可以使用 、、 和关键字分别代替 、 和。例如,前面的切入点可以更好地写成 遵循:​​&amp;&amp;​​​​and​​​​or​​​​not​​​​&amp;&amp;​​​​||​​​​!​

<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 引用,并且不能 用作命名切入点以形成复合切入点。命名的切入点支持 因此,基于架构的定义样式比@AspectJ提供的定义样式更受限制 风格。​​id​

5.5.3. 声明建议

基于架构的 AOP 支持使用与@AspectJ样式相同的五种建议,并且它们具有 完全相同的语义。

建议前

在建议运行之前 在匹配的方法执行之前。它是使用 theelement 在 anby 内部声明的,如以下示例所示:​​<aop:aspect>​​​​<aop:before>​

<aop:aspect id="beforeExample" ref="aBean">

<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>

...

</aop:aspect>

这里,是在顶部定义的切入点() 水平。要改为内联定义切入点,请将属性替换为 属性,如下所示:​​dataAccessOperation​​​​id​​​​<aop:config>​​​​pointcut-ref​​​​pointcut​

<aop:aspect id="beforeExample" ref="aBean">

<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>

...
</aop:aspect>

正如我们在讨论@AspectJ样式时指出的那样,使用命名切入点可以 显著提高代码的可读性。

属性标识提供主体的方法 () 的建议。必须为方面元素引用的 Bean 定义此方法 其中包含建议。在执行数据访问操作(方法执行)之前 与切入点表达式匹配的连接点),方法在方面 Bean 被调用。​​method​​​​doAccessCheck​​​​doAccessCheck​

返回建议后

返回建议后,当匹配的方法执行正常完成时运行。是的 在阿宁内部声明的方式与之前建议相同。以下示例 演示如何声明它:​​<aop:aspect>​

<aop:aspect id="afterReturningExample" ref="aBean">

<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>

...
</aop:aspect>

与@AspectJ样式一样,您可以在建议正文中获取返回值。 为此,请使用属性指定参数的名称 应传递返回值,如以下示例所示:​​returning​

<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) {...
抛出建议后

抛出建议后,当匹配的方法执行退出时,通过抛出 例外。它是使用元素在 anby 内部声明的, 如以下示例所示:​​<aop:aspect>​​​​after-throwing​

<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>
周围建议

最后一种建议是围绕建议围绕建议运行“围绕”匹配 方法的执行。它有机会在方法之前和之后做工作 运行并确定方法何时、如何运行,甚至是否实际运行。 如果需要在方法之前和之后共享状态,通常会使用“围绕建议” 以线程安全的方式执行 - 例如,启动和停止计时器。

您可以使用元素声明周围的建议。建议方法应 定义其返回类型,并且该方法的第一个参数必须是 类型。在 advice 方法的主体中,必须调用 thein 才能运行基础方法。 调用无参数将导致调用方的原始参数 在调用基础方法时提供给该方法。对于高级用例,有 是方法的重载变体,它接受参数数组 ().数组中的值将用作基础的参数 方法,当它被调用时。请参阅周围建议,​了解有关呼叫的注意事项。​​aop:around​​​​Object​​​​ProceedingJoinPoint​​​​proceed()​​​​ProceedingJoinPoint​​​​proceed()​​​​proceed()​​​​Object[]​​​​proceed​​​​Object[]​

下面的示例演示如何在 XML 中声明 around 建议:

<aop:aspect id="aroundExample" ref="aBean">

<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>

...
</aop:aspect>

建议的实现可以与 @AspectJ示例(当然减去注释),如以下示例所示:​​doBasicProfiling​

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
建议参数

基于架构的声明样式支持完全类型的建议,其方式与 描述@AspectJ支持 — 通过按名称匹配切入点参数 建议方法参数。有关详细信息,请参阅建议参数。如果你愿意 显式指定建议方法的参数名称(不依赖于 检测策略前面所述),您可以使用建议元素的属性来执行此操作,该属性的处理方式与建议批注中的属性相同(如确定参数名称中所述)。 下面的示例演示如何在 XML 中指定参数名称:​​arg-names​​​​argNames​

<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 DefaultPersonService implements PersonService {

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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://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 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
建议订购

当多条建议需要在同一连接点运行时(执行方法) 排序规则如建议订购中所述。优先级 方面之间通过元素中的属性确定或 通过将 theannotation 添加到支持该方面的 Bean 或具有 Bean 实现接口。​​order​​​​<aop:aspect>​​​​@Order​​​​Ordered​

5.5.4. 简介

介绍(在 AspectJ 中称为类型间声明)让一个方面声明 建议对象实现给定接口并提供 该接口代表这些对象。

您可以使用元素进行介绍。 您可以使用 theelement 声明匹配类型具有新的父类型(因此得名)。 例如,给定一个名为 的接口和一个名为该接口的实现,以下方面声明服务的所有实现者 接口也实现接口。(为了公开统计数据 例如,通过 JMX。​​aop:declare-parents​​​​aop:aspect​​​​aop:declare-parents​​​​UsageTracked​​​​DefaultUsageTracked​​​​UsageTracked​

<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.CommonPointcuts.businessService()
and this(usageTracked)"
method="recordUsage"/>

</aop:aspect>

然后,支持 thebean 的类将包含以下方法:​​usageTracking​

public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}

要实现的接口由属性确定。这 属性的值是 AspectJ 类型模式。任何豆子 匹配类型实现接口。请注意,在之前 前面示例的建议,服务Bean可以直接用作 界面。要以编程方式访问 Bean,您可以编写 以后:​​implement-interface​​​​types-matching​​​​UsageTracked​​​​UsageTracked​

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

5.5.5. 方面实例化模型

架构定义方面唯一支持的实例化模型是单一实例 型。将来的版本可能会支持其他实例化模型。

5.5.6. 顾问

“顾问”的概念来自Spring中定义的AOP支持 并且在AspectJ中没有直接等价物。顾问就像一个小顾问 自成一体的方面,只有一条建议。建议本身是 由 Bean 表示,并且必须实现Spring 中的建议类型中描述的建议接口之一。顾问可以利用AspectJ的切入点表达式。

弹簧支持带有元素的顾问概念。你最 通常看到它与交易建议结合使用,交易建议也有自己的 Spring 中的命名空间支持。以下示例显示了一个顾问:​​<aop:advisor>​

<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>

除了前面示例中使用的属性外,还可以使用 theattribute 内联定义切入点表达式。​​pointcut-ref​​​​pointcut​

定义顾问的优先级,以便建议可以参与订购, 使用属性定义顾问的值。​​order​​​​Ordered​

5.5.7. AOP 模式示例

本部分显示使用架构支持重写AOP 示例中的并发锁定失败重试示例的外观。

业务服务的执行有时可能会由于并发问题而失败(对于 例如,死锁失败者)。如果重试该操作,则很可能成功 在下一次尝试中。对于适合重试的业务服务 条件(不需要返回给用户进行冲突的幂等操作 resolution),我们希望透明地重试操作以避免客户端看到 a。这是一个明显跨越的要求 服务层中的多个服务,因此非常适合通过 方面。​​PessimisticLockingFailureException​

因为我们想重试操作,所以我们需要使用周围的建议,以便我们可以 多次调用。以下清单显示了基本方面实现 (这是使用模式支持的常规 Java 类):​​proceed​

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;
}
}

请注意,方面实现了接口,以便我们可以设置 高于交易建议的方面(我们每次都想要一笔新的交易 重试)。属性都是由 Spring 配置的。这 主要动作发生在周围的建议方法中。我们努力 进行。如果我们失败了,我们再试一次, 除非我们已经用尽了所有的重试尝试。​​Ordered​​​​maxRetries​​​​order​​​​doConcurrentOperation​​​​PessimisticLockingFailureException​

相应的弹簧配置如下:

<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.3.23

标签:String,框架,示例,核心技术,Spring,AOP,public,表达式
From: https://blog.51cto.com/u_15326439/5856342

相关文章

  • Spring 框架的核心技术(二)
    1.9.基于注释的容器配置在配置Spring方面,注释是否比XML更好?基于注释的配置的引入提出了一个问题,即这是否方法比XML“更好”。简短的回答是“视情况而定”。长答案是......
  • springboot openfeign服务端与客户端调用演示demo
    文章目录​​serverdemo演示​​​​创建server项目​​​​application.properties配置​​​​importjar[pom.xml]​​​​创建服务端的restfulcontroller​​​​验......
  • 极速交易场景下的三大核心技术点,超低时延网卡如何拆解?
    量化投资是通过数量化方式及计算机程序化发出买卖指令,以获取稳定收益为目的的交易方式。量化投资在国内落地发展已有10余年,始终保持的迅猛的发展态势,管理规模突破100亿的量......
  • SpringBoot 2学习笔记(二)
    45、web实验-抽取公共页面官方文档-TemplateLayout公共页面/templates/common.html <!DOCTYPEhtml> <htmllang="en"xmlns:th="http://www.thymeleaf.org">......
  • SpringBoot 2学习笔记(一)
    01、基础入门-SpringBoot2课程介绍SpringBoot2核心技术SpringBoot2响应式编程学习要求-熟悉Spring基础-熟悉Maven使用环境要求Java8及以上Maven......
  • SpringMVC
    README一、目录0、简介1、@RequestMapping注解2、获取请求参数3、域对象共享数据4、视图5、RESTful6、HttpMessageConverter7、拦截器和异常处理8、完......
  • Spring 中定时任务cron表达式问题
    1.问题:Cronexpressionmustconsistof6fields(found7in“0/5****?*“)@Scheduled(cron="0/5****?*")2.原因:年的项1099~2099年,为默认。因此只需要......
  • springMVC-解读<url-pattern/>- 用 / 替换掉 *.xxx的报错解决办法
    2.4解读1配置详解(1)*.do在没有特殊要求的情况下,SpringMVC的中央调度器DispatcherServlet的常使用后辍匹配方式,如写为*.do或者*.action,*.mvc等。(2)/......
  • Java SpringBoot FTP 上传下载文件
    POM添加依赖<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.7</version></dependency><!--......
  • Springboot整合Jwt实现用户认证
    前言相信大家在进行用户认证中或多或少都要对用户进行认证,当前进行认证的方式有基于session、token等主流方式,但是目前使用最广泛的还是基于JWT的用户认证,特别适用于......