AOP即面向切面编程,是对OOP面向对象编程的补充与完善。主要用来解决系统中事务、日志以及权限等问题。其在不改变原有逻辑的基础上进行增强和扩展。
1. 相关概念
- Join point :连接点,程序执行过程中的点,如方法的执行或异常的处理。在Spring中,连接点始终代表方法的执行。
- Pointcut :切点,连接点的集合,主要体现为切入点表达式,Spring默认使用AspectJ切入点表达语言;
- Weaving:织入,将切面应用到目标对象并创建代理对象的过程;
- Advice:通知,切点在特定连接点上的操作,见下文;
- Aspect:切面,通常为定义切入点和通知的类;
- Target object :目标对象,被代理对象;
- AOP proxy:代理对象,Spring中是JDK代理或CGLIB代理;
- Introduction:引入,在运行期引入新的接口及实现。
2. 通知类型
- Before:前置通知,在连接点方法前调用;
- After:后置通知,在连接点方法后调用;
- AfterReturning:返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常;
- AfterThrowing:异常通知,当连接点方法异常时调用;
- Around:环绕通知,覆盖原有方法,但允许通过反射调用原有方法;
3. 注解方式使用
- 创建Maven工程
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 目录结构
- 定义目标类接口
package com.demo.aop;
public interface HelloWorld {
public void hello();
public void world();
}
- 目标类
package com.demo.aop.impl;
import com.demo.aop.HelloWorld;
import org.springframework.stereotype.Component;
@Component
public class HelloWorldImpl1 implements HelloWorld {
public void hello() {
System.out.println("******************Hello Spring**********************");
}
public void world() {
System.out.println("******************Hello Java**********************");
int i = 1 / 0;
}
}
- 切面类
package com.demo.aop.aspects;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class HelloWorldAspects {
@Pointcut("execution(* com.demo.aop.HelloWorld.*(..))")
public void pointCut() {}
@Before("pointCut()")
public void before() {
System.out.println("前置通知...");
}
@After("pointCut()")
public void after() {
System.out.println("后置通知...");
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("返回通知...");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("异常通知...");
}
}
- 主类
import com.demo.aop.HelloWorld;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy //开启 AOP 注解并自动生成代理对象
@ComponentScan("com.demo.aop") //开启注解扫描
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorldImpl1");
helloWorld.hello();
System.out.println("-----------------------------");
helloWorld.world();
}
}
- 运行结果
前置通知...
******************Hello Spring**********************
返回通知...
后置通知...
-----------------------------
前置通知...
******************Hello Java**********************
异常通知...
后置通知...
Exception in thread "main" java.lang.ArithmeticException: / by zero
4. 环绕通知
- 新增HelloWorldImpl2类
- 修改切面类
package com.demo.aop.aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class HelloWorldAspects {
@Pointcut("execution(* com.demo.aop.impl.HelloWorldImpl1.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before() {
System.out.println("前置通知...");
}
@After("pointCut()")
public void after() {
System.out.println("后置通知...");
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("返回通知...");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("异常通知...");
}
@Pointcut("execution(* com.demo.aop.impl.HelloWorldImpl2.*(..))")
public void pointCutAround() {
}
// 环绕通知
@Around("pointCutAround()")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("执行之前...");
try {
joinPoint.proceed();
System.out.println("返回通知...");
} catch (Throwable throwable) {
System.out.println("异常通知...");
throw new ArithmeticException();
} finally {
System.out.println("执行之后...");
}
}
}
- 修改主方法:
try…catch防止异常导致终止运行
import com.demo.aop.HelloWorld;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy //开启 AOP 注解并自动生成代理对象
@ComponentScan("com.demo.aop") //开启注解扫描
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorldImpl1");
helloWorld.hello();
System.out.println("-----------------------------");
try {
helloWorld.world();
}catch (Throwable throwable){}
System.out.println("-------------环绕通知----------------");
HelloWorld helloWorld2 = (HelloWorld) context.getBean("helloWorldImpl2");
helloWorld2.hello();
System.out.println("-----------------------------");
try {
helloWorld2.world();
}catch (Throwable throwable){}
}
}
- 结果
前置通知...
******************Hello Spring**********************
返回通知...
后置通知...
-----------------------------
前置通知...
******************Hello Java**********************
异常通知...
后置通知...
-------------环绕通知----------------
执行之前...
******************Hello Spring**********************
返回通知...
执行之后...
-----------------------------
执行之前...
******************Hello Java**********************
异常通知...
执行之后...