代理模式感觉是一个复杂的设计模式,变种很多,如:远程代理、虚拟代理、静态代理、动态代理、安全代理等;
定义###
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问;
使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理的对象可以是远程对象、开销大的对象,或需要安全控制的对象;
类图
- Subject:抽象主题角色,提供了 RealSubject和Proxy共用的接口;
- RealSubject: 被代理角色,她是真正做事情的对象,被proxy控制代理和访问的对象;
- Proxy: 代理角色,持有Subject对象引用(RealSubject);
先从最简单的 “静态代理”模式开始
1. 静态代理
举个用户登录的逻辑例子;
// 登录接口
public interface LoginInterface {
void doLogin(String name);
}
// 登录类
public class Login implements LoginInterface {
@Override
public void doLogin(String name) {
System.out.println(name + " 登录了系统...");
}
}
好的,新需求来了,我们希望知道用户登录操作花了多少时间,代理上场了
public class LoginProxy implements LoginInterface {
private LoginInterface login;
public LoginProxy(LoginInterface login) {
this.login = login;
}
@Override
public void doLogin(String name) {
System.out.println("登录之前做点事情。。。");
long startTime = System.currentTimeMillis();
// 还是原来的对象
login.doLogin(name);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
long costTime = System.currentTimeMillis() - startTime;
System.out.println("登录之后做点事情....");
System.out.println("登录花费了 " + costTime + " 毫秒");
}
}
调用代码:
// 原来的登录
def login = new Login();
login.doLogin("better");
// 使用静态代理测试代码
login = new Login();
def loginProxy = new LoginProxy(login);
loginProxy.doLogin("cc")
恩。这就是静态代理模式的例子,确实比较简单。但功能还是挺强大的,原有的设计没怎么变动;
但是总感觉这种样子特别像 包装器模式,回想包装器模式,不就是增强原有对象行为的功能吗?这真的太像了,这就是包装器模式吗?真是这样吗?
那我们弄个包装器吧;
包装器实现代码
public class LoginDecorator implements LoginInterface {
LoginInterface mLogin;
public LoginDecorator(LoginInterface mLogin) {
this.mLogin = mLogin;
}
@Override
public void doLogin(String name) {
mLogin.doLogin(name);
System.out.println("欢迎 " + name + " 使用系统");
}
}
使用上,也没什么区别,测试代码,如下:
// 包装器与代理对象对比
login = new Login();
LoginDecorator decorator = new LoginDecorator(login);
proxy = new LoginProxy(login);
decorator.doLogin("better")
proxy.doLogin("better")
嗯,来了新需求了,对应名为 admin 的用户,拒绝为其服务;
代理类修改,如下doLogin如下:
@Override
public void doLogin(String name) {
System.out.println("登录之前做点事情。。。");
if ("admin".equals(name)) {
System.out.println("你滚...");
return;
}
long startTime = System.currentTimeMillis();
login.doLogin(name);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
long costTime = System.currentTimeMillis() - startTime;
System.out.println("登录之后做点事情....");
System.out.println("登录花费了 " + costTime + " 毫秒");
}
那么装饰者呢,当然不能舍弃原来的 调用呢,那么 差别也就显而易见了
我们对比一下;
** 对比装饰模式 **
装饰:对原对象行为方法,添加了一些点缀,最终还是会调用原对象中的行为的;
代理:代理不一样,拥有被代理对象的控制权,可决定原对象的方法,是否执行;
另外从名称上,我们就要了解其含义,这2个名称定义就体现出意义了;千万不要去钻牛角尖;
2. 虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理直到真正需要一个对象的时候,才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身,当对象创建后,代理就会将请求直接委托给对象;
head first 一书中的虚拟代理的例子非常不错,加载图片,是一个耗时操作,当请求获取图片的时候,如果虚拟代码还没有创建好,就返回默认的;
公共接口:
/**swing 中的接口类**/
public interface Icon
{
void paintIcon(Component c, Graphics g, int x, int y);
int getIconWidth();
int getIconHeight();
}
具体实现类:
Swing 中的 ImageIcon.java
虚拟代理类:
package com.better.pattern.proxy.virtual_proxy;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* 代理类
*/
public class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageUrl;
Thread loadThread;
boolean isLoaded = false;
public ImageProxy(URL imageUrl) {
this.imageUrl = imageUrl;
}
@Override
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
}
return 800;
}
@Override
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
}
return 600;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("正在加载图片,请等待....", x + 300, y + 200);
if (!isLoaded) {
isLoaded = true;
loadThread = new Thread(() -> {
try {
imageIcon = new ImageIcon(imageUrl, "Image Cover");
c.repaint(); // 重绘
} catch (Exception e) {
}
});
loadThread.start();
}
}
}
}
虚拟代理总结
- 从例子中可以看出,当请求方法时,如果目标不存在,返回默认值,直到虚拟代理创建时,则将请求方法转发给目标对象;
- 虚拟代理负责创建(目标对象是在虚拟代理中创建的);
- 程序中,默认的值,是特别重要的,如果目标对象不存在,我们可由虚拟代理返回默认值,避免不必要的bug;
3. 动态代理
在Java lang包中有一些类,用来完成动态代理,动态代理就是在运行时,动态地创建代理类,实现一个或多个接口,并将方法的调用转到所指定的类;
需要了解到2个类:Proxy类与InvocationHandler接口;
Proxy为Java为我们创建好的代理类,InvocationHandler接口则是响应代理类的任何操作;
以登录的例子,继续举例,想在登录前后,加上日志:
实现了登录接口的类
public class LoginImpl implements LoginInterface {
@Override
public void login(String userName) {
System.out.println("" + userName + " 执行了登录请求");
}
}
创建日志类,实现InvocationHandler接口
public class LoggerInteceptor implements InvocationHandler {
private Object target; // 目标对象
public LoggerInteceptor(Object target) {
this.target = target;
}
private void before(Object proxy, Method method, Object[] args) {
System.out.println("执行之前: " + target.getClass().getName() + "-" + method.getName() + ",参数 " + args[0]);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(proxy, method, args);
Object o = method.invoke(target, args); // 目标执行
after(proxy, method, args);
return o;
}
private void after(Object proxy, Method method, Object[] args) {
System.out.println("执行之后:" + target.getClass().getName() + "-" + method.getName() + ", 参数 " + args[0]);
}
}
创建动态代理类,传入 InvocationHandler对象
def target = new LoginImpl();
/*Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是InvocationHandler类型,
也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,
以$开头,proxy为中,最后一个数字表示对象的标号***/
def proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new LoggerInteceptor(target))
proxy.login("better")
上面就是Java动态代理的简单例子,如果想加入其它功能,我们只需要传入新的InvocationHandler即可;就可以实现新的功能;