从零开始写Spring AOP框架-(链式代理)

Spring AOP的核心是Cglib和JDK的动态代理,那我们先写Spring AOP的前置增强和后置增强

示例代码链接

第一次尝试

我们对Greeting类进行前置和后置增强

public class Greeting {
    public void greet(String name){
        System.out.println("Hello,"+name);
    }
}

先写一个前置增强

public class BeforeProxy implements MethodInterceptor {
    public <T> T getProxy(Class<T> clazz) {
        return (T) Enhancer.create(clazz, this);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result=methodProxy.invokeSuper(o,objects);
        return result;
    }

    private void before() {
        System.out.println("call before");
    }
}

再写一个后置增强

public class AfterProxy implements MethodInterceptor {

    public <T> T getProxy(Class<T> clazz) {
        return (T) Enhancer.create(clazz, this);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result=methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }

    private void after() {
        System.out.println("call after");
    }
}

应该就是这样写吧?先用 BeforeProxy 去增强 Greeting,得到一个代理对象beforeGreeting。然后再用 AfterProxy 去增强上一步得到的代理对象,得到一个新的代理对象 beforeAfterGreeting。最后将这个新的代理对象强制转型为目标类对象,并调用目标类对象的 greet() 方法。我们通过Client输出一下。

public class Client {
    public static void main(String args[]) {
        Greeting beforeGreeting= new BeforeProxy().getProxy(Greeting.class);
        Greeting beforeAfterGreeting=new AfterProxy().getProxy(beforeGreeting.getClass());
        beforeAfterGreeting.greet("gethin");
    }
}

运行时报错!而且抛出了一个 net.sf.cglib.core.CodeGenerationException,可见这是 CGLib 的内部异常。遇到这种异常,一般都是很让人抓狂。看来 CGLib 自动生成的类,不能被自己再次增强了。如何解决呢?不妨借鉴 Servlet 的 Filter Chain的设计模式,它是“责任链模式”的一种变体,在 JavaEE 设计模式中命名为“拦截过滤器模式”。我们可以将每个 Proxy 用一根链子串起来,形成一个 Proxy Chain。然后调用这个 Proxy Chain,让它去依次调用 Chain 中的每个 Proxy。

第二次尝试

我们通过责任链模式,重新设计一下。
定义一个Proxy

public interface Proxy {
    Object doProxy(ProxyChain proxyChain);
}

编写代理链

public class ProxyChain {
    private Object targetObject;
    private MethodProxy methodProxy;
    private Object[] params;
    private Method method;
    List<Proxy> proxyList;
    private int currentIndex=0;

    public ProxyChain(Object targetObject, MethodProxy methodProxy, Object[] params, Method method, List<Proxy> proxyList) {
        this.targetObject = targetObject;
        this.methodProxy = methodProxy;
        this.params = params;
        this.method = method;
        this.proxyList = proxyList;
    }

    public Object doProxyChain() throws Throwable {
        Object result;
        if(currentIndex<proxyList.size()){
            result=proxyList.get(currentIndex++).doProxy(this);
        }else {
            result=methodProxy.invokeSuper(targetObject,params);
        }
        return result;
    }

    public Object getTargetObject() {
        return targetObject;
    }

    public MethodProxy getMethodProxy() {
        return methodProxy;
    }

    public Object[] getParams() {
        return params;
    }

    public Method getMethod() {
        return method;
    }

    public List<Proxy> getProxyList() {
        return proxyList;
    }
}

编写代理工厂类

public class ProxyFactory {
    public static <T> T createProxy(Class<T> tClass, final List<Proxy> proxyList){
       return (T) Enhancer.create(tClass, new MethodInterceptor() {
            public Object intercept(Object targetObject, Method originMethod, Object[] params, MethodProxy methodProxy) throws Throwable {
                return new ProxyChain(targetObject,methodProxy,params,originMethod,proxyList).doProxyChain();
            }
        });
    }
}

我们的目标不是为了实现 Proxy,而是为了实现 AOP。为了实现 AOP,采用了“模板方法模式”,弄一个 AbstractProxy 抽象类,让它去实现 Proxy 接口,并在其中定义方法调用模板,在需要横向拦截的地方,定义一些“钩子方法”。Spring 源码中大量使用了这一技巧。

public abstract class AbstractProxy implements Proxy {
    public Object doProxy(ProxyChain proxyChain) {
        Object targetObject = proxyChain.getTargetObject();
        Object[] params = proxyChain.getParams();
        MethodProxy methodProxy = proxyChain.getMethodProxy();
        Object result=null;
        begin();
        try {
            before(targetObject, params, methodProxy);
            result = proxyChain.doProxyChain();
            after(targetObject, params, methodProxy);
        } catch (Throwable throwable) {
            error(targetObject, params, methodProxy);
        }
        end();
        return result;
    }
    public void end() {
    }
    public void error(Object targetObject, Object[] params, MethodProxy methodProxy) {
    }
    public void after(Object targetObject, Object[] params, MethodProxy methodProxy) {
    }
    public void before(Object targetObject, Object[] params, MethodProxy methodProxy) {
    }
    public void begin() {

    }
}

重新编写前置类

public class BeforeProxy extends AbstractProxy {
    @Override
    public void before(Object targetObject, Object[] params, MethodProxy methodProxy) {
        System.out.println("call before");
    }
}

重新编写后置类

public class AfterProxy extends AbstractProxy {
    @Override
    public void after(Object targetObject, Object[] params, MethodProxy methodProxy) {
        System.out.println("call after");
    }
}

Client

public class Client6 {
    public static void main(String args[]) {
        BeforeProxy beforeProxy = new BeforeProxy();
        AfterProxy afterProxy = new AfterProxy();
        List<Proxy> proxyList = new ArrayList<Proxy>();
        proxyList.add(afterProxy);
        proxyList.add(beforeProxy);
        Student student = ProxyFactory.createProxy(Student.class, proxyList);
        student.sayHello();
    }
}

最终如愿输出结果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容