美文网首页
设计模式浅析----代理模式

设计模式浅析----代理模式

作者: 精品农民 | 来源:发表于2018-12-13 15:04 被阅读0次

引子

代理模式是java中很常见的一种设计模式,诸如Spring框架中用到的AOP编程思想遵循的就是代理模式的设计原则。用大白话来讲,即A对象中会调用B对象的方法,但是开发人员不想让A对象自己去做这件事,这时候就可以用代理模式的设计思想设计一个中间代理类,让代理类帮助A对象去实现对B对象中方法的调用。联系生活就会发现有很多的例子可以类比成代理模式,比如部分程序员要找女朋友要通过相亲的途径,这时候中间人(媒婆)就相当于一个代理。

重要概念

  • 代理模式中涉及到两个必须存在的角色,即代理类和委托类。
  • 代理类需要持有委托类的实例对象的引用。
  • 按照代理类产生的时间节点,代理模式可被划分为静态代理模式和动态代理模式。

下面结合代码分析代理模式

代码分析

我们先来模拟一个简单的场景,一个人要找房子(买房子,租房子都可以),首先是他自己找。鉴于代理模式中的委托类必须要实现上层接口,所以我们先定义一个接口Person,在接口中定义两个方法。

package com.huang.proxy;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 08:59
 * @Description:
 */
public interface Person {

    public void findHouse();

    public void buyHouse();

}

然后是委托类,实现我们定义的接口

package com.huang.proxy;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 08:59
 * @Description:
 */
public class Huang implements Person {

    private String sex ;

    public Huang(String sex) {
        this.sex = sex;
    }

    @Override
    public void findHouse() {
        System.out.println("面积为120平");
    }

    @Override
    public void buyHouse() {
        System.out.println("buy this house");
    }
}

然后是测试类

package com.huang.proxy;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 09:00
 * @Description:
 */
public class TestProxy {

    public static void main(String[] args) {
        Person huang = new Huang("男");
        huang.findHouse();
    }
}

运行结果如下


image.png

这是我们在编程中习惯的方式,也是面向对象编程的体现。即当我们调用一个对象的属性或是方法时,习惯性的会用new关键字去创建这个对象的实例,通过该实例去调用。此时如果我们要在findHouse前后添加一些逻辑判断(如增加日志等操作),则不得不修改findHouse方法或者是在接口中定义新方法来调用,这就违背了开闭原则(对扩展开放,对修改封闭)。代理模式能很好地解决这一问题。

静态代理

在开发是就定义好代理类的模式被称作静态代理,即代理类由开发人员直接创建。
我们创建一个代理类staticProxy

package com.huang.proxy;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 13:59
 * @Description:
 */
public class StaticProxy implements Person{

    private Person target;

    public StaticProxy(Person target) {
        this.target = target;
    }

    @Override
    public void findHouse() {
        System.out.println("find 120 area house by static proxy");
        target.findHouse();
    }

    @Override
    public void buyHouse() {
        System.out.println("buy this house by static proxy");
        target.buyHouse();
    }
}

可以看到它实现了Person接口,通过构造函数获取委托类实例的引用,在其findHouse和buyHouse方法内部可以加入一些额外的逻辑,并调用了委托类的方法。
测试类中可以这样写

package com.huang.proxy;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 09:00
 * @Description:
 */
public class TestProxy {

    public static void main(String[] args) {
        /*Person huang = new Huang();
        huang.findHouse();*/
        Person huang = new Huang("男");
        Person staticProxy = new StaticProxy(huang);
        staticProxy.findHouse();
        System.out.println("------------------------");
        staticProxy.buyHouse();
    }
}

输出结果如下,可以看到在调用委托类方法前先执行了额外加入的逻辑。


image.png

如果我们将调用委托类的方法这一步看做一个切点,则我们在切点前后想加入一些额外的操作(如日志,验证等),就可以只在代理类中进行添加。

动态代理

与静态代理不同的是,动态代理的代理对象是动态生成的,即不需要开发时去指定。
我们创建一个代理类HouseProxy

package com.huang.proxy;

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

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 09:05
 * @Description:
 */
public class HouseProxy implements InvocationHandler {

    private Person target;

    public HouseProxy(Person target) {
        this.target = target;
    }

    public Object newInstance(Person target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Dynamic proxy start find house");
        if ("findHouse".equals(method.getName())){
            return method.invoke(this.target,args);
        }
        return target.getClass().getMethod("buyHouse").invoke(this.target,null);
    }
}

其中newInstance方法就是动态生成代理对象的方法。
在测试类中进行如下调用

package com.huang.proxy;

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

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 09:00
 * @Description:
 */
public class TestProxy {

    public static void main(String[] args) {
        /*Person huang = new Huang();
        huang.findHouse();*/

        /*Person huang = new Huang("男");
        Person staticProxy = new StaticProxy(huang);
        staticProxy.findHouse();
        System.out.println("------------------------");
        staticProxy.buyHouse();*/

        //创建委托类实例
        Person huang = new Huang("男");
        //代理类需要持有委托类实例的引用
        InvocationHandler houseProxy = new HouseProxy(huang);
        //动态生成代理对象,注意与静态代理不同,静态代理中的代理对象直接是通过new关键字创建
        Person proxy = (Person) Proxy.newProxyInstance(huang.getClass().getClassLoader(),huang.getClass().getInterfaces(),houseProxy);
        proxy.findHouse();
        System.out.println("------------------------");
        proxy.buyHouse();
    }
}

输出结果如下


image.png

在切点前后也执行了相应的操作,如果是统一的操作,只需要在代理类的invoke方法中增加逻辑,非常的灵活,易于后期维护。动态代理的运用也非常的常见,最显而易见的就是Spring中的AOP(面向切面编程),它就是借助于动态代理来实现松耦合。

走的更远

接下来我们继续深入,先来看看Proxy.newProxyInstance的源码

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //判断是否为空,是则报异常
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
        //安全检测    
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        //核心方法,生成代理对象
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        //通过生成的代理对象获取构造函数
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

可见核心方法为getProxyClass0,其源码如下

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    } 

其中proxyClassCache为代理类缓存变量,即如果缓存中存有该代理类,会将该代理类直接返回。那如果缓存中不存在呢?

  /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

很明显,如果缓存中不存在需要的代理类,则会调用代理类工厂ProxyClassFactory生成代理类。
我们自己实现一个ProxyGenerator类,其作用为将我们需要的代理类编译后的class文件输出到本地。代码如下

package com.huang.proxy;

import java.io.FileOutputStream;

/**
 * @Auther: huang.zh
 * @Date: 2018/12/13 10:01
 * @Description:
 */
public class ProxyGenerator {

    public static void main(String[] args) {
        //生成代理类字节码
        byte[] classContent = sun.misc.ProxyGenerator.generateProxyClass("$Proxy0",Huang.class.getInterfaces());
        String filePath = "D://$Proxy0.class";
        try{
            //将代理类字节码写入磁盘目录
            FileOutputStream fos = new FileOutputStream(filePath);
            fos.write(classContent);
            fos.close();
            System.out.println("success");
        } catch (Exception e){
            System.out.println("failed");
        }
    }
}

运行后会在D盘生成代理类的class文件,用反编译工具XJad打开会看到如下结果

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space 

import com.huang.proxy.Person;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy
    implements Person
{

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void findHouse()
    {
        try
        {       
                        //super.h即为父类Proxy中的Invacation实例
            super.h.invoke(this, m3, null);
            return;
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void buyHouse()
    {
        try
        {
                //super.h即为父类Proxy中的Invacation实例  
            super.h.invoke(this, m4, null);
            return;
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch (Error ) { }
        catch (Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            //通过反射机制获取findHouse的Method对象 
            m3 = Class.forName("com.huang.proxy.Person").getMethod("findHouse", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            //通过反射机制获取buyHouse的Method对象 
            m4 = Class.forName("com.huang.proxy.Person").getMethod("buyHouse", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch (NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch (ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

可以看到所有委托类方法的执行都是在调用Invovation.invoke方法去完成的,即使用了动态代理后对委托类方法的调用全部转为invoke方法的调用。

总结

关于代理模式,其中的静态代理和动态代理并无好坏之分,开发人员应该结合场景和自身经验选择一种最合适的。设计模式的存在自有它存在和被推崇的原因,需要我们养成良好的编程思想,多运用设计模式实现代码的高度重用和松耦合。

相关文章

网友评论

      本文标题:设计模式浅析----代理模式

      本文链接:https://www.haomeiwen.com/subject/dsevhqtx.html