Freemarker基本语法与案例讲解

🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的专栏《SpringBoot》。🎯🎯

👉点击这里,就可以查看我的主页啦!👇👇

Java方文山的个人主页

🎁如果感觉还不错的话请给我点赞吧!🎁🎁

💖期待你的加入,一起学习,一起进步!💖💖

请添加图片描述

目录

一、Freemarker简介

1.1.什么是Freemarker

1.2.Freemarker的特点

1.3.Freemarker对比JSP

二、FreeMarker的使用

2.1.数据类型

2.1.1.字符串

2.1.2.数值

2.1.3.布尔值

2.1.4.日期

2.2.常见指令

2.2.1.处理不存在的值

2.2.2.assign

2.2.3.if/elseif/else

2.2.4.list

2.2.5.include

三、Freemarker综合案例

3.1.SpringBoot集成Freemarker

3.2.查询(Select)

3.3.新增(Add)

3.4.修改(Update)

3.5.删除(Delete)


 

一、Freemarker简介

1.1.什么是Freemarker

Freemarker是一个Java模板引擎,用于生成动态的网页、电子邮件、XML文档、JSON等内容。它基于模板和数据模型,将数据与模板进行结合,然后输出所需的结果。Freemarker支持强大的模板标签语言,包括条件语句、循环语句、变量赋值、宏定义等,使得模板设计更加灵活且易于维护。Freemarker也被广泛应用于Java Web框架中,如Spring MVC、Struts、Play等。

1.2.Freemarker的特点

  1. 简单易用:Freemarker使用简洁的语法和标签,使模板设计和使用变得简单易懂。它支持常见的逻辑判断、循环和变量赋值等操作,使得模板编写更加灵活和可读性更强。

  2. 独立性:Freemarker是一个独立的模板引擎,不依赖于特定的Web框架或容器。这使得它在各种Java应用中都可以方便地使用,无论是基于Web的应用还是独立的命令行工具。

  3. 跨平台支持:由于Freemarker是基于Java开发的,所以它可以运行在几乎所有支持Java的平台上,包括Windows、Linux、Mac等。

  4. 强大的模板功能:Freemarker提供了丰富的模板功能,包括条件语句、循环语句、宏定义、函数调用等。这些功能可以帮助开发人员实现复杂的逻辑和数据处理,使得模板更加灵活和可扩展。

  5. 安全性:Freemarker具有良好的安全性,可以防止常见的模板注入攻击。它提供了严格的模板语法检查和输出过滤机制,可以有效地防止恶意用户篡改模板或注入恶意代码。

总的来说,Freemarker是一个功能强大、灵活易用、跨平台的Java模板引擎,广泛应用于各种Java应用开发中。它可以帮助开发人员将动态数据与静态模板进行分离,提高代码的可维护性和可重用性。

1.3.Freemarker对比JSP

当涉及到Freemarker和JSP的详细比较时,我们可以从以下几个方面进行更深入的讨论:

  1. 语法和标记:

    • JSP使用类似于HTML的标记语言,在其中插入Java代码需要使用特殊的标签,如<% %>。这使得JSP对于前端开发人员来说更容易理解和维护。
    • Freemarker则采用自己的表达式语法,使用${}或#{}来引用变量或执行函数。这种语法更加简洁,对于Java开发人员更友好。
  2. 数据源引用

    • JSP中,可以通过JavaBean、EL表达式或JSTL标签库等方式来引用外部数据源。这样可以直接在JSP页面中操作和展示数据。
    • Freemarker则需要将数据源封装成一个数据模型对象,并将模型对象传递给模板。这种方式更为灵活,可以将数据源与视图分离,提高代码的可维护性和可重用性。
  3. 性能:

    • JSP需要在第一次请求时编译成Java Servlet才能运行,这会导致一定的延迟。但一旦编译完成,后续的请求会比较快速。
    • Freemarker是基于纯Java代码实现的,不需要编译过程,因此在整体性能上可能更快,尤其是在大量页面渲染的情况下。
  4. 可读性和可维护性:

    • JSP的语法类似于HTML,对于前端开发人员来说更易于理解和维护。但对于Java开发人员来说,JSP中的Java代码可能会比较繁琐。
    • Freemarker采用了更简单直观的语法,易于阅读和维护。它提供了更丰富的模板功能,如条件语句、循环语句、宏定义等,使得模板更加灵活和可扩展。

二、FreeMarker的使用

2.1.数据类型

2.1.1.字符串

在文本中确定字符串值的方法是看双引号,比如: "some text",或单引号,比如: 'some text'。这两种形式是等同的。 如果文本自身包含用于字符引用的引号 ( "')或反斜杠时, 应该在它们的前面再加一个反斜杠;这就是转义。 转义允许直接在文本中输入任何字符, 也包括换行。

${"It's \"quoted\" and
this is a backslash: \\"}

字符串类型处理:

方法含义
?substring(start,end)截取字符串(左闭右开)
?uncap_first首字母小写输出
?cap_first首字母大写输出
?lower_case字母转小写输出
?upper_case字母转大写输出
?length获取字符串长度
?starts_with("xx")?string是否以指定字符开头(boolean类型)
?ends_with("xx")?string是否以指定字符结尾(boolean类型)
?index_of("xx")获取指定字符的索引
?trim去除字符串前后空格
?replace("xx","xx")替换指定字符串

字符串空值情况处理:

FreeMarker 的变量必须赋值,否则就会抛出异常。而对于 FreeMarker 来说,null 值和不存在的变量是完全一样的,因为 FreeMarker 无法理解 null 值。

<#-- 如果值不存在,直接输出会报错 -->
<#--${str}-->
<#-- 使用!,当值不存在时,默认显示空字符串 -->
${str!}<br>
<#-- 使用!"xx",当值不存在时,默认显示指定字符串 -->
${str!"这是一个默认值"}<br>
<#-- 使用??,判断字符串是否为空;返回布尔类型。如果想要输出,需要将布尔类型转换成字符串 -->
${(str??)?string}<br>

2.1.2.数值

输入不带引号的数字就可以直接指定一个数字, 必须使用点作为小数的分隔符而不能是其他的分组分隔符。

${0.45}<br>
${18}<br>
<#-- 将数值转换成字符串输出 -->
${1000?c} <br>
<#-- 将数值转换成货币类型的字符串输出 -->
${1000?string.currency} <br>
<#-- 将数值转换成百分比类型的字符串输出 -->
${0.45?string.percent} <br>
<#-- 将浮点型数值保留指定小数位输出 (##表示保留两位小数) -->
${0.45723123?string["0.##"]} <br>

2.1.3.布尔值

直接写 true 或者 false 就表示一个布尔值了,不需使用引号。

在freemarker中布尔类型不能直接输出;如果输出要先转成字符串

${flag?c}<br>
${flag?string}<br>
${flag?string("yes","no")}<br>

2.1.4.日期

日期变量可以存储和日期/时间相关的数据。

在freemarker中日期类型不能直接输出;如果输出要先转成日期型或字符串

日期格式输出:

输出方式说明
?date年月日
?time时分秒
?datetime年月日时分秒
?string("自定义格式")指定格式
<#-- 输出日期格式 -->
${createDate?date} <br>
<#-- 输出时间格式 -->
${createDate?time} <br>
<#-- 输出日期时间格式 -->
${createDate?datetime} <br>
<#-- 输出格式化日期格式 -->
${createDate?string("yyyy年MM月dd日 HH时mm分ss秒")} <br>

2.2.常见指令

2.2.1.处理不存在的值

当试图访问一个不存在的变量时, FreeMarker 将会报错而导致模板执行中断。 通常我们可以使用两个特殊操作符来压制这个错误,控制这种错误情况。被控制的变量可以是顶层变量,哈希表或序列的子变量。 此外这些操作符还能处理方法调用的返回值不存在的情况。

  • 默认值操作符

使用形式: unsafe_expr!default_exprunsafe_expr! or (unsafe_expr)!default_expr(unsafe_expr)!

这个操作符允许你为可能不存在的变量指定一个默认值。

${message!"default Value."}
<#assign message="Zking">
${message!"default Value."}

输出结果如下:

default Value.
Zking

如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。(这是 FreeMarker 允许多类型值的体现)请注意,如果想让默认值为 0false,则不能省略它。

(${message!})
<#assign message = "Zking">
(${message!})

  • 不存在值检测操作符

使用形式: unsafe_expr??(unsafe_expr)??

这个操作符告诉我们一个值是否存在。基于这种情况, 结果是 truefalse

<#if name??>
  存在
<#else>
  不存在
</#if>

  • exists用在逻辑判断

exists用作逻辑判断,返回的是true或者false。

<#if name?exists>
    ${name}
</#if>

  • if_exists用来打印东西

if_exists用于输出的时候,如果存在则输出,不存在就输出空字符串。

${name?if_exists}

2.2.2.assign

使用该指令你可以创建一个新的变量, 或者替换一个已经存在的变量。语法格式如下:

<#assign name1=value1 name2=value2 ... nameN=valueN>
或
<#assign name>
  capture this
</#assign>

案例演示:

<#-- 创建一个str的变量 -->
<#assign str="hello">
<#-- 输出str -->
${str} <br>
<#-- 一次创建多个变量 -->
<#assign num=1 names=["zhangsan","lisi","wangwu"] >
${num} -- ${names?join(",")}

2.2.3.if/elseif/else

你可以使用 ifelseifelse 指令来条件判断是否越过模板的一个部分。 *condition* 必须计算成布尔值, 否则错误将会中止模板处理。elseifelse 必须出现在 if 内部 (也就是,在 if 的开始标签和结束标签之间)。 if 中可以包含任意数量的 elseif(包括0个) 而且结束时 else 是可选的。

<#if condition>
  ...
<#elseif condition2>
  ...
<#elseif condition3>
  ...
...
<#else>
  ...
</#if>

2.2.4.list

list 指令执行在 list 开始标签和 list 结束标签 ( list 中间的部分) 之间的代码, 对于在序列(或集合)中每个值指定为它的第一个参数。 对于每次迭代,循环变量将会存储当前项的值。

循环变量仅仅存在于 list 标签体内。 而且从循环中调用的宏/函数不会看到它(就像它只是局部变量一样)。

<#list sequence as item>
    Part repeated for each item
<#else>
    Part executed when there are 0 items
</#list>

list 中的 else 仅从 FreeMarker 2.3.23 版本开始支持。

注意:

  • else 部分是可选的, 而且仅仅从 FreeMarker 2.3.23 版本开始支持。

  • sequence: 将我们想要迭代的项,算作是序列或集合的表达式

  • item: 循环变量的名称 (不是表达式)

<#list arrs as item>
    ${item}
<#else>
    集合是空的
</#list>

2.2.5.include

可以使用它在你的模板中插入另外一个 FreeMarker 模板文件 (由 path 参数指定)

<#list arrs as item>
    ${item}
<#else>
    集合是空的
</#list>

这里:

  • path: 要包含文件的路径;

  • options: 一个或多个这样的选项: encoding=encoding, parse=parse

    • encoding: 算作是字符串的表达式

    • parse: 算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)

    • ignore_missing: 算作是布尔值的表达式

<h1>Hello Freemarker</h1>
<#include "/common/head.ftl">

 

三、Freemarker综合案例

3.1.SpringBoot集成Freemarker

配置pom.xml,引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

application.yml或application.properties文件,添加以下配置

spring:
  freemarker:
    # 设置模板后缀名
    suffix: .ftl
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path: classpath:/templates
    # 设置静态文件路径,js,css等
    mvc:
    static-path-pattern: /static/**

刚刚开始我们右键创建类是没有Freemarker选项的,所以我们进行配置File>Settings>Editor>File and Code Templates

首先复制HTML模板

修改负责的HTML模板

在resources下创建一个名为templates的包随后创建一个FreeMarker文件

index.ftl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
欢迎来到FreeMarker所编写的页面!!
</body>
</html>

我们再写一个controller类进行访问

@Controller
public class HomeController {
    @RequestMapping("/")
    public String index(){
        return "index";
        //这里的return的字符串和页面名称对应
    }

}

访问成功!!

 

3.2.查询(Select)

为了让我们的页面更加美观加入bootstrap框架

 创建一个公共页面用于引用资源

<#--运行路径-->
<#assign ctx>
    ${springMacroRequestContext.contextPath}
</#assign>

<#--bootstrap的css-->
<link rel="stylesheet" href="${ctx}/bootstrap-3.4.1-dist/css/bootstrap.min.css">
<#--bootstrap的js-->
<script src="${ctx}/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
<#--jquery的js-->
<script src="${ctx}/jquery-3.6.1.js"></script>

index.ftl代码

<a href="${ctx}/booktoedit">新增书籍</a>
<#--表格内容-->
<#if books??>
    <table class="table table-striped">
        <thead>
        <tr>
            <th scope="col">书籍ID</th>
            <th scope="col">书籍名称</th>
            <th scope="col">价格</th>
            <th scope="col">书籍类型</th>
            <th scope="col">操作</th>
        </tr>
        </thead>
        <tbody>
        <#list books as b>
            <tr>
                <th scope="row">${b.id}</th>
                <td>${b.bookname}</td>
                <td>${b.price}</td>
                <td>${b.booktype}</td>
                <td>
                    <a href="${ctx}/booktoedit?id=${b.id}">修改</a>
                    <a href="${ctx}/delete?id=${b.id}">删除</a>
                </td>
            </tr>
        </#list>
    </table>
    </tbody>
</#if>

controller代码

@RestController
@RequestMapping("/book")
public class TBookController {
    @Autowired
    private TBookService tbookservice;

    @RequestMapping("/list")
    public Object list(PageBean pageBean) {
        PageHelper.startPage(pageBean.getPage(),pageBean.getRows());
        return tbookservice.selectBook(pageBean);

    }
}

效果演示

3.3.新增(Add)

这里写一个共用页面,用于新增和修改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>书籍编辑页面</title>
</head>
<body>
<#include 'head.ftl' />
<#--如果有值就是修改否者就是新增-->
<#if b??>
<#-- 修改-->
    <form action="${ctx }/edit">
        书籍ID: <input type="text" name="id" value="${b.id}" readonly>
        书籍名称:<input type="text" name="bookname" value="${b.bookname }">
        书籍价格:<input type="text" name="price" value="${b.price }">
        书籍类别:<input type="text" name="booktype" value="${b.booktype }">
        <input type="submit">
    </form>
<#else>
<#-- 新增 -->
    <form action="${ctx }/add" method="post">
        书籍ID:<input type="text" name="id" value="">
        书籍名称:<input type="text" name="bookname" value="">
        书籍价格:<input type="text" name="price" value="">
        书籍类别:<input type="text" name="booktype" value="">
        <input type="submit">
    </form>
</#if>

</body>
</html>

controller代码

  /*根据ID查询书籍*/
    @RequestMapping("/booktoedit")
    public String toedit(TBook book, HttpServletRequest request) {
    /*不为空说明是修改*/
        if (book.getId() != null) {
            Long id=Long.parseLong(book.getId()+"");
            TBook tBook = tbookservice.selectByPrimaryKey(id);
            System.out.println(tBook);
            request.setAttribute("b",tBook);
        }
        return "toedit";
        /*这里的return的字符串和页面名称对应*/
    }

    /*新增书籍*/
    @RequestMapping("/add")
    public String add(TBook book){
        int insert = tbookservice.insert(book);
        /*重定向到查询的方法*/
        return "redirect:/";
    }

效果演示 

3.4.修改(Update)

页面就是刚刚上面的,共用一个ftl文件,至于controller代码如下

   /*修改书籍*/
    @RequestMapping("/edit")
    public String edit(TBook book){
        tbookservice.updateByPrimaryKey(book);
        /*重定向到查询的方法*/
        return "redirect:/";
    }

效果演示

3.5.删除(Delete)

删除就是个a标签跳转

 <a href="${ctx}/delete?id=${b.id}">删除</a>

controller代码

   /*删除书籍*/
    @RequestMapping("/delete")
    public String delete(TBook book){
        Long id=Long.parseLong(book.getId()+"");
        tbookservice.deleteByPrimaryKey(id);
        /*重定向到查询的方法*/
        return "redirect:/";
    }

效果演示

 请添加图片描述

到这里我的分享就结束了,欢迎到评论区探讨交流!!

💖如果觉得有用的话还请点个赞吧 💖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/252767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二十八、目录操作

二十八、目录操作 QDir 简单的QDir小例子 #include <QCoreApplication> #include <QDir> #include <QStringList> #include <QDebug>// 定义一个函数&#xff0c;输入一个目录路径&#xff0c;返回该目录及其子目录中所有文件的大小 qint64 getDirFi…

5.3数据通路的功能和基本结构

数据通路的基本结构: 1.CPU内部单总线方式。 2.CPU内部多总线方式。 3.专用数据通路方式。 内部总线是指同一部件&#xff0c;如CPU内部连接各寄存器及运算部件之间的总线; 系统总线是指同一台计算机系统的各部件&#xff0c;如CPU、内存、通道和各类I/O接口间互相连接的总线。…

[强网杯2023] 只作了几个小题

感觉现在比赛越来越难了&#xff0c;只作了几个小题&#xff0c;赛后把看到的wp复现到的也一并记录一下。 misc/fuzz 这就是个猜数的题&#xff0c;哪个数字对了就在哪一位上显示1一共9位&#xff0c;不过服务器返回的前两个并不一定正确&#xff0c;多试几把。 Enter a stri…

Vue 项目关于在生产环境下调试

前言 开发项目时&#xff0c;在本地调试没问题&#xff0c;但是部署到生产会遇到一些很奇怪的问题&#xff0c;本地又没法调&#xff0c;就需要在生产环境/域名下进行调试。 在这里介绍一个插件Vue force dev ,浏览器扩展里下载 即便是设置了Vue.config.devtoolsfalse 只要安…

【数据结构】复习题(一)

一、选择题 1.组成数据的基本单位是()。 A. 数据项 B.数据类型 C.数据元素 D.数据变量 2.设数据结构A{D,R},其中D&#xff5b;1,2,3,4},R{r},r{<1,2>,<2,3>,< 3,4>,<4,1>}&#xff0c;则数据结构A是()。 A.线性结构 B.树型结构 C.图型结构 D.集合 3.…

计算机网络网络层(期末、考研)

计算机网络总复习链接&#x1f517; 目录 路由算法静态路由与动态路由距离-向量算法链路状态路由算法层次路由 IPv4&#xff08;这个必考&#xff09;IPv4分组IPv4地址与NAT子网划分与子网掩码、CIDRARP、DHCP与ICMP地址解析协议ARP动态主机配置协议DHCP IPv6IPv6特点 路由协议…

山西电力市场日前价格预测【2023-12-16】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-16&#xff09;山西电力市场全天平均日前电价为259.00元/MWh。其中&#xff0c;最高日前电价为333.74元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出…

alibaba druid连接池

alibaba druid连接池 如果是SpringBoot 3.x&#xff0c;使用以下依赖 com.alibaba druid-spring-boot-3-starter ${druid-spring-boot-starter.version} application.yml配置 登录页面配置 切面监控springboot类 对 Web 请求的监控 配置filter&#xff0c;收集统计信息&#x…

深度剖析JavaScript中冒泡和捕获机制、事件代理

JS事件传播的两种机制包括冒泡和捕获&#xff0c;下面将具体剖析它们之间本质的区别。 事件冒泡: 先触发子元素的事件&#xff0c;再触发父元素的事件。 创建一个 ul label 和 li label, 分别绑定一个父id 和 子 id, 再通过创建 script&#xff0c;去绑定各自的点击事件。 <…

【C++】封装:练习案例-设计立方体类

练习案例&#xff1a;设计立方体类 设计立方体类(Cube) 求出立方体的面积和体积 分别用全局函数和成员函数判断两个立方体是否相等。 思路&#xff1a; 1&#xff09;创建立方体类 2&#xff09;设计属性 长&#xff0c;高&#xff0c;宽 3&#xff09;设计行为 获取立方…

配置 vim 默认显示行号 行数 :set number

vi ~/.vimrc 最后添加一行 :set number保存退出&#xff0c;再次 vim 打开文件&#xff0c;默认就会显示行号了

SpringBoot - application.yml 多环境切换解决方案

问题描述 这个问题玩过 SpringCloud 的小伙伴估计会想到用 bootstrap.yml 来解决这个问题。但是如果说为了解决这个问题引入了一堆的 SpringCloud Jar&#xff0c;就感觉杀鸡用牛刀。 于是今天我们想只有 application.yml 自己就可以解决这个问题&#xff0c;如何搞定&#x…

vue中2种取值的方式

1.url是这种方式的&#xff1a;http://localhost:3000/user/1 取得参数的方式为&#xff1a;this.$route.params.id 2.url为get方式用&#xff1f;拼接参数的&#xff1a;http://localhost:3000/user?phone131121123&companyId2ahttp://localhost:3000/ 取得参数值的方式…

linux脚本中 #!/bin/sh、#!/bin/bash

我们通常看到的脚本文件总是有以下这样的开头&#xff1a; #!/bin/bash本文解释一下这是什么&#xff0c;以及为什么要写它。 首先解释一下 #! &#xff0c;因为 #!有个专有的名词&#xff0c;叫 shebang 发音类似中文的 “蛇棒” 。为什么叫 shebang 呢&#xff1f; 首先 #…

连锁管理系统是什么?有哪些功能?

连锁管理系统帮助门店实现POS收银管理、门店管理、采购订货管理、线上商城搭建、供应链管理一体化管理系统&#xff0c;快速提高门店管理效率&#xff0c;无论你的门店有多少&#xff0c;连锁总部都能通过系统随时洞察监管门店的所有运营数据。 连锁管理系统由&#xff1a;1个…

Web安全漏洞分析—文件包含

在当今数字化时代&#xff0c;随着Web应用程序的广泛应用&#xff0c;网络安全问题愈加凸显。其中&#xff0c;文件包含漏洞作为一种常见但危险的安全隐患&#xff0c;为恶意攻击者提供了可乘之机。在这篇博客中&#xff0c;我们将深入探讨文件包含漏洞的本质、攻击手法以及应对…

visio打出根号,下标,并调整符号的大小

插入公式对象 打出根号和带下标的字母 调整符号大小 把做好的公式符号弄到visio中的图中 ctrla 复制符号 进入visio中粘贴 并 调整大小 调整大小直接拖动边框上的圆点即可。

【数据结构】八大排序之希尔排序算法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.优化直接插入排序算法 我们在之前对直接插入排序算法的优化部分通过对直接插入排序的分析可以得到一个结论,即: 进行直接插入排序的数组,如果越接近局部有序,则后续进行直…

软件设计师——计算机组成原理(三)

&#x1f4d1;前言 本文主要是【计算机组成原理】——软件设计师——计算机组成原理的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 …

计算机网络:运输层

0 本节主要内容 问题描述 解决思路 1 问题描述 1.1 知识回顾 利用如下拓扑对前面的知识进行回顾。 图1 拓扑图 问题&#xff1a;源主机 H 1 \textrm{H}_1 H1​要和目的主机 H 2 \textrm{H}_2 H2​进行通信&#xff0c;源主机 H 1 \textrm{H}_1 H1​要构建数据包封装来自应…