SpringAOP
SpringAOP
什么是AOP
AOP即面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
在不改变原有的逻辑的基础上,增加一些额外的功能。
aop是对OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP的相关概念
横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
Aspect(切面):通常是一个类,里面可以定义切入点和通知
JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)
Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
weave(织入):将切面应用到目标对象并导致代理对象创建的过程
introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
AOP通知类型介绍
1、Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
2、AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
3、AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名
来访问目标方法中所抛出的异常对象
4、After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
5、Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
手动实现AOP
JDK动态代理的方式
jdk动态代理对“装饰者”设计模式的简化。使用前提:必须有接口
需要的类
1、目标类:接口+实现
2、切面类:用于存通知
3、代理类:由工厂生成
目标类
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("noauto.jdk.add");
}
@Override
public void updateUser() {
System.out.println("noauto.jdk.update");
}
@Override
public void deleteUser() {
System.out.println("noauto.jdk.delete");
}
}
切面类
public class MyAspect {
public void before(){
System.out.println("前置通知");
}
public void after(){
System.out.println("后置通知");
}
}
代理类
public class MyBenFactory {
public static UserService createUserService(){
//切面类
MyAspect myAspect = new MyAspect();
//目标类
UserServiceImpl userService = new UserServiceImpl();
/**
* 代理类
* Proxy.newProxyInstance( )
* ClassLoader loader:类加载器,动态代理类运行时创建,任何类都需要类加载器加载到内存
* 一般情况:当前类.class.getClassLoader();
* 目标类实例.getClass().getClassLoader();
* Class<?>[] interfaces 代理类需要实现的所有接口
* 方式一:目标类实例.getClass().getInterFaces();
* 方式二:new Class[]{UserService.class}
* InvocationHandler h:处理类,接口,必须进行实现,一般采用匿名内部类
* 提供了一个invoke方法:代理类的每一个方法执行时,都将调用一次invoke
* 1:proxy:代理对象
* 2:method:代理对象当前执行的方法的描述对象(反射)
* 执行方法名:method.getName()
* 执行方法:method.invoke(对象,参数)
* 3:args:方法的实际参数
*/
UserService proxUserService
= (UserService)Proxy.newProxyInstance(
MyBenFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myAspect.before();
Object obj = method.invoke( userService, args );
myAspect.after();
return obj;
}
} );
return proxUserService;
}
}
测试类
public class TestNoAutoJDK {
@Test
public void demo01(){
UserService userService = MyBenFactory.createUserService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
结果:
前置通知
noauto.jdk.add
后置通知
前置通知
noauto.jdk.update
后置通知
前置通知
noauto.jdk.delete
后置通知
CGLIB动态代理的方式
CGLIB:字节码增强,没有接口,只有实现类。
运行原理:在运行时创建目标类的子类,从而对目标类进行增强
导入jar包:spring的核心包已经导入了cglib、asm(cglib的依赖包)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
需要的类
目标类
public class UserServiceImpl {
public void addUser() {
System.out.println("noauto.cglib.add");
}
public void updateUser() {
System.out.println("noauto.cglib.update");
}
public void deleteUser() {
System.out.println("noauto.cglib.delete");
}
}
切面类
public class MyAspect {
public void before(){
System.out.println("前置通知cglib");
}
public void after(){
System.out.println("后置通知cglib");
}
}
代理类
上面是Callback的一些子类
public class MyBenFactory {
public static UserServiceImpl createUserService(){
//切面类
MyAspect myAspect = new MyAspect();
//目标类
UserServiceImpl userService = new UserServiceImpl();
/**
* 代理类 采用cglib,底层创建目标类的子类
*
*/
//1、核心类
Enhancer enhancer = new Enhancer();
//2、确定父类
enhancer.setSuperclass( userService.getClass() );
//3、设置回调函数,MethodInterceptor接口等效于invocationHandler接口
enhancer.setCallback( new MethodInterceptor() {
/**
* intercept等效于 invoke,前三个参数与jdk动态代理的invoke的参数一样
* @param o
* @param method
* @param objects
* @param methodProxy 方法的代理
使用方式:methodProxy.invokeSuper(userService,objects)。执行当前类(代理类)的父类(父类)中的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
myAspect.before();
//执行目标类的方法
Object obj = method.invoke( userService, objects );
myAspect.after();
return obj;
}
} );
//4、创建代理
UserServiceImpl proxyService = (UserServiceImpl)enhancer.create();
return proxyService;
}
}
测试类
public class TestNoAutoCGLIB {
@Test
public void demo01(){
UserServiceImpl userService = MyBenFactory.createUserService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
结果
前置通知cglib
noauto.cglib.add
后置通知cglib
前置通知cglib
noauto.cglib.update
后置通知cglib
前置通知cglib
noauto.cglib.delete
后置通知cglib
SpringAOP的实现
半自动的实现
前面都是手动创建代理对象,这里就让Spring创建代理对象,我们从spring容器中手动的获取代理对象
导入jar包:AOP、spring核心jar包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
目标类
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("spring.beanfactory.add");
}
@Override
public void updateUser() {
System.out.println("spring.beanfactory.update");
}
@Override
public void deleteUser() {
System.out.println("spring.beanfactory.delete");
}
}
切面类
/**
* @author Administrator
* @author 2020-11-06
* @Description 切面类,要确定通知,以环绕通知为例
**/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object proceed = null;
try {
System.out.println("环绕前");
//手动执行目标方法
proceed = mi.proceed();
System.out.println("环绕后");
}catch (Exception e){
System.out.println("环绕异常");
}
return proceed;
}
}
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建目标类-->
<bean id="userServiceImpl" class="org.ybl.aop.auto.factory_bean.UserServiceImpl"/>
<!--创建切面类-->
<bean id="myAspect" class="org.ybl.aop.auto.factory_bean.MyAspect"/>
<!--创建代理类-->
<bean id="proxyUserService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="org.ybl.aop.auto.factory_bean.UserService"/>
<property name="target" ref="userServiceImpl"/>
<property name="interceptorNames" value="myAspect"/>
<!--为true就是强制使用cglib-->
<property name="optimize" value="true"/>
</bean>
</beans>
测试类
public class TestFactoryBean {
@Test
public void demo01(){
ClassPathXmlApplicationContext cpac = new ClassPathXmlApplicationContext( "classpath:beans.xml" );
UserService userService = (UserService)cpac.getBean( "proxyUserService" );
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
结果
环绕前
spring.beanfactory.add
环绕后
环绕前
spring.beanfactory.update
环绕后
环绕前
spring.beanfactory.delete
环绕后
强制使用CGLIB的情况
不强制使用CGLIB的情况
总结:
如果目标类实现了接口,就用jdk动态代理
没有实现接口,就用CGLIB字节码增强
如果申明optimize=true,不管有没有实现接口,都强制使用CGLIB
全自动的实现
目标类
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("spring.AOP.add");
}
@Override
public void updateUser() {
System.out.println("spring.AOP.update");
}
@Override
public void deleteUser() {
System.out.println("spring.AOP.delete");
}
}
切面类
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object proceed = null;
try {
System.out.println("环绕前");
//手动执行目标方法
proceed = mi.proceed();
System.out.println("环绕后");
}catch (Exception e){
System.out.println("环绕异常");
}
return proceed;
}
}
beans.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建目标类-->
<bean id="userServiceImpl" class="org.ybl.aop.auto.springaop.UserServiceImpl"/>
<!--创建切面类-->
<bean id="myAspect" class="org.ybl.aop.auto.springaop.MyAspect"/>
<!--
1、使用<aop:config>进行配置
<aop:pointcut>切入点,从目标对象获得具体方法
<aop:advisor>特殊的切面,只有一个通知和一个切入点
advice-ref:通知引用
pointcut-ref:切入点引用
2、切入点表达式
execution(* org.ybl.aop.auto.factory_bean.*.*(..))
第一个*:表示任意返回值
第二个*:表示org.ybl.aop.auto.factory_bean下面的任意类
第三个*:表示这些类下面的任意方法
..:表示上面这些方法的多个参数(类型、个数都可以不一样)
-->
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* org.ybl.aop.auto.springaop.*.*(..))"/>
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
测试类
public class TestSpringAOP {
@Test
public void demo01(){
ClassPathXmlApplicationContext cpac
= new ClassPathXmlApplicationContext( "classpath:beans.xml" );
UserService userService = (UserService)cpac.getBean( "userServiceImpl" );
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
总结:
这种方式默认使用的是jdk动态代理,也可以通过<aop:config proxy-target-class="true">
这种方式来设置使用CGLIB动态代理
AspectJ
介绍:
是一个基于Java语言的AOP框架。Spring2.0后新增了堆AspectJ切点表达式的支持。@AspectJ是AspectJ1.5新增功能给你,通过JDK5注解技术,允许直接在Bean类中定义切面,新版本Spring框架,建议使用AspectJ方式来开发AOP
切入点表达式
1、execution()
用于描述方法
语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
修饰符,一般省略
public 公共方法
* 任意
返回值,不能省略
void 返回没有值
String 返回值字符串
* 任意
包,[省略]
com.xxx.crm 固定包
com.xxx.crm.*.service crm包下面子包任意 (例如:com.itheima.crm.staff.service)
com.xxx.crm.. crm包下面的所有子包(含自己)
com.xxx.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包
类,[省略]
UserServiceImpl 指定类
*Impl 以Impl结尾
User* 以User开头
* 任意
方法名,不能省略
addUser 固定方法
add* 以add开头
*Do 以Do结尾
* 任意
(参数)
() 无参
(int) 一个整型
(int ,int) 两个
(..) 参数任意
throws ,可省略,一般不写。
例子:
execution(* com.xxx.crm.*service..*.*(...))
AspectJ通知类型
一共6种,了解5种
before:前置通知(应用:各种校验)
在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理)
方法正常返回后执行,如果方法中抛出异常,通知无法执行
必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情)
方法执行前后分别执行,可以阻止方法的执行
必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息)
方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场)
方法执行完毕后执行,无论方法中是否出现异常
基于xml的配置
准备
1、目标类:接口+实现
2、切面类:编写多个通知,采用aspectj,通知名称任意(方法名任意)
3、aop编程,将通知应用到目标类
目标类
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("spring.aspectj.add");
}
@Override
public void updateUser() {
System.out.println("spring.aspectj.update");
}
@Override
public void deleteUser() {
System.out.println("spring.aspectj.delete");
}
}
切面类
public class MyAspect {
public void myBefore(JoinPoint joinPoint) {
System.out.println( "前置通知 : " + joinPoint.getSignature().getName() );
}
public void myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println( "后置通知 : " + joinPoint.getSignature().getName() + " , 返回值:" + ret );
System.out.println("==========");
}
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println( "环绕前" );
//手动执行目标方法
Object obj = joinPoint.proceed();
System.out.println( "环绕后" );
return obj;
}
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println( "抛出异常通知 : " + e.getMessage() );
}
public void myAfter(JoinPoint joinPoint) {
System.out.println( "最终通知" );
}
}
beans.xml
<!-- 1 创建目标类 -->
<bean id="userServiceImpl" class="org.ybl.aop.auto.aspect.UserServiceImpl"></bean>
<!-- 2 创建切面类(通知) -->
<bean id="myAspect" class="org.ybl.aop.auto.aspect.MyAspect"></bean>
<!--3 aop编程
<aop:aspect> 将切面类 声明“切面”,从而获得通知(方法)
ref 切面类引用
<aop:pointcut> 声明一个切入点,所有的通知都可以使用。 expression 切入点表达式 id 名称,用于其它通知引用
-->
<aop:config>
<aop:aspect ref="myAspect">
<aop:pointcut expression="execution(* org.ybl.aop.auto.aspect.UserServiceImpl.*(..))" id="myPointCut"/>
<!-- 3.1 前置通知 <aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知,及方法名
pointcut :切入点表达式,此表达式只能当前通知使用。
pointcut-ref : 切入点引用,可以与其他通知共享切入点。
通知方法格式:public void myBefore(JoinPoint joinPoint){
参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等
例如:
-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 3.2后置通知 ,目标方法后执行,获得返回值 <aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二个参数的名称
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
参数1:连接点描述 参数2:类型Object,参数名 returning="ret" 配置的
例如: <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
<!-- 3.3 环绕通知 <aop:around method="" pointcut-ref=""/>
通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
返回值类型:Object
方法名:任意
参数:org.aspectj.lang.ProceedingJoinPoint
抛出异常 执行目标方法:Object obj = joinPoint.proceed();
例如:
-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 3.4 抛出异常 <aop:after-throwing method="" pointcut-ref="" throwing=""/>
throwing :通知方法的第二个参数名称
通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
参数1:连接点描述对象
参数2:获得异常信息,类型Throwable ,参数名由throwing="e" 配置
例如:
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 3.5 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
测试类
public class TestAspectjXML {
@Test
public void demo01(){
ClassPathXmlApplicationContext cpac
= new ClassPathXmlApplicationContext( "classpath:beans-aspectj.xml" );
UserService userService = (UserService)cpac.getBean( "userServiceImpl" );
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
结果
前置通知 : addUser
环绕前
spring.aspectj.add
最终通知
环绕后
后置通知 : addUser , 返回值:null
==========
前置通知 : updateUser
环绕前
spring.aspectj.update
最终通知
环绕后
后置通知 : updateUser , 返回值:null
==========
前置通知 : deleteUser
环绕前
spring.aspectj.delete
最终通知
环绕后
后置通知 : deleteUser , 返回值:null
==========
基于注解的方式
目标类
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("spring.anno.add");
}
@Override
public void updateUser() {
System.out.println("spring.anno.update");
}
@Override
public void deleteUser() {
System.out.println("spring.anno.delete");
int i = 1/0;
}
}
切面类
@Component
@Aspect
public class MyAspect {
/**
* 公共的切入点表达式
*/
@Pointcut("execution(* org.ybl.aop.auto.aspect.anno.UserServiceImpl.*(..))")
public void myPontcut() {
}
@Before(value = "myPontcut()")
public void myBefore(JoinPoint joinPoint) {
System.out.println( "前置通知 : " + joinPoint.getSignature().getName() );
}
@AfterReturning(value = "myPontcut()", returning = "ret")
public void myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println( "后置通知 : " + joinPoint.getSignature().getName() + " , 返回值:" + ret );
System.out.println( "==========" );
}
@Around(value = "myPontcut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println( "环绕前" );
//手动执行目标方法
Object obj = joinPoint.proceed();
System.out.println( "环绕后" );
return obj;
}
@AfterThrowing(value = "myPontcut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println( "抛出异常通知 : " + e.getMessage() );
}
@After(value = "myPontcut()")
public void myAfter(JoinPoint joinPoint) {
System.out.println( "最终通知" );
}
}
beans.xml
<!--扫描注解类-->
<context:component-scan base-package="org.ybl.aop.auto.aspect.anno"/>
<!--确定aop的注解要生效-->
<aop:aspectj-autoproxy/>
测试类
public class TestAspectjAnno {
@Test
public void demo01(){
ClassPathXmlApplicationContext cpac
= new ClassPathXmlApplicationContext( "classpath:beans-aspectjanno.xml" );
UserService userService = (UserService)cpac.getBean( "userServiceImpl" );
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
结果
环绕前
前置通知 : addUser
spring.anno.add
后置通知 : addUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : updateUser
spring.anno.update
后置通知 : updateUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : deleteUser
spring.anno.delete
抛出异常通知 : / by zero
最终通知
java.lang.ArithmeticException: / by zero