知識庫 / Spring / Spring Web RSS 訂閱

React 和 Nashorn 構建的可變異構應用

Spring Web
HongKong
5
01:18 PM · Dec 06 ,2025

1. 概述

本教程將幫助您理解什麼是異構應用。我們還將討論 Nashorn,即與 Java 捆綁的 JavaScript 引擎。

此外,我們還將探討如何使用 Nashorn 以及像 React 這樣的前端庫來創建異構應用。

2. 歷史簡述

傳統上,客户端和服務器應用程序都是以高度依賴服務器端的方式編寫的。可以把 PHP 想象成一個腳本引擎,它主要生成靜態 HTML 並讓 Web 瀏覽器進行渲染。

Netscape 在九十年代中期帶來了 支持 JavaScript 的瀏覽器功能。 這使得一部分處理從服務器端轉移到客户端瀏覽器。 開發者們長期以來一直在與 Web 瀏覽器中 JavaScript 支持相關的各種問題作鬥爭。

隨着對更快、更具交互性用户體驗需求的增長,界限被進一步推向。 最早改變遊戲規則的框架之一是 jQuery。 它提供了許多用户友好的函數以及對 AJAX 的顯著增強支持。

很快,許多前端開發框架開始出現,極大地改善了開發人員的體驗。 從 Google 的 AngularJS 到 Facebook 的 React,以及後來的 Vue,它們開始吸引開發人員的注意力。

憑藉現代瀏覽器支持、強大的框架和必要的工具,趨勢正在轉向客户端

對於日益快速的移動設備上提供沉浸式體驗,需要更多的客户端處理。

3. 什麼是異構應用?

我們之前已經瞭解了前端框架如何幫助我們開發一個用户界面完全在客户端渲染的 Web 應用程序。

然而,也可能使用同一個框架在服務器端,並生成相同的用户界面。

現在,我們不必侷限於客户端或服務器端解決方案。更好的方法是擁有一個解決方案,其中客户端和服務器都可以處理相同的前端代碼,並生成相同的用户界面。

這種方法具有優勢,我們稍後會討論。

 

此類 Web 應用程序被稱為異構或通用。目前,客户端語言主要為 JavaScript。因此,為了使異構應用程序正常工作,我們必須在服務器端也使用 JavaScript。

Node.js 是構建服務器端渲染應用程序的最常見選擇。

4. 什麼是 Nashorn?

So, Nashorn 在哪裏,我們為什麼使用它? Nashorn 是 一個與 Java 默認打包的 JavaScript 引擎。 因此,如果我們的 Web 應用程序後端已經使用 Java 構建,並且我們想要構建一個異構應用程序,Nashorn 相當實用!

Nashorn 作為 Java 8 的一部分發布。 它的主要目的是允許在 Java 中嵌入 JavaScript 應用程序。

Nashorn 將 JavaScript 編譯為 Java 字節碼,然後傳遞給 JVM 執行。 與較早的引擎 Rhino 相比,這提供了更好的性能。

5. 創建異構應用

我們已經充分了解了上下文。在此,我們的應用程序將顯示一個斐波那契數列,並提供一個按鈕以生成和顯示該數列中的下一個數字。現在,讓我們創建一個簡單的異構應用,包含後端和前端:

  • 前端:一個基於 React.js 的簡單前端
  • 後端:一個使用 Nashorn 處理 JavaScript 的簡單 Spring Boot 後端

6. 應用前端

我們將使用 React.js 來構建我們的前端。React 是一個流行的 JavaScript 庫,用於構建單頁應用程序。它 幫助我們分解複雜的用户界面 為具有可選狀態和單向數據綁定的一組分層組件。

React 會解析這個層次結構,並創建一個內存中的數據結構,稱為虛擬 DOM。這有助於 React 找到不同狀態之間的差異,並對瀏覽器 DOM 進行最小的修改。

6.1. React 組件

讓我們創建一個第一個 React 組件:

var App = React.createClass({displayName: "App",
    handleSubmit: function() {
    	var last = this.state.data[this.state.data.length-1];
    	var secondLast = this.state.data[this.state.data.length-2];
        $.ajax({
            url: '/next/'+last+'/'+secondLast,
            dataType: 'text',
            success: function(msg) {
                var series = this.state.data;
                series.push(msg);
                this.setState({data: series});
            }.bind(this),
            error: function(xhr, status, err) {
                console.error('/next', status, err.toString());
            }.bind(this)
        });
    },
    componentDidMount: function() {
    	this.setState({data: this.props.data});
    },	
    getInitialState: function() {
        return {data: []};
    },	
    render: function() {
        return (
            React.createElement("div", {className: "app"},
            	React.createElement("h2", null, "Fibonacci Generator"),
            	React.createElement("h2", null, this.state.data.toString()),
                React.createElement("input", {type: "submit", value: "Next", onClick: this.handleSubmit})
            )     
        );
    }
});

現在,讓我們來理解上述代碼的作用:

  • 首先,我們定義了一個名為“App”的React類組件
  • 該組件中最重要的是“render”函數,它負責生成用户界面
  • 我們提供了組件樣式,組件可以利用它
  • 我們利用組件狀態來存儲和顯示序列
  • 雖然狀態初始化為空列表,但當組件掛載時,它會從組件接收到的屬性中獲取數據
  • 點擊“Add”按鈕時,會發出一個jQuery調用到REST服務
  • 該調用會獲取序列中的下一個數字並將它附加到組件的狀態
  • 組件的狀態的變化會自動重新渲染組件

6.2. 使用 React 組件

React 會查找 HTML 頁面中包含一個名為“div”的元素來錨定其內容。我們只需要提供一個包含該“div”元素的 HTML 頁面並加載 JS 文件即可。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello React</title>
    <script type="text/javascript" src="js/react.js"></script>
    <script type="text/javascript" src="js/react-dom.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
    ReactDOM.render(
        React.createElement(App, {data: [0,1,1]}),
        document.getElementById("root")
    );
</script>
</body>
</html>

那麼,我們已經完成了以下工作:

  • 我們導入了所需的 JS 庫,react、react-dom 和 jQuery
  • 之後,我們定義了一個名為“root”的“div”元素
  • 我們還導入了包含我們 React 組件的 JS 文件
  • 接下來,我們使用一些種子數據(前三個斐波那契數列)調用了 React 組件“App”

7. 應用後端

現在,讓我們看看如何為我們的應用程序創建一個合適的後端。我們已經決定使用 Spring Boot 結合 Spring Web 來構建這個應用程序。更重要的是,我們決定使用 Nashorn 處理我們上一部分開發的基於 JavaScript 的前端

7.1. Maven 依賴

為了我們的簡單應用程序,我們將使用 JSP 與 Spring MVC 結合使用,因此我們需要在 POM 中添加一些依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

第一個是用於 Web 應用程序的標準 Spring Boot 依賴項 。第二個是用於編譯 JSPs 所需的

7.2. Web 控制器

現在,讓我們創建我們的 Web 控制器,它將處理我們的 JavaScript 文件並使用 JSP 返回 HTML:

@Controller
public class MyWebController {
    @RequestMapping("/")
    public String index(Map<String, Object> model) throws Exception {
        ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
        nashorn.eval(new FileReader("static/js/react.js"));
        nashorn.eval(new FileReader("static/js/react-dom-server.js"));
        nashorn.eval(new FileReader("static/app.js"));
        Object html = nashorn.eval(
          "ReactDOMServer.renderToString(" + 
            "React.createElement(App, {data: [0,1,1]})" + 
          ");");
        model.put("content", String.valueOf(html));
        return "index";
    }
}

那麼,這裏究竟發生了什麼呢?

  • 我們從 ScriptEngineManager 中獲取一個類型為 Nashorn 的 ScriptEngine 實例
  • 然後,我們加載了與 React、react.js 和 react-dom-server.js 相關的庫
  • 我們還加載了包含我們 React 組件 “App” 的 JS 文件
  • 最後,我們評估一個 JS 片段,創建了帶有組件 “App” 和一些種子數據 React 元素
  • 這為我們提供了 React 的輸出,作為 Object 類型的 HTML 片段
  • 我們將這個 HTML 片段作為數據傳遞給相關的視圖——JSP

7.3. JSP

現在,我們如何處理這個 HTML 片段在我們的 JSP 中的作用?

請回想一下,React 會自動將它的輸出添加到名為“root”的“div”元素中。但是,我們將生成的服務器端 HTML 片段手動添加到相同的元素中 在我們的 JSP 中。

讓我們看看現在的 JSP 看起來是怎樣的:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello React!</title>
    <script type="text/javascript" src="js/react.js"></script>
    <script type="text/javascript" src="js/react-dom.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root">${content}</div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
	ReactDOM.render(
        React.createElement(App, {data: [0,1,1]}),
        document.getElementById("root")
    );
</script>
</body>
</html>

這是我們之前創建的相同頁面,只是我們在此添加了 HTML 片段到“root” div 中,而該 div 之前是空的。

7.4. REST 控制器

最後,我們需要一個服務器端 REST 端點,它能為我們提供序列中下一個斐波那契數:

@RestController
public class MyRestController {
    @RequestMapping("/next/{last}/{secondLast}")
    public int index(
      @PathVariable("last") int last, 
      @PathVariable("secondLast") int secondLast) throws Exception {
        return last + secondLast;
    }
}

這裏沒有複雜的內容,只是一個簡單的 Spring REST 控制器。

8. 運行應用程序

現在,我們已經完成了前端和後端,是時候運行應用程序了。

我們應該像往常一樣啓動 Spring Boot 應用程序,並利用引導類。

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

當我們運行這個類時,Spring Boot 會編譯我們的 JSPs 並將其提供給嵌入式 Tomcat,以及整個 Web 應用程序的其他部分

現在,如果訪問我們的站點,我們會看到:

讓我們理解事件的順序:

  • 瀏覽器請求此頁面
  • 當對該頁面的請求到達時,Spring Web 控制器處理 JS 文件
  • Nashorn 引擎生成 HTML 片段並將此片段傳遞給 JSP
  • JSP 將此 HTML 片段添加到“root” div 元素中,最終返回上述 HTML 頁面
  • 瀏覽器渲染 HTML,同時開始下載 JS 文件
  • 最後,頁面已準備好客户端操作——我們可以添加該系列中的更多數字

重要的是理解如果 React 在目標“div”元素中找到 HTML 片段時會發生什麼。在這種情況下,React 會將此片段與它已有的內容進行比較,如果找到可讀片段,則不會替換它。這正是服務器端渲染和異構應用程序所驅動的。

9. 更多可能性是什麼?

在我們的簡單示例中,我們只是觸及了可能性的一小部分。基於現代 JS 框架的前端應用程序變得越來越強大和複雜。 隨着複雜性的增加,我們需要關注很多事情:

  • 我們僅在應用程序中創建了一個 React 組件,而實際上,這可以成為 多個組件形成層級結構,並通過 props 傳遞數據
  • 我們希望為每個組件 創建單獨的 JS 文件,以便保持它們的可管理性,並通過 “exports/require” 或 “export/import” 管理它們的依賴項
  • 此外,可能無法僅在組件內部管理狀態;我們可能需要使用 Redux 這樣的狀態管理庫
  • 更重要的是,我們可能需要通過動作的副作用與外部服務交互; 這可能需要我們使用 redux-thunkRedux-Saga 這樣的模式
  • 最重要的是,我們希望 利用 JSX,即 JS 的語法擴展,用於描述用户界面

雖然 Nashorn 與純 JS 完全兼容,但它可能不支持上述所有功能。 這些功能很多需要轉譯和填充,因為 JS 兼容性問題。

在這種情況下,通常的做法是 利用模塊打包器,如 WebpackRollup。 它們的主要功能是處理所有 React 源代碼文件並將它們打包到一個單獨的 JS 文件中,以及所有依賴項。 這不可避免地需要像 Babel 這樣的現代 JavaScript 編譯器,用於將 JavaScript 編譯為向後兼容的版本。

最終的包僅包含舊版本的 JS,瀏覽器可以理解,Nashorn 也遵循它。

10. 異構應用的好處

我們已經討論了異構應用,甚至已經創建了一個簡單的應用程序。那麼,我們為什麼應該關心它們呢? 讓我們來了解一下使用異構應用的一些關鍵好處。

10.1. 首次頁面渲染

一個同構應用程序最顯著的優勢在於 首次頁面渲染的速度更快。 在典型的客户端渲染應用程序中,瀏覽器首先會下載所有 JavaScript 和 CSS 資源。

隨後,它們會加載並開始渲染第一頁。 如果我們從服務器端渲染第一頁,這可以大大加快渲染速度,從而提供更佳的用户體驗。

10.2. 搜索引擎友好性

另一個經常被與服務器端渲染聯繫起來的優勢是與搜索引擎優化(SEO)相關。 普遍認為,搜索引擎爬蟲無法處理 JavaScript,因此無法索引通過諸如 React 之類的客户端庫渲染的頁面。 因此,服務器端渲染的頁面更具搜索引擎友好性。 然而,需要注意的是,現代搜索引擎爬蟲聲稱能夠處理 JavaScript。

11. 結論

在本教程中,我們回顧了同構應用程序的基本概念以及 Nashorn JavaScript 引擎。我們還進一步探討了如何使用 Spring Boot、React 和 Nashorn 構建同構應用程序。

然後,我們討論了擴展前端應用程序的其他可能性以及使用同構應用程序的益處。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.