代理模式-java

概念:

由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。这种类型的设计模式属于结构型模式。

实现方式:

代理模式分两种: 静态代理,编译期生成;动态代理,运行时动态生成 (JDK代理,CGLib代理)。

实现1:静态代理

抽象主题类(subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法 (定义的是规范)。
真实主题类(Real subject):实现抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,他可以访问 控制或扩展真实主题的功能。

示例:

定义规范抽象主题类售卖手机SellPhone,真实主题类手机的厂商PhoneFirm,代理类手机的待售商店PhoneShop

//抽象主题,定义规范 subject
public interface SellPhone {
   void sell();
}
//手机厂商,用于真实的售卖手机 Real subject
public class PhoneFirm implements SellPhone {
    @Override
    public void sell() {
        System.out.println("手机厂商卖出手机");
    }
}
//代理类,代理了手机厂商售卖手机,这里可以对sell方法进行修改做各种业务逻辑 Proxy
public class PhoneShop implements SellPhone {
    //声明手机厂商
    private PhoneFirm phoneFirm = new PhoneFirm();
    @Override
    public void sell() {
        System.out.println("代理商开始售卖手机...");
        //所代理的厂商开始售卖
        phoneFirm.sell();
    }
}
public class Test {
    public static void main(String[] args) {
        //测试调用
        PhoneShop phoneShop = new PhoneShop();
        phoneShop.sell();
    }
}

实现2:动态代理(JDK动态代理)

java中提供了一个动态代理类Proxy,Proxy并不是我们定义的代理对象类,而是提供了一个创建对象的静态方法(newProxyInstance方法)获取代理对象。存在于内存中的代理对象。

示例:

以售卖手机为例,SellPhonePhoneFirm保持不变,这里提供一个获取代理对象的工厂类ProxyFactory

//抽象主题,定义规范 subject
public interface SellPhone {
   void sell();
}
//手机厂商,用于真实的售卖手机 Real subject
public class PhoneFirm implements SellPhone {
    @Override
    public void sell() {
        System.out.println("手机厂商卖出手机");
    }
}
//获取代理对象的工厂类
public class ProxyFactory {
    //目标对象  代理类所代理的对象 
    PhoneFirm phoneFirm = new PhoneFirm();
    public SellPhone getProxyObject() {
        //loader, 类加载器,用于加载代理类,可以通过目标对象获取类加载器
        //interfaces,代理类实现的接口的字节码对象
        //h,代理对象的调用处理程序
        // return 返回代理对象
        return (SellPhone) Proxy.newProxyInstance(
                phoneFirm.getClass().getClassLoader(),
                phoneFirm.getClass().getInterfaces(),
                new InvocationHandler() {
                    //proxy 代理对象  也就是Proxy.newProxyInstance返回的对象
                    //method 接口中的方法进行封装的method对象
                    //args 调用方法传递的实际参数
                    //return 方法的返回值 method方法的
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("手机代理商售出手机-jdk动态代理");
                        //执行目标对象的方法
                        return method.invoke(phoneFirm, args);
                    }
                }
        );
    }
}
public class Test {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        //获取代理对象
        SellPhone sellPhone = proxyFactory.getProxyObject();
        sellPhone.sell();//调用售卖方法
      
      //用于获取jdk代理在内存中生成的代理对象
      //System.out.println(sellPhone.getClass());
      //while (true);
    }
}

jdk动态代理关键代码在ProxyFactory中,通过Proxy.newProxyInstance获取代理对象,在其参数InvocationHandler的invoke方法中可以做统一的逻辑处理。
案例执行流程:
1、在测试类中通过代理对象调用sell()方法
2、根据多态的特性,执行的是代理类($Proxy0)中的sell()方法。
3、代理类中的sell()方法调用了InvocationHandler接口实现类对象的invoke方法
4、invoke方法通过反射执行了真实对象所属类(PhoneFirm)sell方法

扩展:

介绍一种查看代理类对象的方法,通过阿里的 Arthas 查看代理结构。
步骤1:让程序一直运行中,这里使用while(true)死循环实现,代码在上面的Test类的注释掉的部分。
步骤2:执行命令:java -jar arthas-boot.jar
步骤3:根据提示选择我们的程序
步骤4:执行命令:jad com.sun.proxy.$Proxy0 进行查看
(执行过程下图所示)

image.png

image.png

将命令行中显示的代码粘贴出来,如下:

public final class $Proxy0 extends Proxy implements SellPhone {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.du.javadesignpatterns.patterns.structure_patterns.proxy.demo2.SellPhone").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

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

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

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

上述代码中,只需要关注Method m3sell()方法和构造方法,构造方法中将InvocationHandler传递给了父类(Proxy),父类中将对象赋值给了属性hsell()方法中调用了this.h.invoke(this, m3, null),也就是我们传递进去的InvocationHandler对象的invoke方法。在invoke方法中,我们可以调用真实对象的方法,也可以处理一些统一的逻辑。

在Android的开源框架Retrofit中,请求统一封装核心就是使用了jdk动态代理模式。

实现2:动态代理(cglib动态代理)

CGLIB是一个功能强大,高性能的代码生成包。它可以为没有实现接口的类提供代理。

示例:

还是同一案例,与jdk动态代理不同的是,这里没有SellPhone接口

//手机厂商类
public class PhoneFirm {
    public void sell() {
        System.out.println("手机厂商卖出手机");
    }
}
//代理对象工厂,获取代理对象
public class ProxyFactory {
    private PhoneFirm phoneFirm = new PhoneFirm();
    public PhoneFirm getProxyObj() {
        //创建Enhancer对象, 和jdk中的Proxy类似
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(PhoneFirm.class);
        //设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("手机代理商售出手机-cglib代理");
                return method.invoke(phoneFirm, objects);
            }
        });
        //创建代理对象 并返回
        return (PhoneFirm) enhancer.create();
    }
}
public class Test {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        PhoneFirm proxyObj = proxyFactory.getProxyObj();
        proxyObj.sell();
//        System.out.println(proxyObj.getClass());
//        while (true);
    }
}

cglib动态代理关键代码在ProxyFactory中,通过Enhancercreate方法获取代理对象,setCallback的接口回调中可以做统一的逻辑处理。

同样这里也可以使用阿里的 Arthas 查看代理结构

JDK代理和CGLib代理对比:

CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。需要注意的是,CGLib不能对final修饰的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类
jdk1.6、1.7、1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK动态代理高于CGLib代理效率,只有当进行大量调用时,jdk1.6、1.7CGLib代理效率低一点,但是到jdk1.8的时候,JDK动态代理高于CGLib。所以如果有接口使用JDK动态代理,没有接口使用CGLib代理。

动态代理和静态代理对比:

动态代理最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行零活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加代码维护的复杂度。

优点:

1、代理模式再客户端与目标对象之间起到一个中介作用和保护目标对象的作用
2、代理对象可以扩展目标对象的功能
3、代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

缺点:

增加系统的复杂度

你可能感兴趣的:(代理模式-java)