Android面试题(10): 动态代理和代理模式

时间:2021-06-19 14:15:07

1. 代理模式(Proxy Pattern)

代理模式根据不用的职责和应用场景有很多种形式,例如远程代理,虚拟代理,保护代理,防火墙代理、智能引用代理、缓存代理、同步代理、复杂隐藏代理、写入复制代理等。
但是它们都有一个共同的特点,为其它对象提供一种代理以控制对这个对象的访问。

1.1 和装饰者模式的区别

一般情况下,我们容易将装饰者模式和代理模式搞混。
- 装饰者模式:是在不使用继承,不改变原有对象的情况下增加或扩展对象行为,但是并不会禁用你对象的某个行为。
- 代理模式:而代理模式是控制这个对象的访问。

例如我们看看下面这个虚拟代理的例子:
当一个对象的创建开销很大,我们想要在真正使用到的时候才真正创建这个对象的时候,就可以用到虚拟代理。

interface Image {
    public void displayImage();
}
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) { 
        this.filename = filename;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        System.out.println("Loading " + filename);
    }

    public void displayImage() { 
        System.out.println("Displaying " + filename); 
    }
}
class ProxyImage implements Image {
    private String filename;
    private Image image;

    public ProxyImage(String filename) { 
        this.filename = filename; 
    }
    public void displayImage() {
        if(image == null)
              image = new RealImage(filename);
        image.displayImage();
    }
}
class ProxyExample {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");     

        image1.displayImage(); // loading necessary
        image2.displayImage(); // loading necessary
    }
}

例如ProxyImage,我们隐藏了RealImage中的loadImageFromDisk()方法,创建ProxyImage对象并不会立刻创建RealImage对象,实现了对象的懒加载。

这和装饰者模式是有些出入的,它们的职责和目的不一样。装饰模式主要是强调对对象的功能扩展,而代理模式虽然也可以扩展对象的功能,但更偏向于对象的访问限制。

2. 动态代理

假设有这么一个Person类。

public interface Person {

    String getName();

    void setName(String name);

    String getGender();

    void setGender(String gender);

    int getAge();

    void setAge(int age);
}
public class PersonImpl implements Person {
    private String name;
    private String gender;
    private int age;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getGender() {
        return gender;
    }

    @Override
    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }
}

如果我们想记录这个Person对象的信息修改次数怎么办呢?
我们是不是可以弄一个装饰类扩展一下。

public class DecorPerson implements Person {

    private PersonImpl person;
    private int modifiedTimes;

    public DecorPerson(PersonImpl person) {
        this.person = person;
    }

    @Override
    public String getName() {
        return person.getName();
    }

    @Override
    public void setName(String name) {
        modifiedTimes++;
        person.setName(name);
    }

    @Override
    public String getGender() {
        return person.getGender();
    }

    @Override
    public void setGender(String gender) {
        modifiedTimes++;
        person.setGender(gender);
    }

    @Override
    public int getAge() {
        return person.getAge();
    }

    @Override
    public void setAge(int age) {
        modifiedTimes++;
        person.setAge(age);
    }

    public int getModifiedTimes() {
        return modifiedTimes;
    }
}

如果Person类拥有非常非常多setXxx()方法,为每一个setter方法都增加代码会不会非常繁琐。或者当我们再给Person类添加一个job属性时,这个DecorPerson是不是也要跟着修改呢。
所以Java就提供了一个代理类给我们使用java.lang.reflect.Proxy,可以通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)这个方法创建代理对象。

首先我们需要实现一个InvocationHandler对象。

public class PersonInvocationHandler implements InvocationHandler {

    private Person person;
    private int modifiedTimes;

    public PersonInvocationHandler(Person person) {
        this.person = person;
    }

    public int getModifiedTimes() {
        return modifiedTimes;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().startsWith("set")) {
            modifiedTimes++;
        }
        return method.invoke(person, args);
    }
}

紧接着我们通过Proxy类创建Person的代理对象。

public class Main {

    public static void main(String[] args) {
    // write your code here
        PersonInvocationHandler handler = new PersonInvocationHandler(new PersonImpl());

        Person person = (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                new Class[]{Person.class},
                handler);

        person.setAge(17);
        person.setName("aitsuki");
        person.setGender("male");
        person.setAge(18)

        System.out.println(handler.getModifiedTimes()); // 最终打印结果为 4
    }
}

当然,如果我们需要隐藏Person的来历,我们可以提供一个工厂用来创建Person,让用户感觉不到这其实是一个代理对象。

public class PersonFactory {

    public static Person getPerson() {
        PersonInvocationHandler handler = new PersonInvocationHandler(new PersonImpl());

        return (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                new Class[]{Person.class},
                handler);
    }
}

上面演示了使用Proxy类实现动态代理的方式。那么现在就来说说动态代理的概念:

动态代理,严格意义上来说指的并不是一种代理模式,而是一种反射的运用技术,它可以在程序运行时通过反射创建一个目标接口的代理对象,是实现代理模式的一种途径。
如果你非要说动态代理是一种设计模式,它反而更像是装饰者模式或监听器模式,因为它是监听目标对象的方法调用,实现对某个方法的扩展。从而提高程序的扩展性和灵活性,以及是JAVA实现AOP(面向切面编程)的一个重要技术。

那么相对于动态代理就出现了“静态代理”这个词了,指的就需要在源码级别中维护一个代理类,当然,“静态代理”也并不是指一种代理模式了。

3. 面向切面编程(AOP–aspect oriented programming)

既然说到了动态代理,那么再简单说说面向切面编程。

面向切面编程可以看作是对面向对象编程的扩展,“切面”,我们可以理解为“关注点”。
例如上面的Person对象,它的主要关注点就是对象的操作,比如修改名字,性别等。而它的横切关注点则是日志记录,比如记录对象的修改次数。

但是如果直接在Person的实现类中直接写入日志操作的相关代码,那么日志系统就和Person产生了耦合,需要将日志记录这部分逻辑给单独的提取出来,而java中,动态代理则是面向切面编程的一个重要的实现手段。