切入点表达式
注意,不是参数,是参数类型
可以使用通配符描述切入点,快速描述
■ *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的通配符出现
execution(public∗com.itheima.∗.UserService.find(∗))
匹配com.itheima包下的任意类中的UserService类接口中所有find开头的带有一个参数的方法
■ ... :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(publicUsercom..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名为findById的方法
■ +:专用于匹配子类类型
execution(∗.∗.Service+..(∗..))
书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符对接口的开发采用public修饰
- 返回值类型对于增删改查类使用精确型匹配(可省略访问控制修饰符描述)
- 包含书签是尽量不使用..匹配,效率较低,常常做单个匹配或精确匹配
- 接口名称/类名名称与模块相关的最好采用..匹配,例如UserService书写为*Service,绑定业务层接口名称
- 方法名称书写以动词开头清晰匹配,例如采用..匹配,可以采用书写如getBy或.selectAll书写为selectAll
- 参数则较为复杂,根据具体方法灵活调整
- 通常不使用异常作为..匹配规则
AOP通知类型
@Around通知(用的最多)
@Pointcut("execution(* com.itheima.dao.BookDao.save())")
private void pointcut() {} // 切入点方法(save),具体实现为空
// 使用@Around注解定义一个环绕通知,用于在目标方法执行前后添加自定义逻辑
@Around("pointcut()") // pointcut()是切入点表达式,指定在哪些方法上应用此通知
public void way(ProceedingJoinPoint pjp) throws Throwable {
// 在目标方法执行前的逻辑
System.out.println("执行前");
// 执行目标方法
pjp.proceed(); // pjp.proceed()调用目标方法,允许原始方法继续执行
// 在目标方法执行后的逻辑
System.out.println("执行后");
}
返回Int的@Around通知
// 定义一个切入点,匹配com.itheima.dao.BookDao类中的select()方法
@Around("execution(int com.itheima.dao.BookDao.select(int))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前的逻辑
System.out.println("Before method: " + joinPoint.getSignature());
// 调用目标方法并获取返回值
Integer result = (Integer) joinPoint.proceed();
// 方法执行后的逻辑
System.out.println("After method: " + joinPoint.getSignature());
System.out.println("Method returned: " + result);
return result; // 返回目标方法的结果
}
上面的定义切入点可以直接用@Around声明。
如果切点函数的返回值为int或其他类型,强转为Integer(在函数前面限定返回值为Object)那么接收再最后return。
@AfterReturning:没有抛异常才会最后执行,可以访问方法的返回值。适合于记录成功操作的日志或修改返回结果。
@AfterThrowing:在目标方法抛出异常后执行通知,可以用于异常处理和错误记录。
案例:模拟测量运行10000次时间
@Around("execution(* com.itheima.mapper.UserMapper.SelectStudentById(int))") // 修改为匹配所有方法
public void testtime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 记录开始时间
// 执行目标方法 10,000 次
int iterations = 10000;
for (int i = 0; i < iterations; i++) {
// 调用目标方法并获取返回值
joinPoint.proceed();
}
long endTime = System.currentTimeMillis(); // 记录结束时间
long totalTime = endTime - startTime; // 计算总时间
// 输出总时间
System.out.println(joinPoint.getSignature() + "Total time for " + iterations + " executions: " + totalTime + " ms");
// 或者返回某个结果
}
@Test
public void TestSelectStudentById(){
User user = userMapper.SelectStudentById(2);
}
AOP通知获取参数
// 没有返回值的方法 @Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); } // 有返回值的方法 @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); Object ret = pjp.proceed(); return ret; }
抛出异常了解一下
案例:百度网盘密码格式处理
问题引入
@Around("DataAdvice.servicePt()")
public Object trimString(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 对原始参数的每一个参数进行操作
for (int i = 0; i < args.length; i++) {
// 如果是字符串数据
if (args[i].getClass().equals(String.class)) {
// 取出数据,trim()操作后,更新数据
args[i] = args[i].toString().trim();
}
}
return pjp.proceed(args);
}