什么是AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
也就是,使用代理技术,在不修改代码的基础上,对已有方法进行增强。
AOP专业术语
目标类:需要被增强的类
连接点:需要被增强的类中可能要被增强的方法
切入点:就是被增强了的方法
通知/增强:增强的方法
切面:切入点和通知点的结合
织入:增强添加到目标类的具体链接点上的过程
代理:一个类被AOP织入增强后 就产生了一个结果代理对象
代理模式
AOP有两种代理模式 一种是JDK动态代理 一种是cglib代理
第一种必须要有接口和实现类 在有接口的情况下 自动采用JDK动态代理
第二种是没有接口的情况下 采用cglib代理 (使用前提 不能被final修饰)
切入点表达式
execution:用于匹配方法执行的连接点
execution表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数类型列表)[throws 异常])
返回值:
*任意返回值
包名:
包名.包名.*.dao 任意模块下的dao
包名.包名.*sys 固定后缀的包
包名.包名.. 任意包或子包
类名:
*类名 固定后缀
类名* 固定前缀
* 任意类
参数:
(..) 参数任意
(*) 任意一个参数
下面有一些练习参考
全匹配方式: public void com.czxy.service.impl.CustomerServiceImpl.saveCustomer() 访问修饰符可以省略 void com.czxy.service.impl.CustomerServiceImpl.saveCustomer() 返回值可以使用*号,表示任意返回值 * com.czxy.service.impl.CustomerServiceImpl.saveCustomer() 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.CustomerServiceImpl.saveCustomer() 使用..来表示当前包,及其子包 * com..CustomerServiceImpl.saveCustomer() 类名可以使用*号,表示任意类 * com..*.saveCustomer() 方法名可以使用*号,表示任意方法 * com..*.*() 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * com..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * com..*.*(..) 全通配方式: * *..*.*(..)
通知类型
前置通知
@Before 在方法的前面使用
@Before("execution(* com.czxy.demo16_aop.service..*.*(..))") public void myBeforeAdvice() { System.out.println("开启事务"); }
后置通知
@AfterReturning 在方法的后面使用 returning指定方法的的返回值变量名 方法形参名必须和返回值变量名一样 切入点(目标类被增强的方法)返回什么 返回值变量就是什么
1.// 返回值:类型 变量名 // 类型,必须是Object // 变量名,需要通过returning设置,且提供对应的方法参数 @AfterReturning(value="切入点表达式", returning = "返回值变量名") public void 方法名(Object 返回值变量名) { }
环绕通知
@Around 在方法的前后执行 ProceedingJoinPoint对象是JoinPoint的子接口 该对象只能用在@Around的切面方法中有两个方法:
Object proceed() throws Throwable 执行目标方法
Object proceed(Object[] var1) throws Throwable 传入的新的参数去执行目标方法
// 环绕通知:必须手动执行目标方法(连接点) // 1) ProceedingJoinPoint 可执行连接点 // 2) proceedingJoinPoint.proceed() 执行切入点(目标类的方法) // 3) 需要将切入点返回值返回给下一个调用者 @Around("execution(* com.czxy.demo16_aop.service..*.*(..))") public Object myAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前2"); // 执行目标 Object proceed = proceedingJoinPoint.proceed(); System.out.println("环绕后2"); return proceed; }
异常通知
@AfterThrowing 当切入点出现异常的时候 异常通知才会执行 把异常信息封装在Throwable中
// 异常通知,可以获得异常具体信息,异常类型(Throwable) 异常变量名 @AfterThrowing(value="execution(* com.czxy.demo16_aop.service..*.*(..))",throwing = "e") public void myAfterThrowingAdvice(Throwable e) { System.out.println("异常通知:" + e.getMessage()); }
最终通知
@Afrer 无论切入点是否报错 最终都会执行最终通知
@After("execution(* com.czxy.demo16_aop.service..*.*(..))") public void myAfterAdvice() { System.out.println("释放资源"); }
抽取切入点
当需要修改切入点表达式的时候 这时候就要一个一个去修改 很麻烦 所以我们把切入点抽取出来 方便我们修改切点表达式
// 抽取公共切入点表达式 @Pointcut("execution(* com.czxy.demo16_aop.service..*.*(..))") private void myPointcut() { } // 前置通知 // @Before("myPointcut()") public void myBeforeAdvice() { System.out.println("开启事务2"); }
小结
try{
// 前置通知、环绕通知
// 目标类的方法
// 后置通知、环绕通知
} catch() {
// 抛出异常通知
} finally {
// 最终通知
}
//前置通知 @Before
//后置通知 @AfterReturning
//环绕通知 @Around
//抛出异常通知 @AfterThrowing
//最终通知 @After
配置类
配置类必须开启切面支持 使用@EnableAspectJAutoProxy 而切面类需要使用@Aspect来标识它是一个切面类