java中的代理模式

代理模式

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

简单来说代理模式就是通过一个代理对象去访问一个实际对象,并且可以像装饰模式一样给对象添加一些功能。

静态代理

所谓静态代理即在程序运行前代理类就已经存在,也就是说我们编写代码的时候就已经把代理类的代码写好了,而动态代理则是在程序运行时自动生成代理类。

描述起来太过抽象,看一下代码就明白是怎么回事了

  • main
public class Main {

    public static void main(String[] args) {
        Water water = new Water();
        WaterProxy waterProxy = new WaterProxy(water);
        waterProxy.drink();
    }

}
  • 接口
//代理类与被代理类共同实现的接口
public interface Drink {
    void drink();
}

  • 被代理类
//被代理的类
public class Water implements Drink {

    @Override
    public void drink() {
        System.out.println("drink water");
    }

}
  • 代理类
//代理类
//与被代理类实现同一个接口
public class DrinkProxy implements Drink {
    
    private Drink drinkImpl;
    
    //通过构造函数传入Water对象
    public DrinkProxy(Drink drinkImpl) {
        this.drinkImpl = drinkImpl;
    }
    
    @Override
    public void drink() {
        //在执行被代理对象的方法前做一些事情
        System.out.println("before drink");
        //执行被代理对象的方法
        drinkImpl.drink();
        //在执行被代理对象的方法后做一些事
        System.out.println("after drink");
    }

}

执行结果

before drink
drink water
after drink

动态代理

有时候我们只想改变代理类所代理的类,但是代理对象执行实际对象的方法前后所做的事情是一样的,正所谓铁打的代理类,流水的被代理类。而采用静态代理就只能代理实现了同一接口的类,如果要代理任意类则必须写很多重复的代理类。此时我们可以采用动态代理,java已经为实现动态代理提供了一套比较方便的工具。

  • java.lang.reflect.Proxy类中可以动态生成代理对象的方法
  /**
     *返回实现了指定接口的对象,调用代理对象的方法会调用 
     *InvocationHandler的invoke方法
     *
     * @param   loader 获取代理类所使用的类加载器
     * @param   interfaces 代理类所要实现的接口
     * @param   h 实现了InvocationHandler接口的对象
     * @return  代理对象
     */
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
  • InvocationHandler接口
/**
 *每个代理类都有一个关联的InvocationHandler
 *当代理对象执行一个方法的时候会直接执行invoke方法
 */
public interface InvocationHandler {

    /**
     * @param   调用该方法的代理对象
     * @param   method 代理对象所调用的方法
     * @param   args 调用的方法的参数
     * @return  调用的方法的返回值
     */
    public Object invoke(Object proxy, Method method, Object[] args)
}

描述总是比较抽象,还是看实际例子比较好理解

例子

  • InvocationHandler接口的实现类
public class CommonInvocationHandler implements InvocationHandler {
    
    //被代理的对象
    private Object proxied;
    
    public CommonInvocationHandler(Object proxied) {
        this.proxied = proxied;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在调用被代理对象的方法前做一些事情
        System.out.println("before doing something");
        //调用被代理对象的方法
        Object result = method.invoke(proxied, args);
        //在调用被代理对象的方法后做一些事情
        System.out.println("after doing something");;
        return result;
    }

}
  • Main

public class Main {

    public static void main(String[] args) {
        //被代理的对象
        Water water = new Water();
        //动态获取代理对象
        Drink waterProxy = 
                (Drink) Proxy.newProxyInstance(water.getClass().getClassLoader(),
                        water.getClass().getInterfaces(), 
                        new CommonInvocationHandler(water));
        //通过代理对象调用方法
        waterProxy.drink();
    }

}

  • 输出结果
before doing something
drink water
after doing something

也可以不要具体的被代理对象,但是必须有相应的接口(没有实现接口的类可以使用cglib实现动态代理)才可以动态获取代理对象。像最近比较火的Retrofit就直接通过声明好的接口使用动态代理进行网络请求。

例子

简单的模拟一下retrofit

  • POST注解
//Post请求注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
  String value() default "";
}
  • Query注解
//Post请求注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
  String value() default "";
}
  • Service接口
public interface Service {
    //用POST注解声明请求的方式和相对路径
    @POST("/login")
    //@Query注解声明请求的参数名
    void login(@Query("username")String username, 
            @Query("password")String password);
}

  • Main
public class Main {

    public static void main(String[] args) {
        // 动态获取Service接口的代理
        Service service = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(),
                new Class[] { Service.class }, new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 通过注解获取请求的相对路径
                        String retativePath = ((POST) method.getAnnotations()[0]).value();
                        System.out.println("relative path: " + retativePath);
                        // 获取参数的注解
                        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                        // 通过参数的注解获取请求参数
                        for (int i = 0; i < parameterAnnotations.length; i++) {
                            if (parameterAnnotations[i].length != 0) {
                                for (int j = 0; j < parameterAnnotations[i].length; j++) {
                                    Query query = (Query) parameterAnnotations[i][j];
                                    System.out.println(query.value() + ": " + args[i].toString());
                                }
                            }
                        }
                        return null;
                    }
                });
        // 调用代理对象的方法
        service.login("hello", "world");
    }

}

参考

JAVA动态代理

代理模式

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

推荐阅读更多精彩内容

  • 本文动态代理部分内容大量引自:http://www.ibm.com/developerworks/cn/java/...
    端木轩阅读 396评论 0 0
  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而...
    luweicheng24阅读 221评论 0 0
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 3,181评论 2 10
  • 版权声明:本文为博主原创文章,未经博主允许不得转载 前言 Java 代理模式在 Android 中有很多的应用。比...
    cc荣宣阅读 781评论 0 7
  • 你的孩子不管考上什么大学(名牌大学、重点大学还是普通大学),都不能从根本上决定他的命运,不代表他就家庭幸福、事业有...
    自有说阅读 215评论 0 0