Java设计模式之-代理模式(Proxy)

多年以前在学习设计模式时,一直以为代理就是这个事情我不做了,交给别人做。现在重学设计模式,才发现自己还是太天真,而且经历过这么多事情也明白,就算再好的代理,还是需要自己做一些事情的。

代理模式:在不修改一个类的前提下,实现一个该类的代理类来,代理类需要实现所有行为,并且可以根据需要在行为中增加其他逻辑和细节。

举个例子,王晶需要找周星驰拍一部新电影,即希望调用Zhou.act();。但是王晶不能直接联系周星驰,他需要联系星爷的经纪人,或者我们称之为代理人,去商讨拍电影的细节工作。这个时候,这位代理人其实需要做更多细节性的工作,比如说

  • ZhouProxyer.bargainContract();
  • ZhouProxyer.schedule();
  • ZhouProxyer.act(); // 实际调用Zhou.act();
  • ZhouProxyer.complete();
  • ZhouProxyer.summary();

我们可以看到,作为星爷的代理人,其实他在星爷真正拍电影前后做了很多事情,这些事不需要星爷操心,只需保证自己的act()正常即可。
从这个例子中,我们可以进行抽象,将主要的主体提炼出来:


代理模式
  • Client:客户(王晶),类的使用者;
  • Subject:定义行为的抽象类或接口;
  • RealSubject:真实的底层实现类,实现了Subject定义的行为(周星驰);
  • Proxy:代理类,也实现了Subject定义的行为,且拥有RealSubject的一个实例(经纪人);

现在,我们用三种代理模式来实现上述的这个关系。

静态代理

静态,说白了就是写死的代码,按照你写的代码实现代理机制。上面说的几个主要主体,都需要自己定义和生成。
现在我们先来将Subject接口定义出来。

package com.designpattern.proxy;

public interface IActor {
    public void act();

}

而后是星爷的类,它实现了IActor接口,其中为了方便,随便用了一个勤汉单例:

package com.designpattern.proxy;

public class Zhou implements IActor {
    private static Zhou instance = new Zhou();
    public static Zhou getInstance(){
        return instance;
    }
    @Override
    public void act() {
        System.out.println("Here comes Zhou's newest movie: Crazy biscuit!");
    }
}

然后我们再定义一个Zhou的代理人,同样也实现了IActor:

package com.designpattern.proxy;

public class ZhouProxy implements IActor {
    private IActor zhou;

    public ZhouProxy(IActor zhou){
        this.zhou = zhou;
    }

    @Override
    public void act() {
        bargainContract();
        schedule();
        zhou.act();
        complete();
        summary();
    }

    private void complete() {
        System.out.println("Movie complete.");
    }

    private void summary() {
        System.out.println("Movie's summary.");
    }

    private void schedule() {
        System.out.println("Make a schedule for the new movie.");
    }

    private void bargainContract() {
        System.out.println("Bargain the price of movie contract");
    }
}

最后我们写一个王晶出来,王晶的main方法就是拍电影:

package com.designpattern.proxy;

public class Wang {
    public static void main(String[] args){
        IActor zhouProxy = new ZhouProxy(Zhou.getInstance());
        zhouProxy.act();
    }
}

好的,我们运行一下,看到输出的结果:

Bargain the price of movie contract
Make a schedule for the new movie.
Here comes Zhou's newest movie: Crazy biscuit!
Movie complete.
Movie's summary.

从代码中我们能看到,王晶其实只找到了代理人,但是经过调用代理人的act方法,我们仍能完成让周星驰拍电影的需求。这样下来,其实静态代理模式已经实现了。


动态代理

动态,就是在运行时能够根据实际情况进行判断并生成代理类的模式。它不需要具体定义某个类,只需要编写一些代理类需要做的事情即可。我们直接运用JDK中InvocationHandler来定义代理人,利用Proxy类来获得动态代理的实例。

我们先来看接口InvocationHandler,其实就定义了一个方法:

package java.lang.reflect;

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

那么我们现在写一个使用动态代理的代理类ActorProxy,它实现了InvocationHandler:

package com.designpattern.proxy;

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

public class ActorProxy implements InvocationHandler {
    private Object actor ;
    public ActorProxy(Object actor){
        super();
        this.actor = actor;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        bargainContract();
        schedule();
        method.invoke(actor, args);
        complete();
        summary();
        return null;//如果method有返回,也可以返回其值
    }

    private void complete() {..}
    private void summary() {..}
    private void schedule() {..}
    private void bargainContract() {..}
}

我们再重写Wang的main函数,调用这个类进行动态代理操作:

public class Wang {
    public static void main(String[] args){
        IActor actor = (IActor) Proxy.newProxyInstance(Zhou.getInstance().getClass().getClassLoader(), Zhou.getInstance().getClass().getInterfaces(), new ActorProxy(Zhou.getInstance()));
        actor.act();
    }
}

而结果和静态代理的一致。其中Proxy.newProxyInstance()方法,不用往里看,我们先看一下它的参数:

  1. ClassLoader loader:表示需要代理的类的加载器;
  2. Class<?>[] interfaces:表示需要代理的类实现的接口;
  3. InvocationHandler h:即代理类;

根据参数其实我们能够想到,代理类其实就是在相同的加载器loader中,加载一个实现了相同interfaces接口的代理类,并且该代理类的行为是在第三个参数h中定义的。

Cglib

不知道大家有没有想到一个问题,如果我想实现一个没有实现任何接口的类的代理类,或者说一个类虽然实现了某些接口,但是我希望针对类中非重载的方法进行代理,应该如何实现呢?其实这也是在Spring框架中需要解决的一个问题,而答案自然也在Spring框架中找。

使用Cglib库,需要注意以下几点:

  1. 需要引入cglib的jar文件,Spring的核心包(spring-core-x.x.x.jar)中已经包括了Cglib功能,当然你也可以只下Cglib的包;
  2. 代理的类不能为final,否则报错;
  3. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法;

这里想说一句,网上各种教程,全部都是在说OSCHINA的Gradle源,但是人家早就在2015年06月29日停止服务了。所以我在网上找到了阿里爸爸的Gradle库地址

repositories {
    def REPOSITORY_URL = 'http://maven.aliyun.com/nexus/content/groups/public/'
    all { ArtifactRepository repo ->
        if(repo instanceof MavenArtifactRepository){
            def url = repo.url.toString()
            if (url.startsWith('https://repo1.maven.org/maven2') || url.startsWith('https://jcenter.bintray.com/')) {
                project.logger.lifecycle "Repository ${repo.url} replaced by $REPOSITORY_URL."
                remove repo
            }
        }
    }
    maven {
        url REPOSITORY_URL
    }
}

然后如果没有配置过Spring的话,可以直接下Cglib的包:

compile group: 'cglib', name: 'cglib', version: '3.2.2'

OK,准备工作已经完毕了,现在我们来定义一个没有实现接口的Zhou:

package com.designpattern.proxy;

public class OnlyZhou {
    public void onlyAct(){
        System.out.println("Only Zhou Action.");
    }

    public final void finalAct() {
        System.out.println("This is a final act!");
    }
}

而后,我们定义一个使用Cglib库的代理:

package com.designpattern.proxy;

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

import java.lang.reflect.Method;

public class ActorCglibProxy implements MethodInterceptor {

    private ActorCglibProxy(){}
    public static OnlyZhou getProxyInstance(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OnlyZhou.class);
        enhancer.setCallback(new ActorCglibProxy());
        return (OnlyZhou) enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        bargainContract();
        schedule();
        proxy.invokeSuper(obj, args); //注意,此处不同!!!
        complete();
        summary();
        return null;
    }
    private void complete() {..}
    private void summary() {..}
    private void schedule() {..}
    private void bargainContract() {..}
}

然后我们来调用这个新的代理,先调用了一个正常的方法,而后调用了一个final的方法:

public class Wang {
    public static void main(String[] args){
        OnlyZhou onlyZhou = ActorCglibProxy.getProxyInstance();
        onlyZhou.onlyAct();
        onlyZhou.finalAct();
    }
}

可以看到结果为:

Bargain the price of movie contract
Make a schedule for the new movie.
Only Zhou Action.
Movie complete.
Movie's summary.
This is a final act!

其中前五行是被截获(intercept)的方法输出的,而最后一行是出自一个final方法,上面说了final方法不会被截获,所以只输出了一行信息。

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

推荐阅读更多精彩内容