通知 Advice
通知(Advice), 切面的一种实现, 可以完成简单织入功能(织入功能就是在这里完成的)。常用通知有:前置通知、后置通知、环绕通知、异常处理通知。
- 通知的使用步骤
对于通知的定义、配置与使用,主要分为以下几步:
(1)定义目标类
定义目标类,就是定义之前的普通 Bean 类,也就是即将被增强的 Bean 类。
(2)定义通知类
通知类是指,实现了相应通知类型接口的类。当前,实现了这些接口,就要实现这些接口中的方法,而这些方法的执行,则是根据不同类型的通知,其执行时机不同。
前置通知:
在目标方法执行之前执行。
后置通知:
在目标方法执行之后执行。
环绕通知:
在目标方法执行之前与之后均执行。
异常处理通知:
在目标方法执行过程中,若发生指定异常,则执行通知中的方法一。
(3)注册目标类
即在 Spring 配置文件中注册目标对象 Bean。
![]()
(4)注册通知切面
即在 Spring 配置文件中注册定义的通知对象 Bean。
![]()
(5)注册代理工厂 Bean 类对象 ProxyFactoryBean
![]()
这里的代理使用的是 ProxyFactoryBean 类。代理对象的配置,是与 JDK 的 Proxy 代理参数是一致的,都需要指定三部分:目标类,接口,切面。
<property name="target" ref="目标对象 Bean 的 id " />
指定目标对象的 Bean 的 id。也可写为如下形式:
<property name="targetName”value="目标对象 Bean 的 id " />
<property name="proxyInterfaces" value="接口全限定性名" />
设置目标对象所实现的业务接口,要求给出接口的全既定性类名。此属性可以不进行设置,因为打开 ProxyFactoryBean 的源码,可以看到其有个自动检测目标类的所有接口属性 autodetectInterfaces,默认值为 true。即不设置也可以自动检测到。当然,此时使用的是 jdk 的 Proxy 动态代理。
![]()
如果目标对象没有实现业务接口,则可以不设置。此时使用的是 CGLIB 动态代理。
<property name-="interceptorNames" value=“通知的id" />
指定切面,这里指通知。注意,这里对于 id 的指定使用的是 value 属性,而非 ref。因为该属性名为 xxNames,是名称,所以其值为字符串,而非对象。
(6)客户端访问动态代理对象
客户端访问的是动态代理对象,而非原目标对象。因为代理对象可以将交叉业务逻辑按照通知类型,动态的织入到目标对象的执行中。
![]()
(7)在容器中的整体配置
![]()
- 通知详解
(1)前置通知(MethodBeforeAdvice)
定义前置通知,需要实现 MethodBeforeAdvice 接口。该接口中有一个方法before(), 会在目标方法执行之前执行。前置通知的特点:
① 在目标方法执行之前先执行。
② 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行。
③ 不改变目标方法执行的结果。
举例:aop01_01 包。
定义业务接口与目标类,
![]()
![]()
定义前置通知:
![]()
配置文件配置:
![]()
对于测试类,需要注意,从容器中获取的事代理对象,而非目标对象。
![]()
查看后台运行情况,可以看到代理生成使用的是 JDK 代理机制。
![]()
(2)后置通知 AfterReturningAdvice
定义后置通知,需要实现接口 AfterReturningAdvice 该接口中有一个方法afterReturning(),会在目标方法执行之后执行。后置通知的特点:
① 在目标方法执行之后执行。
② 不改变目标方法的执行流程后置通知代码不能阻止目标方法执行。
③ 不改变 目标方法执行的结果。
举例:aop01_02 包。
修改业务接口与实现类:
![]()
![]()
定义切面:通知
![]()
配置文件配置:
![]()
(3)环绕通知 MethodInterceptor
定义环绕通知,需要实现 MethodInterceptor 接口。环绕通知,也叫方法拦截器,可以在目标方法调用之前及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。注意,org.aopalliance.intercept.MethodInterceptor 才是需要的包。
举例:aop01_03 包。
![]()
![]()
![]()
(4)异常通知 ThrowsAdvice
定义异常通知,需要实现 ThrowsAdvice 接口。该接口的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处理。当该接口处理完异常后,会简单地将异常再次抛出给目标方法。
不过,这个接口较为特殊,从形式上看,该接口中没有必须要实现的方法。但,这个接口却确实有必须要实现的方法 afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其中一种,若要都定义到接口中,则势必要使程序员在使用时必须要实现这四个方法。这是很麻烦的。所以就将该接口定义为了标识接口(没有方法的接口)。
这四个方法在打开 ThrowsAdvice 源码后,上侧的注释部分可以看到:
![]()
不过,在这四种形式中,常用的形式如下:
public void afterThrowing(自定义的异常类e)
这里的参数 e 为,与具体业务相关的用户自定义的异常类对象。容器会根据异常类型的不同,自动选择不同的该方法执行。这些方法的执行是在目标方法执行结束后执行的。
其它参数则与前面两个通知中方法的参数意义相同。
举例:aop01_04 包。
本例实现用户身份验证。当用户名不正确时,抛出用户名有误异常;当密码不正确时,抛出密码有误异常。当然,在抛出这些异常后,都要做一些其它处理。
Step1:
定义异常类的父类。
![]()
Step2:
定义两个异常类的子类。
![]()
![]()
Step3:
定义业务接口类。要抛出异常父类。
![]()
Step4:
定义目标类。
![]()
Step5:
定义异常通知。
![]()
Step6:
配置文件配置。
![]()
Step7:
定义测试类。
![]()
通知的其他方法
(1)给目标方法织入多个切面
若要给目标方法织入多个切面,则需要在配置代理对象的切面属性时,设定为 list。
举例: aop01_05 包。
将前置通知与后置通知拷贝到同一个项目中。
![]()
(2)无接口的 CGLIB 代理生成
若不存在接口,则 ProxyFactoryBean 会自动采用 CGLIB 方式生成动态代理。
举例: aop01_06 包。
![]()
查看后台运行情况,可以看到代理生成使用的是 CGLIB 代理机制。
![]()
(3)有接口的 CGLIB 代理生成 proxyTargetClass 属性
若存在接口,但又需要使用 CGLIB 生成代理对象,此时,只需要在配置文件中增加一个 proxyTargetClass 属性设置,用于指定强制使用 CGLIB 代理机制。
举例: aop01_07 包。
![]()
也可指定 optimize (优化)的值为 true ,强制使用 CGLIB 代理机制。
![]()
查看后台运行情况,可以看到代理生成使用的是 CGLIB 代理机制。
![]()
网友评论