java代理模式与动态代理的实现

1.什么是代理模式

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。本文将详细介绍代理模式与动态代理的实现方式。


百度图-侵删.jpg

组成:
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
(以上摘自百度百科)

2.为什么使用代理模式

1.真实角色的职责清晰。
2.对真实角色的访问保护。
3.高扩展性。
比如:在数据库的操作业务中,数据库对象就是真实角色,只负责增删改查。beginTransaction 和commit不属于它的职责,但实际业务总是需要调用怎么办?可以创建一个代理,持有数据库的真实访问,外部总是通过代理来操作数据。请看下面的栗子↓

3.一颗栗子

就拿数据库举个栗子吧,首先根据基本组成,定义接口和真实类、代理类。

数据库接口-增删改查

interface IDao {
        void add(Object o);
        
        void delete(Object o);
        
        void update(int id, Object o);

        Object find(int id);
    }

真实类 实现(此处用一个map模拟实际数据操作)

class SimpleDao implements IDao {
        private List<Object> mDataBase = new ArrayList<>();

        @Override
        public void add(Object o) {
            mDataBase.add(o);
        }

        @Override
        public void delete(Object o) {
            mDataBase.remove(o);
        }

        @Override
        public void update(int id, Object o) {
            mDataBase.remove(id);
            mDataBase.add(id, o);
        }

        @Override
        public Object find(int id) {
            return mDataBase.get(id);
        }
    }

代理类 实现

class DaoProxy implements IDao {
        private IDao mDao;

        public DaoProxy(IDao dao) {
            this.mDao = dao;
        }

        private void beginTransaction() {
            System.out.println("读写开始前,开启数据库!");
        }

        private void commit() {
            System.out.println("读写结束,关闭数据库!");
        }

        @Override
        public void add(Object o) {
            beginTransaction();
            mDao.add(o);
            commit();
        }
        ......  //废话就别看了下面都一样
    }

实际使用时

SimpleDao simpleDao = new SimpleDao();
DaoProxy proxy = new DaoProxy(simpleDao);
proxy.add(new Object());
......

这就是简单的静态代理实现啦,这样看貌似成了装饰器模式?别急,我们先来分析一下两种模式的定义:
装饰器模式:侧重对于目标类中核心逻辑的扩展,依然是以目标类为中心。
代理模式:更加侧重于对目标类的访问限制与处理,某些场景甚至不需要调用目标类的实现,比如👇

        @Override
        public void add(Object o) {
            if(amIHappy()) {
                beginTransaction();
                mDao.add(o);
                commit();
            } else {
                System.out.println("不高兴,我不干!");
            }
        }

        private boolean amIHappy() {
            ......
        }

上面就是代理模式的静态代理基本实现方式了,然后我们接着来看看java的动态代理。

动态代理

1.动态代理简介

首先,相对于静态代理,动态代理中的代理类不需要实现与目标类相同的接口,而且不依赖于接口的具体实现,理论上可以代理所有的类所有方法。但因为要考虑到涉及到的业务,所以要求面向接口代理。

实现机制:运行时创建一个虚拟的代理类,在代理的目标方法实际执行时,通过java的反射技术获取到该方法对象,并在执行前或执行后添加需要的操作,这需要实现一个InvocationHandler接口,来看一个具体的实现

2.又是一颗栗子

class DaoProxy {

        这里需要注意,返回值只能是一个接口,而不能是具体的是实现类
        public IDao createProxy(IDao target) {
            此方法生成的虚拟类是根据目标的Class文件拿到的父类接口生成,因此不能强转成实现类
            return (IDao) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            beginTransaction();
                            Object invoke = method.invoke(target, args);
                            commit();
                            return invoke;
                        }
                    });
        }

        private void beginTransaction() {
            System.out.println("读写开始前,开启数据库!");
        }

        private void commit() {
            System.out.println("读写结束,关闭数据库!");
        }
    }

如上,实际返回的是一个匿名内部代理类,类型为$Proxy1(数字可能是其它),可以强转成目标类的父类接口使用,等于是生成了一个全是空方法的接口实现类。然后再实际执行到某个方法的时候,会执行 invoke 方法中的逻辑,这里我们调用了目标类的具体实现。
具体使用:

SimpleDao simpleDao = new SimpleDao();
IDao proxy = new DaoProxy().createProxy(simpleDao);
proxy.add(new Object());
......

//------------我是手动分割线------------

3.无需实现的接口代理

接下来,我们要将一个比较极端的代理方式,可以完全抛开具体实现,进行接口的完全代理。这种代理方式中我们只关心方法传入的参数和方法本身,重点逻辑全在代理中实现,完全无需接口的具体实现。👇

class LogProxy {

        public <T> T createProxy(Class<T> targetInterface) {
            return (T) Proxy.newProxyInstance(targetInterface.getClassLoader(), new Class[]{ targetInterface },
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.print("开始输出日志------->");
                            System.out.print("调用方法:" + method.getName() + makeArgsText(args));
                            System.out.print("<-------日志输出结束");
                            System.out.println();
                            return null;
                        }
                    });
        }

        private String makeArgsText(Object[] args) {
            StringBuilder builder = new StringBuilder();
            builder.append(",   共有").append(args.length).append("个参数:  ");
            for(Object item : args) {
                builder.append(item.toString()).append(" & ");
            }
            return builder.toString();
        }
    }

使用此代理并开始执行:

IDao proxy = new LogProxy().createProxy(IDao.class);
proxy.add("item");
proxy.update(0, "newItem");

得到的结果是:

开始输出日志------->调用方法:add,   共有1个参数:  “item”,<-------日志输出结束
开始输出日志------->调用方法:update,   共有2个参数:  “0”,“newItem”,<-------日志输出结束

这种代理方式使用到的场景,都是完全不关心接口的实现逻辑,代理类中已经完全集成了需要的操作,只需要取到接口方法上定义的注解和实际调用时传入的参数,用这些数据进行实际的操作。
安卓开发中使用的Retrofit框架就是通过这种方式实现的动态代理,日后有时间会再更一篇文章来深入的扒一扒Retrofit框架的实现原理。

以上就是本文的全部内容,如有不足请多指教,共同进步。

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

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,741评论 0 14
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,962评论 6 13
  • 《寻隐者不遇》 《所见》 我的观点: 对于古诗词的教学,我一直比较随缘,因为要挖掘的东西很多,可是总是需要根据学生...
    而今起阅读 561评论 0 2
  • 统合综效——创造性合作的原则。 统合综效的来源是:总体大于部分之和。也就是1+1>2。它的精髓就是判断和尊重差异,...
    陈果1225阅读 1,263评论 4 2
  • 自律给你自由!在成为你想成为的自己的路上,最大的阻碍不是外在,而是你自己!以平静的心态去面对当下,给自己一份计划,...
    Fineyoga阳阳阅读 246评论 0 0