这是学习黑马程序SSM的课程笔记,这小节主要是AOP的介绍
原视频地址:https://www.bilibili.com/video/BV1Fi4y1S7ix
AOP
简介
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它用于分离横切关注点(cross-cutting concerns)与主要业务逻辑的模块。横切关注点是那些存在于应用程序各处的功能,如日志记录、事务管理、安全性检查、性能优化等。AOP的主要目标是提高代码的模块化性,降低重复性代码,并提高代码的可维护性。
AOP的核心思想是将横切关注点抽象为切面(Aspect),然后将切面与主要业务逻辑代码进行解耦。这样,你可以在不修改主要业务逻辑代码的情况下,轻松地添加、修改或删除横切关注点。你的源代码不需要改,我就可以为它增加功能。无侵入式。
AOP工作流程
JDK代理(Java Dynamic Proxy)是Java语言中的一种动态代理机制,它允许在运行时创建代理类来代理实现了特定接口的目标对象。这种代理机制通常用于AOP(Aspect-Oriented Programming)和其他与横切关注点(cross-cutting concerns)相关的任务,如日志记录、事务管理、权限控制等。
JDK代理主要涉及以下两个核心组件:
-
InvocationHandler(调用处理器):这是一个接口,通常需要用户实现。它定义了代理对象的方法调用处理逻辑。当代理对象的方法被调用时,实现了InvocationHandler的类的
invoke
方法会被调用,从而允许开发人员在方法调用前后插入自定义的逻辑。 -
Proxy类:这是Java标准库提供的一个类,用于在运行时创建代理类。开发人员可以使用
Proxy.newProxyInstance
方法来创建代理对象,该方法需要传递一个类加载器、一组接口和一个InvocationHandler。
下面是一个简单的示例,演示了如何使用JDK代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface MyInterface {
void doSomething();
}
// 实现InvocationHandler接口的代理处理器类
class MyInvocationHandler implements InvocationHandler {
private final MyInterface target;
public MyInvocationHandler(MyInterface 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;
}
}
public class Main {
public static void main(String[] args) {
MyInterface realObject = new MyInterface() {
@Override
public void doSomething() {
System.out.println("Real object is doing something.");
}
};
// 创建代理对象
MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(
Main.class.getClassLoader(),
new Class[] { MyInterface.class },
new MyInvocationHandler(realObject)
);
// 通过代理对象调用方法
proxyObject.doSomething();
}
}
在上述示例中,我们定义了一个接口MyInterface
和一个实现了InvocationHandler
接口的代理处理器类MyInvocationHandler
。然后,我们通过Proxy.newProxyInstance
方法创建了一个代理对象,该代理对象实现了MyInterface
接口,并在代理对象的方法调用前后插入了自定义的逻辑。
JDK代理是一种非常有用的技术,用于实现动态代理和AOP,它可以帮助开发人员在运行时创建代理对象,并在不修改原始类的情况下添加额外的功能。
AOP切入点表达式
AOP通知类型
代码示例
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@Before:前置通知,在原始方法运行之前执行
// @Before("pt()")
public void before() {
System.out.println("before advice ...");
}
//@After:后置通知,在原始方法运行之后执行
// @After("pt2()")
public void after() {
System.out.println("after advice ...");
}
//@Around:环绕通知,在原始方法运行的前后执行
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
// @Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
// @AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
测量业务层接口执行方法
@Component
@Aspect
public class ProjectAdvice {
//匹配业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//设置环绕通知,在原始操作的运行前后记录执行时间
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
}
AOP通知获取数据
JoinPoint
是AOP(Aspect-Oriented Programming)中的一个概念,它表示在程序执行过程中可以被拦截的点。在Spring框架和AspectJ等AOP框架中,JoinPoint
是一个重要的上下文对象,用于获取和操作连接点的信息。
JoinPoint
提供了以下信息和功能:
-
连接点的方法信息:你可以通过
JoinPoint
获取正在执行的方法的信息,包括方法名、参数、返回类型等。 -
目标对象信息:你可以获得目标对象(被代理的对象)的引用。
-
连接点的静态部分:连接点是程序执行过程中的一个具体时刻,它包括一个静态部分(例如方法签名)和一个动态部分(例如方法参数的值)。
-
对连接点进行干预:在环绕通知中,你可以使用
ProceedingJoinPoint
对象来控制连接点的执行,包括是否执行连接点,是否修改连接点的参数或返回值等。
在Spring框架中,当你创建切面并使用注解 @Before
、@After
、@Around
等时,你可以将 JoinPoint
作为方法参数来访问连接点的信息。例如:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.MyService.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 访问连接点的方法信息
String methodName = joinPoint.getSignature().getName();
System.out.println("Before executing method: " + methodName);
// 访问目标对象信息
Object targetObject = joinPoint.getTarget();
System.out.println("Target object: " + targetObject.getClass().getName());
}
}
在上述示例中,JoinPoint
被用于获取连接点的方法名和目标对象信息,并在前置通知中输出这些信息。
日志例子
当谈论AOP(Aspect-Oriented Programming,面向切面编程),可以使用一个通俗易懂的例子来解释它:
例子:日志记录
假设你正在开发一个电子商务网站,你希望记录每次用户购买商品的操作以进行日志记录。你可以使用AOP来实现这个功能。
传统方法是在每个购买操作的方法中手动插入日志记录的代码,例如:
public class ShoppingCartService {
public void purchaseItem(User user, Product product) {
// 执行购买操作
// 记录日志
Logger.log("User " + user.getUsername() + " purchased " + product.getName());
}
}
但这样做会导致以下问题:
-
污染业务逻辑:日志记录代码与购买操作混在一起,使代码难以阅读和维护。
-
重复代码:如果你的应用有多个地方需要记录日志,你需要在每个地方复制相同的日志记录代码。
使用AOP,你可以将日志记录操作抽象为一个切面(Aspect),并将其应用到需要的地方,而不必在每个方法中手动插入日志记录代码。
首先,你创建一个日志切面:
@Aspect
public class LoggingAspect {
@Before("execution(* com.example..*Service.purchase*(..)) && args(user, product)")
public void logPurchase(User user, Product product) {
Logger.log("User " + user.getUsername() + " purchased " + product.getName());
}
}
然后,在需要记录日志的地方,你只需使用 @LogPurchase
注解标记:
@Service
public class ShoppingCartService {
@LogPurchase
public void purchaseItem(User user, Product product) {
// 执行购买操作
}
}
这样,你将日志记录的关注点从业务逻辑中分离出来,代码更加清晰,可维护性更高。而且,如果你想要在其他地方记录购买操作的日志,只需使用 @LogPurchase
注解即可,不必复制日志记录代码。
总之,AOP允许你通过将横切关注点(如日志记录、安全性、事务管理等)与主要业务逻辑分离,以实现更清晰、可维护和可重用的代码。这个示例中的日志记录就是AOP的一个典型应用。