概念
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。
1、为什么要引入AOP?
看个案例,我们两个数进行加减乘除,并在执行方法前后加入一些日志输出。
package com.zcm.spring_aop.service.v1;
/**
* @program: demo
* @ClassName:MyCalculator
* @Description:
* @Author:zcm
* @Date:2021/3/4 9:44
*/
public class MyCalculator implements Calculatoy {
public int add(int i, int j) {
System.out.println("add 方法开始执行,参数为:" + i + "," + j);
int result = i + j;
System.out.println("add 方法开始完成结果为:" + result);
return result;
}
public int sub(int i, int j) {
System.out.println("sub 方法开始执行,参数为:" + i + "," + j);
int result = i - j;
System.out.println("sub 方法开始完成结果为:" + result);
return result;
}
public int mult(int i, int j) {
System.out.println("mult 方法开始执行,参数为:" + i + "," + j);
int result = i * j;
System.out.println("mult 方法开始完成结果为:" + result);
return result;
}
public int except(int i, int j) {
System.out.println("except 方法开始执行,参数为:" + i + "," + j);
int result = i / j;
System.out.println("except 方法开始完成结果为:" + result);
return result;
}
public static void main(String[] args) {
MyCalculator myCalculator = new MyCalculator();
myCalculator.add(4, 2);
myCalculator.sub(4, 2);
myCalculator.mult(4, 2);
myCalculator.except(4, 2);
}
}
interface Calculatoy {
public int add(int i, int j);
public int sub(int i, int j);
public int mult(int i, int j);
public int except(int i, int j);
}
以上是最简单的javaSE的代码实现,上面这段代码只是简单的做一些日志输出,自己写的代码会加入一些已异常处理的日志,以上这段代码就非常死板和冗余,那么我们可不可以以上代码稍微简化一些呢?看案例2:
package com.zcm.spring_aop.service.v2;
import java.util.*;
/**
* @program: demo
* @ClassName:MyCalculator
* @Description:
* @Author:zcm
* @Date:2021/3/4 14:38
*/
public class MyCalculator implements Calculator {
public int add(int i, int j) {
LogUtil.start(i, j);
int result = i + j;
LogUtil.stop(result);
return result;
}
public int sub(int i, int j) {
LogUtil.start(i, j);
int result = i - j;
LogUtil.stop(result);
return result;
}
public int mult(int i, int j) {
LogUtil.start(i, j);
int result = i * j;
LogUtil.stop(result);
return result;
}
public int except(int i, int j) {
LogUtil.start(i, j);
int result = i /j;
LogUtil.stop(result);
return result;
}
}
/**
* @Description:计算的接口
* @Author: zcm
* @Version:v.2.7.1
* @Date:2021/3/4 14:47
*/
interface Calculator {
public int add(int i, int j);
public int sub(int i, int j);
public int mult(int i, int j);
public int except(int i, int j);
}
/**
* @Description:日志工具类
* @Author: zcm
* @Version:v.2.7.1
* @Date:2021/3/4 14:47
*/
class LogUtil {
public static void start(Object... objects) {
System.out.println("XXX方法开始执行,使用的参数是:" + Arrays.asList(objects));
}
public static void stop(Object... objects) {
System.out.println("XXX方法执行结束,结果是:" + Arrays.asList(objects));
}
public static void exception(Exception e) {
System.out.println("XXX方法执行异常:" + e.getMessage());
}
public static void end() {
System.out.println("XXX方法执行结束~~~~~");
}
}
class Test {
public static void main(String[] args) {
MyCalculator myCalculator = new MyCalculator();
myCalculator.add(8, 2);
myCalculator.sub(8, 2);
myCalculator.mult(8, 2);
myCalculator.except(8, 2);
}
}
案例2比案例1 简化了一部分代码,但是案例2没办法知道那个方法打印出来的日志,在真正的研发一款项目中,日志是非常重要的,线上除了什么问题全靠这个来快速定位问题,那么真正的日志肯定是要知道那个类或者那个接口中的具体的那个方法中的第几行等等,所以这个代码还不够完善,我们来进一步优化。
package com.zcm.spring_aop.service.v3;
import java.lang.reflect.*;
import java.util.*;
/**
* @program: demo
* @ClassName:Mycalculator
* @Description:必须要有接口,如果没有接口,不能使用,这种方式是用jdk提供的reflect包下的类 但是在生产环境中我不能保证每个类都有实现的接口,所有有第二种方式cglib
* cglib在实现的时候有没有接口都无所谓
* 在spring中使用了两种动态代理的方式,一种是jdk提供的(刚刚完成的)另外一种就是cglib
* @Author:zcm
* @Date:2021/3/4 14:55
*/
class MyCalculator implements Calculator {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mult(int i, int j) {
return i * j;
}
public int except(int i, int j) {
return i / j;
}
}
interface Calculator {
public int add(int i, int j);
public int sub(int i, int j);
public int mult(int i, int j);
public int except(int i, int j);
}
/**
* @program: demo
* @ClassName:CalculatorProxy
* @Description:代理对象的类
* @Author:zcm
* @Date:2021/3/4 10:16
*/
class CalculatorProxy {
/**
* @Description:为传入的参数对象创建一个动态代理对象
* @Author: zcm
* @Version:v.2.7.1
* @Date:2021/3/4 15:25
*/
public static Calculator getProxy(final Calculator calculatoy) {
//1.获取类加载器
ClassLoader loader = calculatoy.getClass().getClassLoader();
//2.获取被代理的接口
Class<?>[] interfaces = calculatoy.getClass().getInterfaces();
//3.获取方法执行器,执行被代理对象的目标方法
InvocationHandler invocationHandler = new InvocationHandler() {
/**
*@Description:执行目标方法
*@Author: zcm
* @param:proxy 代理对象。给jdk使用,任何时候都不要操作该对象
* @param method 当前要执行法人目标对象的方法
* @param args 这个方法调用时外界传进来的参数
*@Version:v.2.7.1
*@Date:2021/3/4 15:25
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println(proxy.getClass());
System.out.println(method.getName() + "方法开始执行,参数是:" + Arrays.asList(args));
//利用反射执行目标方法后的返回值
result = method.invoke(calculatoy, args);
System.out.println(method.getName() + "方法开始结束,结果是:" + Arrays.asList(result));
} catch (Exception e) {
System.out.println(method.getName() + "方法发生异常,参数是:" + Arrays.asList(args));
} finally {
System.out.println(method.getName() + "方法执行结束了");
}
//最后结果返回
return result;
}
};
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, invocationHandler);
return (Calculator) proxyInstance;
}
}
/**
* @Description:动态代理
* @Author: zcm
* @Version:v.2.7.1
* @Date:2021/3/4 17:58
*/
class DynamicProxy implements InvocationHandler {
public DynamicProxy() {
}
/**
* @Description:被代理对象
* @Author: zcm
* @Version:v.2.7.1
* @Date:2021/3/4 17:58
*/
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
Object result = null;
try {
System.out.println(method.getName() + "方法开始执行,参数是:" + Arrays.asList(args));
result = method.invoke(object, args);
System.out.println(method.getName() + "方法开始结束,结果是:" + Arrays.asList(result));
} catch (Exception e) {
System.out.println(method.getName() + "方法发生异常,参数是:" + Arrays.asList(args));
} finally {
System.out.println(method.getName() + "方法执行结束了");
}
return result;
}
}
/**
*@Description:
* JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
* CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
*@Author: zcm
*@Version:v.2.7.1
*@Date:2021/3/4 18:05
*/
class TestProxy {
public static void main(String[] args) {
//代理类使用方式一:
System.out.println("代理类使用方式一:");
//1.代理类传入被代理类
Calculator calculator = (Calculator) CalculatorProxy.getProxy(new MyCalculator());
calculator.add(1, 1);
calculator.sub(1, 1);
calculator.mult(1, 1);
calculator.except(1, 1);
//代理类使用方式二:
System.out.println("代理类使用方式二:");
System.out.println("----------------------------------------");
System.out.println("========================================");
MyCalculator object = new MyCalculator();
InvocationHandler invocationHandler = new DynamicProxy(object);
Calculator o = (Calculator) Proxy.newProxyInstance(
invocationHandler.getClass().getClassLoader(),
object.getClass().getInterfaces(),
invocationHandler);
o.add(1, 1);
o.sub(1, 1);
System.out.println(o.getClass());
o.mult(1, 0);
o.except(1, 0);
}
}
案例3这种写法已经很完美了,这种动态代理的实现方式调用的是jdk的基本实现,如果需要代理的目标对象没有实现任何接口,那么是无法为他创建代理对象的,这也是致命的缺陷。而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。
一、AOP核心
- 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以`@Aspect`注解(@AspectJ 注解方式)来实现。
- 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
- 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
- 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
- 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 `IsModified`接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
- 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
- AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。 - 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
二、AOP的通知类型
- 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
- 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
- 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
- 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
- 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
三、AOP使用场景
- 权限认证
- 日志管理
- 安全认证
- 事务控制
四、AOP的使用
方式之一【注解方式】:
1.pom.xml依赖
<properties>
<lombok.version>1.18.10</lombok.version>
<spring.version>5.2.3.RELEASE</spring.version>
<logging.version>1.2</logging.version>
<alibaba.version>1.1.8</alibaba.version>
<mysql.version>8.0.17</mysql.version>
<junit.version>4.12</junit.version>
<elasticsearch.version>5.4.3</elasticsearch.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
LogUtil.java
package com.zcm.util;
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.*;
import java.util.*;
/**
* @program: spring-aop
* @ClassName:LogUtil
* @Description:
* @Author:zcm
* @Date:2021/3/22 17:52
*/
@Component
@Aspect
public class LogUtil {
/**
* @Description:如果有多个匹配的表达式相同,定义一个没有返回值的空方法, 给该方法添加@PointCut注解,后续在使用的时候可以直接调用方法名称
* 此处的方法只是起一个声明的作用,能够供内部的其他通知方法进行调用
* 如果想要添加其他参数,必须要添加args(参数列表),ArgNames(参数列表)
* @Before(value = "execution(public Integer com.zcm.spring_aop.service.v4.MyCalculator.*(Integer,Integer))
* && args(joinPoint,k)",argNames = "joinPoint,k")
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 15:57
*/
@Pointcut(value = "execution(* com.zcm.service.impl.CalculateServiceImpl.*(..))")
public void myPointcut() {
}
/**
* @Description:前置通知: 使用AOP的时候想要获取方法中的参数,方法名称必须使用JoinPoint对象,并且此参数必须是第一位。
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 15:53
*/
@Before(value = "myPointcut()")
public Integer start(JoinPoint joinPoint) {
//获取第一位参数
Integer i = (Integer) joinPoint.getArgs()[0];
//获取第二位参数
Integer j = (Integer) joinPoint.getArgs()[1];
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println("log---------" + name + "执行开始参数是:" + i + ":" + j);
return i + j;
}
/**
* @Description:后置通知【finally块】 通知方法在定义的时候有没有什么特殊的要求?
* 通知方法在定义的时候对于访问修饰符、返回值类型都没有明确的要求, 但是要注意,参数不能随便添加
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 9:36
*/
@After(value = "myPointcut()")
public void stop(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println("log---------" + name + "执行结束,finally-------------");
}
/**
* @Description:返回通知 如果方法中有返回值,那么必须要在注解中添加
* Returing="result" ,这个result必须要和参数列表中的参数名称保持一致
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 9:35
*/
@AfterReturning(value = "myPointcut()", returning = "result")
public Integer returning(JoinPoint joinPoint, Integer result) {
String name = joinPoint.getSignature().getName();
System.out.println("log---------" + name + "执行直接结束,返回值是:" + result);
return result;
}
/**
* @Description:异常通知:如果需要添加异常信息,那么在注解中要添加Throwing="e"
* 这个e的名称必须跟参数列表中的名称保持一致
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 9:35
*/
@AfterThrowing(value = "myPointcut()", throwing = "e")
public void throwing(JoinPoint joinPoint, Exception e) {
String name = joinPoint.getSignature().getName();
System.out.println("log---------" + name + "执行发生异常:" + e.getMessage());
}
/**
* @Description:环绕通知: 环绕通知在执行的时候是优先于普通通知的
* 如果是正常结束,那么执行顺序是:
* 环绕前置通知--》@Before--》环绕后置通知--》环绕返回通知--》@After--》@AfterReturning
* 如果是异常结束,那么执行顺序是:
* 环绕前置通知--》@Before--》环绕异常通知--》环绕返回通知--》@After--》@AfterReturing
* 如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到的,如果想让普通通知接收到需要进行抛出 throw throwable
* 执行顺序改为:
* 环绕前置通知--》@Before--》环绕异常通知--》环绕返回通知--》@After--》@AfterThrowing
* @Author: zcm
* @Version:v.3.0.4
* @Date:2021/3/23 9:35
*/
@Around(value = "myPointcut()")
public void around(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("log-----环绕通知start:" + name + "方法开始执行,参数为:" + Arrays.asList(args));
result = joinPoint.proceed(args);
System.out.println("log---环绕通知stop:" + name + "方法执行结束");
} catch (Throwable throwable) {
System.out.println("log-----环绕异常通知:" + name + "--方法,出现异常:" + throwable.getMessage());
} finally {
System.out.println("log-----环绕返回通知:" + name + "方法返回结果:" + result);
}
}
}
切入点表达式
最精确的匹配方式
@Pointcut("execution(public Integer com.zcm.service.impl.C*ServiceImpl.add(Integer,Integer))")
public void myPointcut() {}
通配符的方式
一、 “*”
1.匹配一个或者多个字符
@Pointcut("execution(public Integer com.zcm.service.impl.C*ServiceImpl.*(Integer,Integer))")
public void myPointcut() {}
2.匹配任意类型的参数,只能匹配一个
@Pointcut("execution(public Integer com.zcm.service.impl.C*ServiceImpl.*(*,Integer))")
public void myPointcut() {}
3.“*”在进行匹配的时候,只能匹配一层路径,“*”不能匹配访问修饰符,如果不确定访问修饰符是什么,可以省略不写,返回值可以使用“*”来代替。
@Pointcut("execution(* com.zcm.service.impl.C*ServiceImpl.*(*,*))")
public void myPointcut() {}
二、 “..”
1.可以匹配多个参数,任意类型
@Pointcut("execution(* com.zcm.service.impl.C*ServiceImpl.*(..))")
public void myPointcut() {}
2.可以匹配多层路径
@Pointcut("execution(* com.zcm..C*ServiceImpl.*(..))")
public void myPointcut() {}
结合“*”和“..”最省事的写法【如果表达式是以*开头,那么可以代替所有】
@Pointcut("execution(* *(..))")
public void myPointcut() {}
@Pointcut("execution(* com..*(..))")
public void myPointcut() {}
在使用表达式的时候还支持逻辑运算
&&:多个条件必须满足【必须两个条件都满足】
@Pointcut("execution(* com.zcm.service.impl.CalculateServiceImpl.*v(..))&&execution(* com.zcm.service.impl.CalculateServiceImpl.d*(..))")
public void myPointcut() {}
||:多个条件只要满足其中一个即可【只满足了一个】
@Pointcut("execution(* com.zcm.service.impl.CalculateServiceImpl.v*(..))||execution(* com.zcm.service.impl.CalculateServiceImpl.d*(..))")
public void myPointcut() { }
!:取反,除了这种情况其他都满足。
@Pointcut("!(execution(* com.zcm.service.impl.CalculateServiceImpl.a*(..))||execution(* com.zcm.service.impl.CalculateServiceImpl.d*(..)))")
public void myPointcut() { }
总结:使用通配符不是越简洁越好,再项目中选择适用或者符合规则的匹配方式,定义标识符的时候必要要遵循项目规范。
执行顺序
如果正常执行:@Before——》@After——》@AfterReturning
如果异常结束:@Before——》@After——》@AfterThrowing
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.zcm"></context:component-scan>
<!--开启AOP注解-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
应用程序中存在多个切面类的时候,怎么控制所有切面的执行顺序?默认按照切面类的名称首字母排序操作,按照字典序,如果先要调整规定的顺序,@Order注解同时加入具体的值,值越小,越优先。
测试
import com.zcm.service.*;
import com.zcm.service.impl.*;
import org.junit.*;
import org.springframework.context.support.*;
/**
* @program: spring-aop
* @ClassName:TestAop
* @Description:
* @Author:zcm
* @Date:2021/3/22 17:58
*/
public class TestAop {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test_01() {
CalculateService calculateService = context.getBean("calculateServiceImpl",CalculateServiceImpl.class);
calculateService.add(1, 3);
calculateService.sub(1, 3);
calculateService.mul(1, 3);
calculateService.div(1, 0);
}
}
方式之二【xml格式】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!--声明Bean-->
<bean id="logUtil" class="com.zcm.util.LogUtil"></bean>
<!--声明Bean-->
<bean id="calculateServiceImpl" class="com.zcm.service.impl.CalculateServiceImpl"></bean>
<!--声明切面-->
<aop:config proxy-target-class="true">
<!--将通用的表达式给抽取出来-->
<aop:pointcut id="myPointcut"
expression="execution(public Integer com.zcm.service.impl.CalculateServiceImpl.*(Integer,Integer))"/>
<aop:aspect ref="logUtil">
<!--执行方法之前通知-->
<aop:before method="start" pointcut-ref="myPointcut"></aop:before>
<!--执行方法结束通知-->
<aop:after method="stop" pointcut-ref="myPointcut"></aop:after>
<!--执行方法返回结果之后通知-->
<aop:after-returning method="returning" pointcut-ref="myPointcut" returning="result"></aop:after-returning>
<!--执行方法发生异常通知-->
<aop:after-throwing method="throwing" pointcut-ref="myPointcut" throwing="e"></aop:after-throwing>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>