AOP的全称是Aspect Oriented Programming(面向切面编程)
OOP语言提供了类与类之间纵向的关系(继承、接口),而AOP补充了横向的关系(比如在不改变目标类中源代码的情况下给com.john.demo.dao包下所有类中以insert和update开头的方法添加事务管理)
AspectJ是一个专门主打面向切面编程的框架。 它是使用一种特殊的语言(扩展自Java语言)来编写切面代码,后缀是.aj格式,并且需要使用专门的编译器将其编译成jvm可以运行的class文件。
SpringAOP底层也是使用了AspectJ的方案,但是在上层做了很多封装层面的工作,可以让开发人员直接使用Java代码来编写切面。并且由于使用的是标准的Java语言,所以并不需要在额外安装一个专门的编译器。但是由于开发人员直接接触的是Spring AOP,那么凡是Spring中没有实现的那些AOP功能,我们就用不了了,这种情况下只能跟产品经理撕逼或者去学习原生的AspectJ。
切面(Aspect)
简单来说,切面就是我们要往目标代码中插入进去的代码。
连接点(Join Pointer)
理论上所有可能会被切入的地方都可以称之为连接点
切入点(Pointcut)
选择某个连接点切入,将切面代码织入进去。这个连接点就叫做切入点。
织入(Weaving)
把切面代码糅合到目标代码中的过程就是织入。
通知(Advice)
通知决定了切面代码织入到目标代码中后,运行的时机(比如是在目标方法执行前,还是执行后)。
把aop的schema引入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
创建一个切面类,并且以bean的方式配置到IOC容器中
package com.lanou3g.spring;
public class MyAspect {
public void wakeup() {
System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?");
}
public void goToBed() {
System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了");
}
public void afterRetuing(Object message) {
System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message);
}
public void afterThrowing(Throwable ex) {
System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage());
}
/**
* 环绕通知
* 可以接受一个ProceedingJoinPoint参数
* 通过此参数可以获取到被切入方法的所有信息
* 还可以通过此参数来决定是否调用目标方法
*/
public void aroundAdvice(ProceedingJoinPoint joinPoint) {
// 连接点参数可以获取到被切入方法的所有信息
// 这里演示了如何获取被切入方法的名称
String targetMethodName = joinPoint.getSignature().getName();
System.out.println("[环绕通知]被切入的方法名:" + targetMethodName);
//
System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!");
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!");
}
}
<bean id="myAspect" class="com.lanou3g.spring.MyAspect" />
使用aop:config标签配置aop(将切面、切入点、通知结合到一起)
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.lanou3g.spring..*.oneDay*(..))" />
<aop:aspect ref="myAspect">
<aop:before method="wakeup" pointcut-ref="beforeOneDay" />
<aop:after method="goToBed" pointcut-ref="beforeOneDay" />
<aop:around method="aroundAdvice" pointcut-ref="beforeOneDay" />
<aop:after-returning method="afterRetuing" pointcut-ref="beforeOneDay" returning="message" />
<aop:after-throwing method="afterThrowing" pointcut-ref="beforeOneDay" throwing="ex" />
aop:aspect>
aop:config>
开启AOP注解支持
方式一:注解的方式
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
方式二:xml中开启
<aop:aspectj-autoproxy/>
/**
* 该切面用来插入起床的逻辑
*/
@Aspect
@Component //@Aspect注解没有将bean交给ioc容器管理的功能
public class MyAspect {
@Before("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
public void wakeup() {
System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?");
}
@After("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
public void goToBed() {
System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了");
}
@AfterReturning(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", returning = "message")
public void afterRetuing(Object message) {
System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message);
}
@AfterThrowing(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", throwing = "ex")
public void afterThrowing(Throwable ex) {
System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage());
}
/**
* 环绕通知
* 可以接受一个ProceedingJoinPoint参数
* 通过此参数可以获取到被切入方法的所有信息
* 还可以通过此参数来决定是否调用目标方法
*/
// @Around("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
// 连接点参数可以获取到被切入方法的所有信息
// 这里演示了如何获取被切入方法的名称
String targetMethodName = joinPoint.getSignature().getName();
System.out.println("[环绕通知]被切入的方法名:" + targetMethodName);
//
System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!");
Object ret = null;
try {
ret = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!");
return ret;
}
}
注意:@Aspect注解没有将bean交给ioc容器管理的功能,我们需要额外添加一个@Component注解
官方建议我们将所有的切入点统一定义到一个地方管理,在配置通知时通过引入的方式来使用。方便后期维护(一处修改,处处生效)
@Component
public class MyPointcut {
// 通过@Pointcut注解定义一个切入点
@Pointcut("execution(* oneDay(..))")
public void allOneDayMehtod() {}
}
参见定义切面部分
参见定义切面部分