Spring--AOP
AOP简介
AOP是Aspect Oriented Programing的简称,被译为“面向切面的编程”。
按照应用程序重构的思想,如果多个类中出现了相同的代码,
那么就应该考虑将这些相同的代码抽象出来定义成一个父类或者使用委托,
但是如果在整个的应用程序中都是用相同的一个或者几个基类,
往往会导致应用程序变得更加复杂。
而面向切面可以替代继承和委托,而且在很多的场合下可以使程序变得更加的简洁。
AOP中关键性概念
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出. 目标(Target):被通知(被代理)的对象
注1:完成具体的业务逻辑
注1:完成具体的业务逻辑
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
注2:完成切面编程
注2:完成切面编程
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),
例子:外科医生+护士
注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
例子:外科医生+护士
注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
代码实例
IBookBiz 接口
1 package com.yuan.aop.biz; 2 3 public interface IBookBiz { 4 // 购书 5 public boolean buy(String userName, String bookName, Double price); 6 7 // 发表书评 8 public void comment(String userName, String comments); 9 }
BookBizImpl 接口实现类
1 package com.yuan.aop.biz.impl; 2 3 import com.yuan.aop.biz.IBookBiz; 4 import com.yuan.aop.ex.PriceException; 5 6 public class BookBizImpl implements IBookBiz { 7 8 public BookBizImpl() { 9 super(); 10 } 11 12 public boolean buy(String userName, String bookName, Double price) { 13 // 通过控制台的输出方式模拟购书 14 if (null == price || price <= 0) { 15 throw new PriceException("book price exception"); 16 } 17 System.out.println(userName + " buy " + bookName + ", spend " + price); 18 return true; 19 } 20 21 public void comment(String userName, String comments) { 22 // 通过控制台的输出方式模拟发表书评 23 System.out.println(userName + " say:" + comments); 24 } 25 26 }
PriceException 异常处理
1 package com.yuan.aop.ex; 2 3 public class PriceException extends RuntimeException { 4 5 public PriceException() { 6 super(); 7 } 8 9 public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 10 super(message, cause, enableSuppression, writableStackTrace); 11 } 12 13 public PriceException(String message, Throwable cause) { 14 super(message, cause); 15 } 16 17 public PriceException(String message) { 18 super(message); 19 } 20 21 public PriceException(Throwable cause) { 22 super(cause); 23 } 24 25 }
spring-context.xml配置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 default-autowire="byName" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xmlns:aop="http://www.springframework.org/schema/aop" 8 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 9 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 11 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> 12 <bean class="com.yuan.ioc.biz.impl.UserBizImpl2" id="userBiz"></bean> 13 <bean class="com.yuan.ioc.web.UserAction" id="yyy"> 14 <!-- set注入用property标签 --> 15 <!-- <property name="userBiz" ref="userBiz"></property> --> 16 <!-- <property name="uname" value="zs"></property> 17 <property name="age" value="19"></property> --> 18 <!-- 构造注入用constructor-arg标签 --> 19 <constructor-arg name="uname" value="ls"></constructor-arg> 20 <constructor-arg name="age" value="19"></constructor-arg> 21 <property name="hobby"> 22 <list> 23 <value>游戏</value> 24 <value>听歌</value> 25 <value>篮球</value> 26 </list> 27 </property> 28 </bean> 29 30 31 <bean class="com.yuan.ioc.web.OrderAction" id="xxx"> 32 <!-- <property name="userBiz" ref="userBiz"></property> --> 33 </bean> 34 35 <!-- ************************** AOP ************************************** --> 36 <!-- 目标对象 --> 37 <bean id="bookBiz" class="com.yuan.aop.biz.impl.BookBizImpl"></bean> 38 <!-- 通知 --> 39 <!-- 前置通知 --> 40 <bean id="myBefore" class="com.yuan.aop.advice.MyMethodBeforeAdvice"></bean> 41 <!-- 后置通知 --> 42 <bean id="myAfter" class="com.yuan.aop.advice.MyAfterReturningAdvice"></bean> 43 <!-- 环绕通知 --> 44 <bean id="myInterceptor" class="com.yuan.aop.advice.MyMethodInterceptor"></bean> 45 <!-- 异常通知 --> 46 <bean id="myThrowsAdvice" class="com.yuan.aop.advice.MyThrowsAdvice"></bean> 47 <!-- 过滤通知 --> 48 <bean id="myAfter2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 49 <property name="advice" ref="myAfter"></property> 50 <property name="pattern" value=".*buy"></property> 51 </bean> 52 53 <!-- 由代理工厂来组装目标对象及通知 --> 54 <bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 55 <property name="target" ref="bookBiz"></property> 56 <property name="proxyInterfaces"> 57 <list> 58 <value>com.yuan.aop.biz.IBookBiz</value> 59 </list> 60 </property> 61 <property name="interceptorNames"> 62 <list> 63 <value>myBefore</value> 64 <!-- <value>myAfter</value> --> 65 <value>myAfter2</value> 66 <value>myInterceptor</value> 67 <value>myThrowsAdvice</value> 68 </list> 69 </property> 70 </bean> 71 72 73 74 </beans>
AopTest 测试类
1 package com.yuan.aop.test; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 import com.yuan.aop.biz.IBookBiz; 7 import com.yuan.aop.biz.impl.BookBizImpl; 8 import com.yuan.ioc.web.OrderAction; 9 import com.yuan.ioc.web.UserAction; 10 11 public class AopTest { 12 13 public static void main(String[] args) { 14 15 ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml"); 16 IBookBiz bean = (IBookBiz) springContext.getBean("bookProxy"); 17 System.out.println(bean.getClass()); 18 boolean buy = bean.buy("张三", "圣墟", 55d); 19 bean.comment("张三", "hhhhhhhh"); 20 } 21 22 }
前置通知(org.springframework.aop.MethodBeforeAdvice):在连接点之前执行的通知()
1 package com.yuan.aop.advice; 2 3 import java.lang.reflect.Method; 4 import java.util.Arrays; 5 6 import org.springframework.aop.MethodBeforeAdvice; 7 8 9 /** 10 * 买书、评论前加系统日志 11 * @author ly 12 * 13 */ 14 public class MyMethodBeforeAdvice implements MethodBeforeAdvice { 15 16 @Override 17 public void before(Method method, Object[] args, Object target) throws Throwable { 18 String clzName = target.getClass().getName(); 19 String methodName = method.getName(); 20 String params = Arrays.toString(args); 21 System.out.println("【买书、评论前加系统日志】"+clzName + "." + methodName +"("+params+")"); 22 } 23 24 }
后置通知(org.springframework.aop.AfterReturningAdvice):在连接点正常完成后执行的通知
1 package com.yuan.aop.advice; 2 3 import java.lang.reflect.Method; 4 import java.util.Arrays; 5 6 import org.springframework.aop.AfterReturningAdvice; 7 8 /** 9 * 买书返利(存在bug) 10 * @author ly 11 * 12 */ 13 public class MyAfterReturningAdvice implements AfterReturningAdvice { 14 15 @Override 16 public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { 17 String clzName = target.getClass().getName(); 18 String methodName = method.getName(); 19 String params = Arrays.toString(args); 20 System.out.println("【买书返利的后置通知】"+clzName + "." + methodName +"("+params+")"+"\t目标对象方法调用后的返回值"+returnValue); 21 } 22 23 24 25 }
环绕通知(org.aopalliance.intercept.MethodInterceptor):
包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
这个接口里面没有定义方法,我们要求我们的类必须实现afterThrows这个方法
public void afterThrowing( [Method method,] [Object args,] [Object target,] Throwable throwable );
public void afterThrowing( [Method method,] [Object args,] [Object target,] Throwable throwable );
1 package com.yuan.aop.advice; 2 3 import java.util.Arrays; 4 5 import org.aopalliance.intercept.MethodInterceptor; 6 import org.aopalliance.intercept.MethodInvocation; 7 import org.omg.PortableInterceptor.INACTIVE; 8 9 public class MyMethodInterceptor implements MethodInterceptor { 10 11 @Override 12 public Object invoke(MethodInvocation invocation) throws Throwable { 13 String clzName = invocation.getThis().getClass().getName(); 14 String methodName = invocation.getMethod().getName(); 15 String params = Arrays.toString(invocation.getArguments()); 16 System.out.println("【环绕通知】"+clzName + "." + methodName +"("+params+")"); 17 Object returnValue = invocation.proceed(); 18 System.out.println("【环绕通知】:\t目标对象方法调用后的返回值"+returnValue); 19 return returnValue; 20 } 21 22 }
异常通知(org.springframework.aop.ThrowsAdvice):这个通知会在方法抛出异常退出时执行
案例: 书本价格为负数时抛出一个异常,通过异常通知取消此订单
案例: 书本价格为负数时抛出一个异常,通过异常通知取消此订单
1 package com.yuan.aop.advice; 2 3 import org.springframework.aop.ThrowsAdvice; 4 5 import com.yuan.aop.ex.PriceException; 6 7 /** 8 * 异常通知 9 * @author ly 10 * 11 *案例:张三向李四转账 12 * biz.transfer(user1,user2) 13 * UserDao.update(user1) 14 * UserDao.update(user2) 15 * 16 * 17 */ 18 public class MyThrowsAdvice implements ThrowsAdvice { 19 20 public void afterThrowing( PriceException ex) { 21 System.out.println("价格输入有无,购买失效,请重新输入!!!"); 22 } 23 24 25 26 }
输出显示:
适配器(org.springframework.aop.support.RegexpMethodPointcutAdvisor) 适配器=通知(Advice)+切入点(Pointcut)
案例:通过适配器解决发书评时也返利的问题
适配器不需要创建类,只需要在spring-context.xml中配置bean就OK了
1 <!-- bean配置 --> 2 <bean id="myAfter2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 3 <property name="advice" ref="myAfter"></property> 4 <property name="pattern" value=".*buy"></property> 5 </bean> 6 7 然后将代理工厂中value为myAfter替换成myAfter2 8 <value>myBefore</value> 9 <!-- <value>myAfter</value> --> 10 <value>myAfter2</value> 11 <value>myInterceptor</value> 12 <value>myThrowsAdvice</value>
最终显示结果:
![](https://img2018.cnblogs.com/blog/1691589/201908/1691589-20190815112727297-1976916188.png)