4.1 AOP入门
l 从代理机制初探AOP
例子4.1.1
interface IHi {
public void sayHi(String name);
}
class HiSpeaker implements IHi {
public void sayHi(String name) {
System.out.println("Hi, " + name);
}
}
public class LogHandler implements InvocationHandler {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Object delgate;
public Object bind(Object delgate) {
this.delgate = delgate;
return Proxy.newProxyInstance(delgate.getClass().getClassLoader(),
delgate.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
log("method starts ... " + method);
result = method.invoke(delgate, args);
log("method ends ... " + method);
} catch (Exception e) {
log(e.toString());
}
return result;
}
private void log(String msg) {
logger.log(Level.INFO, msg);
}
public static void main(String args[]) {
LogHandler log = new LogHandler();
IHi helloProxy = (IHi)log.bind(new HiSpeaker());
helloProxy.sayHi("张怡宁");
}
}
运行结果:
Hi, 张怡宁
2008-8-25 22:22:40 com.spring.ch4.LogHandler log
信息: method starts ... public abstract void com.spring.ch4.IHi.sayHi(java.lang.String)
2008-8-25 22:22:40 com.spring.ch4.LogHandler log
信息: method ends ... public abstract void com.spring.ch4.IHi.sayHi(java.lang.String)
|
l AOP观念与术语
Ø Cross-cutting concern
在前面的例子4.1.1中,日志的动作原先被横切(Cross-cutting)入至HiSpeaker本身所负责的业务流程之中,另外类似日志这类的动作,如安全(security)、检查、事务(Transaction)等系统层面的服务(Service),在一些应用程序之中常被见到安插到各个对象的处理流程中,这些动作在Aop术语中称为Cross-cutting concerns。 Corss-cutting concern若直接编写在负责业务的对象流程中,会使得维护程序的成本增高,若要修改或者移除对象中的日志功能,则必须修改所有编写日志服务的程序代码,然后重新编译,另一方面,Cross-cutting concerns 混杂于业务逻辑之中,使得业务对象本身的逻辑或者程序的编写更为复杂。
Ø Aspect
将散落于业务逻辑之中的Cross-cutting concerns 收集起来,设计成为各个独立可重用的对象,这些对象成为Aspect。例如在上面的例子4.1.1中,将日志的动作设计成一个LogHandler类,LogHandler类在AOP的术语就是Aspect的一个具体实例。在AOP中着重于Aspect的辨认,使之从业务流程中独立出来。在需要该服务的时候,织入(Weave)至应用程序之上;不需要服务的时候,可以马上从程序中脱离,且应用程序中的可用组件不做任何修改。例如例子4.1.1的HiSpeaker就是一个可重用的组件,在他需要日志服务时并不用修改本身的程序代码。另一方面,对于程序中可重用的组件来说,按照AOP的设计方法,他不用知道提供服务的对象是否成立,具体的说,与服务相关的API不会出现可重用的应用程序之中,因而可提高这些组件的重用性,可以将这些组件应用到其他的程序中,不会因为加入了某些服务而与目前的应用程序框架发生耦合。
Ø Advice
Aspect当中对Cross-cutting concerns的具体实现称为Advice。以日志的动作而言,Advice中会包括日志程序代码是如何实现的,像例子4.1.1的LogHandler的invoke()方法,就是Advice的一个具体实例。Advice中包括了Cross-cutting concerns的行为或所要提供的服务。
Ø Joinpoint
Advice在程序执行时加入业务程序的点或时机称为Joinpoint,具体来说就是Advice在程序中被执行的时机。Spring只支持方法的Joinpoint,执行时机可能是某个方法被执行之前或之后,或是方法中某个异常发生的时候。
Ø Pointcut
Pointcut定义了感兴趣的Jointpoint,当调用的方法符合Pointcut表示式时,将Advice织入到程序上提供服务。具体在Spring中,可以在定义文件或Annotation中编写Pointcut,说明哪些Advice要应用到方法的前后。
Ø Target
一个Advice被应用的对象或目标对象,例子4.1.1中的HiSpeaker就是LogHandler中Advice的Target。
Ø Introduction
对于一个现存的类,Introduction可以为其增加行为,且不用修改该类的程序,具体来说,可以为某个已编写或者编译完的类,在执行时期动态地加入一些方法或行为,而不用修改或新增任何一行程序代码。
Ø Proxy
Spring的AOP主要通过动态代理来完成的,可用于代理任何的接口。另一方面,Spring也可以使用CGLIB代理,用以代理类,像一些遗留类(Legacy classes)。
Ø Weaver
Advice被应用到对象之上的过程称为织入(Weave),在AOP中织入的方式有几个时间点:编译时期,类加载时期,执行时期。
l Spring AOP(省略)
4.2 Advice
Advice实现了Aspect的真正逻辑,具体来说在Java中就是一个类或者设计成一个方法(由一个类来集中管理许多的Advices)。由于织入Targets的时机不同,Spring提供了几种不同的Advice,如Before Advice、After Advice、Around Advice、Throw Advice。可以从编写、使用Advices来认识Spring AOP。
l Before Advice
Before Advice 会在目标对象的方法执行之前被调用,可以实现org.springframework.aop.MethodBeforeAdvice接口来实现Before Advice的逻辑 。
例子4.2.1
<bean id="logBeforeAdvice" class="com.spring.ch4.LogBeforeAdvice"></bean>
<bean id="helloSpeaker" class="com.spring.ch4.HelloSpeaker"></bean>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.spring.ch4.IHello"></property>
<property name="target" ref="helloSpeaker"></property>
<property name="interceptorNames">
<list><value>logBeforeAdvice</value></list>
</property>
</bean>
interface IHello {
public void hello(String name);
}
class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
class LogBeforeAdvice implements MethodBeforeAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void before(Method method, Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "before method starts... " + method);
}
}
public class BeforeAdviceDemo {
public static void main(String args[]) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
IHello proxy = (IHello)context.getBean("helloProxy");
proxy.hello("张怡宁");
}
}
运行结果为:
2008-8-26 22:38:48 com.spring.ch4.LogBeforeAdvice before
信息: before method starts... public abstract void com.spring.ch4.IHello.hello(java.lang.String)
Hello, 张怡宁
|
l After Advice
After Advice 会在目标方法执行之后被调用,可以实现org.springframework.aop.AfterReturningAdvice。
class LogAfterAdvice implements AfterReturningAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterReturning(Object object, Method method, Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "after method starts... " + method);
}
}
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.spring.ch4.IHello"></property>
<property name="target" ref="helloSpeaker"></property>
<property name="interceptorNames">
<list>
<value>logBeforeAdvice</value>
<value>logAfterAdvice</value>
</list>
</property>
</bean>
运行结果为:
2008-8-26 22:55:53 com.spring.ch4.LogBeforeAdvice before
信息: before method starts... public abstract void com.spring.ch4.IHello.hello(java.lang.String)
Hello, 张怡宁
2008-8-26 22:55:53 com.spring.ch4.LogAfterAdvice afterReturning
信息: after method starts... public abstract void com.spring.ch4.IHello.hello(java.lang.String)
|
l Around Advice
如果要在方法执行前后加入Advices的服务逻辑,可以通过实现org.aopalliance.intercept.MethodInterceptor接口,在方法执行前、执行后执行相关的服务。MethodInvocation的proceed()方法来执行目标对象的方法。Proceed()会回传方法执行后的Object执行结果,所以在invoke()结束之前,你有机会修改这个对象,或者回传一个不相干的对象。
public class LogInterceptor implements MethodInterceptor {
private Logger logger = Logger.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
logger.log(Level.INFO, "method starts..." + methodInvocation.getMethod());
Object obj = null;
try {
obj = methodInvocation.proceed();
} catch (Exception e) {
} finally {
logger.log(Level.INFO, "method ends..." + methodInvocation.getMethod());
}
return obj;
}
}
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.spring.ch4.IHello"></property>
<property name="target" ref="helloSpeaker"></property>
<property name="interceptorNames">
<list>
<!--
<value>logBeforeAdvice</value>
<value>logAfterAdvice</value>
-->
<value>logInterceptor</value>
</list>
</property>
</bean>
<bean id="logInterceptor" class="com.spring.ch4.LogInterceptor"></bean>
2008-8-28 21:43:50 com.spring.ch4.LogInterceptor invoke
信息: method starts...public abstract void com.spring.ch4.IHello.hello1(java.lang.String)
2008-8-28 21:43:50 com.spring.ch4.LogInterceptor invoke
信息: method ends...public abstract void com.spring.ch4.IHello.hello1(java.lang.String)
Hello1, 张怡宁
|
l Throw Advice
如果要在异常发生时通知某些服务对象做某些事,可以使用Throws Advice,在Spring中要使用,必须使用org.springframework.aop.ThrowsAdvice接口,然而这个接口没有定义任何方法,只是一个标签接口,可以在当中定义afterThrowing([Method],[args],[target], subClassOfThrowable);括号中的设置可以省略,方法中一定要的是subClassOfThrowable。注意到当异常发生的时候,Throw Advice的任务只是执行对应的方法,并不能在Throw Advice并不介入应用程序的异常处理,异常处理仍旧是应用程序本省所要负责的,如果想要在Throw Advice处理时中止应用程序的处理流程,做法是丢出其他的异常。
interface IHello {
public void hello1(String name) throws Throwable;
}
class HelloSpeaker implements IHello {
public void hello1(String name) throws Throwable {
System.out.println("Hello2, " + name);
throw new Exception("发生异常...");
}
}
public class SomeThrowAdvice implements ThrowsAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterThrowing(Method method, Object[] args, Object target, Throwable subclass) {
logger.log(Level.INFO,"Logging that a " + subclass + "Exception was thrwon in " + method.getName());
}
}
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.spring.ch4.IHello"></property>
<property name="target" ref="helloSpeaker"></property>
<property name="interceptorNames">
<list>
<!--
<value>logBeforeAdvice</value>
<value>logAfterAdvice</value>
<value>logInterceptor</value>
-->
<value>someThrowAdvice</value>
</list>
</property>
</bean>
Hello2, 张怡宁
2008-8-28 22:15:15 com.spring.ch4.SomeThrowAdvice afterThrowing
信息: Logging that a java.lang.Exception: 发生异常...Exception was thrwon in hello1
java.lang.Exception: 发生异常...
|
4.3 Pointcut、Advisor
l NameMatchMethodPointcutAdvisor
Spring中内建的Pointcut都有对应的PointcutAdvisor, org.springframework.aop.support.NameMatchMethodPointcutAdvisor是最基本的Pointcut,用来提供spring中静态的Pointcut实例,可使用表达式指定Advice应用目标上的方法名称,或者是用*来指定,例如当hello*表示执行代理对上以hello作为开头的方法名称,它都会应用制定的Advices。
interface IName {
public void helloNewbie(String name);
public void helloMaster(String name);
}
class NameSpeaker implements IName {
public void helloMaster(String name) {
System.out.println("你好 " + name +" master!");
}
public void helloNewbie(String name) {
System.out.println("你好 " + name +" newbie!");
}
}
public class NameMatchDemo {
public static void main(String args[]) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
IName name = (IName)context.getBean("helloProxy");
name.helloMaster("刘德华");
name.helloNewbie("张怡宁");
}
}
<bean id="logBeforeAdvice" class="com.spring.ch4.LogBeforeAdvice"></bean>
<bean id="nameAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="*Newbie"></property>
<!—
<property name=”mappedNames”>
<list>
<value>helloNewbie</value>
<value>helloMaster</value>
</list>
</property>
-->
<property name="advice" ref="logBeforeAdvice"></property>
</bean>
<bean id="nameSpeaker" class="com.spring.ch4.NameSpeaker"></bean>
<bean id="helloProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"
value="com.spring.ch4.IName">
</property>
<property name="target" ref="nameSpeaker"></property>
<property name="interceptorNames">
<list>
<value>nameAdvisor</value>
</list>
</property>
</bean>
你好 刘德华 master!
你好 张怡宁 newbie!
2008-9-2 21:09:25 com.spring.ch4.LogBeforeAdvice before
信息: before method starts... public abstract void com.spring.ch4.IName.helloNewbie(java.lang.String)
|
l RegExpMethodPointcutAdvisor
. 符合任何单一字符
+ 前一个字符出现一次或者多次
* 前一个字符出现零次或者多次
\ Escape任何Regular expression使用到的符号
RegexpMethodPointcutAdvisor的”pattern”属性让你指定要符合的完整类名称(包括包名称)和方法名称,例如若要求符合com.test.Ihello下得到hello开始的方法名称,则要如下编写:
com\.test\.Ihello\.hello.*
<bean id="regExpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*Newbie"></property>
<property name="advice" ref="logBeforeAdvice"></property>
</bean>
<bean id="nameSpeaker" class="com.spring.ch4.NameSpeaker"></bean>
<bean id="helloProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"
value="com.spring.ch4.IName">
</property>
<property name="target" ref="nameSpeaker"></property>
<property name="interceptorNames">
<list>
<value>regExpAdvisor</value>
</list>
</property>
</bean>
|
l ControlFlowPointcut
Org.springframework.aop.support.ControlFlowPointcut是spring所提供的类,作用是判断在方法的执行堆栈中,某个指定类的某个方法是否曾经要求目标对象执行某个动作,由用户这在执行时期才会确定是否介入Advices,所以Spring提供的是动态Pointcut功能。
4.4 Introduction
在spring中,Introduction是一种特殊的Advice,从行为上来看,它不像Before Advice, After Advice等Advice在方法前后介入服务,而是直接介入整个对象的行为,就好像对象凭空多了一些可操作的行为,为对象动态加入原先没有的职责。
l IntroductionInterceptor
Introduction是个特别的Advice,从用户的角度来看,动态地为对象增加可操作的方法显的不可思议,可以通过实现org.springframework.aop.IntroductionInterceptor来实现Introduction。IntroductionInterceptor继承了MethodInterceptor和DynamicIntroductionAdvice接口,其中implementsInterface()方法如果传回true,表示目前的IntroductionInterceptor实现了规定的接口(也就是额外要增加行为的接口),此时你要执行invoke(),让目标对象执行额外的行为,不可以使用MethodInvocation的proceed()方法,因为要执行的是对象上原来没有的行为,执行proceed()方法没有意义。
interface ISome {
public void doSome();
}
class SomeA implements ISome {
public void doSome() {
System.out.println("原来对象的职责...");
}
}
interface IOther {
public void doOther();
}
class OtherIntroduction implements IntroductionInterceptor, IOther {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (implementsInterface(methodInvocation.getMethod().getDeclaringClass())) {
return methodInvocation.getMethod().invoke(this, methodInvocation.getArguments());
} else {
return methodInvocation.proceed();
}
}
public boolean implementsInterface(Class cls) {
return cls.isAssignableFrom(IOther.class);
}
public void doOther() {
System.out.println("增加对象的职责...");
}
}
public class IntroductionDemo {
public static void main(String args[]) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
ISome some = (ISome)context.getBean("otherProxy");
some.doSome();
((IOther)some).doOther();
}
}
<bean id="someA" class="com.spring.ch4.SomeA"></bean>
<bean id="otherIntroduction" class="com.spring.ch4.OtherIntroduction"></bean>
<bean id="otherAdviser" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg ref="otherIntroduction"></constructor-arg>
<constructor-arg value="com.spring.ch4.IOther"></constructor-arg>
</bean>
<bean id="otherProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"
value="com.spring.ch4.ISome">
</property>
<property name="target" ref="someA"></property>
<property name="interceptorNames">
<list>
<value>otherAdviser</value>
</list>
</property>
</bean>
原来对象的职责...
增加对象的职责...
|
l DelegatingIntroductionInterceptor
org.springframework.aop.support.DelegatingIntroductionInterceptor是Spring Aop中为IntroductionInterceptor接口所提供的实现类。你可以直接继承这个类,添加希望为目标增加的行为,并可以有自己的状态,比如让对象拥有”是”,”否”锁定的状态。
interface ISome {
public void setSome(String some) ;
public String getSome();
}
class SomeA implements ISome {
private String some;
public String getSome() {
return some;
}
public void setSome(String some) {
this.some = some;
}
}
interface ILockable {
public void lock();
public void unlock();
public boolean isLock();
}
class OtherIntroduction extends DelegatingIntroductionInterceptor implements ILockable {
private boolean locked;
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (isLock() && methodInvocation.getMethod().getName().indexOf("set") == 0) {
throw new AopConfigException("对象锁定!!");
}
return super.invoke(methodInvocation);
}
public boolean isLock() {
return locked;
}
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
}
public class IntroductionDemo {
public static void main(String args[]) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
ISome some = (ISome)context.getBean("otherProxy");
some.setSome("张怡宁");
System.out.println(some.getSome());
try {
((ILockable)some).lock();
some.setSome("默认");
System.out.println(some.getSome());
} catch (Exception e) {
e.printStackTrace();
}
((ILockable)some).unlock();
some.setSome("默认X");
System.out.println(some.getSome());
}
}
<bean id="someA" class="com.spring.ch4.SomeA"></bean>
<bean id="otherIntroduction" class="com.spring.ch4.OtherIntroduction"></bean>
<bean id="otherAdviser" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg ref="otherIntroduction"></constructor-arg>
<constructor-arg value="com.spring.ch4.ILockable"></constructor-arg>
</bean>
<bean id="otherProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"
value="com.spring.ch4.ISome">
</property>
<property name="target" ref="someA"></property>
<property name="interceptorNames">
<list>
<value>otherAdviser</value>
</list>
</property>
</bean>
张怡宁
org.springframework.aop.framework.AopConfigException: 对象锁定!!
at com.spring.ch4.OtherIntroduction.invoke(IntroductionDemo.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.spring.ch4.$Proxy1.setSome(Unknown Source)
at com.spring.ch4.IntroductionDemo.main(IntroductionDemo.java:60)
默认X
|
4.5 Autoproxing
自动代理可以不用为每一个目标对象手动定义代理对象,使用自动代理,你可以通过Bean名称或是Pointcut对比,自动为符合对比条件的目标对象建立代理对象。
l BeanNameAutoProxyCreator
如果要为目标提供Advice,则必须为他们建立代理对象,在程序规模大的时候,如果要提供Advice的目标对象很多,一个一个为他们建立代理是麻烦的事,Spring为一些情况提供了自动代理。
你可以为目标对象取好适当的Bean名称,例如某些服务对象取名为xxxService,这个样使用org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator来为这些Bean设置自动代理,只要改一下配置文件就可以了:
interface ISome {
public void setSome(String some) ;
public String getSome();
}
class SomeA implements ISome {
private String some;
public String getSome() {
return some;
}
public void setSome(String some) {
this.some = some;
}
}
interface ILockable {
public void lock();
public void unlock();
public boolean isLock();
}
class OtherIntroduction extends DelegatingIntroductionInterceptor implements ILockable {
private boolean locked;
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (isLock() && methodInvocation.getMethod().getName().indexOf("set") == 0) {
throw new AopConfigException("对象锁定!!");
}
return super.invoke(methodInvocation);
}
public boolean isLock() {
return locked;
}
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
}
public class IntroductionDemo {
public static void main(String args[]) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
ISome some = (ISome)context.getBean("someService");
some.setSome("张怡宁");
System.out.println(some.getSome());
try {
((ILockable)some).lock();
some.setSome("默认");
System.out.println(some.getSome());
} catch (Exception e) {
e.printStackTrace();
}
((ILockable)some).unlock();
some.setSome("默认X");
System.out.println(some.getSome());
}
}
<bean id="someService" class="com.spring.ch4.SomeA"></bean>
<bean id="otherIntroduction" class="com.spring.ch4.OtherIntroduction"></bean>
<bean id="otherAdviser" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg ref="otherIntroduction"></constructor-arg>
<constructor-arg value="com.spring.ch4.ILockable"></constructor-arg>
</bean>
<bean id="otherProxy"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames" value="otherAdviser"></property>
</bean>
org.springframework.aop.framework.AopConfigException: 对象锁定!!
at com.spring.ch4.OtherIntroduction.invoke(IntroductionDemo.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.spring.ch4.$Proxy1.setSome(Unknown Source)
at com.spring.ch4.IntroductionDemo.main(IntroductionDemo.java:60)
张怡宁
默认X
|
l DefaultAdvisorAutoProxyCreator
在Bean文件定义被读取完之后,DefaultAdvisorAutoProxyCreator会自动搜索所有的Advisor,并自动将Advisor应用到符合Pointcuts的目标对象上。
<bean id="someService" class="com.spring.ch4.SomeA"></bean>
<bean id="otherIntroduction" class="com.spring.ch4.OtherIntroduction"></bean>
<bean id="otherAdviser" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg ref="otherIntroduction"></constructor-arg>
<constructor-arg value="com.spring.ch4.ILockable"></constructor-arg>
</bean>
<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
执行结果同上
|
</script>