Aop 原理篇
**
一、AOP 是什么
AOP(Aspect-Oriented Programming,面向切面编程)是一种重要的编程范式,它与 OOP(面向对象编程)相辅相成。OOP 通过封装、继承和多态来组织代码,关注的是对象的属性和行为;而 AOP 则专注于解决系统中各个模块之间的横切关注点问题,比如日志记录、事务管理、权限控制等。
这些横切关注点如果分散在各个业务逻辑代码中,会导致代码冗余、维护困难。AOP 的核心思想是将这些横切关注点抽取出来,形成一个独立的切面,然后在需要的时候动态地将切面切入到业务逻辑中,实现了业务逻辑与横切关注点的解耦。
二、AOP 核心概念
要理解 AOP 的原理,首先需要掌握其核心概念:
- 切面(Aspect) :切面是横切关注点的模块化封装,它包含了切点和通知等信息。例如,一个日志切面就包含了日志记录的切点和具体的日志记录逻辑。
- 切点(Pointcut) :切点用于定义哪些方法需要被增强,即确定切面在何处切入。它通常通过表达式来描述,比如指定某个包下的所有方法,或者带有特定注解的方法等。
- 通知(Advice) :通知定义了在切点匹配的方法执行前后或异常抛出时等特定时机所要执行的操作。常见的通知类型有:
-
- 前置通知(Before):在目标方法执行之前执行。
-
- 后置通知(After):在目标方法执行之后,无论是否发生异常都执行。
-
- 返回通知(AfterReturning):在目标方法正常返回之后执行。
-
- 异常通知(AfterThrowing):在目标方法抛出异常之后执行。
-
- 环绕通知(Around):包围目标方法执行,可以在目标方法执行前后自定义一些操作,并且可以决定是否执行目标方法。
- 连接点(Joinpoint) :程序执行过程中的一些特定点,比如方法的调用、字段的访问等。切点是连接点的集合,它筛选出了需要被增强的连接点。
- 织入(Weaving) :织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译期、类加载期或运行期进行。
三、AOP 实现原理
AOP 的实现主要基于动态代理机制,在运行期为目标对象创建代理对象,通过代理对象来实现对目标方法的增强。常见的动态代理方式有 JDK 动态代理和 CGLIB 动态代理。
3.1 JDK 动态代理
JDK 动态代理是 Java 自带的动态代理机制,它基于接口实现。要求目标对象必须实现一个或多个接口,代理对象会实现与目标对象相同的接口,从而可以替代目标对象进行操作。
JDK 动态代理的核心类是java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。InvocationHandler是一个接口,它定义了一个invoke方法,用于处理代理对象的方法调用。当通过代理对象调用方法时,会先调用InvocationHandler的invoke方法,在该方法中可以实现对目标方法的增强。
下面是一个 JDK 动态代理的简单示例:
typescript
// 定义接口
public interface UserService {
void addUser();
}
// 实现接口的目标类
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户");
}
}
// 实现InvocationHandler接口的通知处理器
public class LogInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前记录日志"); // 前置通知
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("方法执行后记录日志"); // 后置通知
return result;
}
}
// 测试类
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.addUser();
}
}
运行结果:
方法执行前记录日志
添加用户
方法执行后记录日志
在上述示例中,Proxy.newProxyInstance方法用于创建代理对象,它接收三个参数:类加载器、目标对象实现的接口数组和InvocationHandler对象。当调用代理对象的addUser方法时,会触发LogInvocationHandler的invoke方法,从而实现了日志记录的增强。
3.2 CGLIB 动态代理
CGLIB(Code Generation Library)是一个第三方代码生成库,它可以在运行期为目标类生成子类,从而实现动态代理。与 JDK 动态代理不同,CGLIB 不需要目标对象实现接口,它适用于没有实现接口的类。
CGLIB 的核心类是net.sf.cglib.proxy.Enhancer和net.sf.cglib.proxy.MethodInterceptor。MethodInterceptor是一个接口,它定义了一个intercept方法,用于处理代理对象的方法调用。当通过代理对象调用方法时,会先调用MethodInterceptor的intercept方法,在该方法中可以实现对目标方法的增强。
下面是一个 CGLIB 动态代理的简单示例:
typescript
// 目标类(没有实现接口)
public class OrderService {
public void addOrder() {
System.out.println("添加订单");
}
}
// 实现MethodInterceptor接口的拦截器
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法执行前记录日志"); // 前置通知
Object result = proxy.invokeSuper(obj, args); // 调用目标方法
System.out.println("方法执行后记录日志"); // 后置通知
return result;
}
}
// 测试类
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class); // 设置目标类为父类
enhancer.setCallback(new LogMethodInterceptor()); // 设置拦截器
OrderService proxy = (OrderService) enhancer.create(); // 创建代理对象
proxy.addOrder();
}
}
运行结果:
方法执行前记录日志
添加订单
方法执行后记录日志
在上述示例中,Enhancer用于创建代理对象,通过setSuperclass方法设置目标类为父类,通过setCallback方法设置拦截器。当调用代理对象的addOrder方法时,会触发LogMethodInterceptor的intercept方法,实现了日志记录的增强。
3.3 JDK 动态代理与 CGLIB 动态代理的区别
- 依赖条件:JDK 动态代理要求目标对象实现接口;CGLIB 动态代理不需要目标对象实现接口,通过生成目标类的子类来实现代理。
- 效率:在生成代理对象时,JDK 动态代理的效率要高于 CGLIB;但在调用代理方法时,CGLIB 的效率通常要高于 JDK 动态代理,尤其是当方法调用次数较多时。
- 适用场景:如果目标对象实现了接口,优先使用 JDK 动态代理;如果目标对象没有实现接口,只能使用 CGLIB 动态代理。
四、Spring AOP 原理
Spring AOP 是基于动态代理实现的,它会根据目标对象是否实现接口来选择使用 JDK 动态代理还是 CGLIB 动态代理。如果目标对象实现了接口,Spring AOP 默认使用 JDK 动态代理;如果目标对象没有实现接口,Spring AOP 会使用 CGLIB 动态代理。当然,也可以通过配置强制使用 CGLIB 动态代理。
Spring AOP 的核心流程如下:
- 解析切面:Spring 在启动时会扫描配置的切面类,解析出切点和通知等信息。
- 匹配切点:根据切点表达式,找出所有需要被增强的目标方法。
- 创建代理对象:对于匹配到切点的目标对象,Spring 会为其创建代理对象。
- 织入通知:当通过代理对象调用目标方法时,代理对象会按照通知的类型和顺序,在目标方法执行前后或异常抛出时等时机执行通知中的逻辑,实现对目标方法的增强。
Spring AOP 还引入了一些特有的概念,如Advisor(通知器),它是切点和通知的组合,用于将切面中的信息传递给 Spring AOP 的代理机制。PointcutAdvisor是最常用的Advisor类型,它包含一个切点和一个通知。
五、总结
AOP 通过将横切关注点抽取为切面,利用动态代理机制在运行期实现了对业务逻辑的增强,有效解决了代码冗余和维护困难的问题。JDK 动态代理和 CGLIB 动态代理是 AOP 实现的两种主要方式,各有其适用场景。
Spring AOP 基于动态代理,简化了 AOP 的使用,使得开发者可以更专注于业务逻辑和横切关注点的实现。理解 AOP 的原理,有助于我们更好地使用 AOP 来优化代码结构,提高系统的可维护性和扩展性。