代理模式浅析

本文深入讲解代理模式,探讨其在解决远程对象访问、安全控制及进程外访问等问题时的应用。对比静态代理与动态代理,剖析Java动态代理实现原理,包括Proxy类与InvocationHandler接口的使用。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。

在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

1.静态代理

废话不多说,直接上代码

public class Test {

    public static void main(String[] args) {
    	//写法一
        StudyJava studyJava = new StudyJava();
        Programmer programmer = new StudyC(studyJava);
        programmer.coding();
        //写法二
        new StudyC(new StudyJava()).coding();
    }
}

interface Programmer{
     void coding();
}

class StudyJava implements Programmer{

    @Override
    public void coding() {
        System.out.println("学习使我快乐");
    }
}

class StudyC implements Programmer{
    private StudyJava s;

    public StudyC(StudyJava s) {
        this.s = s;
    }
    public void isDif(boolean b){
        if (b == true){
            System.out.println("Abandon Java");
        }
        else {
            System.out.println("Continue");
        }
    }

    @Override
    public void coding() {
        s.coding();
        isDif(true);
    }
}
学习使我快乐
Abandon Java
  • 1.2透明代理(普通代理)
public StudyC(StudyJava s) {
        this.s = s;
    }

变为

 public StudyC() {
        this.s = new StudyJava();
    }
class StudyC implements Programmer{
    private StudyJava s;
    
    public StudyC() {
        this.s = new StudyJava();
    }
    public void isDif(boolean b){
        if (b == true){
            System.out.println("Abandon Java");
        }
        else {
            System.out.println("Continue");
        }
    }
     @Override
    public void coding() {
        s.coding();
        isDif(true);
    }
}

在具体调用时,类似于透明效果,外界不知真正的功能实施者,只能看到代理者

//写法一
  Programmer programmer = new StudyC();
  programmer.coding();
  //写法二
  new StudyC().coding();

结果依然相同

学习使我快乐
Abandon Java
静态代理的缺点

虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

  1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
  1. 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

2.动态代理

如何改进?

当然是让代理类动态的生成啦,也就是动态代理。

为什么类可以动态的生成?

这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

Java 实现动态代理主要涉及以下几个类:

java.lang.reflect.Proxy: 这是生成代理类的主类,通过 Proxy 类生成的代理类都继承了 Proxy 类,即 DynamicProxyClass extends Proxy
java.lang.reflect.InvocationHandler: 这里称它为"调用处理器",它是一个接口,我们动态生成的代理类需要完成的具体内容需要自己定义一个类,而这个类必须实现 InvocationHandler 接口。

Proxy
代理类一定是 public 和 final

//创建代理对象  
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

第一个参数是类加载器对象(即哪个类加载器来加载这个代理类到 JVM 的方法区)
第二个参数是接口(表明你这个代理类需要实现哪些接口)
第三个参数是调用处理器类实例(指定代理类中具体要干什么)。

这个函数是 JDK 为了程序员方便创建代理对象而封装的一个函数,因此你调用newProxyInstance()时直接创建了代理对象(略去了创建代理类的代码)。

InvocationHandler接口

它是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args)

proxy 一般是指代理类(没有定义代理类的名字,Sun 虚拟机中的Proxy 类将生成一个以字符串 $Proxy 开头的类名)
method是被代理的方法,args为该方法的参数数组。

简单示例:

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

public class Test{
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 传入ClassLoader
                new Class[] { Hello.class }, // 传入要实现的接口
                handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }

}
interface Hello {
    void morning(String name);
}
public abstract void Reflect.Proxy.Hello.morning(java.lang.String)
Good morning, BobWebbQin

在运行期动态创建一个interface实例的方法如下:

  • 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
  1. 使用的ClassLoader,通常就是接口类的ClassLoader;
  2. 需要实现的接口数组,至少需要传入一个接口进去;
  3. 用来处理接口方法调用的InvocationHandler实例。
    将返回的Object强制转型为接口。

所谓 DynamicProx y是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个 DynamicProxy 其实就是一个 Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值