设计模式之代理模式

前言

  在生活中,我们经常遇到这样的角色:房产中介、婚介、经纪人、快递、产品代理商等,这些都是代理模式的实际体现,代理对象在客户端和目标对象其一个中介的作用。为什么出现这种模式呢,其实也是单一职责模式的体现,就像一个人,如果做一个工作就比较容易做好,如果一个人同时做多分工作,那就很难做好,容易出错。这时候就是代理模式大显身手的时候了,举一个代理商的例子,比如某公司刚生产出一批新电脑,需要销售,一般都会找很多代理商来代销售(比如某东,某猫),而不是自己去销售,因为如果找代理商来代销售只需要签订代理合同就可以了,而且可以和很多家代理商合作,这些代理商的代理点分布在全国各地,所以可以很容易的推广这个产品,代理商专注销售,生产厂商专注生产,这样就可以明确分工,合作共赢。

定义

为其他对象提供一种代理,以控制对这个对象的访问,代理模式属于结构型设计模式。

1.静态代理

先来看一下结构图(摘自《大话设计模式》)

 

 

第一步:编写需要被代理的类(RealSubject)和代理类(Proxy)的公共接口(Subject)

/**
 * 定义了RealSubject和Proxy的公共接口,这样,在任何使用RealSubject的地方都可以用Proxy替代
 */
public abstract class Subject {

    public abstract void request();
} 

 第二步:编写需要被代理的类(RealSubject)

/**
 * 定义Proxy代理的真正实体
 */
public class RealSubject extends Subject {
    @Override
    public void request() {
        System.out.println("真实的请求");
    }
}

 

第三步:编写代理类(Proxy),代理对RealSubject的访问

/**
 *保存一个引用使得代理可以访问实体,并提供一个和RealSubject相同的接口,这样代理就可以替代实体
 */
public class Proxy extends Subject {

    private Subject realSubject;
    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        realSubject.request();
    }
}

第四步:编写客户端代码,通过访问代理类访问真实对象

public class Client {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

 第五步:编写增强方法,增强RealSubject的功能

public class Proxy extends Subject {

    private Subject realSubject;
    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        before();
        realSubject.request();
        after();
    }

    private void before() {
        System.out.println("调用前增强");
    }

    private void after() {
        System.out.println("调用后增强");
    }
}

运行结果

 如果项目中有多个需要增强的类,就需要对每个类编写代理类,这样就会非常麻烦,而且很多情况下,我们需要为多个类增强同样的功能,比如,日志记录,项目中很多地方都需要记录日志。还有就是如果需要在被代理中添加方法,再对这个方法进行代理的话,就需要同时修改代理类的逻辑,违反开闭原则。那么,就需要用动态代理解决静态代理不够灵活的问题了。

2.动态代理

  下面举一个婚介帮忙介绍对象的例子

第一步:编写Person接口,声明找对象的方法

public interface Person {

    /**
     * 找对象
     */
    void findLove();
}

第二步:编写具体的客户类

public class Customer implements Person {
    
    @Override
    public void findLove() {
        System.out.println("见面、吃饭、看电影...");
    }
}

第三步:编写婚介类,代理客户找对象

/**
 * 婚介代理类
 */
public class HunJie implements InvocationHandler {

    private Object target;

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

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

    private void after() {
        System.out.println("after()");
    }

    private void before(Object[] args) {
        System.out.println("找到符合条件的单身人士");
        System.out.println("安排见面");
    }
}

第四步:编写客户端

public class Client {

    public static void main(String[] args) {
        Person person = (Person)new HunJie().getInstance(new Customer());
        person.findLove();
    }
}

运行结果

 Person是一切客户的父类,当然,婚介也就可以作为代理为所有人找对象了。

3.Java实现动态代理

上面婚介介绍对象的例子已经实现了动态代理,在java中,常用的实现动态代理的方式有2种:jdk动态代理和cglib动态代理。

3.1JDK动态代理

3.1.1

婚介介绍对象的例子使用的是jdk动态代理,jdk动态代理中需要注意3点

第一点:被代理的对象必须首先接口,如果一个对象没有实现任何接口,则这个对象无法被代理;

第二点:代理对象必须实现InvocationHandler 接口,这个接口是jdk中定义的用于实现动态代理的接口;

第三点:通过getInstance()得到的是代理对象,而非实际的被代理的对象,代理对象实现了被代理对象的所有接口,所以需要转换为被代理对象的接口类型。

为了搞清楚jdk动态代理的实现原理,下面我们修改测试类,将代理类person保存为文件,然后使用jad反编译,分析代理类的源代码

public static void main(String[] args) throws Exception{
        Object instance = new HunJie().getInstance(new Customer());
        Person person = (Person)instance;
        person.findLove();

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        OutputStream os = new FileOutputStream("E://$Proxy0.class");
        os.write(bytes);
        os.close();
    }

为了证明instance对象为代理类,下面看一下调试结果

 jad反编译结果:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

import java.lang.reflect.*;

public final class $Proxy0 extends Proxy
    implements Person
{

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

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

    //Person接口的方法
    public final void findLove()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()//Object类的toString方法
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

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

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

    //拿到方法签名,便于反射调用
    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("Person").getMethod("findLove", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", 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());
        }
    }
}
View Code

 

上面的h即为InvocationHandler 接口,所以当调用person.findLove()时,会调用上面代理对象的findLove()方法,然后反射调用InvocationHandler的实现类,也就是HunJie的 invoke()方法,HunJie的 invoke()方法再次反射,调用被代理对象Customer的findLove()方法。

之所以称之为动态代理,是因为动态代理是在运行时动态生成字节码,编译,加载,运行的,过程可以分为以下5个步骤:

1.拿到被代理对象的引用,反射获取它的所有接口;Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this)中的第二个参数

2.JDK Proxy类重新生成一个新的类,此类要实现被代理对象的所有接口;

3.动态生成java代码;

4.编译生成.class;

5.重新加载到JVM中运行。

好了,到这里就彻底搞清楚jdk动态代理的实现原理了。

3.2Cglib(基于ASM)动态代理

  简介:CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

  第一步:引入依赖,这里引入cglib或spring的依赖都可以

  第二步:编写代理类

/**
 * Cglib婚介代理类
 */
public class CglibHunJie implements MethodInterceptor {


    public Object getInstance(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);
        //将被代理类设置为父类
        enhancer.setSuperclass(target.getClass());
        return enhancer.create();
    }

    private void after() {
        System.out.println("CglibHunJie after()");
    }

    private void before(Object[] args) {
        System.out.println("CglibHunJie找到符合条件的单身人士");
        System.out.println("CglibHunJie安排见面");
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        before(objects);
        Object invoke = methodProxy.invokeSuper(o, objects);
        after();
        return invoke;
    }
}

  第三步:编写测试类

public class Client {

    public static void main(String[] args) {
        Person person = (Person)new CglibHunJie().getInstance(new Customer());
        person.findLove();
    }
}

  第四步:运行

3.3CGLib 和 JDK 动态代理对比

  1.JDK动态代理实现代理对象的接口,Cglib是继承了代理对象。

  2.JDK动态代理只能代理实现了接口的代理对象,Cglib动态代理只能代理代理对象中非final的方法。

  3.JDK和Cglib都是运行时生成字节码,JDK是直接写Class字节码,Cglib通过ASM框架写字节码,Cglib实现更复杂,生成代理类效率比JDK低。

  4.JDK动态代理通过反射机制调用代理对象的方法,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高 。

4.总结

4.1静态代理与动态代理的区别

  1.静态代理只能通过手动完成代理操作,如果被代理对象添加新的方法,代理类需要同步修改,违背开闭原则。

  2.动态代理通过运行时动态生成字节码的方式,取消了对被代理类扩展的限制,符合开闭原则。

4.2代理模式的优缺点

优点

  1.代理模式将调用方与真正被调用的目标对象隔离,可以起到保护目标对象的作用。

  2.一定程度上降低了系统的耦合性,提高了可扩展性。

  3.可以对目标对象增强。

缺点

  1.代理模式会造成系统设计中类的数量增加。
  2.在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
  3.增加了系统的复杂度 。

 

 

posted @ 2019-09-25 13:34  Ethan-Wu  阅读(217)  评论(0编辑  收藏  举报