如何使用事件更新 Thymeleaf

Posted

技术标签:

【中文标题】如何使用事件更新 Thymeleaf【英文标题】:How to update Thymeleaf with events 【发布时间】:2018-11-14 20:39:21 【问题描述】:

我配置了一个 Eventbus,它将使用监视器事件更新 Map。因此,对于每个名称,地图将始终包含最新的监视器事件。作为用户,您可以访问 URL“/overview”查看最近的事件,该 URL 显示一个基本的 html 页面。

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

@Controller
public class OverviewController 
    private final Map<String, MonitorResponse> monitorEvents;

    @Autowired
    public OverviewController(EventBus eventBus) 
       monitorEvents = new HashMap<>();
       eventBus.register(this);
    

    @RequestMapping("/overview")
    public String overview(Model model) 
       model.addAttribute("monitorResponseMap", monitorEvents);
       return "overview";
    

    @Subscribe
    public void subscribe(MonitorEvent monitorEvent) 
        monitorEvents.put(monitorEvent.getName(), monitorEvent.getMonitorResponse());
    

HTML:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <link rel="stylesheet" href="/bootstrap-4.0.0/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="/css/styles.css"/>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-sm-"
             th:each="monitorevent : $monitorResponseMap"
             style="margin: 10px;padding:20px;border-radius: 10px;"
             th:classappend="$monitorevent.value.getMonitorStatus().name()">
            <div class="card-title" style="font-weight: bolder" th:text="$monitorevent.key"></div>
            <p th:text="$monitorevent.value.getMessage()"></p>
        </div>
    </div>
</div>

<script src="/jquery-3.3.1.min.js"></script>
<script src="/bootstrap-4.0.0/js/bootstrap.min.js"></script>
</body>
</html>

当有新事件时,当调用订阅方法时,动态更新 UI 的最简单方法是什么? 现在我对页面进行自动 javascript 刷新(使用标记 meta(http-equiv=refresh),但这对用户来说很烦人。使用 websockets?Ajax?

【问题讨论】:

服务器端事件可能是更好的选择,因为它是单向的(服务器到客户端)并且有一些开销。这里有一些例子github.com/aliakh/demo-spring-sse 【参考方案1】:

感谢Venu Duggireddy,让我认识了SSE,跟着https://github.com/aliakh/demo-spring-sse的demo学习

这很容易解决我的问题:

SSE 控制器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
public class SseController 

    private final SseService sseService;

    @Autowired
    public SseController(SseService sseService) 
        this.sseService = sseService;
    

    @RequestMapping(path = "/sse/infinite", method = RequestMethod.GET)
    public SseEmitter getInfiniteMessages() 
        return sseService.getInfiniteMessages();
    

当我想更新前端时,我可以简单地调用 SseService,用 notifyListeners :

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Component
public class SseService 
    private final static Logger LOGGER = LoggerFactory.getLogger(SseService.class);

    private final SseEmitter emitter;

    @Autowired
    public SseService() 
        emitter = new SseEmitter();
    

    SseEmitter getInfiniteMessages() 
        return emitter;
    

    public void notifyListeners(MonitorEvent monitorEvent) 
        try 
            emitter.send(monitorEvent);
         catch (IOException e) 
            LOGGER.error("Sse message " + monitorEvent + "not send", e);
        
    

在 HTML 中:

<script type="application/javascript">
    if (!!window.EventSource) 
        var eventSource = new EventSource("/sse/infinite");

        eventSource.onmessage = function (e) 
            var message = JSON.parse(e.data);
            console.log(message);
            //updating my HTML here
        ;

        eventSource.onerror = function (e) 
            if (e.readyState === EventSource.CONNECTING) 
                console.log('event: CONNECTING');
             else if (e.readyState === EventSource.OPEN) 
                console.log('event: OPEN');
             else if (e.readyState === EventSource.CLOSING) 
                console.log('event: CLOSING');
             else if (e.readyState === EventSource.CLOSED) 
                console.log('event: CLOSED');
            
        ;
     else 
        alert('The browser does not support Server-Sent Events');
    
</script>

【讨论】:

以上是关于如何使用事件更新 Thymeleaf的主要内容,如果未能解决你的问题,请参考以下文章

Spring Webflux 服务器发送事件 Thymeleaf

Thymeleaf 表更新而无需重新加载页面

@ModelAttribute 使用 thymeleaf 返回空值

如何使用 Thymeleaf 写入响应标头

如何使用 SpEL 处理 Thymeleaf 中的本地化消息

如何使用模板Thymeleaf来自动生成java代码