主要内容
1. AOP概述
2. Test Code
3. AOP原理与实现
3.1 原理分析
3.2 设计实现
4. 总结
1. AOP概述
AOP(Aspect Oriented Programming),即面向切面编程。被认为是对面向对象编程OOP的一种极大补充,大量应用于处理一些具有横切逻辑的系统中。比如:事务、缓存、安全检查等等。
为什么需要AOP?有什么OOP解决不了的吗?
在OOP的世界中,一切是以对象为核心,我们所构建的系统就是若干个具有独立状态和行为的对象组成。但随着软件日益复杂,OOP渐渐无法很好的解决我们所面对的问题。比如上面提到的事务处理,不用AOP的思想当然也可以解决:
方案1: 对于一个事务的处理过程进行分析,我们知道每个事务都包含事务的开启,提交、回滚等操作。那么最简单粗暴的方法就是将事务开启、提交、回滚的代码进行Ctrl+C、Ctrl+V。
方案2: 方案1思路太low,需要高级点的,OOP中的抽象、继承、封装啊!那好,构造一个公共的基类,将事务开启、提交、回滚等操作封装在基类中,然后每一个需要处理事务的类都继承这个基类,进行相应的方法调用即可。这不就实现代码复用了嘛。。。
这样就解决问题了?继续。。。
随着系统的发展,需求新增、变更必然是相当频繁的。新需求来了:需要某些处于事务中的方法执行前加上请求者的身份验证的操作。
哎。。。继续改!于是乎在基类中新增身份验证的处理逻辑。同时修改子类中对应方法的处理逻辑。
然后。。。新需求又来了。。。
这就是痛点,有痛点就会有解决方案:AOP应运而生。
本文主要探究AOP的原理、实现AOP所用到的一些方法。
2. Test Code
public class AdvisorChainTest extends BaseTest{
static class ProxyBuilder{
public static Object buildProxy(Class<?> interfaces, Object target, List<MethodInterceptor> list){
return Proxy.newProxyInstance(ProxyBuilder.class.getClassLoader(),
new Class<?>[]{interfaces}, new DefaultInvocationHandler(target, list));
}
}
@Test
public void testAOP() {
Set<String> exclusionMethodNames = new HashSet<String>();
exclusionMethodNames.add("delete");//拦截delete方法
//过滤方法的拦截器
MethodFilterInterceptor filter = new MethodFilterInterceptor(exclusionMethodNames);
//记录方法执行时间拦截器
TimeLogInterceptor time = new TimeLogInterceptor();
List<MethodInterceptor> list = Arrays.asList(filter,time);
UserService user = new UserServiceImpl();
UserService proxy = (UserService)ProxyBuilder.buildProxy(UserService.class,user,list);
proxy.update(null);
assertTrue(time.msg.contains("update execute time:"));
try{
proxy.delete(null);
fail("method delete is not allowed!");
}catch(Exception e){
assertEquals(MethodNotAllowedException.class, e.getClass());
}
}
}
在测试用例中,实现了两个拦截器:TimeLogInterceptor,MethodFilterInterceptor;一个用于记录方法执行时间,另一个用于方法过滤。
3. AOP原理实现
3.1 原理分析
在上一个小节AOP概述中,已经分析出AOP要解决的问题都有一个共性:即所谓的横切逻辑。比如测试用例中的TimeLogInterceptor,用于统计方法执行的时间。对于这一需求,首先想到的使用代理的方法。当然,这里指的就是动态代理。实现思路如下:
通过代理对象拦截对目标方法的调用操作,在回调方法中添加需要的横切逻辑。
还有一个问题:我是实现的横切逻辑不止一个的情况怎么办?难道在Proxy对象的回调方法中进行方法堆积吗?这不又回到了第一节中的方案1了。针对这种问题,一个比较好的处理思路是:责任链。结构图如下:
软件设计准则:在重要的过程上设置拦截接口,这是体现软件可扩展性的一种基本实现方式。
3.2 设计实现
- 对每次方法调用进行抽象:MethodInvocation接口,拦截器接口:MethodInterceptor
/**
* MethodInvocation代表方法的执行
*
* @author wqx
*
*/
public interface MethodInvocation {
/**
* 获取方法对象
*
* @return
*/
public Method getMethod();
/**
* 获取参数
*
* @return
*/
public Object[] getParameters();
/**
* 执行下一个方法
*
* @return
* @throws Exception
*/
Object executeNext() throws Exception;
}
public interface MethodInterceptor {
Object invoke(MethodInvocation invocation) throws Exception;
}
- MethodInvocation的默认实现
/**
* MethodInvocation的默认实现
*
* @author wqx
*
*/
public class DefaultMethodInvocation implements MethodInvocation {
//目标对象
private Object target;
//代理
private Object proxy;
//目标方法
private Method method;
//参数
private Object[] parameters;
//拦截器链
private List<?> interceptors;
//当前执行的Interceptor的索引(范围:0-interceptors.size()-1),初始为-1
private int currentIndex = -1;
public DefaultMethodInvocation(Object target,Object proxy,Method method,Object[] parameters, List<?> interceptors){
this.target = target;
this.proxy = proxy;
this.method = method;
this.parameters = parameters;
this.interceptors = interceptors;
}
@Override
public Object executeNext() throws Exception {
//判断拦截器链是否执行完
if(this.currentIndex == this.interceptors.size() - 1){
//如果执行完,直接执行目标方法
method.setAccessible(true);
return method.invoke(target, parameters);
}
Object interceptor = this.interceptors.get(++this.currentIndex);
MethodInterceptor methodInterceptor = (MethodInterceptor)interceptor;
return methodInterceptor.invoke(this);
}
//getter and seter
}
- 要实现动态代理,当然需要实现InvocationHandler接口啦
public class DefaultInvocationHandler implements InvocationHandler {
private Object target;
private List<MethodInterceptor> interceptorsChain;
public DefaultInvocationHandler(Object target, List<MethodInterceptor> interceptorsChain){
this.target = target;
this.interceptorsChain = interceptorsChain;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
MethodInvocation methodInvocation;
List<MethodInterceptor> chain = interceptorsChain;
Object relVal;
if(chain != null && !chain.isEmpty()){
methodInvocation = new DefaultMethodInvocation(target,proxy,method,args,chain);
relVal = methodInvocation.executeNext();
}else{//直接调用目标方法
relVal = method.invoke(target, args);
}
return relVal;
}
}
在回调方法invoke中,我们首先判断拦截器链是否需要执行,如果需要执行拦截器链,那么就将这次方法调用信息封装成MethodInvocation,然后调用methodInvocation.executeNext()。在DefaultMethodInvocation的实现中可以看到,executeNext() 方法要做的就是查看拦截器链interceptors是否执行完毕,如果执行完了,那么调用目标方法method.invoke(target, parameters),如果拦截器链没有执行完,那么就获取下一个拦截器并执行。
//获取下一个拦截器
Object interceptor = this.interceptors.get(++this.currentIndex);
MethodInterceptor methodInterceptor = (MethodInterceptor)interceptor;
//执行拦截器的invoke方法
return methodInterceptor.invoke(this);
4. 总结
在如今的系统中,AOP的应用程度已经非常广泛。本文对AOP概念进行简单的阐述,并对AOP的原理进行了简单的实现。主要用到的方法是将横切逻辑在代理类的回调方法中实现,并通过责任链模式实现了软件设计中的一个重要基本特性:在重要过程中设置拦截接口。比如本文中提到的通过代理类进行方法调用。那么在方法调用这个过程中我们可能需要实现很多逻辑:统计调用次数;统计调用时间;添加黑白名单进行过滤等等。没有哪个框架可以Cover所有的需求,允许外置行为,这是框架基本的扩展方法。