1.说明
spring中的通知有5种
1.1前置通知:before 在切入点方法执行之前实施增强 1.2后置通知:after-returning 切入点方法正常执行之后实施增强,它和异常通知只能执行一个 1.3异常通知:after-throwing 在切入点方法执行过程中出现异常之后实施增强 1.4最终通知:after 无论切入点方法执行时是否出现异常,它都会在其后面执行 1.5环绕通知:around 它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式 通常情况下,环绕通知都是独立使用的
2.创建maven项目,添加相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cong</groupId> <artifactId>spring_aop_advice</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> </dependencies> </project>
3.在java目录下创建com.cong.pojo.Account类(在此案例中可以没有,为了简单,没有写持久层代码,个人习惯还是添加了)
package com.cong.pojo; public class Account { private int id; private String name; private float money; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public float getMoney() { return money; } public void setMoney(float money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
4.在java目录下创建包com.cong.service,以及相关的业务层代码(为了简单,这里只演示一个方法)
package com.cong.service; import com.cong.pojo.Account; public interface AccountService { void saveAccount(Account account); } package com.cong.service; import com.cong.pojo.Account; public class AccountServiceImpl implements AccountService { @Override public void saveAccount(Account account) { System.out.println("save account:" + account.toString()); } }
5.在java目录下创建通知类com.cong.utils.MyTransaction
package com.cong.utils; public class MyTransaction { //前置通知 public void beforeTransaction(){ System.out.println("前置通知MyTransaction类中的beforeTransaction方法执行了..."); } //后置通知 public void afterReturningTransaction(){ System.out.println("后置通知MyTransaction类中的afterReturningTransaction方法执行了..."); } //异常通知 public void afterThrowingTransaction(){ System.out.println("异常通知MyTransaction类中的afterThrowingTransaction方法执行了..."); } //最终通知 public void afterTransaction(){ System.out.println("最终通知MyTransaction类中的afterTransaction方法执行了..."); } //环绕通知 public void aroundTransaction(){ System.out.println("环绕通知MyTransaction类中的aroundTransaction方法执行了..."); } }
6.在resources目录下创建bean.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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="account" class="com.cong.pojo.Account"> <property name="id" value="1"></property> <property name="name" value="cong"></property> <property name="money" value="100"></property> </bean> <bean id="accountService" class="com.cong.service.AccountServiceImpl"></bean> <bean id="myTransaction" class="com.cong.utils.MyTransaction"></bean> <!-- 配置aop --> <aop:config> <!-- 切入点表达式,所谓切入点就是你需要增强的方法 --> <aop:pointcut id="accountPointcut" expression="execution(* com.cong.service.AccountServiceImpl.*(..))"></aop:pointcut> <!-- 配置切面,切面就是增强的过程中需要用到的类 --> <aop:aspect ref="myTransaction"> <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> <!-- 前置通知 --> <aop:before method="beforeTransaction" pointcut-ref="accountPointcut"></aop:before> <!-- 后置通知 --> <aop:after-returning method="afterReturningTransaction" pointcut-ref="accountPointcut"></aop:after-returning> <!-- 异常通知 --> <aop:after-throwing method="afterThrowingTransaction" pointcut-ref="accountPointcut"></aop:after-throwing> <!-- 最终通知 --> <aop:after method="afterTransaction" pointcut-ref="accountPointcut"></aop:after> <!-- 环绕通知 --> <aop:around method="aroundTransaction" pointcut-ref="accountPointcut"></aop:around> </aop:aspect> </aop:config> </beans>
7.在test,java目录下创建AopTest测试类
import com.cong.pojo.Account; import com.cong.service.AccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Account account = (Account) context.getBean("account"); AccountService accountService = (AccountService) context.getBean("accountService"); accountService.saveAccount(account); } }
8.执行,结果如下,很明显,与我们想象中的不一样
前置通知MyTransaction类中的beforeTransaction方法执行了...
环绕通知MyTransaction类中的aroundTransaction方法执行了...
最终通知MyTransaction类中的afterTransaction方法执行了...
后置通知MyTransaction类中的afterReturningTransaction方法执行了...
9.在解决之前,我们先将bean.xml中的环绕通知的配置注释掉
<!-- 环绕通知 --> <!--<aop:around method="aroundTransaction" pointcut-ref="accountPointcut"></aop:around>-->
10.再执行一遍测试类,可以正常执行了
可是没有出现异常通知,因为后置通知执行了,证明程序没有出错(后置通知与异常通知只能执行一个)
前置通知MyTransaction类中的beforeTransaction方法执行了... save account:Account{id=1, name='cong', money=100.0} 后置通知MyTransaction类中的afterReturningTransaction方法执行了... 最终通知MyTransaction类中的afterTransaction方法执行了...
11.在AccountService的方法中添加一个会出错的语句
public class AccountServiceImpl implements AccountService { @Override public void saveAccount(Account account) { System.out.println("save account:" + account.toString()); int i = 1/0; } }
12.再执行一遍,可以看到,异常通知出来了,后置通知不见了
前置通知MyTransaction类中的beforeTransaction方法执行了... Exception in thread "main" java.lang.ArithmeticException: / by zero save account:Account{id=1, name='cong', money=100.0} 异常通知MyTransaction类中的afterThrowingTransaction方法执行了... 最终通知MyTransaction类中的afterTransaction方法执行了...
13.接下来我们看一下环绕通知没有注释掉的时候出现的与我们想象中不一样的情况
* 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
14.改写环绕通知的代码,前后环绕通知的代码比较
//环绕通知 public void aroundTransaction(){ System.out.println("环绕通知MyTransaction类中的aroundTransaction方法执行了..."); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint){ Object res = null; try { Object [] args = proceedingJoinPoint.getArgs();//得到方法执行所需的参数 beforeTransaction();//前置通知 res=proceedingJoinPoint.proceed(args);//明确调用业务层方法(通过切入点表达式配置) afterReturningTransaction();//后置通知 return res; } catch (Throwable throwable) { afterThrowingTransaction();//异常通知 throw new RuntimeException(throwable); }finally { afterTransaction();//最终通知 } }
15.注释掉配置文件中除了环绕通知的其它通知,同时注释掉int i = 1/0
<!-- 配置aop --> <aop:config> <!-- 切入点表达式,所谓切入点就是你需要增强的方法 --> <aop:pointcut id="accountPointcut" expression="execution(* com.cong.service.AccountServiceImpl.*(..))"></aop:pointcut> <!-- 配置切面,切面就是增强的过程中需要用到的类 --> <aop:aspect ref="myTransaction"> <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> <!-- 前置通知 --> <!--<aop:before method="beforeTransaction" pointcut-ref="accountPointcut"></aop:before>--> <!--<!– 后置通知 –>--> <!--<aop:after-returning method="afterReturningTransaction" pointcut-ref="accountPointcut"></aop:after-returning>--> <!--<!– 异常通知 –>--> <!--<aop:after-throwing method="afterThrowingTransaction" pointcut-ref="accountPointcut"></aop:after-throwing>--> <!--<!– 最终通知 –>--> <!--<aop:after method="afterTransaction" pointcut-ref="accountPointcut"></aop:after>--> <!--<!– 环绕通知 –>--> <aop:around method="aroundTransaction" pointcut-ref="accountPointcut"></aop:around> </aop:aspect> </aop:config>
16.运行一下测试类,结果如下
前置通知MyTransaction类中的beforeTransaction方法执行了... save account:Account{id=1, name='cong', money=100.0} 后置通知MyTransaction类中的afterReturningTransaction方法执行了... 最终通知MyTransaction类中的afterTransaction方法执行了...
17.解除int i = 1/0的注释,再执行一遍,结果如下
前置通知MyTransaction类中的beforeTransaction方法执行了... Exception in thread "main" java.lang.RuntimeException: java.lang.ArithmeticException: / by zero save account:Account{id=1, name='cong', money=100.0} 异常通知MyTransaction类中的afterThrowingTransaction方法执行了... at com.cong.utils.MyTransaction.aroundTransaction(MyTransaction.java:46) 最终通知MyTransaction类中的afterTransaction方法执行了... at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498)
18.以上,便是spring中通知的内容了