代理模式

代理模式即为其他对象提供一种代理,以控制对这个对象的访问。简单来说,代理模式其实就是我们在本应直接访问的对象上包装了一层,提供给用户访问的都是代理类,实际上代理类可以和委托类实现相同的接口,用户调用代理类里的方法,然后代理类经过一定处理并调用委托类的方法。

代理模式通常用于拦截方法调用,更进一步就是实现面向切面编程(AOP)。

代理模式示例

通过上述分析,实际上代理模式其实非常简单,下面我们编写一个使用代理模式进行方法调用拦截的例子。

UserLogin.java

public interface UserLogin {
    public void login();
}

User.java

public class User implements UserLogin {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public void login() {
        LoginService loginService = new LoginService();
        loginService.doLogin();
    }
}

UserProxy.java

public class UserProxy implements UserLogin {
    private UserLogin user;

    public UserProxy(UserLogin user) {
        this.user = user;
    }

    @Override
    public void login() {
        if (!((User) user).getPassword().equals("123")) {
            System.out.println("密码错误");
        } else {
            System.out.println("密码正确");
            user.login();
        }
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        UserLogin userLogin = new UserProxy(new User("Tom", "123"));
        userLogin.login();
    }
}

代码运行结果如下图。

从运行结果可知拦截确实起作用了,其实代码原理非常简单,就是代理类对委托类进行了一层包装,用户调用代理类的方法,代理类经过一些处理后,再调用委托类的方法(或者根本就是拦截下来不调用)。

代理模式和装饰模式的区别

编写上面代理模式代码时我们可能有种似曾相识的感觉,实际上,上面代理模式实现和装饰模式的代码结构是一样的。但是我们一定要分清楚,代理模式和装饰模式解决的不是同一种问题。装饰模式提供多个装饰器给用户自由组合,这些装饰器能够修饰原来的操作,而代理模式提供一个代理类,可以对委托类方法调用进行拦截。

基于反射的动态代理

Java语言提供了强大的反射机制,如果使用Java语言进行开发,其实我们可以使用Java提供的动态代理工具类java.lang.reflect.Proxy实现更灵活的代理写法。相应的,前面基于接口的代理模式可以称为静态代理

那么静态代理有什么不足呢?实际上,静态代理还有以下几个问题:

  1. 一个静态代理类只服务于一种接口
  2. 接口类一旦修改,委托类和代理类都要修改,这比较麻烦

这里我们使用Java语言编写一个最简单的动态代理。

SayHello.java

public interface SayHello {
    void sayHello();
}

ProxyHandler.java

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

public class ProxyHandler implements InvocationHandler {
    private Object target;

    public ProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理方法被调用");
        return method.invoke(target, args);
    }
}

Student.java

public class Student implements SayHello {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, " + this.name);
    }
}

Main.java

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

public class Main {
    public static void main(String[] args) {
        Student tom = new Student("Tom");
        InvocationHandler ih = new ProxyHandler(tom);
        Class[] interfaces = {SayHello.class};
        Object proxy = Proxy.newProxyInstance(SayHello.class.getClassLoader(), interfaces, ih);
        ((SayHello) proxy).sayHello();
    }
}

上述代码中,我们通过InvocationHandler使用反射API对Java方法调用进行拦截,然后调用Proxy.newProxyInstance()传入需要使用的接口数组并返回代理对象。使用代理对象,后续就像静态代理一样了。

我们可以看到,动态代理将上文提到的静态代理的缺点都克服了。实际上,不是一遇到代理模式就要用动态代理,反射API是一组比较难用好的接口,大量使用可能程序会变得难懂(至少是对初级程序员来说)。所以一般程序建议先考虑静态代理模式,对于面向切面编程,通常有Spring这种框架帮我们实现动态代理。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap