AOP原理与应用

2022-05-12 14:18:53 浏览数 (1)

主要内容
代码语言:javascript复制
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

代码语言:javascript复制
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

还有一个问题:我是实现的横切逻辑不止一个的情况怎么办?难道在Proxy对象的回调方法中进行方法堆积吗?这不又回到了第一节中的方案1了。针对这种问题,一个比较好的处理思路是:责任链。结构图如下:

软件设计准则:在重要的过程上设置拦截接口,这是体现软件可扩展性的一种基本实现方式。

Proxy Chain of Responsibility

3.2 设计实现

  1. 对每次方法调用进行抽象:MethodInvocation接口,拦截器接口:MethodInterceptor
代码语言:javascript复制
/**
 * 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;
    
}
  1. MethodInvocation的默认实现
代码语言:javascript复制
/**
 * 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
}
  1. 要实现动态代理,当然需要实现InvocationHandler接口啦
代码语言:javascript复制
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),如果拦截器链没有执行完,那么就获取下一个拦截器并执行。

代码语言:javascript复制
//获取下一个拦截器
Object interceptor = this.interceptors.get(  this.currentIndex);
MethodInterceptor methodInterceptor = (MethodInterceptor)interceptor;
//执行拦截器的invoke方法
return methodInterceptor.invoke(this);

4. 总结

在如今的系统中,AOP的应用程度已经非常广泛。本文对AOP概念进行简单的阐述,并对AOP的原理进行了简单的实现。主要用到的方法是将横切逻辑在代理类的回调方法中实现,并通过责任链模式实现了软件设计中的一个重要基本特性:在重要过程中设置拦截接口。比如本文中提到的通过代理类进行方法调用。那么在方法调用这个过程中我们可能需要实现很多逻辑:统计调用次数;统计调用时间;添加黑白名单进行过滤等等。没有哪个框架可以Cover所有的需求,允许外置行为,这是框架基本的扩展方法。

完整源码

0 人点赞