likes
comments
collection
share

代理模式详解

作者站长头像
站长
· 阅读数 2

1. 代理模式定义

代理模式(Proxy):为其他对象提供一种代理以控制对这种对象的访问,可以提供额外的功能或控制。

通俗来说,当无法或者直接访问某个对象,可通过一个代理对象间接访问。代理模式可以理解为生活中的代理,服装代理、卖货代理等。

代理模式有什么好处?

作为使用者与真实对象没有直接交集,不会操作到真实对象。通过代理隔离。

1. 代理模式的结构

代理模式详解

代理模式中的三种角色:

  • 抽象角色:代理角色和真实角色对外提供的公共方法,一般为接口或者抽象类,定义一种行为。
  • 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,最终要引用的对象。真正的业务逻辑在此。
  • 代理角色:需要实现抽象角色接口,是真实角色的代理,内部含有真实角色对象的引用,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的控制流程都放到代理角色中处理。

注:一般,代理可以理解为代码增强,可以在源代码逻辑的前后增加其他代码逻辑。

真实角色和代理角色实现了同样的接口。

3. 静态代理

静态代理: 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类,手动创建代理类,在程序运行前代理类的.class文件就已存在

静态代理的具体实现

在不改变原有方法的基础上打印日志,以该需求为例:

1. 首先定义抽象接口:

被代理类和代理类都需要实现该接口。

public interface Calculator {
    void add(int a, int b);
}

2. 真实角色类,被代理类:

实现了Calculator接口。

public class CalculatorImpl implements Calculator{
    @Override
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }
}

3. 代理类,需要含有被代理类对象的引用:

实现了Calculator接口,含有被代理类CalculatorImpl对象的引用。

public class Proxy implements Calculator{
    CalculatorImpl calculatorImpl;

    public Proxy(CalculatorImpl calculatorImpl) {
        this.calculatorImpl = calculatorImpl;
    }

    @Override
    public void add(int a, int b) {
        System.out.println("Before add calculator");
        calculatorImpl.add(a, b);
        System.out.println("After add calculator");
    }
}

4. 客户端:

public class Client {
    public static void main(String[] args) {
        CalculatorImpl calculatorImpl = new CalculatorImpl();
        Proxy proxy = new Proxy(calculatorImpl);
        proxy.add(1, 1);
    }
}

优化

一般来说,被代理对象和代理对象是一对一的关系。当然一个代理对象对应多个被代理对象也是可以的。

假设另外一个功能也实现了这个接口,这时可以:

public class Proxy implements Calculator{
    Calculator calculator;

    public Proxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public void add(int a, int b) {
        calculator.add(a, b);
    }
}

面向接口编程,这样一个代理对象可以对应多个被代理对象,客户端必须传入一个具体的实现类。但这时一个代理类只能实现一个接口。

静态代理的缺点

  • 代码冗余:代理类与被代理类有重复的的代码。
  • 维护困难: 接口若发生变化,代理类与被代理类都要进行相应的修改。
  • 不适合大规模使用:如果代理大量的类,手动创建代理类较繁琐。
  • 只能代理特定的接口:如果要代理的类不实现接口,就无法使用静态代理。
  • 不支持横切关注点的复用:例如需要在不同的代理类中写相同的日志。安全检查等。无法做到复用。
  • 一个代理类只能实现一个接口:一对多时,一个代理类只能实现一个接口,一个代理类不能实现全部功能。

4. 动态代理

动态代理之所以存在,是为了解决静态代理的那些限制。

动态代理:在运行时动态创建代理类和其实例。通过反射机制实现。JDK提供ProxyInvocationHandler来完成这件事。

Proxy类

Java中的 java.lang.reflect.Proxy 类是动态代理的核心组件之一。这个类用于在运行时创建代理类的实例。它提供了一个静态方法 newProxyInstance() 用于创建代理对象。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
throws IllegalArgumentException

newProxyInstance有三个参数:

  • loader:用于定义代理类的类加载器。
  • interfaces:代理类要实现的接口列表,数组,可以实现多个接口。
  • h:用于将方法调用分派的调用处理程序。
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                           new Class<?>[] { Foo.class },
                                           handler);

强转,创建一个代理对象,这个对象怎么去调用被代理类中的方法的?

这时就用到了InvocationHandlerInvocationHandler起到了一个回调作用。

InvocationHandler接口

java.lang.reflect.InvocationHandler 接口是动态代理的另一个重要组件。该接口只有一个方法 invoke()用于在代理对象的方法被调用时执行自定义的逻辑

当生成的代理对象调用代理对象的方法时,将调用的方法传到invoke()方法中去,invoke() 方法会被调用。

这里可以将InvocationHandler接口看作是一个监听,当代理对象调用方法时,就会执行invoke()中的内容。

invoke() 方法接收三个参数:代理对象,被调用的方法对象和方法的参数,它负责执行代理逻辑。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke方法有三个参数:

  • proxy:代理对象,通常是生成的代理对象。你可以使用它来调用被代理对象的方法,但通常不会使用它,因为会导致递归调用。文末会说明。
  • method: 被代理对象的方法对象,它表示将被调用的方法。你可以使用它来获取方法的信息,例如方法的名称、参数、返回类型等。
  • args: 方法的参数数组,包含了调用方法时传递的参数。

Objectinvoke() 方法的返回值,通常是被代理方法的返回值。如果被代理方法返回基本数据类型,会自动装箱为相应的包装类型。

动态代理的具体实现

1. 定义一个抽象接口:

public interface Calculator {
    void add(int a, int b);
    void reduce(int x, int y);
}

2. 创建被代理对象:

public class CalculatorImpl implements Calculator{

    @Override
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }

    @Override
    public void reduce(int x, int y) {
        System.out.println("a + b = " + (x - y));
    }
}

3. 定义一个类去实现InvocationHandler接口:

public class MyInvocationHandler implements InvocationHandler {
    private Object realObject;

    public MyInvocationHandler(Object realObject) {
        this.realObject = realObject;
    }
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("Before invoking the method: " + method.getName());
        Object o1 = method.invoke(realObject, objects);//通过反射调用被代理类中方法
        System.out.println("After invoking the method: " + method.getName());
        return o1;
    }
}

4. 生成代理对象:

public class Client {
    public static void main(String[] args) {
        //创建被代理对象
        Calculator realObject = new CalculatorImpl();
        //创建代理处理器
        MyInvocationHandler h = new MyInvocationHandler(realObject);
        //创建代理对象
        Calculator o = (Calculator) Proxy.newProxyInstance(//强转,通过该对象调用方法
                Calculator.class.getClassLoader(),
                new Class[]{Calculator.class},
                h);
        //使用代理对象调用方法
        o.add(1, 1);
        o.reduce(1, 1);
    }
}

运行结果:

代理模式详解

注:

Q:为什么InvocationHandler中invoke()方法中的代理对象谨慎使用?

A:因为会导致递归调用。

Calculator o = (Calculator) Proxy.newProxyInstance(
       Calculator.class.getClassLoader(),
       new Class[]{Calculator.class},
       new InvocationHandler() {
           @Override
           public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
               System.out.println(o);
               return method.invoke(realObject, objects);
           }
       });

这段代码报错栈溢出StackOverflowErrorSystem.out.println(o);相当于System.out.println(o.toString());该语句会再次调用invoke()方法,以致循环调用。 代理模式详解

5. 动态代理源码分析

动态代理源码分析请看后续文章...