工 做忙,时间紧,不过事情再多,学习是必须的。记得之前的部门老大说过:“开发人员不可能一天到晚只有工做,确定是须要自我学习。第一:为了更充实本身,保 持进步状态。第二:为了提高技术,提升开发能力。第三:保持程序员对技术和学习的热情,工做的激情。程序员仍是须要把基础打扎实,修炼本身的内功。” 因此赶忙把学习的东西总结一下,加深印象。以前有说了下AOP的原理 (http://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html) 。基于代理模式,了解了jdk动态代理和cglib的用法。可是在真正的使用AOP的时候,不可能写这么厚重的方法。html
Spring有两大核 心,IOC和AOP。IOC在java web项目中无时无刻不在使用。然而AOP用的比较少,的确也是通常的项目用的场所很少。事务控制基本都用,但倒是Spring封装的不须要咱们再去实 现,但Spring的AOP远不止这些,不能由于项目中没有使用,而不去学习及理解。我以为这是做为一个java web软件开发人员必须具有的技能。业内不少将AOP应用在日志记录上,惋惜咱们项目没这么作,后面须要学习下。在这先把Spring AOP的基本用法,在脑子里理一边,作一次积累。java
一、概念术语 程序员
在开始以前,须要理解Spring aop 的一些基本的概念术语(总结的我的理解,并不是Spring官方定义):web
切面(aspect):用来切插业务方法的类。spring
链接点(joinpoint):是切面类和业务类的链接点,其实就是封装了业务方法的一些基本属性,做为通知的参数来解析。数组
通知(advice):在切面类中,声明对业务方法作额外处理的方法。app
切入点(pointcut):业务类中指定的方法,做为切面切入的点。其实就是指定某个方法做为切面切的地方。ide
目标对象(target object):被代理对象。学习
AOP代理(aop proxy):代理对象。测试
通知:
前置通知(before advice):在切入点以前执行。
后置通知(after returning advice):在切入点执行完成后,执行通知。
环绕通知(around advice):包围切入点,调用方法先后完成自定义行为。
异常通知(after throwing advice):在切入点抛出异常后,执行通知。
二、Spring AOP环境
要在项目中使用Spring AOP 则须要在项目中导入除了spring jar包以外,还有aspectjweaver.jar,aopalliance.jar ,asm.jar 和cglib.jar 。
好了,前提工做准备完成,Spring 提供了不少的实现AOP的方式,在学习过程当中,按部就班。进行Spring 接口方式,schema配置方式和注解的三种方式进行学习。好了废话很少说了,开始spring aop学习之旅:
三、方式一:AOP接口
利用Spring AOP接口实现AOP,主要是为了指定自定义通知来供spring AOP机制识别。主要接口:前置通知 MethodBeforeAdvice ,后置通知:AfterReturningAdvice,环绕通知:MethodInterceptor,异常通知:ThrowsAdvice 。见例子代码:
a、业务接口:
/** * 代理类接口,也是业务类接口<br> * * 利用接口的方式,spring aop 将默认经过jdk 动态代理来实现代理类<br> * 不利用接口,则spring aop 将经过cglib 来实现代理类 * * @author yanbin * */ public interface IBaseBusiness { /** * 用做代理的切入点方法 * * @param obj * @return */ public String delete(String obj); /** * 这方法不被切面切 * * @param obj * @return */ public String add(String obj); /** * 这方法切不切呢?能够设置 * * @param obj * @return */ public String modify(String obj); }
b、业务类:
/** * 业务类,也是目标对象 * * @author yanbin * */ public class BaseBusiness implements IBaseBusiness { /** * 切入点 */ public String delete(String obj) { System.out.println("==========调用切入点:" + obj + "说:你敢删除我!===========\n"); return obj + ":瞄~"; } public String add(String obj) { System.out.println("================这个方法不能被切。。。============== \n"); return obj + ":瞄~ 嘿嘿!"; } public String modify(String obj) { System.out.println("=================这个也设置加入切吧====================\n"); return obj + ":瞄改瞄啊!"; } }
c、通知类:
前置通知:
/** * 前置通知。 * * @author yanbin * */ public class BaseBeforeAdvice implements MethodBeforeAdvice { /** * method : 切入的方法 <br> * args :切入方法的参数 <br> * target :目标对象 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("===========进入beforeAdvice()============ \n"); System.out.print("准备在" + target + "对象上用"); System.out.print(method + "方法进行对 '"); System.out.print(args[0] + "'进行删除!\n\n"); System.out.println("要进入切入点方法了 \n"); } }
后置通知:
/** * 后置通知 * * @author yanbin * */ public class BaseAfterReturnAdvice implements AfterReturningAdvice { /** * returnValue :切入点执行完方法的返回值,但不能修改 <br> * method :切入点方法 <br> * args :切入点方法的参数数组 <br> * target :目标对象 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("==========进入afterReturning()=========== \n"); System.out.println("切入点方法执行完了 \n"); System.out.print(args[0] + "在"); System.out.print(target + "对象上被"); System.out.print(method + "方法删除了"); System.out.print("只留下:" + returnValue + "\n\n"); } }
环绕通知:
/** * 环绕通知 * * @author yanbin * */ public class BaseAroundAdvice implements MethodInterceptor { /** * invocation :链接点 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("===========进入around环绕方法!=========== \n"); // 调用目标方法以前执行的动做 System.out.println("调用方法以前: 执行!\n"); // 调用方法的参数 Object[] args = invocation.getArguments(); // 调用的方法 Method method = invocation.getMethod(); // 获取目标对象 Object target = invocation.getThis(); // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行 Object returnValue = invocation.proceed(); System.out.println("===========结束进入around环绕方法!=========== \n"); System.out.println("输出:" + args[0] + ";" + method + ";" + target + ";" + returnValue + "\n"); System.out.println("调用方法结束:以后执行!\n"); return returnValue; } }
异常通知:
/** * 异常通知,接口没有包含任何方法。通知方法自定义 * * @author yanbin * */ public class BaseAfterThrowsAdvice implements ThrowsAdvice { /** * 通知方法,须要按照这种格式书写 * * @param method * 可选:切入的方法 * @param args * 可选:切入的方法的参数 * @param target * 可选:目标对象 * @param throwable * 必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。 */ public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) { System.out.println("删除出错啦"); } }
d、定义指定切点:
/** * 定义一个切点,指定对应方法匹配。来供切面来针对方法进行处理<br> * * 继承NameMatchMethodPointcut类,来用方法名匹配 * * @author yanbin * */ public class Pointcut extends NameMatchMethodPointcut { private static final long serialVersionUID = 3990456017285944475L; @SuppressWarnings("rawtypes") @Override public boolean matches(Method method, Class targetClass) { // 设置单个方法匹配 this.setMappedName("delete"); // 设置多个方法匹配 String[] methods = { "delete", "modify" }; //也能够用“ * ” 来作匹配符号 // this.setMappedName("get*"); this.setMappedNames(methods); return super.matches(method, targetClass); } }
e、配置:
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-autowire="byName"> <!-- ==============================利用spring本身的aop配置================================ --> <!-- 声明一个业务类 --> <bean id="baseBusiness" class="aop.base.BaseBusiness" /> <!-- 声明通知类 --> <bean id="baseBefore" class="aop.base.advice.BaseBeforeAdvice" /> <bean id="baseAfterReturn" class="aop.base.advice.BaseAfterReturnAdvice" /> <bean id="baseAfterThrows" class="aop.base.advice.BaseAfterThrowsAdvice" /> <bean id="baseAround" class="aop.base.advice.BaseAroundAdvice" /> <!-- 指定切点匹配类 --> <bean id="pointcut" class="aop.base.pointcut.Pointcut" /> <!-- 包装通知,指定切点 --> <bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <ref bean="pointcut" /> </property> <property name="advice"> <ref bean="baseBefore" /> </property> </bean> <!-- 使用ProxyFactoryBean 产生代理对象 --> <bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理对象所实现的接口 ,若是有接口能够这样设置 --> <property name="proxyInterfaces"> <value>aop.base.IBaseBusiness</value> </property> <!-- 设置目标对象 --> <property name="target"> <ref local="baseBusiness" /> </property> <!-- 代理对象所使用的拦截器 --> <property name="interceptorNames"> <list> <value>matchBeforeAdvisor</value> <value>baseAfterReturn</value> <value>baseAround</value> </list> </property> </bean> </beans>
f、测试类:
public class Debug { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aop/schema_aop.xml"); IBaseBusiness business = (IBaseBusiness ) context.getBean("businessProxy"); business.delete("猫"); } }
g、测试结果:运行下测试类,清晰明了。因为结果呈现太长就不贴了。
具体的代码实现能够从代码注释中很容易理解 接口方式的实现。结果也可想而知,前置方法会在切入点方法以前执行,后置会在切入点方法执行以后执行,环绕则会在切入点方法执行前执行同事方法结束也会执 行对应的部分。主要是调用proceed()方法来执行切入点方法。来做为环绕通知先后方法的分水岭。而后在实现的过程当中,有几点倒是能够细揣摩一下的。
能够看出在xml 配置 businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里我把他配置了 IBaseBusiness接口。由于在项目开发过程当中,每每业务类都会有对应的接口,以方便利用IOC解耦。但Spring AOP却也能支持没有接口的代理。这就是为何须要导入cglib.jar的包了。看过spring的源码,知道在目标切入对象若是有实现接 口,spring会默认走jdk动态代理来实现代理类。若是没有接口,则会经过cglib来实现代理类。
这个业务类如今有 前置通知,后置通知,环绕三个通知同时做用,可能以及更多的通知进行做用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例 子xml中,则显示执行before通知,而后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。通过测 试,知道spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。因此是前通知依次执行,后通知到切入点执行完以后,从栈里 在后进先出的形式把后通知执行。
在实现过程当中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则须要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。而后经过DefaultPointcutAdvisor来包装通知,指定切点。