AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。
使用AspectJ实现AOP有两种方式:
- 基于XML的声明式AspectJ
- 基于注解的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在元素内。
Spring配置文件中的
在Spring的配置文件中,配置切面使用的是 元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean,最后通过元素的ref属性即可引用该Bean。
| 属性名称 | 描述 |
| id | 用于定义该切面的唯一标识 |
| ref | 用于引用普通的Spring Bean |
在Spring的配置文件中,切入点是通过元素来定义的。
当元素作为元素的子元素时,表示该切入点是全局切入点,它可被多个切面所共享;
当元素作为元素的子元素时, 表示该切入点只对当前切面有效。
当元素写到的外部时,就变成了所有的切面都可使用,但是要想写在外面必须写在元素的上方。
| 属性名称 | 描述 |
| id | 用于指定切入点的唯一标识名称 |
| expression | 用于指定切入点关联的切入点表达式 |
该切入点表达式的含义是:匹配 cn.itcast.service.Impl 包中任意类的任意方法的执行
execution():是方法的主体
第一个 * :返回的类型,使用 * 表示代表所有类型
后面的包名:表示需要拦截的包名
第二个 * :表示类名,使用 * 表示所有的类
第三个 * :表示方法,使用 * 表示所有的方法
. . :表示方法的参数,这里表示任意参数
需要注意的是:第一个 * 与 包名之间有一个空格
配置通知在配置代码中,分别使用 的子元素配置了5种常用通知
| 属性名称 | 描述 |
| pointcut | 用于指定一个切入点表达式 |
| pointcut-ref | 指定一个已经存在的切入点名称,即pointcut中的id的值 |
| method | 最终通知,无论切入点方法是否正常执行它都会在其后面执行 |
| throwing | 异常通知,在切入点方法执行产生异常之后执行。注:它和后置通知永远只能执行一个 |
| returning | 后置通知,在切入点方法执行之后执行。注:它和异常通知永远只能执行一个 |
部分代码如下:
//切面 在此类中编写通知
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print(" 前置通知:模拟执行权限检查. . . , ");
System.out.print(" 目标类是: " + joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print(" 后置通知:模拟记录日志. . . ,");
System.out.println(" 被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
}
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务. . .");
//执行当前门标
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束 执行门标方法之后 模拟关闭事务.. ");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
//最终通知
public void myAfter() {
System.out.println("最终通知 模拟方法结束后的释放资源 . . ");
}
}
前置通知:模拟执行权限检查. . . , 目标类是: com.dfbz.aspectj.dao.Impl.UserDaoImpl@d706f19被植入增强处理的目标方法为: addUser 环绕开始:执行目标方法之前,模拟开启事务. . . 添加用户 最终通知 模拟方法结束后的释放资源 . . 环绕结束 执行门标方法之后 模拟关闭事务.. 后置通知:模拟记录日志. . . , 被植入增强处理的目标方法为: addUser
前置通知:模拟执行权限检查. . . , 目标类是: com.dfbz.aspectj.dao.Impl.UserDaoImpl@4b7dc788被植入增强处理的目标方法为: addUser 环绕开始:执行目标方法之前,模拟开启事务. . . 最终通知 模拟方法结束后的释放资源 . . 异常通知:出错了/ by zero基于注解的声明式AspectJ
基于XML的声明式AspectJ要便捷,但是也有一些缺点,就是在Spring配置文件中需要配置大量的代码信息。
AspectJ框架为AOP的实现提供了一套注解。
| 注解名称 | 描述 |
| @Aspect | 用来定义一个切面 |
| @Pointcut | 用来定义切入点表达式 |
| @Before | 用来定义前置通知 |
| @AfterReturning | 用来定义后置通知 |
| @Around | 用来定义环绕通知 |
| @AfterThrowing | 用来定义异常通知 |
| @After | 用来定义最终通知 |
部分代码如下:
//切面 在此类中编写通知
@Aspect
@Component
public class MyAspect {
//切入点表达式
@Pointcut("execution(* com.dfbz.dao.Impl.*.*(..))")
//切入点
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print(" 前置通知:模拟执行权限检查. . . , ");
System.out.print(" 目标类是: " + joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print(" 后置通知:模拟记录日志. . . ,");
System.out.println(" 被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务. . .");
//执行当前门标
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束 执行门标方法之后 模拟关闭事务.. ");
return obj;
}
//异常通知
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知 模拟方法结束后的释放资源 . . ");
}
}
目标对象类需要添加注解 :
@Repository("userDao")
配置文件如下:
运行结果如下:
环绕开始:执行目标方法之前,模拟开启事务. . . 前置通知:模拟执行权限检查. . . , 目标类是: com.dfbz.dao.Impl.UserDaoImpl@765d7657被植入增强处理的目标方法为: addUser 添加用户 环绕结束 执行门标方法之后 模拟关闭事务.. 最终通知 模拟方法结束后的释放资源 . . 后置通知:模拟记录日志. . . , 被植入增强处理的目标方法为: addUser
注意:
如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的;目标方法之后的后置通知和环绕通知的执行顺序也是未知的。



