假设咱们如今要实现这样一个功能:浏览器要实时展现服务端计算出来的数据。
一种可能的实现是:浏览器频繁(例如定时1秒)向服务端发起请求以得到服务端数据。但定时请求并不能“实时”反应服务端的数据变化状况。
若定时周期为S,则数据延迟周期最大即为S。若想缩短数据延迟周期,则应使S尽可能小,而S越小,浏览器向服务端发起请求的频率越高,又形成网络握手次数越多,影响了效率。所以,此场景应使用服务端实时推送技术。javascript
这里说是推送,其实仍是基于请求-响应机制,只不过发起的请求会在服务端挂起,直到请求超时或服务端有数据推送时才会作出响应,响应的时机彻底由服务端控制。因此,总体效果看起来就像是服务端真的在“实时推送”同样。java
能够利用SpringMVC的DeferredResult
来实现异步长链接的服务端实时推送。web
@RequestMapping("/call") @ResponseBody public DeferredResult<Object> call() { // 泛型Object表示返回结果的类型 DeferredResult<Object> response = new DeferredResult<Object>( 10000, // 请求的超时时间 null); // 超时后响应的结果 response.onCompletion(new Runnable() { @Override public void run() { // 请求处理完成后所作的一些工做 } }); // 设置响应结果 // 调用此方法时当即向浏览器发出响应;未调用时请求被挂起 response.setResult(new Object()); return response; }
<servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> <!-- 添加异步支持 --> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
确保你使用的是servlet 3+spring
var loopCall = function() { $.get("${yourContext}/call", function(r) { loopCall(); console.log("call: "); console.log(r); }); }; loopCall(); // 循环发起异步请求
DeferredResult.setResult()
,请求被唤醒,返回结果接下来,看一个更接近真实场景的示例:浏览器向A系统发起异步长链接请求,等到B系统给A推送数据时,A会马上向浏览器响应结果。浏览器
public interface DeferredData { String getId(); // 惟一标识 }
public interface DeferredResultHolder<DeferredData> { DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult); void add(String key, DeferredResult<DeferredData> deferredResult); DeferredResult<DeferredData> get(String key); void remove(String key); void handleDeferredData(DeferredData deferredData); }
public class SimpleDeferredResultHolder implements DeferredResultHolder<DeferredData> { private Map<String, DeferredResult<DeferredData>> deferredResults = new ConcurrentHashMap<String, DeferredResult<DeferredData>>(); public DeferredResult<DeferredData> newDeferredResult(String key) { return newDeferredResult(key, 30 * 1000L, null); } public DeferredResult<DeferredData> newDeferredResult(String key, long timeout) { return newDeferredResult(key, timeout, null); } public DeferredResult<DeferredData> newDeferredResult(String key, Object timeoutResult) { return newDeferredResult(key, 30 * 1000L, timeoutResult); } @Override public DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult) { DeferredResult<DeferredData> deferredResult = new DeferredResult<DeferredData>(timeout, timeoutResult); add(key, deferredResult); deferredResult.onCompletion(new Runnable() { @Override public void run() { remove(key); } }); return deferredResult; } @Override public void add(String key, DeferredResult<DeferredData> deferredResult) { deferredResults.put(key, deferredResult); } @Override public DeferredResult<DeferredData> get(String key) { return deferredResults.get(key); } @Override public void remove(String key) { deferredResults.remove(key); } @Override public void handleDeferredData(DeferredData deferredData) { String key = deferredData.getId(); DeferredResult<DeferredData> deferredResult = get(key); if (deferredResult != null) { deferredResult.setResult(deferredData); } } }
用mq或dubbo等技术发送均可以,这里用rabbitmq作示例。
若是消费的消费者作了集群部署,则只能使用mq的topic机制分发,推送消息到A的全部部署节点,若使用dubbo则只能调用其中一个节点。所以,这里最好仍是使用mq。spring-mvc
public interface DeferredDataProducer { void sendDeferredData(DeferredData deferredData); }
public interface DeferredDataConsumer { void consume(DeferredData deferredData) throws Exception; }
public class DeferredDataMqProducer implements DeferredDataProducer { @Autowired private AmqpTemplate amqpTemplate; private String exchange; private String routingKey = ""; public void setExchange(String exchange) { this.exchange = exchange; } public void setRoutingKey(String routingKey) { this.routingKey = routingKey; } @Override public void sendDeferredData(DeferredData deferredData) { amqpTemplate.convertAndSend(exchange, routingKey, deferredData); } }
public class DeferredDataMqConsumer implements DeferredDataConsumer { private DeferredResultHolder<DeferredNotification> deferredResultHolder; public void setDeferredResultHolder(DeferredResultHolder<DeferredNotification> deferredResultHolder) { this.deferredResultHolder = deferredResultHolder; } @Override public void consume(DeferredData deferredData) throws Exception; deferredResultHolder.handleDeferredData(deferredData); } }
public class Notification { private String to; // 接收者 private String content; // 内容 // 省略getter和setter方法 }
public DeferredNotification extends Notification implements DeferredResponse { @Override public String getId() { return getTo(); } }
public class NotificationProducer implements DeferredResponseProducer<DeferredNotification> { @Autowired private AmqpTemplate amqpTemplate; @Override public void sendMessage(DeferredNotification notification, String exchange) { amqpTemplate.convertAndSend(exchange, "", notification); } }
@Autowired private DeferredDataProducer<DeferredNotification> deferredDataProducer; public void test() { DeferredNotification n = new DeferredNotification(); n.setTo("abc"); // 会员 n.setContent("哈哈,我是从admin推送过来的"); deferredDataProducer.sendDeferredData(n); }
var loopCall = function() { $.get("${yourContext}/call", function(r) { loopCall(); console.log("call: "); console.log(r); }); }; loopCall(); // 循环发起异步请求
@RequestMapping @Controller public class CallController { @Autowired private DeferredResultHolder deferredResultHolder; @RequestMapping("/call") @ResponseBody public DeferredResult<DeferredData> call() { String id = "abc"; return deferredResultHolder.newDeferredResult(id, 10 * 1000L, null); } }
代码就是这些了,好好理一下思路,实现你本身的功能吧!网络