设计模式中代理模式的学习

从前在看代理模式的时候,经常是一头雾水,和工厂模式相比,感觉这种模式很高深莫测,但是经过一段时间的开发,甚至用过很多反射模式之后。再来看代理模式,感觉也不是很难理解,以下是我对代理模式的学习过程。

对于代理,经过在网上学习了一段时间,也阅读了很多文章,个人总结为在不对对象本身进行修改的情况下,对对象进行功能的扩展即为代理。

通过网上的学习,代理一般可以分为静态代理与动态代理

假设我们现在有如下接口与对象,我们要对其添加一些功能

public interface PlayerInterface {

    void interview();

    void playBall();

}
public class Player implements PlayerInterface {

    public void interview() {

        System.out.println("接受采访");

    }

    public void playBall() {

        System.out.println("开始打球");
    }

}

 我们举例这里有一个球员,球员所进行的采访动作需要经纪人进行安排,而打球不需要,但是球员本身不需要考虑这些。

所以我们就要去通过代理(经纪人),给他去设定新的功能,而不改变球员自身的功能。

一、静态代理

所谓静态代理,即使创建一个新的对象,让其与原球员实现同一个接口,使其方法与原球员完全一致,而后对所有方法进行额外的操作,具体代码如下。

//这里实现了和原对象相同的接口
public class PlayerProxy implements PlayerInterface {

    //将需要代理的对象变为代理对象的一个属性
    private Player player;

    public PlayerProxy(Player player) {
        this.player = player;
    }

    public void interview() {
        
        //在进行真正的player.interview()之前,首先添加其他的操作
        System.out.println("proxy -> 安排");

        player.interview();

    }

    public void playBall() {

        player.playBall();
    }

    public static void main(String[] args) {
        PlayerProxy playerProxy = new PlayerProxy(new Player());
        playerProxy.interview();
        playerProxy.playBall();
    }

}

我们通过与原球员实现同样的接口,新创建一个球员代理类,这样我们就可以固定写好每个方法都需要进行什么额外的操作。

我们首先将需要代理的对象作为此新对象的一个参数。当我们调用interview方法的时候,我们本来是想进行一次采访,我们在真正对于球员的操作之前,添加一个新的操作,如代码所示,进行安排。这样就可以在不改变原对象的情况下,对对象的每个方法进行重新的包装,我们可以在真正执行方法之前进行操作,也可以在之后进行操作,甚至可以改变最终的返回值。当然这就是根据业务进行的自定义操作了。

如上所说,打球是不需要安排的,所以我们并不需要给playball这个方法去添加其他的操作,直接调用原对象的相同方法即可。

//所以调用Main方法的输出结果如下
proxy -> 安排
接受采访
开始打球

但是久而久之会发现这样的方法并不是很好,如果当我们修改接口时,我们的代理类与原对象类,都需要进行重新的编写,如果代理类很多的话,这样将造成很庞大的代码修改量,所以就出现了下一种方法,也就是动态代理。

二、动态代理

我们的接口没有任何改变。但是对象类有了一些变化,仅仅是添加了一个我自己编写的自定义注解。

//声明生命周期
@Retention(RetentionPolicy.RUNTIME)
//保证只能在方法上使用
@Target({ElementType.METHOD})
public @interface WithoutProxy {

}
public class Player implements PlayerInterface {

    public void interview() {

        System.out.println("接受采访");

    }

    //添加的注解
    @WithoutProxy
    public void playBall() {

        System.out.println("开始打球");
    }

}

注解的作用随后再说,在动态代理中,我们主要使用反射的技术,通过invoke方法去调用代理类的方法,来实现代理类的构建,具体代码如下。

public class ProxyFactory {

    //需要代理的类
    private Player player;

    public ProxyFactory(Player player) {
        this.player = player;
    }

    public PlayerInterface getProxyInstance() {
        return (PlayerInterface) Proxy.newProxyInstance(
                player.getClass().getClassLoader(),
                player.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Annotation[] annotations = method.getDeclaredAnnotations();
                        boolean needProxy = true;
                        for (Annotation annotation:
                                annotations) {
                            if(annotation.annotationType().isAssignableFrom(WithoutProxy.class)){
                                needProxy = false;
                            }
                        }
                        if(needProxy) {
                            System.out.println("proxy -> 安排");
                        }
                        return method.invoke(player, args);
                    }
                }
        );
    }

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Player());
        PlayerInterface playerInterface = proxyFactory.getProxyInstance();
        playerInterface.interview();
        playerInterface.playBall();
    }

}

首先我们可以看出,这是一个工厂,我们的这个工厂,将会去返回一个基于原对象类的一个代理类。然后当使用这个代理类进行操作的时候,他回去触发invoke方法,在这里我们可以对这里的所有方法进行包装,也就是说我们不用去对每一个方法都执行相同的处理,导致代码冗余了。

Proxy这个类也是出自 java.lang.reflect包下的,可以看出来他完全是利用的反射的理念去做的动态构建。

这里我们只需要调用Proxy.newProxyInstance方法就可以进行构建了

这个方法需要三个参数:

(1)原类的类加载器

(2)原类所实现的所有接口

(3)处理方法的包装逻辑,我们只需要重写InvocationHandler方法即可

到这里我再说一下我为什么要加上@WithoutProxy注解

首先我们对于所有方法的处理逻辑都写在了InvocationHandler中,所以如果我想对方法进行个性化操作,例如playball方法,我不需要进行代理,那么我必须要有一个特殊的逻辑。

当然我们也可以不用注解,去使用method.getName()方法去按照方法名去判断,那这样也未免太不优美了。

所以我决定通过判断这个方法有没有被@WithoutProxy注解去修饰,如果有的话,则跳过代理,直接执行原方法。

执行代码,会出现如下结果

proxy -> 安排
接受采访
开始打球

三、Cglib动态代理

这个也是我在网上新学到的知识,从上面几个方法可以看出,我们这些操作都需要原类去实现一个接口,才可以进行操作。

首先我们的静态代理需要和原类去实现同一个接口。

我们的动态代理也需要在方法中拿接口去当做参数。

这样就会出现一个问题,当我们频繁使用代理的时候,如果这个类没有接口并且不方便去实现一个接口,该怎么去做。

在网上了解到,Cglib会通过ASM字节码,来根据原类去动态生成一个代理类(原类的子类,所以我们必须要求这个类不是final,即不可继承的,如果类为final,则抛出异常,如果方法为final,则此方法不会被代理),而不需要实现任何的接口,其具体的代码如下。

<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.2.5</version>
</dependency>
public class ProxyCglib implements MethodInterceptor{

    private PlayerNoInterface player;

    public ProxyCglib(PlayerNoInterface player) {
        this.player = player;
    }

    public PlayerNoInterface getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(player.getClass());
        enhancer.setCallback(this);
        return (PlayerNoInterface) enhancer.create();
    }


    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Annotation[] annotations = method.getDeclaredAnnotations();
        boolean needProxy = true;
        for (Annotation annotation:
                annotations) {
            if(annotation.annotationType().isAssignableFrom(WithoutProxy.class)){
                needProxy = false;
            }
        }
        if(needProxy) {
            System.out.println("proxy -> 安排");
        }
        return method.invoke(player, objects);
    }

    public static void main(String[] args) throws NoSuchMethodException {
        ProxyCglib proxyFactory = new ProxyCglib(new PlayerNoInterface());
        PlayerNoInterface playerNoInterface = proxyFactory.getProxyInstance();
        playerNoInterface.interview();
        playerNoInterface.playBall();
    }
}

与动态代理的代码基本相同,只是实现了一个接口并且生成代理类的方式不同了而已。

在这里我们使用cglib的Enhancer类来进行代理类的生成。

我们指定父类为原类

指定回调函数为我们当前类,即指定下面编写的intercept为包装处理的方法。

我们通过执行main函数,最后也可以得到相同的结果。

proxy -> 安排
接受采访
开始打球

 

这就是全部的学习笔记了,如果有大神有更加完善的见解,请批评指正

简单的代码已经传到了git上,如有需要可以看一下

https://github.com/JinxLbj/design_proxy_project.git

猜你喜欢

转载自blog.csdn.net/qq_38182820/article/details/89398196
今日推荐