1. 程式人生 > >15-SpringBoot——Spring MVC基礎-服務端推送技術

15-SpringBoot——Spring MVC基礎-服務端推送技術

Spring MVC基礎-服務端推送技術

【服務端推送技術】

服務端推送技術在我們日常開發中較為常用,可能早期很多人的解決方案是使用Ajax 向伺服器輪詢訊息,使瀏覽器儘可能第一時間獲得服務端的訊息,因為這種方式的輪詢頻率不好控制,所以大大增加了服務端的壓力。

本案例所有的伺服器端推送的方案都是基於:當客戶端向服務端傳送請求,服務端會抓住這個請求不放,等有資料更新的時候才返回給客戶端,當客戶端接收到訊息後,再向服務端傳送請求,周而復始。這種方式的好處是減少了伺服器的請求數量,大大減少了伺服器的壓力。

除了伺服器端推送技術以外,還有一個另外的雙向通訊的技術——WebSocket,本示例將提供基於SSE (Server Send Event 服務端傳送事件)的伺服器端推送和基於Servlet 3.0+的非同步方法特性,其中第一種方式需要新式瀏覽器的支援,第二種方式是跨瀏覽器的。

【程式碼實現】

package com.example.spring.framework.serverpush.controller;

import com.example.spring.framework.serverpush.service.PushService;
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; /** * 非同步任務的實現是通過控制器從另外一個執行緒返回一個DeferredResult,這裡的 * DeferredResult 是從pushService 中獲得的。 * Author: 王俊超 * Date: 2017-07-14 07:44 * All Rights Reserved !!! */
@Controller public class AsyncController { @Autowired PushService pushService; // ①定時任務,定時更新DeferredResulto @RequestMapping("/defer") @ResponseBody public DeferredResult<String> deferredCall() { return pushService.getAsyncUpdate(); // ②返回給客戶端DeferredResulto } }
package com.example.spring.framework.serverpush.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Random;

/**
 * Author: 王俊超
 * Date: 2017-07-14 07:34
 * All Rights Reserved !!!
 */
@Controller
public class SeeController {
    /**
     * ①注意,這裡使用輸出的媒體型別為text/event-stream ,這是伺服器端SSE 的支援,
     * 本例演示每5 秒鐘向瀏覽器推送隨機訊息。
     */
    @RequestMapping(value = "/push", produces = "text/event-stream;charset=UTF-8")
    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";
    }
}
package com.example.spring.framework.serverpush.service;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

/**
 * Author: 王俊超
 * Date: 2017-07-14 07:45
 * All Rights Reserved !!!
 */
@Service
public class PushService {
    private DeferredResult<String> deferredResult;

    public DeferredResult<String> getAsyncUpdate() {
        deferredResult = new DeferredResult<>();
        return deferredResult;
    }

    /**
     * ①在PushService 裡產生DeferredResult ~合控制器使用,通過@Scheduled
     * 註解的方法定時更新DeferredResulto
     */
    @Scheduled(fixedDelay = 5000)
    public void refresh() {
        if (deferredResult != null) {
            deferredResult.setResult(Long.toString(System.currentTimeMillis()));
        }
    }
}
package com.example.spring.framework.serverpush.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
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;

/**
 * Author: 王俊超
 * Date: 2017-07-12 07:22
 * All Rights Reserved !!!
 */
@Configuration
@EnableWebMvc
@EnableScheduling
@ComponentScan("com.example.spring.framework.serverpush")
public class MyMvcConfig extends WebMvcConfigurerAdapter{
    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/classes/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewClass(JstlView.class);

        return viewResolver;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
        super.addResourceHandlers(registry);
    }


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/see").setViewName("/see");
        registry.addViewController("/async").setViewName("/async");
    }
}
package com.example.spring.framework.serverpush;


import com.example.spring.framework.serverpush.config.MyMvcConfig;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

/**
 * Author: 王俊超
 * Date: 2017-07-11 21:51
 * All Rights Reserved !!!
 */
public class WebInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(MyMvcConfig.class);
        ctx.setServletContext(servletContext);

        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher",
                new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);

        // 開啟非同步方法支援
        servlet.setAsyncSupported(true);
    }
}

async.jsp

<%@ 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>


<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
            deferred(); //3
        });
    }


</script>
</body>
</html>

see.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">


    // ①EventSource 物件只有新式的瀏覽器才有( Chrome 、Firefox )等,EventSource 是SSE的客戶端;
    if (!!window.EventSource) { //1
        var source = new EventSource('push');
        s = '';

        // ②新增SSE 客戶端監呀,在此獲得伺服器端推送的訊息。
        source.addEventListener('message', function (e) {
            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>