1.aop理论知识javascript
横切性关注点:对哪些方法拦截,拦截后怎么处理,这些关注就称之为横切性关注点.java
Aspect(切面):指横切性关注点的抽象即为切面,它与类类似,只是二者的关注点不同,类是对物体特征的抽象,而切面是横切性关注点的抽象。
Joinpoint(链接点):所谓链接点是指那些被拦截到的点。在Spring中,这些点指的是方法,由于Spring只支持方法类型的链接点,实际上joinpoint还能够是field或类构造器。
Pointcut(切入点):所谓切入点是指咱们要对那些joinpoin进行拦截的定义。
Advice(通知):所谓通知是指拦截到joinpoint以后所要作的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并致使proxy对象建立的过程称为织入。
Introduction(引入):在不修改类代码的前提下,Introduction能够在运行期为类动态地添加一些方法或Field.spring
要进行AOP编程,首先咱们要在Spring的配置文件中引入aop命名空间,以下面文件中的红色部分所示,配置文件的内容以下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd ">
<aop:aspectj-autoproxy /><!-- 启动对@Aspectj注解的支持-->
</beans>express
Spring提供了两种切面使用方式,实际工做中咱们能够选用其中任何一种:编程
1.使用XML配置方式进行AOP开发app
2. 基于注解方式进行AOP开发测试
首先,必须在配置文件中beans的节点下有以下配置信息:<aop:aspectj-autoproxy/> -- 启动对@Aspectj注解的支持spa
下面经过实例来说解基于spring的AOP实现:.net
1.导入spring开发的基本包(包括切面及注解包)代理
2.定义PersonService接口及bean类PersonServiceBean,以下所示:
Java代码
Java代码
3.编写切面类MyInterceptor,代码以下:
Java代码
说明:
1. //* cn.itcast.service..*.*(..))第一个*表明返回的类型能够是任意的,service后面的..表明包service以及下面的全部子包,*表明这些包下面的全部类;.*表明全部这些类下面的全部方法;(..)表明这些方法的输入参数能够是0或多个.
2.在前置通知的方法中有一个参数,而后再把此参数做为拦截条件(便是说拦截带有一个String类型参数的方法)。args的名字和beforeAdd 方法参数名字相同。
3. afterReturningRes方法的参数就是要返回的参数类型,returning标记的就是的结果,它的取值与该方法参数名相同。
4. 环绕通知要注意的是方法的参数及抛出异常类型的固定写法(方法名能够是任意得),另在该方法中必须执行pjp.proceed()才能让环绕通知中的两处打印代码得以执行。便是说要想环绕通知的拦截处理代码起做用必须调用pjp.proceed方法。 补充:环绕通知一般能够用来测试方法的执行时间,在pjp.proceed前获取一个时间,在pjp.proceed方法后再获取一个时间。最后两个时间相减便可得方法执行时间。
上面基于注解的AOP换成基于XML的方式以下所示:
Java代码
当基于xml方式时,遇到一个未解决问题:不能成功传参给前置通知。当我定义一个前置通知以下所示时:
Java代码
运行测试程序,他不能成功拦截方法:
Java代码
上面的这个拦截类中的拦截方法除了切入点条件外,还必须知足一些辅助条件,使用拦截更精确了,若是你不想太精确,则能够简单点以下所示:
Java代码
替换成xml方式为:
Java代码
当不能前置通知传参数时,基于xml方式配置的这个AOP也能成功拦截save()方法.
Java代码
4.将这些内归入Spring管理(要想切面类起做用,首先要把切面类归入spring容器管理。),配置文件以下所示:
Xml代码
5. 编写junit测试单元以下:
Java代码
1. 执行测试程序后,控制台打印以下所示:
前置通知:xx
进入方法
我是save()方法
后置通知:0
最终通知
退出方法
从打印的结果能够看出当执行
Java代码
方法时,它有String类型的输入参数,拦截时前置通知执行了,因为它的返回类型为int因此后置通知也执行.
2.执行方法:
Java代码
控制台打印的信息以下所示:
进入方法
我是getPersonName()方法
最终通知
退出方法
因为getPersonName()方法是Integer类型的输入参数,同时它的返回类型又是String类型的,因此前置通知和后置通知都没有执行.
3. 将PersonServiceBean中程序
Java代码
前的注释去掉,从新执行
Java代码
控制台打印信息以下所示:
前置通知:xx
进入方法
例外通知:java.lang.ArithmeticException: / by zero
最终通知
当获取代理对象并调用save方法时会抛出异常,例外通知便会得以执行。
4.因为开启了切面编程功能,因此当咱们获取一个被切面类监控管理的bean对象—PersonServiceBean时,它实际上获取的是此对象的一个代理对象,而在spring中对代理对象的处理有以下原则:(1)若是要代理的对象实现了接口,则会按照Proxy的方式来产生代理对象,这便是说产生的代理对象只能是接口类型,好比起用上面注掉的代码
Java代码
就会报错,报错信息以下所示:java.lang.ClassCastException: $Proxy12 cannot be cast to cn.itcast.service.impl.PersonServiceBean.
(2)要代理的对象未实现接口,则按cglib方式来产生代理对象。让PersonServiceBean再也不实现PersonService接口。这时只能执行
Java代码
(3)另外还要注意:要想spring的切面技术起做用,被管理的bean对象只能是经过spring容器获取的对象。好比这里若是直接new出PersonServiceBean对象,则new出的对象是不能被spring的切面类监控管理。若是执行测试:
Java代码
则不会拦截save()方法,save()方法正常执行,控制台打印信息以下所示:我是save()方法.
小结:(1)声明aspect的切面类要归入spring容器管理才能起做用。(2)被管理的bean实例要经过容器的getBeans方法获取。 (3)依据被管理的bean是否实现接口,spring采起两种方式来产生代理对象。(4)在xml文件中启用<aop:aspectj-autoproxy/>
有疑问的地方:
若是我在MyInterceptor类中调换后置通知我最终通知的定义顺序,以下所示:
Java代码
则控制台打倒信息以下所示:
前置通知:xx
进入方法
我是save()方法
最终通知
后置通知:0
退出方法
则最终通知与后置通知的执行顺序与MyInterceptor类中通知定义的顺序相一致.但若是你把最终通知的定义放在MyInterceptor类的最前面,它的执行顺序也与上面的一致.
同时若是将最终通知的定义位于例外通知的前面,则再执行测试的第3步,也会出现与测试3不一致的结果,打印结果以下所示:
前置通知:xx
进入方法
最终通知
例外通知:java.lang.ArithmeticException: / by zero
执行顺序也与它们的定义顺序一致.
无论通知的定义顺序如何,反正前置通知会第一个执行,最终通知与后置通知和例外通知定义的顺序不一致时,他们执行的顺序也会发生改变,最终通知竟能够先于例外通知和后置通知先执行.由于在Spring里有可能在同一个AOP代理里混合通知器和通知类型。 例如,你能够在一个代理配置里使用一个拦截环绕通知,一个异常通知和一个前置通知:Spring将负责自动建立所需的拦截器链。 可能Spring建立拦截器链时与通知定义的顺序有必定关系,因此当最终通知的定义先于后置通知和例外通知时,它也将先于他俩执行.只是个人一种猜想.官方的解释以下:
下面这段话是Spring reference中的原话,不过我也没太看明白是咋回事.
通知顺序
若是有多个通知想要在同一链接点运行会发生什么?Spring AOP遵循跟AspectJ同样的优先规则来肯定通知执行的顺序。 在“进入”链接点的状况下,最高优先级的通知会先执行(因此给定的两个前置通知中,优先级高的那个会先执行)。 在“退出”链接点的状况下,最高优先级的通知会最后执行。(因此给定的两个后置通知中, 优先级高的那个会第二个执行)。
当定义在不一样的切面里的两个通知都须要在一个相同的链接点中运行, 那么除非你指定,不然执行的顺序是未知的。你能够经过指定优先级来控制执行顺序。 在标准的Spring方法中能够在切面类中实现org.springframework.core.Ordered 接口或者用Order注解作到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。
当定义在相同的切面里的两个通知都须要在一个相同的链接点中运行, 执行的顺序是未知的(由于这里没有方法经过反射javac编译的类来获取声明顺序)。 考虑在每一个切面类中按链接点压缩这些通知方法到一个通知方法,或者重构通知的片断到各自的切面类中 - 它能在切面级别进行排序。
5.解析切入点表达式
下面给出一些通用切入点表达式的例子。
任意公共方法的执行:
execution(public * *(..))
任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
AccountService
接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
execution(java.lang.String com.xyz.service..*.*(..))
execution(* com.xyz.service..*.*(java.lang.String,..))
execution(!void com.xyz.service..*.*(..))