主动的对象
不同的调用方式
大多数的网页,都是用户点击某个链接,然后通过浏览器向后台发送请求,最后服务器响应请求,输出数据,并将数据展示到页面上,例如,搜索后点击某个结果链接。
整个过程可以用下面这张图表示。

这种基于请求-响应
的线性过程就是典型的同步调用
——一件事接着一件事发生,直到所有事件全部按顺序完成。
但还有一类应用和这种同步
过程有些不同。例如,在网页上聊天。

从表面上看,它好像也是一种同步
的过程:输入聊天信息,然后点击发送,然后就看到了页面显示的输入内容。
但不要忘记了,聊天通常是个两人及两人以上的多个人参与其中,如果只是显示自己输入的内容当然是同步
的。
但程序怎么能确定对方什么时候会发过来消息呢?
难道对方不输入消息,网页就一直卡在那吗?——并不会。
这种基于网页的聊天应用是通过WebSocket技术实现的。
总体上来说,它就是一个异步调用
的典范,用下面这张图就可以看出它的调用过程和同步
之间的区别。

最主要的区别就在于当WebSocket将消息发送给服务端之后,它不等待服务端的响应就返回了。
如果后面有消息到来,它会从服务端接收,然后在浏览器中显示。
Active Object模式
就像调用过程分为同步
和异步
那样,对象也分为被动对象(Passive Object)
和主动对象(Active Object)
。
所谓被动对象
是只能通过其他对象或方法调用才能执行方法的对象,而主动对象
则是可以自己触发动作或改变状态的对象,它内部封装有独立的线程。
主动对象
包含六大组件。
Proxy
:从名字就可以知道,这是一个代理对象,用于向外部暴露异步方法,它是主动对象
执行的入口,它将会返回一个Future
对象给调用者。Future
:因为是异步调用,所以调用者只能通过Future
获取执行结果。MethodRequest
:所有发给Proxy
的请求都被封装成MethodRequest
对象。ActivationQueue
:这是一个任务缓冲区,类似于线程池,如果请求的任务太多,就会把它们保存到队列中去,或者执行完当前任务后,再从缓冲区中取出并执行待计算的任务。Scheduler
:任务调度器,由它来决定任何何时执行以及如何执行。Servant
:它最终实现Proxy
中所暴露的异步方法,以及对应的任务,然后将结果绑定到Future
上。

长短链接转换
在大多数的UGC网站中,都有长短网址的转换功能。例如,它会把https://xxx.com/abc.html?xxx=xxx&yyy=yyy&zzz=zzz
转换为https://t.cn/cRVXLS8P
这样的短网址。

下面是通过Active Object
模式进行长短网址转换的完整代码。
/**
* 访问信息
*
*/
public class AccessInfo {
/**
* 短网址
*
*/
private String shortUrl;
public String getShortUrl() {
return shortUrl;
}
public void setShortUrl(String shortUrl) {
this.shortUrl = shortUrl;
}
@Override
public String toString() {
return "{shortUrl='" + shortUrl + "'}";
}
}
/**
* 请求持久化
*
*/
public interface RequestPersistence extends Closeable {
/**
* 访问文章的请求
*
*/
void store(AccessInfo accessInfo);
}
/**
* 数据库持久化存储
*
*/
public class DatabasePersistence implements RequestPersistence {
@Override
public void store(AccessInfo accessInfo) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("写请求:" + accessInfo + " 到数据库完成");
}
@Override
public void close() throws IOException {}
}
/**
* 主动模式的代理类Proxy,持久化访问请求
*
*/
public class AsyncRequestPersistence implements RequestPersistence {
/**
* 单例对象
*
*/
private static final AsyncRequestPersistence INSTANCE = new AsyncRequestPersistence();
/**
* 获取实例对象
*
*/
public static AsyncRequestPersistence getInstance() {
return INSTANCE;
}
/**
* 任务调度线程池,对应Scheduler
*
*/
private final ThreadPoolExecutor scheduler;
/**
* 模式角色,对应Servant
*
*/
private final DatabasePersistence delegate = new DatabasePersistence();
/**
* 初始化Scheduler
*
*/
private AsyncRequestPersistence() {
this.scheduler = new ThreadPoolExecutor(1,
3,
60 * 60,
TimeUnit.SECONDS,
// 模式角色的:ActionObject.Scheduler
new ArrayBlockingQueue<>(200),
r -> new Thread(r, "AsyncRequestPersistence"));
// 线程池拒绝策略
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
}
@Override
public void store(AccessInfo accessInfo) {
// 利用Callable封装成MethodRequest后入队
Callable<Boolean> methodRequest = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.out.println("请求出队,通过Servant来执行");
delegate.store(accessInfo);
return Boolean.TRUE;
}
};
System.out.println("封装MethodRequest请求,提交到Scheduler队列");
// 将MethodRequest放入Scheduler队列
scheduler.submit(methodRequest);
}
@Override
public void close() throws IOException {
scheduler.shutdown();
}
}
/**
* 将短网址转为长网址
* 如果转换失败了,将请求的来源进行进行缓存
*
*/
public class AccessHandler {
/**
* 假设拦截到一个请求某个文章的请求
*
*/
public String intercept(AccessInfo accessInfo) {
// 将短网址转为长网址
String targetUrl = null;
try {
targetUrl = convertShortNumber(accessInfo.getShortUrl());
} catch (Exception e) {
System.out.println("短网址:" + accessInfo.getShortUrl() + " 转换失败,调用proxy代理类的异步方法持久化数据");
// 通过主动模式的代理类来持久化请求,对应Proxy
AsyncRequestPersistence.getInstance().store(accessInfo);
}
return targetUrl;
}
/**
* 转换方法
*
*/
private String convertShortNumber(String shortUrl) {
// 查询数据库中短网址对应的长网址
throw new NullPointerException("转短网址失败");
}
public static void main(String[] args) {
AccessHandler accessHandler = new AccessHandler();
AccessInfo accessInfo = new AccessInfo();
accessInfo.setShortUrl("https://baidu.com");
String result = accessHandler.intercept(accessInfo);
System.out.println(result);
}
}
感谢支持
更多内容,请移步《超级个体》。