Spring AOP 剖析(4)

Spring AOP 的实现机制

Spring AOP 的设计哲学也是简单而强大的。 它不打算将所有的 AOP 需求全部囊括在内,而是要以有限的 20% 的 AOP

支持,在满足 80% 的 AOP 需求。 如果觉得 Spring AOP 无法满足你所需要的那 80% 之外的需求,那么可以求助于

AspectJ 来完成, Spring AOP 对 AspectJ 提供了很好的集成。

Spring AOP 属于第二代 AOP, 采用动态代理机制和字节码生成技术实现 。与最初的 AspectJ 采用编译器将横切逻辑织入

目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象


中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。


1.  动态代理机制

关于代理模式:


在这个场景中,Client 想要请求具体的 SubjectImpl 实例,但是 Client 无法直接请求真正要访问的资源 SubjectImpl

而是必须通过 ISubject 资源的访问代理类 SubjectProxy 进行。

package prx.aop.proxy;

public interface ISubject {

	public void request();
}
 
package prx.aop.proxy;

public class SubjectImpl implements ISubject {

	public void request() {
		System.out.println("this is the subjectImpl request logic");
	}

}
 
package prx.aop.proxy;

public class SubjectProxy implements ISubject {
	private ISubject subject;
	
	public SubjectProxy() {
		this.subject = new SubjectImpl();
	}
	
	public void request() {
		subject.request();
	}

}
 
package prx.aop.proxy;

public class Client {

	public static void main(String[] args) {
		ISubject proxyObject = new SubjectProxy();
		
		//Client 想访问 SubjectImpl,但是找它不到,就去找 它的代理 SubjectProxy, 要 这个代理去通知 SubjectImpl
		proxyObject.request();
	}
}

当 Client 通过 request() 请求服务的时候, SubjectProxy 将请求转发给了 SubjectImpl 。 从这个角度上说,

SubjectProxy 反而有多此一举之嫌了。 不过, SubjectProxy 的作用不只局限于请求的转发, 更多时候是对请求添加

更多访问限制。

假设现在要增加系统需求,对 SubjectImpl 增加一个限制条件,只有在 上午 9 点 到 下午 6 点之间才能求情数据。

这是我们只需要修改 代理对象 就可以了。

package prx.aop.proxy;

import org.joda.time.TimeOfDay;

public class SubjectProxy implements ISubject {
	private ISubject subject;
	
	public SubjectProxy() {
		this.subject = new SubjectImpl();
	}
	
	public void request() {
		TimeOfDay startTime = new TimeOfDay(9, 0, 0);
		TimeOfDay endTime = new TimeOfDay(17, 59, 59);
		TimeOfDay currentTime = new TimeOfDay();
		
		if(! (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) ) {
			return;
		}
		
		subject.request();
	}

}

代理对象 SubjectProxy 就像是 SubjectImpl 的影子, 只不过这个影子通常拥有更过的功能。 如果 SubjectImpl 是

系统中的 Joinpoint 所在的对象, 即目标对象,那么就可以为这个目标对象创建一个代理对象,然后将横切逻辑添加到

这个代理对象中。 当系统使用这个代理对象运行的时候,原有逻辑的实现和横切逻辑就完全融合到一个系统中了。


Spring AOP 本质上就是采用这种代理机制实现的 , 但是,还有点差别。

这是因为, 系统中可不一定就 ISubject 的实现类有 request() 方法, IRequestable 接口以及相应实现类可能也有

request() 方法, 它们也是我们需要横切的关注点。

package prx.aop.proxy;

public interface IRequestable {

	public void request();
}
 
package prx.aop.proxy;

public class RequestableImpl implements IRequestable {

	public void request() {
		System.out.println(" request processed in RequestableImpl ");
	}

}

为了能够为 IRequestable 相应实现类也织入以上的横切逻辑, 我们又得提供对应的代理对象

package prx.aop.proxy;

import org.joda.time.TimeOfDay;

public class RequestableProxy implements IRequestable {
	private IRequestable targetObject;
	
	public RequestableProxy(IRequestable targetObject) {
		this.targetObject = targetObject;
	}
	
	public void request() {
		TimeOfDay startTime = new TimeOfDay(9, 0, 0);
		TimeOfDay endTime = new TimeOfDay(17, 59, 59);
		TimeOfDay currentTime = new TimeOfDay();
		
		if(! (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) ) {
			return;
		}
		
		targetObject.request();
	}

}

并且将该代理对象而不是目标对象绑定到系统中

IRequestable targetObject = new RequestableImpl();
IRequestable proxy = new RequestableProxy(targetObject);
proxy.request();

于是问题出现。 虽然 Joinpoint 相同 (request() 方法的执行 ), 但是对应的目标对象类型是不一样的。 针对不一样的

目标对象类型,需要为其单独实现一个代理对象。 而实际上,这些代理对象所有添加的横切逻辑是一样的。 当系统中存在

成百上千的符合 Pointcut 匹配条件的目标对象时,我们就要为这成百上千的目标对象创建成百上千的代理对象。

这种为对应的目标对象创建静态代理的方法,原理上是可行的,但具体应用上存在问题,所以要寻找其他方法,以避免以上

问题。

动态代理

JDK 1.3 后引入了一种称之为动态代理 (Dynamic Proxy)的机制。使用该机制,我们可以为指定的接口在系统运行期间

动态的生成代理对象, 从而帮助我们走出最初使用静态代理实现 AOP 的窘境。

动态代理机制的实现主要由  java.lang.reflect.Proxy 类  和 java.lang.reflect.InvocationHandler 接口组成。

使用动态代理实现前面的 “request时间控制” 功能。

package prx.aop.proxy;

public interface ISubject {

	public void request();
}
 
package prx.aop.proxy;

public class SubjectImpl implements ISubject {

	public void request() {
		System.out.println("this is the subjectImpl request logic");
	}

}
 
package prx.aop.proxy;

public interface IRequestable {

	public void request();
}
 
package prx.aop.proxy;

public class RequestableImpl implements IRequestable {

	public void request() {
		System.out.println(" request processed in RequestableImpl ");
	}

}
 
package prx.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.joda.time.TimeOfDay;

public class RequestInvocationHandler implements InvocationHandler{
	private Object target;
	
	public RequestInvocationHandler(Object target) {
		this.target = target;
	}
	
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if(method.getName().equals("request")) {
			TimeOfDay startTime = new TimeOfDay(9, 0, 0);
			TimeOfDay endTime = new TimeOfDay(17, 59, 59);
			TimeOfDay currentTime = new TimeOfDay();
			
			if(! (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) ) {
				System.out.println("service is not avaliable now.");
				return null;
			} else {
				System.out.println("service is avaliable now");
			}
			
			return method.invoke(target, args);
		}
		return null;
	}

}
 
package prx.aop.proxy;

import java.lang.reflect.Proxy;

public class Client {

	public static void main(String[] args) {
		ISubject subject = (ISubject) Proxy.newProxyInstance(
				Client.class.getClassLoader(), 
				new Class[]{ISubject.class}, 
				new RequestInvocationHandler(new SubjectImpl()));
		subject.request();
		
		IRequestable requestable = (IRequestable) Proxy.newProxyInstance(
				Client.class.getClassLoader(), 
				new Class[]{IRequestable.class}, 
				new RequestInvocationHandler(new RequestableImpl()));
		requestable.request();
	}
}

即使还有更多的目标对象,只要它依然织入的横切逻辑相同,用 RequestInvocationHandler 一个类并通过 Proxy 为

它们生成相应的动态代理实例就可以满足要求。 当 Proxy 动态生成的代理对象上相应的接口方法被调用时,对应的

InvocationHandler 就会拦截相应的方法调用,并进行相应处理。

InvocationHandler 就是我们事先横切逻辑的地方,它是横切逻辑的载体,作用跟 Advice 是一样的。

动态代理虽好,但是不能满足所有的需求。因为动态代理机制只能对实现了相应 Interface 的类使用,如果某个类没有

实现任何的 Interface,就无法使用动态代理机制为其生成动态代理对象。 虽然面向接口编程应该是提倡的做法,但

不排除其他的编程实践。 对于没有实现任何 Interface 的目标对象,我们需要寻找其他方式为其动态的生成代理对象。


2.  动态字节码生成

使用动态字节码生成技术扩展对象行为的原理是:对目标对象进行集成扩展,为其生成相应的子类,而子类可以通过覆写来


扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式


相同的效果了。


使用继承的方式来扩展对象定义,也不能像静态代理模式那样,为每个不同类型的目标对象都创建相应的扩展子类。

所以,需要借组与 CGLIB 这样的动态字节码生成库,在系统运行期间动态的为目标对象生成相应的扩展子类。

package prx.aop.proxy;

public class Requestable {

	public void request() {
		System.out.println("requestable without implementint any interface");
	}
}
 
package prx.aop.proxy;

import java.lang.reflect.Method;

import org.joda.time.TimeOfDay;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RequestCallback implements MethodInterceptor {

	public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		if(method.getName().equals("request")) {
			TimeOfDay startTime = new TimeOfDay(9, 0, 0);
			TimeOfDay endTime = new TimeOfDay(17, 59, 59);
			TimeOfDay currentTime = new TimeOfDay();
			
			if(! (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) ) {
				System.out.println("service is not avaliable now.");
				return null;
			} else {
				System.out.println("service is avaliable now");
			}
			
			return proxy.invokeSuper(object, args);
		}
		return null;
	}

}
 
package prx.aop.proxy;

import net.sf.cglib.proxy.Enhancer;

public class Client {

	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Requestable.class);
		enhancer.setCallback(new RequestCallback());
		
		Requestable proxy = (Requestable) enhancer.create();
		proxy.request();
	}
}

RequestCallback 实现了对 request() 方法请求进行访问控制的横切逻辑。 然后通过 CGLIB 的 Enhancer 为

目标对象动态地成成一个子类,并将 RequestCallback 中的横切逻辑附加到该子类中。

通过为 enhancer 指定需要生成的子类对应的父类,以及 Callback 实现, enhancer 最终生成了需要的代理对象实例。

使用 CGLIB 对类进行扩展的唯一限制就是 无法对 final 方法进行覆写。

以上两种技术(即:动态代理 与 动态字节码生成) 就是 Spring AOP 所使用的核心技术。 也就是 Spring AOP 的

Weaving And Weaver 的实现原理了。

猜你喜欢

转载自pengranxiang.iteye.com/blog/1627217
今日推荐