代理模式即为其他对象提供一种代理,以控制对这个对象的访问。简单来说,代理模式其实就是我们在本应直接访问的对象上包装了一层,提供给用户访问的都是代理类,实际上代理类可以和委托类实现相同的接口,用户调用代理类里的方法,然后代理类经过一定处理并调用委托类的方法。
代理模式通常用于拦截方法调用,更进一步就是实现面向切面编程(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
实现更灵活的代理写法。相应的,前面基于接口的代理模式可以称为静态代理。
那么静态代理有什么不足呢?实际上,静态代理还有以下几个问题:
这里我们使用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这种框架帮我们实现动态代理。