5.3 AOP的3个关键概念* P+ B9 r' ]5 V' Q9 E
因为AOP的概念难于理解,所以在前面首先对Java动态代理机制进行了一下讲解,从而使读者能够循序渐进地来理解AOP的思想。
9 J6 d# ^4 M9 F" I$ Y) B, }$ s学习AOP,关键在于理解AOP的思想,能够使用AOP。对于AOP众多的概念,读者只要理解3个重要的概念即可。这3个概念是
Pointcut、Advice和Advisor。
( s' ^8 C* p( ~5 o W) Z3 I) w3 o; \ n6 [
5.3.1 切入点(Pointcut)
Z5 [& B7 d4 x" f在介绍Pointcut前,有必要先介绍一下Join Point(连接点)的概念。Join
, |* n% F# K1 A1 g4 mPoint指的是程序运行中的某个阶段点,如某个方法调用、异常抛出等。前面示例中的doAuditing()方法就是一个Join
/ p" e3 e$ I3 p! G4 w/ k. |
Point,表示程序是要在这个地方加入Advice。
, y% l, z' K' R$ Q7 q) r) ^( G
Pointcut是Join Point的集合,它是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发。
* s, G+ A( Y4 w* q5 q9 r" s6 P$ D3 z: H5 A8 n6 r3 k
org.springframework.aop.Pointcut & j' X* z2 u' \ q7 R( [# y
- A% O7 ^" a/ |% U1 R' c+ _8 H+ j
接口用来指定通知到特定的类和方法。查看Spring下载包里的源文件Pointcut.java,路径是spring-framework-2.0-m1\src\org\springframework\aop,可以看到Pointcut.java.源代码如下:
复制内容到剪贴板
代码:
//******* Pointcut.java**************
package org.springframework.aop;
public interface Pointcut
{
//用来将切入点限定在给定的目标类中
ClassFilter getClassFilter();
//用来判断切入点是否匹配目标类给定的方法
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}代码说明:
" i' i2 i0 m6 ^( k, ~) x6 d
●
接口ClassFilter,用来将切入点限定在给定的目标类中。9 I$ Q( p4 r5 D- i( [
●
接口MethodMatcher,用来判断切入点是否匹配目标类给定的方法。
( }# J* x$ z1 G从上面可以看出,在接口Pointcut中,主要包含两个接口:ClassFilter和MethodMatcher,有利于代码的重用。
2 \7 |0 Z7 Z, K/ `1 s* Z
2 O* @/ Z$ G! }; m' @0 ?5.3.2 通知(Advice)% k6 ^$ E0 y& x$ r, o/ m1 G
Advice是某个连接点所采用的处理逻辑,也就是向连接点注入的代码。前面示例中提取出来输出日志信息的代码就是一个Advice,表示要在Join Point加入这段代码。
0 ^8 {& O, g1 _& s# V( Q2 D8 j; S. T" R0 N
5.3.3 Advisor! j& H, Z* I1 F" Q) {
( V/ M+ I# }! e: @ s5 WAdvisor是Pointcut和Advice的配置器,它包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码。
+ Z: E6 { {4 S s/ x, U; j
上面只是粗略地对AOP的3个概念进行一下说明,目的是让读者能够较快地进入到AOP中,接下来将会分别对这3个概念进行更加详细的讲解。
# E. m" N! a, X0 P; I
) [2 h( r! u5 P8 n8 O+ Z8 y9 H Y5.4 Spring的3种切入点(Pointcut)实现' h) I. x+ e5 y6 E
上节讲过,Pointcut是Join
4 b% V/ \1 L8 V+ [
Point的集合,它是程序中需要注入Advice的位置的集合。Spring主要提供了3种切入点(Pointcut)的实现:
静态切入点、动态切入点和自定义切入点,下面分别进行讲解。
_/ m5 l0 {' J* U
+ N& V L6 q" a- U
5.4.1 静态切入点" l& z2 Y8 \- G* k
静态切入点只限于给定的方法和目标类,而不考虑方法的参数。
2 z- v; R( u' Q, s2 Y0 mSpring在调用静态切入点时只在第一次的时候计算静态切入点的位置,然后把它缓存起来,以后就不需要再进行计算。
, `' ]6 L! R' d! [# N) |
3 M" y% m0 n5 F4 D: Q" N7 j使用
org.springframework.aop.support.RegexpMethodPointcut 0 _' F; }! H' g3 Q+ n5 y' k% ^; F
; m% r; N# j; L/ v- N
可以实现静态切入点,RegexpMethodPointcut是一个通用的正则表达式切入点,它是通过Jakarta ORO来实现的,需要把jakarta-oro-2.0.8.jar加入到ClassPath中,它的正则表达式语法和Jakarta ORO的正则表达式语法是一样的。使用RegexpMethodPointcut的一个示例代码如下:
复制内容到剪贴板
代码:
<bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.RegexpMethodPointcut">
<property name="patterns">
<!--设定切入点-->
<list>
<value>.*save.*</value>
<value>.*do.* </value>
</list>
</property>
</bean>代码说明:
$ ~4 Z, M% K: X ● .*save.*,表示所有以save开头的方法都是切入点。- y+ F9 N4 Y! x' E6 ~ N" K7 L2 Y7 J
● .* do.*,表示所有以do开头的方法都是切入点。2 _7 z6 K# ^7 H Q
# y) R5 t( W7 g7 R
5.4.2 动态切入点
: b9 u; I" i2 p' P, s- p4 y动态切入点与静态切入点的区别是,它不仅限定于给点的方法和类,动态切入点还可以指定方法的参数。% ?' I, O$ U2 z( ^; g
因为参数的变化性,所以动态切入点不能缓存,需要每次调用的时候都进行计算,因此使用动态切入点有很大的性能损耗。
7 q( B& I+ F# U
当切入点需要在执行时根据参数值来调用通知时,就需要使用动态切入点。
: e8 t) m5 e% z+ `) `Spring提供了一个内建的动态切入点:控制流切入点。此切入点匹配基于当前线程的调用堆栈。开发人员只有在当前线程执行时找到特定的类和特定的方法才返回true。
. q3 r1 Y! s" t7 l! F其实大多数的切入点可以使用静态切入点,所以很少有机会创建动态切入点。! d( h) X$ u: [
0 s9 c- H M# k; I- I" S5.4.3 自定义切入点
4 k* }* X4 j- [( m因为Spring中的切入点是Java类,而不是语言特性(如AspectJ),因此可以定义自定义切入点。
+ P/ V/ C( e1 o: A3 J因为AOP还没有完全成熟,Spring提供的文档在这方面也没有提供更详细的解释,所以这里将不再对动态切入点和自定义切入点进行更加详细的介绍。
, D+ L( h* |1 `1 e3 ^) K) V/ G/ ^/ Y. S" L
5.5 Spring的通知(Advice)# d. T; ]. |* E5 Q* T1 v# O( J
Spring提供了5种Advice类型:
& d& `% i) H) h& n `2 q
Interception Around (在JointPoint前后) N4 }1 U" |6 |, s! I7 F' E
Before (JointPoint前)
1 m) I' w9 l% GAfter Returning (JointPoint后)
* ]: f# w! j2 y# a5 d# f# Y _Throw (JointPoint抛出异常时)
9 e7 n2 f0 D) Y% OIntroduction (JointPoint调用完毕后)
2 D, w3 N3 t: _& Q0 U它们分别在以下情况下被调用:在JointPoint前后、JointPoint前、JointPoint后、JointPoint抛出异常时、JointPoint调用完毕后。下面来进行更详细的讲解。
5 K, s" a0 ^+ o8 k9 c6 A+ d
0 S8 M! Z g6 A- Z- P$ Z5.5.1 Interception Around通知
7 J4 n w3 l6 {" _* e: N) `2 SInterception Around通知会在JointPoint的前后执行,前面示例中的LogProxy就是一个Interception Around通知,它在考勤审核程序的前后都执行了。
$ y c/ H6 C9 t* b6 g, o7 g# PSpring中最基本的通知类型便是Interception Around通知。实现Interception Around通知的类需要实现接口MethodInterceptor,示例代码如下:
复制内容到剪贴板
代码:
public class LogInterceptor implements MethodInterceptor
{
public Object invoke(MethodInvocation invocation) throws Throwable
{
System.out.println(" 开始审核数据...");
Object rval = invocation.proceed();
System.out.println(" 审核数据结束…");
return rval;
}
}5.5.2 Before通知
: @6 B0 P5 M" X( JBefore通知只在JointPoint前执行,实现Before通知的类需要实现接口MethodBeforeAdvice,示例代码如下:
复制内容到剪贴板
代码:
public class LogBeforeAdvice implements MethodBeforeAdvice
{
public void before(Method m, Object[] args, Object target) throws Throwable
{
System.out.println(" 开始审核数据...");
}
}5.5.3 After Returning通知2 t" b5 G0 \% k
After Returning通知只在JointPoint后执行,实现After Returning通知的类需要实现接口AfterReturningAdvice,示例代码如下:
复制内容到剪贴板
代码:
public class LogAfterAdvice implements AfterReturningAdvice
{
public void afterReturning (Method m, Object[] args, Object target) throws Throwable
{
System.out.println(" 审核数据结束...");
}
}5.5.4 Throw通知
- y* ~6 V& [$ |2 D3 U; I) sThrow通知只在JointPoint抛出异常时执行,实现Throw通知的类需要实现接口ThrowsAdvice,示例代码如下:
复制内容到剪贴板
代码:
public class LogThrowAdvice implements ThrowsAdvice
{
public void afterThrowing (RemoteException ex) throws Throwable
{
System.out.println(" 审核数据抛出异常,请检查..." + ex);
}
}5.5.5 Introduction通知3 f- Q( c# T9 v7 l4 C( l' S6 u
Introduction通知只在JointPoint调用完毕后执行,实现Introduction通知的类需要实现接口IntroductionAdvisor和接口IntroductionInterceptor。
# C6 p1 d$ r5 J前面所讲的知识点,更多的是理论,下面的章节将会讲述更多的实例,来帮助读者更好地理解上面的理论知识。
# Z; I4 G9 c6 Y; b7 {; m+ Z' \ m6 }; L4 d
5.6 Spring的Advisor
: m9 C0 y9 v4 ]8 d前面讲过,
Advisor是Pointcut和Advice的配置器,它是将Advice注入程序中Pointcut位置的代码。0 U1 W8 X$ _8 ?) ]8 `, l
org.springframework.aop.support.DefaultPointcutAdvisor是最通用的Advisor类。在Spring中,主要通过XML的方式来配置Pointcut和Advice
' X, x! q% D* Q1 ^+ o, x& j% l/ K8 k2 X8 @7 Q7 w( V. z; F4 O: U" I
5.7 用ProxyFactoryBean创建AOP代理
% y N# l; O/ q- u5 V使用Spring提供的类org.springframework.aop.framework.ProxyFactoryBean是创建AOP的最基本的方式。
& c9 l! p) d/ T# J8 C l- m
& U5 _+ S( a; x n1 j7 x5.7.1 使用ProxyFactoryBean代理目标类的所有方法
% x# L8 S1 F9 `0 g6 [7 ?9 l在Spring中,ProxyFactoryBean是在XML中进行配置的,它的示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--这里代理的是接口-->
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<!--是ProxyFactoryBean要代理的目标类-->
<property name="target">
<ref bean="timeBook"/>
</property>
<!--程序中的Advice-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>代码说明:
5 B7 \, v: n, U6 l8 `$ J ● id为log的Bean,是程序中的Advice。
- e* }7 `+ q Q# u3 I
● id为timeBook的Bean,是ProxyFactoryBean要代理的目标类。
/ J4 \: p7 u$ V$ L: D
● id为logProxy的Bean,就是ProxyFactoryBean。
3 S7 J* Z: B5 v* j; a
● ProxyFactoryBean的proxyInterfaces属性,指明要代理的接口。
4 M1 B) N+ Y3 r/ h0 l
● ProxyFactoryBean的target属性,指明要代理的目标类,这个目标类实现了上面proxyInterfaces属性指定的接口。
$ w) |8 u7 H( X$ ?* J0 p ● ProxyFactoryBean的interceptorNames属性,指明要在代理的目标类中插入的Adivce。
. U h0 |( D' d, O- y
● ProxyFactoryBean还有一个proxyTargetClass属性,如果这个属性被设定为“true”,说明ProxyFactoryBean要代理的不是接口类,而是要使用CGLIB方式来进行代理,后面会详细讲解使用CGLIB方式来进行代理。
9 l( Y9 ~7 {$ E+ z7 h# c
. r3 G7 r: h$ z+ u2 Y" Q; U%注意:ProxyFactoryBean的proxyInterfaces属性只支持使用字符串的方式进行注入,不支持使用Bean的依赖方式进行注入。* m/ t( x, o( W- {/ l
" \- {$ F( g! ]" [' Z5.7.2 使用ProxyFactoryBean代理目标类的指定方法
6 E1 o7 c# Q' o, F2 p) d5 o" G2 U \/ N8 O3 T5 s3 a
在上面的示例中,Advice会代理目标类的所有方法。
1 K6 Y# ^. z& f3 C& z% Z6 `1 c如果要代理目标类的指定方法,则需要使用Spring提供的
, @9 L* O, L" I3 q1 X( @org.springframework.aop.support.RegexpMethodPointcutAdvisor类。
) r- \; c9 @* F% x5 Z7 _0 b# _代理目标类的指定方法的示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--代理目标类的指定方法-->
<bean id="logAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="log"/>
</property>
<!--指定要代理的方法-->
<property name="patterns">
<value>.*doAuditing.* </value>
</property>
</bean>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</beans>代码说明:
2 g! g0 S N* N1 u0 \ ● 在id为logAdvisor的Bean中设定Advice和要指定的方法。
: B0 g/ b9 j2 M, B: i
● 把id为logProxy的Bean的interceptorNames属性值改为logAdvisor。
6 t: V0 P! u+ {7 K: b0 J% ?* a
● logAdvisor的advice属性指定Advice。
2 R+ e' q5 i* I* A( I1 ~) ^& M0 Y6 C ● logAdvisor的patterns属性指定要代理的方法。“.doAuditing”表示只有doAuditing()方法才使用指定的Advice。
# e9 d1 }& L5 J4 e
3 I% A0 {+ v$ J0 w( Vpatterns属性值使用的是正则表达式,关于正则表达式的使用,下节将会进行讲解。
- x: f ~! p# E" t( W' y" `
%注意:因为要使用正则表达式,所以要把spring-framework-2.0-m1\lib\oro目录下的
$ j8 l4 e3 H- c5 i1 U
jakarta-oro-2.0.8.jar加入到ClassPath下,加入方法可参考第2章。
+ \/ q: l; L+ n( W4 P& d1 P6 `$ \0 v/ k9 ?
5.7.3 正则表达式简介, m3 k& Z; I O) q( J
正则表达式最早是由数学家Stephen
, ?- E7 K+ j5 B3 |
Kleene于1956年在对自然语言的递增研究成果的基础上提出来的。正则表达式并非一门专用语言,但它可用于在一个文件中查找字符。下面把几个常用的进行一下讲解:
e. l9 E# R2 n: {, Y, u1 ^引用:
$ Q( s, ^' e( c3 v/ X(1)“.”,可以用来匹配任何一个字符。比如:正则表达式为“g.f”,它就会匹配“gaf”、“g1f”、“g*f”和“g #f”等。
$ K+ K. X( D+ X7 g, r(2)“[]”,只有[]里指定的字符才能匹配。比如:正则表达式为“g[abc]f”,它就只能匹配“gaf”、“gbf”和“gcf”,而不会匹配“g1f”、“g*f”和“g#f”等。 g( ^8 N/ U: u% @. ~& A) p
(3)“*”,表示匹配次数,可以任意次,用来确定紧靠该符号左边的符号出现的次数。比如:正则表达式为“g.*f”,它能匹配“gaf”、“gaaf”、“gf”和“g*f”等。
4 f( d! Y8 c1 f, [(4)“?”,可以匹配0或1次,用来确定紧靠该符号左边的符号出现的次数。比如:正则表达式为“g.?f”,它能匹配“gaf”“g*f”等。
* N9 M& y1 r/ s5 V4 z* L! k(5)“\”,是正则表达式的连接符。比如:正则表达式为“g.\-f”,它能匹配“g-f”、“ga-f”和“g*-f”等。
8 P, ?; ^6 u$ @. \
: ?; j5 d7 U. d, B8 N上面的示例中,只对TimeBook类的doAuditing()方法有效,如果要对TimeBook类中所有以do开头的方法有效,可以这样设定“.*do.*”,示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--代理目标类的指定方法-->
<bean id="logAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="log"/>
</property>
/*对所有以do开头的方法有效*/
<property name="patterns">
<value>.*do.* </value>
</property>
</bean>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</beans>上述代码对TimeBook类中所有以do开头的方法都有效。如果要对TimeBook类中所有方法有效,可以这样设定“.*com\.gc\.impl\.TimeBookInterface.*”,示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--代理目标类的指定方法-->
<bean id="logAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="log"/>
</property>
/*注意包路径的定义*/
<property name="patterns">
<value> .*com\.gc\.impl\.TimeBookInterface.*</value>
</property>
</bean>
<!--设定代理类-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</beans>上述代码对TimeBook类中所有的方法都有效。
9 z+ z1 E+ A0 K%注意:包的路径要用“\”连接符来表示“.”不是正则表达式的“.”。
; K, s* @0 q' }$ V R5 {+ n- |$ [. ], J! }7 G* k
5.8 把输出日志的实例改成用Spring的AOP来实现; h0 d) m$ p6 D% c, c) j
' s% _3 }: l5 w
在前面输出日志信息的实例中,没有用到Spring的任何组件,只是使用了Java的动态代理机制,但是却实实在在地体现了AOP的思想,如果使用Spring提供的AOP功能,应该怎样来实现前面那个实例呢?下面笔者就主要讲解如何把前面的那个实例改成通过Spring提供的AOP来实现。
1 n7 I6 n9 {4 x9 K9 G7 i2 v# V) k n' x; Y
5.8.1 采用Interception Around通知的形式实现
& A2 X6 o+ u O, J5 ^& I0 ~( cInterception Around通知会在JointPoint的前后执行,实现Interception Around通知的类需要实现接口MethodInterceptor。
其实现思路是:首先实现接口MethodInterceptor,在invoke()方法里编写负责输出日志信息的代码,具体的业务逻辑还使用前面的接口TimeBookInterface和它的实现类TimeBook,然后在Spring的配置文档中定义Pointcut,最后编写测试程序,执行测试程序,查看输出结果。具体步骤如下:
9 I$ H( _4 x5 \8 o3 U f) r
d* L8 e* t1 K(1)编写负责输出日志信息的类LogAround,该类实现了接口MethodInterceptor,重写了invoke()方法。LogAround.java的示例代码如下:
复制内容到剪贴板
代码:
//******* LogAround.java**************
package com.gc.action;
import org.aopalliance.intercept.MethodInvocation;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
// Interception Around通知会在JointPoint的前后执行
public class LogAround implements MethodInterceptor
{
private Logger logger = Logger.getLogger(this.getClass().getName());
//负责输出日志信息的代码
public Object invoke(MethodInvocation mi) throws Throwable
{
logger.log(Level.INFO, mi.getArguments()[0] + " 开始审核数据....");
try {
Object result = mi.proceed();
//返回值即是被调用的方法的返回值
return result;
}
finally
{
logger.log(Level.INFO, mi.getArguments()[0] + " 审核数据结束....");
}
}
}代码说明:
+ I# x! _5 }. }6 r Z2 N$ r! \ ●
参数MethodInvocation,通过它可以获得方法的名称、程序传入的参数Object[]等。
& N, X: o+ h( q5 N ●
proceed()方法,通过它即可执行被调用的方法。
* ^+ S/ Q# ~" g7 l2 b& U, v1 C ●
return result,返回值即是被调用的方法的返回值。
U' T3 Z' z# h, p. T7 I( m8 D
" d% w4 t9 x( I* O; d(2)使用com.gc.impl包中的接口TimeBookInterface。TimeBookInterface.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TimeBookInterface.java**************
package com.gc.impl;
import org.apache.log4j.Level;
public interface TimeBookInterface
{
//负责具体的业务逻辑
public void doAuditing(String name);
}(3)使用com.gc.action包中的类TimeBook,doAuditing()方法中编写具体的考勤审核代码。TimeBook.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TimeBook.java**************
package com.gc.action;
import com.gc.impl.TimeBookInterface;
public class TimeBook implements TimeBookInterface
{
public void doAuditing(String name)
{
//审核数据的相关程序
……
}
}(4)定义Spring的配置文档config.xml。config.xml的示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!--使用依赖注入完成变量值设定-->
<bean id="HelloWorld" class="com.gc.action.HelloWorld" depends-on="date">
<property name="msg">
<value>HelloWorld</value>
</property>
<!--使用Bean进行参考-->
<property name="date">
<ref bean="date"/>
</property>
</bean>
<bean id="date" class="java.util.Date"/>
<!--以下是使用Spring AOP实现日志输出的Bean-->
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--使用Spring提供的ProxyFactoryBean来实现代理-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>代码说明:
+ |$ X) v8 N+ A% I$ D; t5 V& _ ●
id为log的Bean,负责输出日志信息。' h7 m# f% ]) @
●
id为timeBook的Bean,负责具体的业务逻辑考勤审核。* @2 I8 c' M- [6 S3 C% h
●
id为logProxy的Bean,使用Spring提供的ProxyFactoryBean来实现代理,在该Bean里要定义相关的属性,包括要代理的接口、目标类以及要使用的Interceptor。$ P0 _4 P$ w8 ]. E( a5 ]' u
2 s. G0 m0 q6 W7 R. C0 |: c(5)修改测试代码TestHelloWorld,使用Spring提供的代理类ProxyFactoryBean实现日志的输出。TestHelloWorld.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TestHelloWorld.java**************
package com.gc.test;
import com.gc.action.TimeBook;
import com.gc.action.TimeBookProxy;
import com.gc.impl.TimeBookInterface;
public class TestHelloWorld
{
public static void main(String[ ] args)
{
//通过ApplicationContext获取XML
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("logProxy");
timeBookProxy.doAuditing("张三");
}
}(6)运行测试程序,可以得到通过LogAround类输出日志信息,如图5.4所示。
o! W, G6 J- M' M

图5.4 通过LogAround类输出日志信息
上面这个例子即实现了Interception Around通知,最终的效果和前面使用Java代理的效果一样,但是功能却更加强大。假如有一个新的关账程序要实现日志输出,则可以建立新的接口和其实现类,然后只要在配置文件中注册一下就可以使新的实现类也输出日志信息了。
7 B6 P0 |6 F+ e4 c. E0 @(7)在com.gc.impl包中建立新的接口FinanceInterface。FinanceInterface.java的示例代码如下:
复制内容到剪贴板
代码:
//******* FinanceInterface.java**************
package com.gc.impl;
import org.apache.log4j.Level;
//定义为接口主要是为了实现代理
public interface FinanceInterface
{
public void doCheck(String name);
}(8)在com.gc.action包中新建类Finance,doCheck()方法中编写具体的财务关账代码。Finance.java的示例代码如下:
复制内容到剪贴板
代码:
//******* Finance.java**************
package com.gc.action;
import com.gc.impl. FinanceInterface;
public class Financeimplements FinanceInterface
{
public void doCheck (String name)
{
//关账的相关程序
……
}
}(9)定义Spring的配置文档config.xml。config.xml的示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!--使用依赖注入完成变量值设定-->
<bean id="HelloWorld" class="com.gc.action.HelloWorld" depends-on="date">
<property name="msg">
<value>HelloWorld</value>
</property>
<property name="date">
<ref bean="date"/>
</property>
</bean>
<bean id="date" class="java.util.Date"/>
<!--以下是使用Spring AOP实现日志输出的Bean-->
<bean id="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<!--以下是考勤审核-->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
<!--以下是财务关账-->
<bean id="finance" class="com.gc.action.Finance"/>
<bean id="logProxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.FinanceInterface</value>
</property>
<property name="target">
<ref bean="finance"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans> ● id为finance的Bean,负责具体的业务逻辑财务关账。
7 H7 o. J* b) H: t- ^* j ● id为logProxy1的Bean,使用Spring提供的ProxyFactoryBean来实现代理,在该Bean里要定义相关的属性,包括要代理的接口、目标类以及要使用的Interceptor。
/ z8 [& e+ m9 a- E
(10)修改测试代码TestHelloWorld,使用Spring提供的代理类ProxyFactoryBean实现日志的输出。TestHelloWorld.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TestHelloWorld.java**************
package com.gc.test;
import com.gc.action.TimeBook;
import com.gc.action.TimeBookProxy;
import com.gc.impl.TimeBookInterface;
import com.gc.impl.FinanceInterface;
public class TestHelloWorld
{
public static void main(String[ ] args)
{
//通过ApplicationContext获取XML
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
FinanceInterface financeProxy = (FinanceInterface)actx.getBean("logProxy1");
financeProxy.doCheck("李四");
}
}(11)运行测试程序,可以得到通过LogAround类输出日志信息,如图5.5所示。

图5.5 通过LogAround类输出日志信息
/ F0 p# | [7 k" I5.8.2 采用Before通知的形式实现
: T4 ^* s/ z9 @3 LBefore通知只在JointPoint的前面执行,实现Before通知的类需要实现接口MethodBeforeAdvice。实现思路是:首先实现接口MethodBeforeAdvice,在before()方法里编写负责输出日志信息的代码,具体的业务逻辑还使用前面的接口TimeBookInterface和它的实现类TimeBook,然后在Spring的配置文档中定义Pointcut,最后测试程序不用改变,执行测试程序,查看输出结果。具体步骤如下:
(1)编写负责输出日志信息的类LogBefore,该类实现了接口MethodBeforeAdvice,重写了before()方法。LogBefore.java的示例代码如下:
复制内容到剪贴板
代码:
//******* LogBefore.java**************
package com.gc.action;
import java.lang.reflect.Method;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;
//实现Before通知的类需要实现接口MethodBeforeAdvice
public class LogBefore implements MethodBeforeAdvice
{
private Logger logger = Logger.getLogger(this.getClass().getName());
// Before通知只在JointPoint的前面执行
public void before(Method method, Object[ ] args, Object target) throws Throwable
{
logger.log(Level.INFO, args[0] + " 开始审核数据....");
}
}(2)使用com.gc.impl包中的接口TimeBookInterface。TimeBookInterface.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TimeBookInterface.java**************
package com.gc.impl;
import org.apache.log4j.Level;
public interface TimeBookInterface
{
public void doAuditing(String name);
}(3)使用com.gc.action包中的类TimeBook,doAuditing()方法中编写具体的考勤审核代码。TimeBook.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TimeBook.java**************
package com.gc.action;
import com.gc.impl.TimeBookInterface;
public class TimeBook implements TimeBookInterface
{
public void doAuditing(String name)
{
//审核数据的相关程序
……
}
}(4)定义Spring的配置文档config.xml。config.xml的示例代码如下:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!--使用依赖注入完成变量值设定-->
<bean id="HelloWorld" class="com.gc.action.HelloWorld" depends-on="date">
<property name="msg">
<value>HelloWorld</value>
</property>
<property name="date">
<ref bean="date"/>
</property>
</bean>
<bean id="date" class="java.util.Date"/>
<!--以下是使用Spring AOP实现日志输出的Bean-->
<bean id="log" class="com.gc.action.LogAop"/>
<!--负责具体的业务逻辑考勤审核-->
<bean id="timeBook" class="com.gc.action.TimeBook"/>
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
<!--以下是实现Before通知-->
<bean id="logBefore" class="com.gc.action.LogBefore"/>
<bean id="logBeforeAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logBefore"/>
</property>
<!--代理指定类中的doAuditing 方法-->
<property name="patterns">
<value>.*doAuditing.* </value>
</property>
</bean>
<bean id="logProxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name="target">
<ref bean="timeBook"/>
</property>
<!--指定要代理的类-->
<property name="interceptorNames">
<list>
<value>logBeforeAdvisor</value>
</list>
</property>
</bean>
</beans>代码说明:
; Q" j @) k! r, v ● id为timeBook的Bean,负责具体的业务逻辑考勤审核。
8 A+ P; ~4 Q7 @; R' b6 v ● id为logProxy2的Bean,使用Spring提供的ProxyFactoryBean来实现代理,在该Bean里要定义相关的属性,包括要代理的接口、目标类以及要使用的Interceptor。
! x% D, v! @5 g4 F! N& f8 E) O ● id为logBeforeAdvisor的Bean,使用类RegexpMethodPointcutAdvisor来实现对切入点的配置
* j7 W! E" @: D) m6 q& w: Y ● 属性名为patterns的值,指明只对doAuditing()方法有效。
(5)修改测试代码TestHelloWorld,使用Spring提供的代理类ProxyFactoryBean实现日志的输出。TestHelloWorld.java的示例代码如下:
复制内容到剪贴板
代码:
//******* TestHelloWorld.java**************
package com.gc.test;
import com.gc.action.TimeBook;
import com.gc.action.TimeBookProxy;
import com.gc.impl.TimeBookInterface;
public class TestHelloWorld
{
public static void main(String[ ] args)
{
//通过ApplicationContext来获取XML
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("logProxy2");
timeBookProxy.doAuditing("张三");
}
}(6)运行测试程序,可以得到通过LogBefore类输出日志信息,如图5.6所示。

图5.6 通过LogBefore类输出日志信息
上面这个例子即实现了Before通知。
$ p. p, X6 V, m, Y% `3 j& ~
5.8.3 采用After Returning通知的形式实现! T6 c+ A; h$ z% z4 ?( L
After Returning通知只在JointPoint的后面执行,实现After Returning通知的类需要实现接口AfterReturningAdvice。
其实现思路是:首先实现接口AfterReturningAdvice,在afterReturning()方法里编写负责输出日志信息的代码,具体的业务逻辑还使用前面的接口TimeBookInterface和它的实现类TimeBook,然后在Spring的配置文档中定义Pointcut,最后测试程序不用改变,执行测试程序,查看输出结果。具体步骤如下:1 r" j, y4 ^) W6 C5 c L2 m+ V6 |
(1)编写负责输出日志信息的类LogAfter,该类实现了接口AfterReturningAdvice,重写了afterReturning()方法。LogAfter.java的示例代码如下: