1.Spring的Aop特点
例如:
网上书城项目、
书籍管理模块
场景一:客户使用系统,上架了国家明令禁止的书籍
场景二:对与平台方而言客户已下单并已付款,工作人员出于私心,将货品拿了,从中牟利
所以,所有系统都会添加一个日志功能,也就是每一个业务功能模块都会有一个日志文件
Aop处理非业务核心代码
2.Aop的中关键概念(Aop的专业术语):
连接点(joinpoint):程序执行过程中明确地点,如方法的调用,或者异常的抛出,
目标:被通知(被代理)的对象
注:完整的具体业务逻辑
通知(advice):在某个特定的连接点上执行的动作,同时advice也是程序代码的具体实现,
例如一个实现日志记录的代码(通知在有些书上也成为处理)
注2:完成切面编程
代理:将通知应用到目标对象后创建的对象(代理=目标+通知)
例子:外科医生+护士
注3:只有代理对象才有Aop功能,而Aop的代码是写在通知方法里面的
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点(也将poiintcut理解成一个条件,此条件决定了容器在说明情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(advice)+切入点(pointcut)
总结:面向切面编程,使代码原有的从上倒下的执行顺序做出改变,加入了spring的Aop的面向切面编程这个思想,那么我们代码的执行顺序则不再是从上到下,而是走到了目标执行对象的时候要查看是否有前置通知,如果有则先执行前置通知在执行目标方法,如果没有则直接执行目标方法,那么再看目标执行代码是否有后置通知,如果有则执行后置通知,如果没有则代码执行完了
3.Spring中的专业术语所对应的案例
首先我们准备五个类
package xzs.aop.biz; public interface BookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
package xzs.aop.biz.impl; import xzs.aop.biz.BookBiz; import xzs.aop.biz.exceptio.PriceException; public class BookBizImpl implements BookBiz { 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); } }
package xzs.aop.biz.exceptio; 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); } }
3.1前置通知
package xzs.aop.biz.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 买书、评论前加系统日志 * @author Administrator * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去 String target = arg2.getClass().getName();//那个类 String methodName = arg0.getName();//调用什么方法 String args = Arrays.toString(arg1);//传递了什么参数 System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了"); } }
Sprint-context.xml中配置
<bean class="xzs.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <bean class="xzs.aop.biz.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>xzs.aop.biz.BookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> </list> </property> </bean>
测试:
3.2后置通知
package xzs.aop.biz.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.AfterReturningAdvice; /** * 买书返利 * @author Administrator * */ public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String target = arg3.getClass().getName(); String methodName = arg1.getName(); String args = Arrays.toString(arg2); System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0); } }
spring-context.xml中
<bean class="xzs.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <bean class="xzs.aop.biz.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <bean class="xzs.aop.biz.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>xzs.aop.biz.BookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> <value>myAfterReturningAdvice</value> </list> </property> </bean>
运行:
3.3环绕通知
package xzs.aop.biz.advice; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 包含了前置和后置通知 * * @author Administrator * */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { String target = arg0.getThis().getClass().getName(); String methodName = arg0.getMethod().getName(); String args = Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了"); // arg0.proceed()就是目标对象的方法 Object proceed = arg0.proceed(); System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed); return proceed; } }
<bean class="xzs.aop.biz.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean> <bean class="xzs.aop.biz.advice.MyMethodInterceptor" id="interceptor"></bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>xzs.aop.biz.BookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <!-- <value>methodBeforeAdvice</value>--> <!-- <value>myAfterReturningAdvice</value>value--> <value>interceptor</value> </list> </property> </bean>
运行:
3.4异常通知
package com.zking.aop.advice; import org.springframework.aop.ThrowsAdvice; import com.zking.aop.exception.PriceException; /** * 出现异常执行系统提示,然后进行处理。价格异常为例 * @author Administrator * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
<bean class="xzs.aop.biz.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>xzs.aop.biz.BookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <!-- <value>methodBeforeAdvice</value>--> <!-- <value>myAfterReturningAdvice</value>value--> <!-- <value>interceptor</value>--> <value>myThrowsAdvice</value> </list> </property> </bean>
3.5过滤通知
在spring的配置文件中直接配置就行
<bean class="xzs.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <bean class="xzs.aop.biz.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <bean class="xzs.aop.biz.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean> <bean class="xzs.aop.biz.advice.MyMethodInterceptor" id="interceptor"></bean> <bean class="xzs.aop.biz.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor"> <property name="advice" ref="myAfterReturningAdvice"></property> <property name="pattern" value=".*buy"></property> </bean> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>xzs.aop.biz.BookBiz</value> </list> </property> <!--配置通知--> <property name="interceptorNames"> <list> <value>methodBeforeAdvice</value> <!-- <value>myAfterReturningAdvice</value>value--> <value>regexpMethodPointcutAdvisor</value> <value>interceptor</value> <value>myThrowsAdvice</value> </list> </property> </bean>
测试:
过滤前:
以上对比可看出如果没有过滤评论就会出来买书返利,很显然不符合常理
4.总结
aop是面向切面的编程,程序由上至下,但是aop面向切面编程不是,aop得到程序执行,
首先当程序执行到目标对象的目标方法时,如果连接点上由前置通知,则先执行前置通知,
在执行目标方法,如果没有前置则继续执行目标方法,再查看目标方法👆是否有后置通知代码:
前置通知,环绕通知,异常通知,过滤通知,代码都是非业务核心代码:如日志,事务的管理(开启,提交,回滚)