Spring之aop
什么是AOP:
Aspect Oriented Programming(AOP)是较为热门的一个话题。AOP,国内大致译作“面向切面编程”。
“面向切面编程”,这样的名字并不是非常容易理解,且容易产生一些误导。
笔者不止一次听到类似“OOP/OOD11即将落伍,AOP是新一代软件开发方式”这样的发言。而在AOP中,Aspect的含义,可能更多的理解为“切面”比较合适。所以笔者更倾向于“面向切面编程”的译法。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
AOP主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等wn及扩展
AOP中关键性概念 :
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
目标(Target):被通知(被代理)的对象
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),请注意:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
定义一个接口,有购书和评论两个方法 IBookBiz:
package com.liuwenwu.aop.biz; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
BookBizImpl 实现接口
package com.liuwenwu.aop.biz.impl; import com.liuwenwu.aop.biz.IBookBiz; import com.liuwenwu.aop.exception.PriceException; /** * 目标 * @author ASUS * */ public class BookBizImpl implements IBookBiz { public BookBizImpl() { super(); } public boolean buy(String userName, String bookName, Double price) { // 通过控制台的输出方式模拟购书 if (null == price || price <= 0) { throw new PriceException("book price exception"); } System.out.println(userName + " buy " + bookName + ", spend " + price); return true; } public void comment(String userName, String comments) { // 通过控制台的输出方式模拟发表书评 System.out.println(userName + " say:" + comments); } }
异常处理 PriceException:
package com.liuwenwu.aop.exception; public class PriceException extends RuntimeException { public PriceException() { super(); } public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public PriceException(String message, Throwable cause) { super(message, cause); } public PriceException(String message) { super(message); } public PriceException(Throwable cause) { super(cause); } }
1、前置通知 MyMethodBeforeAdvice:
package com.liuwenwu.aop.advise; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知 * 买书、评论前加系统日志 * @author ASUS * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 哪个类被调用了 String clzName=arg2.getClass().getName(); // 哪个方法被调了 String methodNname=arg0.getName(); // 参数 String params=Arrays.toString(arg1); System.out.println("【系统日志】:"+clzName+"."+methodNname+"("+params+")"); } }
测试类 Demo1:
package com.liuwenwu.aop.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.liuwenwu.aop.biz.IBookBiz; public class Demo1 { public static void main(String[] args) { // 从spring中获取上下文 ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml"); // IBookBiz bean = (IBookBiz) context.getBean("bookbiz"); // System.out.println(bean.getClass()); //代理对象 IBookBiz bean = (IBookBiz) context.getBean("bookbizproxy"); // System.out.println(bean.getClass()); // 报错之后,程序终止 bean.buy("三毛", "三毛流浪记", 55d); bean.comment("三毛", "真的只有三毛"); } }
前置通知会在方法执行前就运行
【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, 55.0]) 三毛 buy 三毛流浪记, spend 55.0 【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛]) 三毛 say:真的只有三毛
2、后置通知 MyAfterReturningAdvice
package com.liuwenwu.aop.advise; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.AfterReturningAdvice; /** * 后置通知 * 买书返利 * @author ASUS * */ public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String clzName=arg3.getClass().getName(); String methodNname=arg1.getName(); String params=Arrays.toString(arg2); System.out.println("【后置通知(买书返利)】:"+clzName+"."+methodNname+"("+params+")"); } }
后置通知在方法执行后会运行
【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, 55.0]) 三毛 buy 三毛流浪记, spend 55.0 【后置通知(买书返利)】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, 55.0]) 【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛]) 三毛 say:真的只有三毛 【后置通知(买书返利)】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛])
3、环绕通知=前置通知+后置购置 MyMethodInterceptor:
package com.liuwenwu.aop.advise; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 类似拦截器,会包括切入点,目标类前后都会执行代码。 * @author ASUS * */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { String clzName=arg0.getThis().getClass().getName(); String methodNname=arg0.getMethod().getName(); String params=Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知(前后都有)】:"+clzName+"."+methodNname+"("+params+")"); // returnValue是代理对象调用目标方法的返回值 Object returnValue=arg0.proceed(); System.out.println("【环绕通知(前后都有)】:"+clzName+"."+methodNname+"("+params+")"+"方法调用的返回值:"+returnValue); return returnValue; } }
环绕通知在方法执行前后都会运行
【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛]) 【环绕通知(前后都有)】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛]) 三毛 say:真的只有三毛 【环绕通知(前后都有)】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛])方法调用的返回值:null 【后置通知(买书返利)】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛])
4、异常通知
package com.liuwenwu.aop.advise; import org.springframework.aop.ThrowsAdvice; import com.liuwenwu.aop.exception.PriceException; /** * 异常通知 * @author ASUS * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing( PriceException ex ) { System.out.println("价格输入有误,购买失败,请重新输入!!!"); } }
异常通知的作用是在程序出了BUG终止是也会先运行完异常通知里的代码
信息: Loading XML bean definitions from class path resource [spring-context.xml] Exception in thread "main" 【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, -55.0]) 价格输入有误,购买失败,请重新输入!!! com.liuwenwu.aop.exception.PriceException: book price exception
spring-context.xml配置
<!-- aop知识点 --> <!-- 目标 --> <bean id="bookbiz" class="com.liuwenwu.aop.biz.impl.BookBizImpl"></bean> <!-- 前置通知 --> <bean id="myMethodBeforeAdvice" class="com.liuwenwu.aop.advise.MyMethodBeforeAdvice"></bean> <!-- 后置通知 --> <bean id="myAfterReturningAdvice" class="com.liuwenwu.aop.advise.MyAfterReturningAdvice"></bean> <!-- 环绕通知 --> <bean id="myMethodInterceptor" class="com.liuwenwu.aop.advise.MyMethodInterceptor"></bean> <!-- 异常通知 --> <bean id="myThrowsAdvice" class="com.liuwenwu.aop.advise.MyThrowsAdvice"></bean> <!-- 过滤通知 --> <bean id="myAfterReturningAdvicePlus" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 过滤规则 --> <property name="advice" ref="myAfterReturningAdvice"></property> <!-- <property name="pattern" value=".*buy"></property> --> <property name="patterns"> <list> <value>.*buy</value> </list> </property> </bean> <!-- 目标+通知=代理对象 --> <bean id="bookbizproxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 目标 --> <property name="target" ref="bookbiz"></property> <!-- 代理的接口(目标对象所实现的接口) --> <property name="proxyInterfaces"> <list> <value>com.liuwenwu.aop.biz.IBookBiz</value> </list> </property> <!-- 通知 --> <property name="interceptorNames"> <list> <value>myMethodBeforeAdvice</value> <value>myAfterReturningAdvice</value> <!-- <value>myAfterReturningAdvicePlus</value> --> <!-- <value>myMethodInterceptor</value> --> <value>myThrowsAdvice</value> </list> </property> </bean>
5、过滤通知:
<bean id="myAfterReturningAdvicePlus" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 过滤规则 --> <property name="advice" ref="myAfterReturningAdvice"></property> <!-- <property name="pattern" value=".*buy"></property> --> <property name="patterns"> <list> <value>.*buy</value> </list> </property> </bean>
结果:评论后没有返利
【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, 55.0]) 三毛 buy 三毛流浪记, spend 55.0 【后置通知(买书返利)】:com.liuwenwu.aop.biz.impl.BookBizImpl.buy([三毛, 三毛流浪记, 55.0]) 【系统日志】:com.liuwenwu.aop.biz.impl.BookBizImpl.comment([三毛, 真的只有三毛]) 三毛 say:真的只有三毛