理解Java里面的代理模式

前言

代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看成是代理模式的缩影,或者再形象点各种浏览器都可以看成是我们上网的代理角色。

代理模式的定义

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的组成

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

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

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式的优点

(1).职责清晰 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

(3).高扩展性

代理模式的应用场景

(1)最经典的就是Spring AOP的实现,通过代理实现了日志记录,事务,方法耗时等非核心业务本职责的功能的统一控制。

(2)在Hibernate类似相关的框架中,仅仅定义接口就可以通过代理动态实现CURD相关操作。

(3)其他各种字节码操纵的场景。

Java里面的代理

在Java里面总体上有三种代理实现:

(1)静态代理

例子如下: 定义一个接口

public interface Animal {

    public String run();

}

定义一个实现类:

public class Dog implements Animal {

    @Override
    public String run() {
     return    " 小狗跑..... ";
    }
}

定义一个代理类,同样实现该接口:

public class ProxyRole implements Animal {

    private Animal proxy;

    public ProxyRole(Animal proxy) {
        this.proxy = proxy;
    }

    @Override
    public String run() {

        System.out.println(" before execute......  ");
        String result=proxy.run();
        System.out.println(result);
        System.out.println(" after execute......  ");

        return result;
    }
}

如何使用:

public class TestMain {


    public static void main(String[] args) {

        //真实角色.
        Animal dog=new Dog();
        //代理角色.
        ProxyRole proxyRole=new ProxyRole(dog);
        //代替执行.
        proxyRole.run();
        }
}

(2)JDK动态接口代理

JDK动态接口的代理的特点是,其代理角色可以在运行时动态生成而不需要在编译的时候提前定义,正是因为这个特点,所以扩展性和灵活性更强,其原理是在运行时会动态生成一个类,该类实现了所定义的接口,然后在我们把我们还需要定义一个InvocationHandler的实现类,在这个类里面重写invoke方法,并附加自己与业务无关的逻辑,比如计数或者方法耗时,日志等等,最终通过反射调用真实角色的业务方法。

例子如下:

public class CountTimeProxyInvocation implements InvocationHandler {

    private Object target;

    public CountTimeProxyInvocation(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        System.out.println(proxy+"  "+method+" "+ args.toString());
        System.out.println("name:"+proxy.getClass().getName());
        System.out.println(method+"  "+Arrays.toString(args));//传入的参数类型
        long start=System.nanoTime();
        System.out.println("调用之前.......");
//        Object result=ms.get(method.getName()).invoke(target,args);
        Object result=method.invoke(target,args);
        long cost=System.nanoTime()-start;
        System.out.println("调用之后,"+method.getName()+" 耗时: "+cost+" ns");

        return result;
    }
}

上面这个实现了InvocationHandler的类,实现了方法耗时的统计。

private static void testMap(){
        Map map= (Map) Proxy.newProxyInstance(TestCountTimeProxy.class.getClassLoader(),
                new Class[]{Map.class},//必须接口类型,可以有多个接口
                new CountTimeProxyInvocation(new HashMap())
        );

        map.put("t",12);
        map.get("1");
        map.size();
    }

然后,我们就能感受它的强大之处,只要是有接口声明的类,都可以采用这个方法来统计耗时,比如上面的Map例子,正这里如果是List或者是Set同样适用。

(3)Cglib框架的动态类代理

JDK动态代理要求每一个类必须要有接口定义才可以实现动态代理功能,所以有时候我们可能仅仅是为了一个普通类代理,这样以来就必须绑定接口了,而cglib框架可以实现普通类的代理,对于类里面不包含final修饰的方法都可以实现代理,例子如下:

定义一个普通的类:

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

实现代理访问:

// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法.
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象.
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

cglib并不是jdk自带的方案,它需要引入maven的依赖才能使用:

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.8</version>
        </dependency>

动态代理原理分析

代理模式的技术核心有三点:

(1)动态字节码生成

(2)动态字节码类的装载

(3)反射调用真实角色的方法

理解了上面的内容学习代理模式就非常容易,先从JDK动态代理说起,前面说到JDK动态代理的会在运行时生成接口的实现类,并且该实现类会继承Proxy类,所以也注定了JDK动态代理只能代理接口不能代理类,因为Java不支持多继承,在使用的时候我们执行方法调用会从代理角色经过,然后由代理角色调用我们实现的Invocation的invoke方法,在invoke方法里面我们可以自定义额外的功能,最后通过反射调用真实角色方法,完成整个调用链。

而cglib代理的原理与JDK动态代理类似,不同之处在于cglib代理在运行时候,动态的生成了一个继承了真实的角色的代理类,然后实现了cglib的Factory接口,在运行的时候,同样通过代理类转发请求调用,先调用我们设置的callback里面回调的Interceptor方法,这样我们就可以在这个方法里面控制与核心业务无关的逻辑,最后通过反射调用真实角色的方法完成整个调用链。cglib代理功能更强大,其底层集成了ASM直接操作字节码的类库,并且可以控制仅仅对需要代理的方法执行代理,至于其他的方法可以忽略,这样以来可以拥有更好的性能。

总结

本文主要介绍了代理模式的应用,并结合实际的例子详细的描述了Java里面三种代理模式的原理和实现,代理模式是一项非常有用的技术,通过代理角色转发请求,可以实现非常灵活的扩展功能。

原文发布于微信公众号 - 我是攻城师(woshigcs)

原文发表时间:2018-10-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏西二旗一哥

iOS - Dissecting objc_msgSend on ARM64

我们的朋友 ldp 又出现了。这回它读取了 x12 中指针,这个指针指向了要查找的bucket。每个bucket包含了一个选择器和一个 IMP 。 x9 现在包...

12440
来自专栏CSDN技术头条

用 Webhook+Python+Shell 编写一套 Unix 类系统监控工具

本文来自作者 Alinx 在 GitChat 上分享 「用 Webhook+Python+Shell 编写一套 Unix 类系统监控工具」

37250
来自专栏我是攻城师

关于线程可见性一个“诡异”的问题

如果执行上面的代码,大多人可能觉得会死循环,因为这里没有任何的同步策略,比如synchronized,Lock,atomic,volatile等关键字,也就是说...

9830
来自专栏博岩Java大讲堂

Java集合--非阻塞队列(ConcurrentLinkedQueue基础)

42560
来自专栏Python私房菜

简析Python中的四种队列

在Python文档中搜索队列(queue)会发现,Python标准库中包含了四种队列,分别是queue.Queue / asyncio.Queue / mult...

14130
来自专栏与神兽党一起成长

使用commons-pool管理FTP连接

在封装一个FTP工具类文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。

14720
来自专栏Janti

JVM活学活用——类加载机制

类的实例化过程 ---- 有父类的情况 1. 加载父类静态     1.1 为静态属性分配存储空间并赋初始值     1.2 执行静态初始化块和静态初始化...

41280
来自专栏程序员互动联盟

【专业技术】程序在内存中如何分配的?

好多初学者可能对程序在内存中如何布局都有疑问,在我们和用户的沟通过程中也发现有好多同学问相关的问题。这里转一个文章,讲得很不错的,大家可以看一下。 栈主要用来存...

23260
来自专栏C/C++基础

Protocol Buffers C++入门教程

protobuf(Protocol Buffers )是Google的开源项目,是Google的中立于语言、平台,可扩展的用于序列化结构化数据的解决方案。官网见...

1.3K10
来自专栏WindCoder

Java基础小结(三)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection)

7110

扫码关注云+社区

领取腾讯云代金券