
应用AOP
面向切面编程旨在执行横切任务,可以是任何可能通过不同方法重复多次的代码,不能完全结构化到一个单独的模块中。因此,AOP允许我们将它保留在主代码之外并垂直声明它。一个例子是在应用程序中使用安全策略。通常,安全贯穿应用程序的许多元素。更重要的是,应用程序的安全策略应该同样适用于应用程序的所有现有部分和新部分。同时,正在使用的安全策略本身可以发展。这是使用AOP 的最佳场所。另外,另一个例子是记录. 使用 AOP 方法进行日志记录而不是手动添加日志记录功能有几个优点:-
日志记录的代码很容易添加和删除:您需要做的就是添加或删除某些方面的一些配置。
-
所有用于日志记录的源代码都保存在一个地方,因此您无需手动搜索所有使用它的地方。
-
可以在任何地方添加日志记录代码,无论是在已经编写的方法和类中还是在新功能中。这减少了编码错误的数量。
此外,当从设计配置中删除一个方面时,您可以确定所有跟踪代码都已消失并且没有遗漏任何内容。
- 方面是独立的代码,可以改进并一次又一次地使用。

AOP的基本原理
为了进一步探讨这个主题,让我们首先了解一下 AOP 的主要概念。 建议——从连接点调用的附加逻辑或代码。建议可以在连接点之前、之后或代替连接点执行(下面将详细介绍)。可能的建议类型:-
之前——这种类型的建议在目标方法(即连接点)执行之前启动。将方面用作类时,我们使用@Before注释将通知标记为之前出现。将方面用作.aj文件时,这将是before()方法。
- After — 在方法(连接点)执行完成后执行的建议,无论是在正常执行还是在抛出异常时。
将方面用作类时,我们可以使用@After注释来指示这是之后的通知。
将方面用作.aj文件时,这是after()方法。
-
返回后——只有当目标方法正常完成且没有错误时才执行此建议。
当方面表示为类时,我们可以使用@AfterReturning批注将通知标记为在成功完成后执行。
将方面用作.aj文件时,这将是after() 返回 (Object obj)方法。
-
抛出后——此建议适用于方法(即连接点)抛出异常的情况。我们可以使用此建议来处理某些类型的失败执行(例如,回滚整个事务或具有所需跟踪级别的日志)。
对于类方面,@AfterThrowing注释用于指示在抛出异常后使用此建议。
将方面用作.aj文件时,这将是after() throwing (Exception e)方法。
-
周围——也许是最重要的建议类型之一。它包含一个方法,即一个连接点,我们可以使用它来选择是否执行给定的连接点方法。
您可以编写在执行连接点方法之前和之后运行的建议代码。
around advice负责调用连接点方法和返回值(如果该方法有返回值)。换句话说,在这个通知中,你可以简单地模拟目标方法的运行而不调用它,并返回你想要的任何结果作为返回结果。
给定方面作为类,我们使用@Around注释来创建包装连接点的建议。当使用.aj文件形式的方面时,此方法将是around()方法。
-
编译时编织——如果你有方面的源代码和使用方面的代码,那么你可以直接使用 AspectJ 编译器编译源代码和方面;
-
编译后编织(二进制编织) ——如果您不能或不想使用源代码转换将方面编织到代码中,您可以采用先前编译的类或 jar 文件并将方面注入其中;
-
加载时编织——这只是二进制编织,延迟到类加载器加载类文件并为 JVM 定义类。
需要一个或多个编织类加载器来支持这一点。它们要么由运行时显式提供,要么由“编织代理”激活。
Java 中的示例
接下来,为了更好地理解AOP,我们将查看“Hello World”式的小示例。首先,我会注意到我们的示例将使用编译时织入。首先,我们需要在pom.xml文件中添加以下依赖项:<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
通常,特殊的ajc编译器是我们使用方面的方式。IntelliJ IDEA默认不包含它,因此在选择它作为应用程序编译器时,您必须指定 5168 75 AspectJ分发的路径。这是第一种方式。第二种,也就是我使用的,是在pom.xml文件中注册以下插件:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<<verbose>true<verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在此之后,最好从Maven重新导入并运行mvn clean compile。现在让我们直接进入示例。
示例 1
让我们创建一个主类。在其中,我们将有一个入口点和一个在控制台上打印传递名称的方法:public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
这里没有什么复杂的。我们传递了一个名称并将其显示在控制台上。如果我们现在运行该程序,我们将在控制台上看到以下内容:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
这个文件有点像一个类。让我们看看这里发生了什么: 切入点是一组连接点; greeting()是这个切入点的名称; : execution指示在执行Main.printName(...)方法的所有 ( * )调用期间应用它。接下来是一个特定的建议 — before() — 在调用目标方法之前执行。: greeting()是这个通知响应的切点。好吧,下面我们看到了方法本身的主体,它是用我们理解的 Java 语言编写的。当我们在这个方面存在的情况下运行main时,我们将得到这个控制台输出:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
在.aj方面文件 之后,这里的一切都变得更加明显:
- @Aspect表示这个类是一个切面;
- @Pointcut("execution(* Main.printName(String))")是所有调用Main.printName时触发的切点,输入参数类型为String;
- @Before("greeting()")是在调用greeting()切点中指定的代码之前应用的建议。
示例 2
假设我们有一些方法可以为客户端执行一些操作,我们从main调用这个方法:public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
让我们使用@Around注解来创建一个“伪事务”:
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
通过ProceedingJoinPoint对象的proceed方法,我们调用 wrapping 方法来确定它在通知中的位置。因此,上述方法中的代码joinPoint.proceed(); 是Before,而下面的代码是After。如果我们运行main,我们会在控制台中看到:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
然后我们得到这个控制台输出:
例 3
在我们的下一个示例中,让我们做一些类似登录到控制台的事情。首先,看一下Main,我们在其中添加了一些伪业务逻辑:public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
在main中,我们使用setValue为value实例变量赋值。然后我们使用getValue获取值,然后调用checkValue看是否超过10个字符。如果是这样,那么将抛出异常。现在让我们看看我们将用来记录方法工作的方面:
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
这里发生了什么? @Pointcut("execution(* *(..))")将加入所有方法的所有调用。 @AfterReturning(value = "methodExecuting()", returning = "returningValue")是将在成功执行目标方法后执行的通知。我们这里有两种情况:
- 当方法有返回值时——if (returningValue!= Null) {
- 当没有返回值时——else {

GO TO FULL VERSION