AspectJ
AspectJ是一个基于java预言的AOP框架,它提供了强大的AOP功能,Spring2.0以后SpringAOP引入了对AspectJ的支持
AspectJ实现AOP有两种方式:
- 基于xml的声明式AspectJ
- 配置切面
在Spring的配置文件中,配置切面使用的事<aop:aspect>元素,该元素会将一个定义好的SpringBean转化成切面Bean,所以要在配置文件中首先定义一个普通的SpringBean,通过<aop:aspect>元素的ref引入即可
<aop:aspect>元素的常用属性:
id用于定义该切面的为一组标识名称
ref:用于引用普通的SpringBean
-
- 配置切入点
在Spring的配置文件中,切入点是通过<aop:pointcut>元素类定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可以被多个切面所共享。当<aop:pointcut>元素作为<aop:adpect>元素的子元素时,表示该切入点支队当前切面有效
<aop:pointcut>常用的属性
id:用于指定切入点的唯一标识名称
expression:用于指定切入点关联的切入点表达式
-
- 配置通知
图解:
实例代码:
建包建类
切面类:
package com.bdqn.cn.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
// 切面类
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:做一个检查动作...");
System.out.println("目标类:"+joinPoint.getTarget());
System.out.println("被植入的目标方法:"+joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟日志记录...");
System.out.println("被植入的目标方法:"+joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedingJoinPoint 是 JoinPoint的子接口,表示可以执行目标方法
* 1、必须是Object返回值类型
* 2、必须接收一个参数,类型为ProceedingJoinPoint
* 3、必须throws Throwable
*
*/
public Object myArround(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("最终通知:模拟方法结束后的释放资源...");
}
}
配置文件编写:
<!-- 目标类 -->
<bean id="userDao" class="com.bdqn.cn.dao.UserDaoImpl" />
<!-- 定义切面Bean -->
<bean id="myAspect" class="com.bdqn.cn.aspect.MyAspect" />
<aop:config>
<!-- 1、配置切面 -->
<aop:aspect id="aspect" ref="myAspect">
<!-- 2、配置切入点 切入点表达式:execution(* *..*.*(..) -->
<aop:pointcut expression="execution(* *..*.*(..))" id="mypointcut"/>
<!-- 3、配置通知 -->
<!-- 配置前置通知 -->
<aop:before method="myBefore" pointcut-ref="mypointcut"/>
<!--
配置后置通知,在方法返回后执行,就可以获得返回值,
returning属性:用于设置后置通知的第二个参数的名称类型是Object
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="mypointcut" returning="returnVal"/>
<!-- 配置环绕通知 -->
<aop:around method="myArround" pointcut-ref="mypointcut"/>
<!-- 配置异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="mypointcut" throwing="e"/>
<!-- 配置最终通知 -->
<aop:after method="myAfter" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
通过观看配置文件,我们知道在<aop>编程里面没有写有关目标类的内容,那么增强代码是如何植入到目标类中的呢?
<aop:config>元素及其子元素
<aop:config>:开发AspectJ的顶层配置元素,在配置文件的<beans>下可以包含多个该元素
<aop:aspect>:配置一个切面,<aop:config>元素的子元素,属性ref指定切面的定义
<aop:pointcut>:配置切入点,<aop:aspect>元素的子元素,属性expression指定通知增强那些方法
切入点表达式:
<aop:pointcut>常用的属性
id:用于指定切入点唯一标识名称
expression:用于指定关联的切入点表达式
万能表达式:execution(* *..*.*(..)
<aop:pointcut expression=" execution(* com.bdqn.cn.dao.*.*(..))" id="唯一标识符"/>
该切入点表达式表示匹配com.bdqn.cn.dao包下的任意方法
第一个*:表示返回类型,使用*表示所有类型
com.bdqn.cn.dao:表示需要拦截的包名
后面第二个*表示类名
第三个*表示方法名
后面(..)表示方法的参数
“..”:表示任意参数
<aop:before>:配置前置通知,<aop:aspect>元素的子元素,属性method指定前置通知的方法,属性pointcut-ref指定关联的切入点
<aop:after-returning>:配置后置返回通知,<aop:aspect>元素的子元素,属性method指定后置通知的方法,属性pointcut-ref指定关联的切入点
<aop:around>:配置环绕通知,<aop:aspect>元素的子元素,属性method表示指定环绕通知方法,属性pointcut-ref指定关联的切入点
<aop:after-throwing>:配置异常通知,<aop:aspect>元素的子元素,属性method表示指定异常通知方法,属性pointcut-ref指定关联的切入点,没有异常发生时,不会执行
<aop:after>:配置最终通知,<aop:aspect>元素的子元素,属性method指定最终通知的方法,属性pointcut-ref指定关联的切入点
AspectJ需要导入的包:
2、注解版AspectJ的使用
@Aspect 用于定义一个切面
@Pointcut 用于定义切入点。在使用时还需定义一个包含名字和任意参数的方法签名来表示切入点名称,实际上这个方法签名就一个返回值为void,且方法为空的普通方法
@Before 用于配置前置通知,在使用时,需要制定一个value属性值,该属性值用于指定一个切入点表达式
@AfterReturning:用于定义后置通知
@Around 用于配置环绕通知
@AfterThrowing 用于配置异常通知
@After 用于配置最终通知
实例代码:
package com.bdqn.cn.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
// 切面类
@Aspect
@Component
public class MyAspect {
// 定义切入点
@Pointcut("execution(* com.bdqn.cn.dao.UserDaoImpl.*(..))")
private void myPointcut(){}
// 前置通知
@Before("myPointcut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:做一个检查动作...");
System.out.println("目标类:"+joinPoint.getTarget());
System.out.println("被植入的目标方法:"+joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning("myPointcut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知:模拟日志记录...");
System.out.println("被植入的目标方法:"+joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedingJoinPoint 是 JoinPoint的子接口,表示可以执行目标方法
* 1、必须是Object返回值类型
* 2、必须接收一个参数,类型为ProceedingJoinPoint
* 3、必须throws Throwable
*
*/
@Around("myPointcut()")
public Object myArround(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("最终通知:模拟方法结束后的释放资源...");
}
}
配置文件
实现类:
package com.bdqn.cn.dao;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户");
}
@Override
public void deleteUser() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
}
测试类:
@Test
public void method(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao ud = (UserDao)app.getBean("userDao");
ud.addUser();
ud.deleteUser();
}