SpringAOP的四种通知类型:前置通知、异常通知、后置通知、异常通知
一、四种常见的通知类型
给出 账户的业务层接口 IAccountService.java,
为了便于演示这四种通知类型,我们就只留下了一个方法。
public interface IAccountService { void saveAccount(); }
给出 账户的业务层接口的实现类 AccountServiceImpl.java
public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); //int i=1/0; } }
给出一个日志类, 用于打印日志
public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } }
给出配置信息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"> <!-- 配置srping的Ioc,把service对象配置进来--> <bean id="accountService" class="service.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切入点表达式 --> <aop:pointcut id="pt1" expression="execution(* service.AccountServiceImpl.saveAccount())"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行--> <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before> <!-- 配置后置通知:在切入点方法正常执行之后值--> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning> <!-- 配置异常通知:在切入点方法执行产生异常之后执行--> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
注意
1)异常通知和后置通知永远只能执行一个
2)配置切入点表达式
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
给出Test类
public class AOPTest { public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); } }
执行结果:
当我们放开AccountServiceImpl类中我们故意制造的异常 int i=1/0;时:
二、环绕通知
环绕通知,只需要稍稍微改变上面例子的两点即可
1、改动日志类 Logger.java
public class Logger { public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
注意:pjp.proceed(args)会报异常,必须用 Throwable t,因为Exception拦不住它
2、改动配置文件
<?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"> <!-- 配置srping的Ioc,把service对象配置进来--> <bean id="accountService" class="service.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切入点表达式 --> <aop:pointcut id="pt1" expression="execution(* service.AccountServiceImpl.saveAccount())"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config> </beans>
分析
- spring中的环绕通知是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
- Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
AOP机制之环绕通知的见解
我们都知道,AOP机制的核心是在不修改源码的基础上对业务层方法的增强。
其中有五个通知类型
- 1、前置通知:在切入点方法之前执行
- 2、后置通知:在切入点方法之后通知
- 3、异常通知:在执行切入点方法过程中出现异常后执行(因此异常通知和后置通知只能执行一个)
- 4、最终通知:无论切入点方法是否正常执行它都会执行
- 5、环绕通知: 当配置环绕通知之后,在环绕通知里面必须要明确调用业务层的方法,如果不调用,就会出现只出现通知,而不执行方法。其中原理和动态代理是一样的,代码如下。
public AccountService getAccountService() { return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { Object rtValue=null; try { //1、开启事务 txManager.beginTransaction(); //2、执行操作,整个过程像对每个方法进行了包装,并返回新的accountService对象 rtValue=method.invoke(accountService,objects); //3、提交事务 txManager.commit(); //4、返回结果 return rtValue; }catch (Exception e){ //5、回滚事务 txManager.rollback(); throw new RuntimeException(e); }finally { //6、释放连接 txManager.release(); } } }); }
如果不明确调用业务层方法,就像一个画皮,没有发挥本质的作用。
除此之外,我认为环绕通知可以代替其他的四个通知,
public Object aroundPrintLog(ProceedingJoinPoint pjp){//环绕通知是不是能够代替其他的通知 Object rtvalue=null; try { /** *这里的一切都是为都是给业务层方法进行增强,例如:把那些方法拿过来,然后核心的还是rtvalue=pjp.proceed(args), *其他的输出只不过是为核心的业务层方法进行修饰 */ Object[] args=pjp.getArgs();//得到方法执行所需的参数 System.out.println("前置通知"); rtvalue=pjp.proceed(args);//明确调用业务层方法 System.out.println("后置通知"); return rtvalue; }catch (Throwable e){ System.out.println("异常通知"); throw new RuntimeException(e); }finally { System.out.println("最终通知"); } }
这个属于典型的环绕通知,其中把输出方法换成相应的通知方法就可以(有不同观点的可以说出来一起讨论)。
最后,分享一下我学这个知识的方法。方法增强,本质上就是对源码不修改的情况下进行方法的加工。就好像烤羊肉串一样,其中的核心就是羊肉,就像公司给你的业务层方法(这个是不让修改的)。在给顾客使用前,收先对羊肉进行刷油、烤、撒料一系列过程,这一过程就是我们对业务层方法的增强,使业务层的功能更加健壮,对应的烧烤也就是更美味。核心的一点就是正确调用业务层的方法,不管在哪类通知中,都能对业务层方法进行正确、有效地增强
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/qq_41949320/article/details/106841824