专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

由《寻秦记》说代理模式(静态,动态,CGLib)

经典穿越剧《寻秦记》被翻拍,看了几张剧照,不忍直视,周末有空倒是回味了一下古天乐版的,今天看来,依旧经典,童年美好回忆。正好最近在看代理模式,想到如果导演再找古天乐拍戏,倒是不一定能找到古天乐(因为他在玩贪玩蓝月??),这时候就可以找到他的经纪人,让他联系古天乐拍戏事宜。这里面的经纪人就暗含了代理模式的思想。

53_1.png

标准定义: 代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

其实Java里面很多地方应用到了代理,比如Spring AOP,Structs里面的拦截器。代理模式主要分为静态代理和动态代理,下面我们举例子说明。

静态代理

静态代理其实就是在程序运行之前,就已经准备好代理类。

假如我们找演员拍《寻秦记》,通过演员的经纪人来商量事宜。

1. 定义接口

首先定义一个演员接口,有一个方法演戏:

public interface Iactor {
    public void act(String dramaName);

2. 定义实现类

定义演员实现类,假设这里是古天乐:

public class GuTianle implements Iactor{
    private String name;

    public GuTianle(String name) {
        this.name = name;
    }

    @Override
    public void act(String dramaName) {
        System.out.println(name + " will act " + dramaName);
    }
}

3.定义代理

代理在这里也就是古天乐的经纪人:

public class GuTianleProxy implements Iactor {
    private Iactor actor;

    public GuTianleProxy(Iactor actor) {
        this.actor = actor;
    }

    @Override
    public void act(String dramaName) {
        actor.act(dramaName);
    }
}

经纪人简单来说就是实现了Iactor接口,内部调用真实古天乐的接口,让他拍戏,这里为什么这么做呢?因为除了让古天乐拍戏外,在代理类的act方法里,你也可以添加别的逻辑,比如古天乐太忙了,你就可以帮他推掉这个戏。或者设置一些别的事。

以上一个接口、两个类就实现了代理模式。

假设这时候导演想要找古天乐拍戏,那么可以先找他的经纪人:

public class Director {
    public static void main(String args[]){
        Iactor actor = new GuTianle("古天乐");

        Iactor proxy = new GuTianleProxy(actor);

        proxy.act("寻秦记");
    }
}

运行一下:

53_2.png

其实由静态代理的例子,我们就可以知道代理模式的大概形式:

53_3.png

首先是Subject,它为RealSubject和Proxy提供了接口,通过实现同一接口,Proxy在RealSubject出现的地方取代它。

RealSubject是真正做事的对象,正如古天乐去拍戏,它是被Proxy代理和控制访问的对象。

Proxy持有RealSubject的引用,某些时候还会负责RealSubject的创建和销毁。客户和RealSubject的交互都必须通过Proxy。所以任何用到RealSubject的地方都可以用Proxy取代,Proxy也控制了对RealSubject的访问,许多时候我们需要这样的控制。例如RealSubject是远程对象,RealSubject创建开销大,RealSubject需要被保护。

就像明星一般都会配个经纪人,帮助抵挡很多事物,设计模式来源于生活嘛~

下面继续看动态代理模式。

动态代理

前面讲的是动态代理,那么什么是动态代理呢?

假设上面的古天乐类有20种方法,我们要是写经纪人类,那么也要写20遍完全一样的代码,去调用古天乐类的方法。当然这是很不爽的,Java动态代理在这时候就很有用了,它可以帮助我们在运行时动态生成代理类。与静态代理相比,接口和实现类都没有变化,变化的只有代理类。

在使用动态代理时,我们需要定义一个位于代理类和委托类之间的中介类,这个类需要实现InvocationHandler接口:

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

public class GuTianleDynamicProxy implements InvocationHandler{
    private Iactor actor;
    public GuTianleDynamicProxy(Iactor actor)
    {
        this.actor = actor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("I will call Gu Tianle...");
        Object result = method.invoke(actor, args);
        System.out.println("Gu Tianle act so wonderful!!");
        return result;
    }
}

当我们调用代理类对象的方法时,会把调用传到invoke方法中,参数method即调用的方法,method.invoke(actor, args)即调用actormethod方法,method方法的参数是args。所有调用方法都是走的这个invoke函数。

下面看看如何产生动态代理并且调用方法的,假设有个动态导演类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicDirector {

    public static void main(String args[]){
        Iactor actor = new GuTianle("古天乐");

        InvocationHandler handler = new GuTianleDynamicProxy(actor);

        ClassLoader cl = actor.getClass().getClassLoader();

        Iactor proxy = (Iactor) Proxy.newProxyInstance(cl, actor.getClass().getInterfaces(), handler);

        proxy.act("寻秦记");
    }
}

上面这段代码中,我们依旧创建了实际的演员古天乐,并且用他创建了中介类实例GuTianleDynamicProxy, 再获取到actorclassLoader,这时候调用ProxynewProxyInstance方法,传入参数生成动态代理类,通过代理执行act方法。

输出:

53_4.png

这一遍我还在调用method前后打印了两个语句,如果古天乐有多个方法,那么每个方法调用前后都会打印这两句,是不是想到了Spring的AOP,差不多就是这么实现的。

总结下基于JDK的动态代理,还是需要定义接口和实现类,然后就是定义一个实现InvocationHandler的中介类,在使用时利用Proxy.newProxyInstance创建动态代理类,最后再调用方法。

上面的静态代理和动态代理都依赖于接口,那么没有实现接口的类我们如何做代理呢?答案就是采用CGlib

CGlib动态代理

cglib是针对类来实现代理的,原理是对一个业务类生成一个子类,并且覆盖其中的业务方法实现代理。

我们继续举例,假设古天乐类不需要实现Iactor,直接定义如下:

public class GuTianle {
    public void act(String dramaName) {
        System.out.println("古天乐 is acting " + dramaName);
    }
}

GClib写一个GuTianle类的中介类:

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 CGlibProxy implements MethodInterceptor{
    private Object actor; //实际的演员,供代理方法中进行真正的方法调用

    public Object getInstance(Object actor) {
        this.actor = actor;
        Enhancer enhancer = new Enhancer();//创建加强器,用来创建动态代理类
        enhancer.setSuperclass(this.actor.getClass());//为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        // 创建动态代理类对象并返回  
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before call...");
        methodProxy.invokeSuper(o, objects);
        System.out.println("after call...");
        return null;
    }
}

最后使用时动态创建代理类,动态代理类是要代理的演员类的子类:

public class CGlibDirector {
    public static void main(String[] args) {
        GuTianle actor = new GuTianle();
        CGlibProxy cglib = new CGlibProxy();
        GuTianle cglib_proxy = (GuTianle)cglib.getInstance(actor);
        cglib_proxy.act("寻秦记");
    }
}

可以得到:

53_5.png

下一篇深入分析下动态代理和CGlib底层的实现原理,欢迎点赞~

未经允许不得转载:搜云库技术团队 » 由《寻秦记》说代理模式(静态,动态,CGLib)

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们