AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。
使用AspectJ实现AOP有两种方式:
- 基于XML的声明式AspectJ
- 基于注解的声明式AspectJ
基于XML的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。
Spring配置文件中的<beans>元素中可以包含多个<aop:config>元素,一个<aop:config> 元素中又可以包含属性和子元素,其子元素包括<aop:aspect>元素下,同样包含了属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在XML文件中配置切面、切入点和通知。
配置切面
在Spring的配置文件中,配置切面使用的是 <aop:aspect> 元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean,最后通过元素的ref属性即可引用该Bean。
属性名称 | 描述 |
id | 用于定义该切面的唯一标识 |
ref | 用于引用普通的Spring Bean |
配置切入点
在Spring的配置文件中,切入点是通过<aop:pointcut>元素来定义的。
当<aop:pointcut>元素作为<aop:config>元素的子元素时,表示该切入点是全局切入点,它可被多个切面所共享;
当<aop:pointcut>元素作为<aop:aspect>元素的子元素时, 表示该切入点只对当前切面有效。
当<aop:pointcut>元素写到<aop:aspect>的外部时,就变成了所有的切面都可使用,但是要想写在外面必须写在<aop:aspect>元素的上方。
属性名称 | 描述 |
id | 用于指定切入点的唯一标识名称 |
expression | 用于指定切入点关联的切入点表达式 |
<!--全局切入点-->
<aop:pointcut id="pt1" expression="execution(* cn.itcast.service.Impl.*.*(..))"/>
</aop:aspect>
</aop:config>
该切入点表达式的含义是:匹配 cn.itcast.service.Impl 包中任意类的任意方法的执行
execution():是方法的主体
第一个 * :返回的类型,使用 * 表示代表所有类型
后面的包名:表示需要拦截的包名
第二个 * :表示类名,使用 * 表示所有的类
第三个 * :表示方法,使用 * 表示所有的方法
. . :表示方法的参数,这里表示任意参数
需要注意的是:第一个 * 与 包名之间有一个空格
配置通知
在配置代码中,分别使用 <aop:aspect>的子元素配置了5种常用通知
属性名称 | 描述 |
pointcut | 用于指定一个切入点表达式 |
pointcut-ref | 指定一个已经存在的切入点名称,即pointcut中的id的值 |
method | 最终通知,无论切入点方法是否正常执行它都会在其后面执行 |
throwing | 异常通知,在切入点方法执行产生异常之后执行。注:它和后置通知永远只能执行一个 |
returning | 后置通知,在切入点方法执行之后执行。注:它和异常通知永远只能执行一个 |
部分代码如下:
//切面 在此类中编写通知
/**
* 通知中使用了 JoinPoint接口 及其子接口 ProceedingJoinPoint
* 作为参数来获得目标对象的类名、目标方法名和目标参数等
*
* 需要注意的是:
* 环绕通知必须接受 ProceedingJoinPoint类型参数
* 返回值必须也是Object类型
* 且必须抛出异常
*
*/
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());
}
/**
* 环绕通知
* ProceedingJoinPoint JoinPoint 子接口,表示可以执行目标方法
* 1.必须是 Object 类型的返回值
* 2.必须接收一个参数,类型为 ProceedingJoinPoint
* 3.必须 throws Throwable
**/
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("最终通知 模拟方法结束后的释放资源 . . ");
}
}
<!--1.目标对象-->
<bean id="userDao" class="com.dfbz.aspectj.dao.Impl.UserDaoImpl"></bean>
<!--2.切面-->
<bean id="myAspect" class="com.dfbz.aspectj.xml.MyAspect"></bean>
<!--3.AOP-->
<!--
注意:
后置通知只有在目标方法成功执行后才会执行后置通知
最终通知不管目标方法是否有异常都会执行最终通知
-->
<aop:config>
<!--配置切面-->
<aop:aspect id="" ref="myAspect">
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.dfbz.aspectj.dao.Impl.*.*(..))"></aop:pointcut>
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="pt"></aop:before>
<!--后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="pt"
returning="joinPoint"></aop:after-returning>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="pt"></aop:around>
<!--异常通知 如果没有异常,将不会执行增强-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="pt" throwing="e"></aop:after-throwing>
<!--最终通知-->
<aop:after method="myAfter" pointcut-ref="pt"></aop:after>
</aop:aspect>
</aop:config>
前置通知:模拟执行权限检查. . . , 目标类是: 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());
}
/**
* 环绕通知
* ProceedingJoinPoint JoinPoint 子接口,表示可以执行目标方法
* 1.必须是 Object 类型的返回值
* 2.必须接收一个参数,类型为 ProceedingJoinPoint
* 3.必须 throws Throwable
**/
@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")
配置文件如下:
<!--指定需要扫描的包,使注解生效-->
<context:component-scan base-package="com.dfbz"/>
<!--启动基于注解的声明式AspectJ-->
<aop:aspectj-autoproxy/>
运行结果如下:
环绕开始:执行目标方法之前,模拟开启事务. . .
前置通知:模拟执行权限检查. . . , 目标类是: com.dfbz.dao.Impl.UserDaoImpl@765d7657被植入增强处理的目标方法为: addUser
添加用户
环绕结束 执行门标方法之后 模拟关闭事务..
最终通知 模拟方法结束后的释放资源 . .
后置通知:模拟记录日志. . . , 被植入增强处理的目标方法为: addUser
注意:
如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的;目标方法之后的后置通知和环绕通知的执行顺序也是未知的。