AOP
说说什么是 AOP
AOP,也就是 Aspect-oriented Programming,译为面向切面编程。
简单点说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。
三分
举个例子,假如我们现在需要在业务代码开始前进行参数校验,在结束后打印日志,该怎么办呢?
我们可以把日志记录
和数据校验
这两个功能抽取出来,形成一个切面,然后在业务代码中引入这个切面,这样就可以实现业务逻辑和通用逻辑的分离。
业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。
我们来回顾一下 Java 语言的执行过程:
AOP 的核心是动态代理,可以使用 JDK 动态代理来实现,也可以使用 CGLIB 来实现。
有哪些核心概念?
-
切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
-
连接点(Join Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造方法
-
切点(Pointcut):对连接点进行拦截的定位
-
通知(Advice):指拦截到连接点之后要执行的代码,也可以称作增强
-
目标对象 (Target):代理的目标对象
-
引介(introduction):一种特殊的增强,可以动态地为类添加一些属性和方法
-
织入(Weabing):织入是将增强添加到目标类的具体连接点上的过程。可以分为 3 种类型的织入:
①、编译期织入:切面在目标类编译时被织入。
②、类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
③、运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面。
Spring 采用运行期织入,而 AspectJ 采用编译期织入和类加载器织入。
有哪些环绕方式?
AOP 一般有 5 种环绕方式:
-
前置通知 (@Before)
-
返回通知 (@AfterReturning)
-
异常通知 (@AfterThrowing)
-
后置通知 (@After)
-
环绕通知 (@Around)
多个切面的情况下,可以通过 @Order
指定先后顺序,数字越小,优先级越高。
总结
总结一下:
AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。
AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和织入(Weaving)等。
① 像日志打印、事务管理等都可以抽离为切面,可以声明在类的方法上。
② 在 Spring AOP 中,连接点总是表示方法的执行。
③ Spring AOP 支持五种类型的通知:前置通知、后置通知、环绕通知、异常通知、最终通知等。
④ 在 AOP 中,切点用于指定我们想要在哪些连接点上执行通知的规则。
⑤ 织入是指将切面应用到目标对象并创建新的代理对象的过程。Spring AOP 默认在运行时通过动态代理方式实现织入。
像 @Transactional
注解,就是一个典型的 AOP 应用,它就是通过 AOP 来实现事务管理的。我们只需要在方法上添加 @Transactional
注解,Spring 就会在方法执行前后添加事务管理的逻辑。
使用 AOP 吗
利用 AOP 实现过日志记录,数据检验和时间记录。
下面是一个基本例子:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Aspect public class LoggingAspect { private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); // 定义一个切入点,表示在哪些方法执行前插入切面代码 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceMethod() {} // 定义一个通知,在切入点方法执行前调用 @Before("serviceMethod()") public void logBeforeServiceMethod(JoinPoint joinPoint) { logger.info("Executing method: " + joinPoint.getSignature().getName()); } }
说说 JDK 动态代理和 CGLIB 代理?
JDK动态代理
JDK动态代理是Java标准库提供的一种动态代理机制,它允许在运行时创建一个实现了一组给定接口的新代理类。这种代理类可以用来代理接口的实例,从而在不修改原始类的情况下,为对象的方法调用添加额外的处理逻辑。
JDK动态代理的核心是java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。下面是JDK动态代理的基本使用步骤:
-
定义接口: 首先,定义一个或多个接口,这些接口定义了代理类将要实现的方法。
-
实现InvocationHandler: 创建一个实现了
InvocationHandler
接口的类,这个类将负责处理代理类上的方法调用。在invoke
方法中,可以添加额外的逻辑,比如日志记录、事务管理、性能监控等。 -
创建代理对象: 使用
Proxy
类的newProxyInstance
方法创建代理对象。这个方法需要三个参数:类加载器、代理类要实现的接口数组、以及一个InvocationHandler
实例。 -
调用代理对象的方法: 通过代理对象调用其方法时,实际上会调用
InvocationHandler
的invoke
方法,并将方法调用转发给原始对象(如果需要)。
// 步骤1: 定义接口 public interface Subject { void doSomething(); } // 步骤2: 实现InvocationHandler import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class SubjectInvocationHandler implements InvocationHandler { private Object target; // 原始对象 public SubjectInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method invocation"); Object result = method.invoke(target, args); // 调用原始对象的方法 System.out.println("After method invocation"); return result; } } // 步骤3: 创建代理对象 import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { // 创建原始对象 Subject realSubject = new RealSubject(); // 创建InvocationHandler InvocationHandler handler = new SubjectInvocationHandler(realSubject); // 创建代理对象 一般这个创建代理要写个方法 Subject proxy = (Subject) Proxy.newProxyInstance( realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler ); // 调用代理对象的方法 proxy.doSomething(); } } // 步骤4: 调用代理对象的方法 class RealSubject implements Subject { @Override public void doSomething() { System.out.println("RealSubject doing something"); } }
cglib动态代理
-
添加CGLIB依赖: 首先,需要在项目中添加CGLIB的依赖。如果使用Maven,可以在
pom.xml
中添加如下依赖:<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
创建MethodInterceptor: 创建一个实现了
net.sf.cglib.proxy.MethodInterceptor
接口的类,这个类将负责处理代理类上的方法调用。在intercept
方法中,可以添加额外的逻辑,比如日志记录、事务管理、性能监控等。 -
创建Enhancer: 使用
net.sf.cglib.proxy.Enhancer
类来创建代理对象。设置目标类的类加载器、设置回调接口(即MethodInterceptor
实例),并指定要代理的目标类。 -
创建代理对象: 调用
Enhancer
的create
方法来创建代理对象。 -
调用代理对象的方法: 通过代理对象调用其方法时,实际上会调用
MethodInterceptor
的intercept
方法,并将方法调用转发给原始对象(如果需要)
简单例子:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 目标类 public class TargetClass { public void doSomething() { System.out.println("TargetClass doing something"); } } // MethodInterceptor实现 public class TargetInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before method invocation"); Object result = proxy.invokeSuper(obj, args); // 调用原始对象的方法 System.out.println("After method invocation"); return result; } } // 客户端代码 public class Client { public static void main(String[] args) { // 创建Enhancer 一般创建代理要写个方法 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TargetClass.class); enhancer.setCallback(new TargetInterceptor()); // 创建代理对象 TargetClass proxy = (TargetClass) enhancer.create(); // 调用代理对象的方法 proxy.doSomething(); } }
在这个示例中,TargetClass
是目标类,它没有实现任何接口。TargetInterceptor
实现了MethodInterceptor
接口,并在intercept
方法中添加了额外的逻辑。客户端代码创建了Enhancer
实例,设置了目标类和回调接口,并创建了代理对象。当调用代理对象的doSomething()
方法时,实际上会调用TargetInterceptor
的intercept
方法,并在调用原始对象的方法前后添加了日志输出。
CGLIB动态代理在需要代理没有实现接口的类时非常有用,但它也有一些限制,比如不能代理final类和final方法。
选择 CGLIB 还是 JDK 动态代理?
-
如果目标对象没有实现任何接口,则只能使用 CGLIB 代理。如果目标对象实现了接口,通常首选 JDK 动态代理。
-
虽然 CGLIB 在代理类的生成过程中可能消耗更多资源,但在运行时具有较高的性能。对于性能敏感且代理对象创建频率不高的场景,可以考虑使用 CGLIB。
-
JDK 动态代理是 Java 原生支持的,不需要额外引入库。而 CGLIB 需要将 CGLIB 库作为依赖加入项目中