源码解析-写一个简易AOP框架

584 阅读4分钟

       在上一篇aop的原理后,本篇决定手撸一个最简易的AOP框架,好让大家能对AOP的实现有个“去繁从简”的认识。这里也分享下自己学习框架源码的心得,学习框架源码效率比较高的一种方式是:先理解框架设计,然后根据自己理解去造一个小轮子(这个框架的简易版本),然后慢慢地再对照框架源码添加特性到那个小轮子,这样吸收到的框架知识是会非常扎实的。该例子主要展示如何扫描切面类中的方法组装调用方法链、使用cglib工具类生成目标类的代理类。整个例子的设计只需要三步,总代码行数不足200行。

第一步,切面注解/代理容器/日志切面定义/异常切面定义等基本组成。

@Retention(RUNTIME)
@Target(TYPE)
public @interface AspectAnnotation {}

public enum InterceptPoint {
    BEFORE, AFTER, EXCEPTION
}

//日志切面
@AspectAnnotation
public class LogAspect {
    public static void before(Object object, Method method, Object[] args) {
        System.out.println("log before");
    }
    public static void after(Object object, Method method, Object[] args, Object result) {
        System.out.println("log end");
    }
}

//异常切面
@AspectAnnotation
public class ExceptionAspect {
    public static void exception(Object object, Method method, Object[] args, Throwable e) {
        System.out.println("log exception");
    }
}

//目标类的
public class ServiceA {
    //目标方法
    public void test() {
        System.out.println("invoke serviceA test method");
    }
}

第二步,利用反射技术来生成拦截方法数组,这里定义了一个以被代理类的类型为key的调用链路的map容器,根据特定的类型就可以得到需要执行的方法链路。

@Retention(RUNTIME)
@Target(TYPE)
public @interface AspectAnnotation {}

public enum InterceptPoint {
    BEFORE, AFTER, EXCEPTION
}

//日志切面
@AspectAnnotation
public class LogAspect {
    public static void before(Object object, Method method, Object[] args) {
        System.out.println("log before");
    }
    public static void after(Object object, Method method, Object[] args, Object result) {
        System.out.println("log end");
    }
}

//异常切面
@AspectAnnotation
public class ExceptionAspect {
    public static void exception(Object object, Method method, Object[] args, Throwable e) {
        System.out.println("log exception");
    }
}

//目标类的
public class ServiceA {
    //目标方法
    public void test() {
        System.out.println("invoke serviceA test method");
    }
}

第二步,利用反射技术来生成拦截方法调用链路,这里定义了一个以被代理类的类型为key的调用链路的map容器,根据特定的类型就可以得到需要执行的方法链路。调用的示意图如下。

//定义切面类数组
static Class[] aspects = new Class[]{LogAspect.class, ExceptionAspect.class};

//代理目标类
static Class[] targetClasses = new Class[]{ServiceA.class};



//根据反射拿到方法对象
private static Method getMethod(Class cls, String name, Class[] parameters) {
    try {
        return cls.getMethod(name, parameters);
    } catch (Exception ex) {
    }
    return null;
}

//添加拦截方法
private static void addInterceptMethod(Class cls, InterceptPoint point, Method method) {
    if (method == null) {
        return;
    }
    Map> map = interceptMethodsMap.get(cls);
    if (map == null) {
        map = new HashMap<>();
        interceptMethodsMap.put(cls, map);
    }
    List methods = map.get(point);
    if (methods == null) {
        methods = new ArrayList<>();
        map.put(point, methods);
    }
    methods.add(method);
}

第三步,使用cglib的MethodInterceptor进行方法拦截,我们把具体的改写后的执行过程重新写到intercept方法中,这样我们就完成了对原代理对象执行方法的拦截调用。

//生成代理对象
private static  T createInstance(Class cls) throws InstantiationException, IllegalAccessException {
    if (!interceptMethodsMap.containsKey(cls)) {
        return cls.newInstance();
    }
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(cls); //设置被代理类
    enhancer.setCallback(new AspectInterceptor()); //设置拦截方法类
    return (T) enhancer.create(); //返回代理类对象
}

//定义拦截方法类
static class AspectInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        
        //获取被代理类的before方法链
        List beforeMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.BEFORE);
        
        //逐个调用链中的方法
        for (Method m : beforeMethods) {
            m.invoke(null, new Object[]{object, method, args});
        }
        
        try {
            //调用被代理类的原始方法
            Object result = proxy.invokeSuper(object, args);
            //获取被代理类的after方法链
            List afterMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.AFTER);
            //逐个调用链中的方法
            for (Method m : afterMethods) {
                m.invoke(null, new Object[]{object, method, args, result});
            }
            return result;
        } catch (Throwable e) {
            //获取被代理类的exception方法链
            List exceptionMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
            //逐个调用链中的方法
            for (Method m : exceptionMethods) {
                m.invoke(null, new Object[]{object, method, args, e});
            }
            throw e;
        }
    }

    static List getInterceptMethods(Class cls, InterceptPoint point) {
        Map> map = interceptMethodsMap.get(cls);
        if (map == null) {
            return Collections.emptyList();
        }
        List methods = map.get(point);
        if (methods == null) {
            return Collections.emptyList();
        }
        return methods;
    }
}

       经过简单三步,我们就完成了极度简易的aop框架的开发,正所谓万变不离其宗,其他的AOP框架如aspectj等实现切面/切点的效果也无非是通过各种手段(xml/annotation)等组装好的方法调用链路,然后使用代理工具库类cglib/jdk的拦截接口,对原有代理对象的方法改写。三步下来,完美设计一个简易aop框架。下一篇将会去看看大名鼎鼎aspectj框架的运行原理。