设计模式之代理模式(Proxy Pattern)

What:

为其他对象提供一种代理以控制对这个对象的访问。

Why:

优点:

1.增强目标对象。可以在执行目标对象方法的前后或者其他地方加上验证、日志等等代码;(Spring框架中的AOP)
2.将调用对象和被调用对象分离,一定程度上降低了耦合度。扩展性好;
3.保护目标对象;
4.职责清晰。目标对象就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成,附带的结果就是编程简洁清晰。

缺点:

1.对象与对象调用之间增加了一层代理,可能会导致执行的速度变慢;
2.实现代理的代码有时会很复杂,添加了额外的工作量;
3.增加系统的复杂度。

Where:

1.需要保护目标类,不希望直接被调用;
2.设置权限。类似Spring AOP的使用。

How:

代理模式有三个角色:

Subject(抽象角色): 通过接口或抽象类声明真实角色实现的业务方法。

RealSubject(目标角色): 实现抽象角色,定义目标角色所要实现的业务逻辑,供代理角色调用。

Proxy(代理角色): 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

ProxyPatternUML

代理模式有三种实现方式:

静态代理: 代理类在程序运行前就编译好,因此称为静态代理。代理对象和被代理对象都要实现相同的接口或者继承相同的父类(除Object之外),一旦接口或者父类添加、修改方法,子类都要统一更改,违背开闭原则,因此静态代理具有一定的局限性。

动态代理: 利用JDK的API,动态的在内存中构建代理对象。不需要继承父类,可扩展性高。

Cglib代理: 上面两种代理方式目标对象都需要实现接口,但有时候并不是所有目标对象都要实现接口,因此可以使用Cglib框架实现普通类的代理。

以下都是以浏览器访问服务器的简单举例:模拟浏览器向服务器发生消息,经过代理转码后服务器收到请求消息并响应信息,经过代理记录日志,最后浏览器才接收到响应信息。


ProxySample

静态代理

IHttpInvoke(抽象角色):

public interface IHttpInvoke {
    String invoke(String request);
}

Server(目标对象):

public class Server implements IHttpInvoke {

    Logger logger = Logger.getLogger(String.valueOf(getClass()));

    @Override
    public String invoke(String request) {
        String response = "没有的,不存在的!";
        return response;
    }
}

HttpInvokeProxy(代理对象):

public class HttpInvokeProxy implements IHttpInvoke {

    private Logger logger = Logger.getLogger(String.valueOf(getClass()));

    private IHttpInvoke iHttpInvoke;

    public HttpInvokeProxy(IHttpInvoke iHttpInvoke) {
        this.iHttpInvoke = iHttpInvoke;
    }

    @Override
    public String invoke(String request) {
        String req = before(request);
        String response = iHttpInvoke.invoke(req);
        after(response);
        return response;
    }

    public String before(String request){
        logger.info("请求数据:" + request);
        byte[] req = request.getBytes();
        String requestData = null;
        try {
            requestData = new String(req,"GBK");
            logger.info("转码成功,返回请求数据");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return requestData;
    }

    public void after(String response){
        logger.info("响应数据: " + response);
    }
}

Browser(浏览器客户端):

public class Browser {
    public static void main(String[] args) {
        String request = "给我一个女朋友!";
        IHttpInvoke httpInvokeProxy = new HttpInvokeProxy(new Server());
        httpInvokeProxy.invoke(request);
    }
}

输出结果:

五月 16, 2019 10:25:02 上午 Structural.ProxyPattern.StaticProxy.HttpInvokeProxy before
信息: 请求数据:给我一个女朋友!
五月 16, 2019 10:25:03 上午 Structural.ProxyPattern.StaticProxy.HttpInvokeProxy before
信息: 转码成功,返回请求数据
五月 16, 2019 10:25:03 上午 Structural.ProxyPattern.StaticProxy.HttpInvokeProxy after
信息: 响应数据: 没有的,不存在的!

静态代理的UML:


静态代理UML

动态代理

IHttpInvoke(抽象角色):

public interface IHttpInvoke {
    String invoke(String request);
}

Server(目标对象):

public class Server implements IHttpInvoke {

    Logger logger = Logger.getLogger(String.valueOf(getClass()));

    @Override
    public String invoke(String request) {
        String response = "没有的,不存在的!";
        return response;
    }
}

HttpInvokeProxy(代理对象):

public class HttpInvokeProxy implements InvocationHandler {

    private Logger logger = Logger.getLogger(String.valueOf(getClass()));

    private Object obj;

    public HttpInvokeProxy(Object obj) {
        this.obj = obj;
    }

    public String before(String request){
        logger.info("请求数据:" + request);
        byte[] req = request.getBytes();
        String requestData = null;
        try {
            requestData = new String(req,"GBK");
            logger.info("转码成功,返回请求数据");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return requestData;
    }

    public void after(String response){
        logger.info("响应数据: " + response);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before((String) args[0]);
        Object object = method.invoke(obj,args);
        after((String) object);
        return object;
    }
}

Browser(浏览器客户端):

public class Browser {
    public static void main(String[] args) {
        String request = "给我一个女朋友!";
        Server server = new Server();
        InvocationHandler invocationHandler = new HttpInvokeProxy(server);
        Class cls = server.getClass();
        IHttpInvoke httpInvoke = (IHttpInvoke) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),invocationHandler);
        httpInvoke.httpInvoke(request);
    }
}

输出结果:

五月 16, 2019 10:53:01 上午 Structural.ProxyPattern.DynamicProxy.HttpInvokeProxy before
信息: 请求数据:给我一个女朋友!
五月 16, 2019 10:53:02 上午 Structural.ProxyPattern.DynamicProxy.HttpInvokeProxy before
信息: 转码成功,返回请求数据
五月 16, 2019 10:53:02 上午 Structural.ProxyPattern.DynamicProxy.HttpInvokeProxy after
信息: 响应数据: 没有的,不存在的!

Cglib代理

Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)

*使用Cglib代理需要引入Cglib的jar包,而且还需要引入asm-all的jar包。注意两个包的版本号,因为不同的版本可能不兼容导致报错。
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm-all</artifactId>
    <version>3.3</version>
</dependency>

Server(目标对象):

public class Server{

    Logger logger = Logger.getLogger(String.valueOf(getClass()));

    public String receiveRequest(String request) {
        String response = "没有的,不存在的!";
        return response;
    }
}

HttpInvokeProxy代理类:

public class HttpInvokeProxy implements MethodInterceptor {

    private Logger logger = Logger.getLogger(String.valueOf(getClass()));

    private Object obj;

    public HttpInvokeProxy() {
    }

    public HttpInvokeProxy(Object obj) {
        this.obj = obj;
    }

    public String before(String request){
        logger.info("请求数据:" + request);
        byte[] req = request.getBytes();
        String requestData = null;
        try {
            requestData = new String(req,"GBK");
            logger.info("转码成功,返回请求数据");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return requestData;
    }

    public void after(String response){
        logger.info("响应数据: " + response);
    }

    public Object getProxy(){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(Server.class);
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        Object object = enhancer.create();
        return object;
    }

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

Browser(浏览器客户端):

public class Browser {
    public static void main(String[] args) {
        String request = "给我一个女朋友!";
        Server server = new Server();
        Server proxy = (Server) new HttpInvokeProxy(server).getProxy();
        proxy.receiveRequest(request);
    }
}

输出结果:

五月 16, 2019 11:52:42 上午 Structural.ProxyPattern.CglibProxy.HttpInvokeProxy before
信息: 请求数据:给我一个女朋友!
五月 16, 2019 11:52:42 上午 Structural.ProxyPattern.CglibProxy.HttpInvokeProxy before
信息: 转码成功,返回请求数据
五月 16, 2019 11:52:42 上午 Structural.ProxyPattern.CglibProxy.HttpInvokeProxy after
信息: 响应数据: 没有的,不存在的!

总结

静态代理作为原始的代理模式设计,代理对象和目标对象都实现同一个接口,在代理对象中指向的是目标对象的实例,这样对外暴露的是代理对象而真正调用的是目标对象。其优点是保护目标对象,提高安全性;但缺点也显而易见,不同的接口要有不同的代理类实现,代码量增加,系统冗余。

动态代理弥补了静态代理需要子类继承或者实现父类的缺点,利用反射机制创建对象,进一步降低了耦合度。动态代理必须依赖接口的实现,但并不是所有的目标对象都会继承父类或者实现接口。

Cglib代理弥补了JDK动态代理不足。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。但由于CGLib采用动态创建子类的方法,对于final方法,无法进行代理。

每种代理方式都有优缺点,我们应该根据实际的开发场景选择动态代理或者使用Cglib代理。

了解更多设计模式:

设计模式系列

参考资料:

https://www.cnblogs.com/cenyu/p/6289209.html

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

推荐阅读更多精彩内容