服务器端推送技术的目的是使浏览器尽可能第一时间获得服务器的消息。
本章节服务器 端推送方案基于:当客户端向服务端发送请求,服务端会抓住这个请求不妨,等有数据集更新的时候才返回给客户端,
当客户端接收到消息后,在向服务端发送请求,周而复始。这种方式的好处事减少了服务器的请求数量,大大减少了服务器的压力。
当然有更好的解决方式----Websocket。下文会有介绍!
基于sse(Server send event 服务端发送事件)的服务端推送和基于Servlet3.0的异步方式特性,第一种方式需要
本章节服务器 端推送方案基于:当客户端向服务端发送请求,服务端会抓住这个请求不妨,等有数据集更新的时候才返回给客户端,
当客户端接收到消息后,在向服务端发送请求,周而复始。这种方式的好处事减少了服务器的请求数量,大大减少了服务器的压力。
当然有更好的解决方式----Websocket。下文会有介绍!
基于sse(Server send event 服务端发送事件)的服务端推送和基于Servlet3.0的异步方式特性,第一种方式需要
新式浏览器的支持,第二种方式是跨浏览器的。
一.sse
1.控制器
package com.boot.springmvc.web.sse; import java.util.Random; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SseController { /** * 注意:这里使用多媒体类型为text/event-stream,这是服务器端sse的支持,演示每5秒向浏览器推送随机消息。 * @return */ @RequestMapping(value = "/push", produces = "text/event-stream") //1 public @ResponseBody String push() { Random r = new Random(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return "data:Testing 1,2,3" + r.nextInt() + "\n\n"; } }
2.添加页面跳转
//演示sse registry.addViewController("/sse").setViewName("sse");
3.jsp页面中添加监听
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>SSE Demo</title> </head> <body> <div id="msgFrompPush"></div> <script type="text/javascript" src="<c:url value="assets/js/jquery.js" />"></script> <script type="text/javascript"> if (!!window.EventSource) { //1 var source = new EventSource('push'); s = ''; source.addEventListener('message', function(e) {//2 s += e.data + "<br/>"; $("#msgFrompPush").html(s); }); source.addEventListener('open', function(e) { console.log("连接打开."); }, false); source.addEventListener('error', function(e) { if (e.readyState == EventSource.CLOSED) { console.log("连接关闭"); } else { console.log(e.readyState); } }, false); } else { console.log("你的浏览器不支持SSE"); } </script> </body> </html>
备注 :EventSource对象只用新式的浏览器才有(chrome、Firefox等),EventSource是sse的客户端;
添加sse客户端监听,在此获得服务器端推送的消息。
二.Servlet3.0+异步方法处理
1.开启Servlet的异步支持
package com.boot.springmvc; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration.Dynamic; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class WebInitializer implements WebApplicationInitializer {// 1 @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(MyMvcConfig.class); ctx.setServletContext(servletContext); // 2 Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); // 3 servlet.addMapping("/"); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true);// 1开启异步 方法的支持 } }
2.控制器配置
package com.boot.springmvc.web.sse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.async.DeferredResult; import com.boot.springmvc.service.PushService; @Controller public class AysncController { @Autowired PushService pushService; //1定时任务类,定时更新DeferredResult<String>结果 @RequestMapping("/defer") @ResponseBody public DeferredResult<String> deferredCall() { //2返回给客户端数据 return pushService.getAsyncUpdate(); } }
备注:异步任务的实现是通过控制器从另外一个线程返回一个DeferredResult<String>,这里的返回结果是从异步任务中获取。
3.定时任务--PushService(调用业务)
package com.boot.springmvc.service; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.context.request.async.DeferredResult; @Service public class PushService { private DeferredResult<String> deferredResult; //1定时生产deferredResult结果 public DeferredResult<String> getAsyncUpdate() { deferredResult = new DeferredResult<String>();//初始化后对象不为null return deferredResult; } @Scheduled(fixedDelay = 5000)//每5秒设值 public void refresh() { if (deferredResult != null) { deferredResult.setResult(new Long(System.currentTimeMillis()).toString()); } } }
4.页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>servlet async support</title> </head> <body> <div> <ur id="async"></ur> </div> <script type="text/javascript" src="/assets/js/jquery.js"></script> <script type="text/javascript"> deferred();//1 function deferred(){ $.get('defer',function(data){ console.log(data); //2 $("#async").append("<li>"+data+"</li>"); deferred(); //3 }); } </script> </body> </html>
5.viewResolver和开启定时任务配置
package com.boot.springmvc; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; import com.boot.springmvc.interceptor.DemoInterceptor; import com.boot.springmvc.messageconvert.MymessageConverter; /** * @Bean public InternalResourceViewResolver viewResolver(); * 注入InternalResourceViewResolver类: * 说明:springmvc下有一个接口叫ViewResolver,(我们的viewResolver都实现该接口),实现这个接口要重写 * resolverName(),这个方法的返回值接口View,而view的职责就是使用model、request、response对象,并 * 渲染视图(不一定是html、可能是json、xml、pdf等)给浏览器 。 * */ @Configuration @EnableWebMvc // 1开启默认配置 @EnableScheduling @ComponentScan("com.boot.springmvc") public class MyMvcConfig extends WebMvcConfigurerAdapter {// 2 // viewResolver @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); // viewResolver.setPrefix("/WEB-INF/classes/views/");//打war后默认编译的路径 viewResolver.setPrefix("/WEB-INF/views/");// 使用tomcat7:run插件后要放的位置 viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; } // 静态资源映射 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/"); }// 3 // 拦截器 @Bean // 1 public DemoInterceptor demoInterceptor() { return new DemoInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) {// 2 registry.addInterceptor(demoInterceptor()); } // viewController @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index1").setViewName("/index1"); // 跳转文件上传页面 registry.addViewController("/toupload").setViewName("/upload"); // 演示HttpMessageConverter registry.addViewController("/converter").setViewName("/converter"); //演示sse //registry.addViewController("/sse").setViewName("sse"); //服务器消息推送,演示异步处理 registry.addViewController("/async").setViewName("async"); } // 文件上传最大限制 @Bean public MultipartResolver multipartResolver() { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(200); return multipartResolver; } // 不忽略"."后面的参数。 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); } /** * 配置自定义的HttpMessageConverter *注: *1.configureMessageConverters:重载会覆盖掉spring mvc默认注册的 多个HttpMessageConverter。 *2.extendsMessageConverter:仅添加一个自定义 的HttpMessageConverter,不覆盖默认注册 的HttpMessageConverter. **/ //使用extendsMessageConverter 添加一个自定义的HttpMessageConverter @Bean public MymessageConverter converter(){ return new MymessageConverter(); } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(converter()); } }