Spring AOP(Aspect-Oriented Programming)是Spring框架或生态的一个重要的功能模块,它能使开发者更容易地利用AOP思想去开发出耦合度更低、更易于管理的代码。
什么是AOP设计模式?
AOP意为面向切面编程。在代码开发的过程中,开发人员可能经常会遇到在不同的需求中会出现相同的业务逻辑代码。于是这些重复的代码被封装成独立的模块,在需要的时候被调用。那么这些模块就被称为"面",而切面就是在不破坏代码结构的前提下去增强功能模块或者在调用模块的前后做一些指定动作的过程。实现原生AOP的方式
如果我们想通过原生代码实现AOP,有JDK代理和CGLIB代理两种方式。
2.1. JDK代理
JDK代理是通过调用JDK自带的Proxy类的newProxyInstance()方法去创建一个代理对象,从而去代理某个实现了一些接口类的实例对象,再去调用并执行被代理对象的方法。

代码实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Testable{
void test();
}
interface Eatable {
void eat();
}
public class Solution implements Testable, Eatable{
public static void main(String[] args) {
Testable testable = (Testable) ProxyClass.getProxyInstance(new Solution());
testable.test();
Eatable eatable = (Eatable) ProxyClass.getProxyInstance(new Solution());
eatable.eat();
}
@Override
public void test() {
System.out.println("Tested");
}
@Override
public void eat() {
System.out.println("Eat");
}
}
class ProxyHandler implements InvocationHandler{
private Object object;
public void bind(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before");
Object o = method.invoke(object, args);
System.out.println("After");
return o;
}
}
class ProxyClass{
public static Object getProxyInstance(Object o){
ProxyHandler invocationHandler = new ProxyHandler();
invocationHandler.bind(o);
return Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), invocationHandler);
}
}
2.2. CGLIB动态代理
CGLIB是一个开源的框架项目,通过使用ASM框架去获取某个类的字节码从而生成该类的子类,进而创建子类代理对象去代理父类对象实例。
这里有一篇专门讲解如何使用CGLIB实现动态代理的blog
- Spring AOP和AspectJ
Spring AOP是Spring官方采用JDK代理实现的AOP框架,而AspectJ是通过CGLIB实现的AOP框架。相比Spring AOP,AspectJ具有更强大的功能和性能。这里有一篇介绍AspectJ框架的blog。
- Spring AOP相关的术语
4.1. 连接点
一个类的所有方法都可以被称作为"连接点"。
4.2 切点
被增强(通知)的方法叫做切点。
4.3. 通知
一个切点被增强的部分叫做通知。通知有五种类型:环绕通知、前置通知、后置通知、返回通知和抛出异常通知。
4.4.切面
为切点增强或通知的过程被称为切面
- 申明切点的语法规则
execution([方法的权限修饰符][方法的返回类型][类全路径][方法名][参数列表])
5.1 示例1:对com.michael.demo.dao.User类中的eat()方法声明为切点
execution(* com.michael.demo.dao.User.eat(…))
其中 *表示所有类型的权限修饰符, 方法返回类型被省略, …表示参数列表
5.2 示例2:对com.michael.demo.dao.User类中的全部方法声明为切点
execution(* com.michael.demo.dao.User.*(…))
5.3 示例3:对com.michael.demo.dao包中的所有类的所有全部方法声明为切点
execution(* com.michael.demo.dao..(…))
- Spring AOP的使用
6.1. 在Spring Boot项目的pom文件中引入Spring AOP的场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
6.2 创建两个类
第一个类中的方法需要被增强,第二类中的方法用于执行增强逻辑代码。两个类需要被@Component注解,被注入Spring容器中,并且增强类需要被@Aspect注解,意为该类为一个增强类。
import org.springframework.stereotype.Component;
//被代理类
@Component
public class User {
public void eat(){
System.out.println("Eating");
}
}
6.3. 增强类的中的增强方法(通知)需要被不同通知类型的注解所标记, 并且应该用切点表达式指名需要被增强的方法。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//代理类
@Aspect
@Component
public class UserProxy {
@Pointcut(value = "execution(* com.example.demo.beans.User.eat(..))")
public void CutPoint(){
}
//环绕增强
@Around("CutPoint()")// @Around("execution(* com.example.demo.beans.User.eat(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("BeforeAround-----------Buying");
//executing proxy method
proceedingJoinPoint.proceed();
System.out.println("AfterAround-----------Resting");
}
//前置增强
@Before(value = "CutPoint()")
public void before(){
System.out.println("Before--------Cooking");
}
//返回增强
@AfterReturning(value = "CutPoint()")
public void returning(){
System.out.println("AfterReturning-------Drinking");
}
//后置增强
@After(value = "CutPoint()")
public void after(){
System.out.println("After--------Washing");
}
}
6.4. 同时我们可以把增强注解的值给抽取出来,使代码更加简洁。首先,声明一个空的方法,并使用@PointCut注解,传入注解的值为切点的表达式。最后,可以使用该方法名去替代原先增强注解中的值。
测试用例:
@Test
public void testAOPBefore(){
user.eat();
}
执行结果:
BeforeAround-----------Buying
Before--------Cooking
Eating
AfterReturning-------Drinking
After--------Washing
AfterAround-----------Resting
- Spring 4(Spring Boot 1.0)和 Spring 5 (Spring Boot 2.0)执行通知顺序的比较
