用 Google Gears 增强您的 Web 应用程序 |
|
2009 年 8 月 20 日
本文将简要介绍如何用 Google Gears 提供的 API 增强您的 Web 应用程序,包括增加离线支持,提高运行速度和本地数据库支持等。
众所周知,Ajax 可以使得 Web 应用程序的响应速度提高一大块,然而云计算和 SaaS(Software as a Service) 的用户仍然期望获得更快的响应速度,那 Web 应用程序还能更快吗?答案是肯定的。随着硬件技术的不断升级,客户端计算机的计算能力逐步提高,如果 Web 应用程序可以充分利用闲置的客户端计算能力,那将进一步提高其响应速度,但如何充分利用客户端计算能力便成了开发者新的挑战, Google Gears 正是帮助 Web 应用程序开发者应对这种挑战的有力工具。其次,用户有时希望在离线的情况下也可以使用 Web 应用程序,这无形当中增加了 Web 应用程序开发者的痛苦指数, Google Gears 恰好可以减轻开发者在这方面的痛苦。最后,企业用户对于采纳云计算和 SaaS 总会存在一种顾虑,那就是企业的核心数据拱手交给第三方公司来存储和管理,这显然使得企业难以保护自己公司的商业机密 , 而 Google Gear 可以允许用户将 Web 应用程序产生的用户数据存储到用户自己的电脑中,而不是将数据存储到云端或者 SaaS 提供商的数据中心。本文将逐个介绍 Google Gears 提供的各种特性和 API,并且会提供使用这些 API 的例子。
Google Gears 是 Google 公司推出的一个开源项目,它是一个浏览器的插件,它会添加新的功能到浏览器,并暴露相应的 JavaScript API 给 Web 应用程序,以此增强 Web 应用程序的功能和性能。目前 Google Gears 支持 Firefox 1.5+, Internet Explorer 6.0+ 和 Safari 3.1.1+ 等主流浏览器。
首先, Google Gears 提供的 LocalServer API 可以将 Web 应用程序的 HTTP 资源缓存到用户的硬盘中,这样可以在 Ajax 的基础上进一步提高 Web 应用程序的响应速度,同时也使得用户在没有网络连接的情况下依然可以使用 Web 应用程序;其次, Google Gears 提供了 WorkerPool API 帮助 Web 应用程序在后台进行耗时的运算以提高 UI 的响应能力, Web 应用程序也可以用这种方式将原来由服务器承担的一部分运算工作交给客户端来处理;第三, Google Gears 提供的 Database API 则可以让 Web 应用程序将数据保存到用户的硬盘中并遵守同源安全策略,以此保护用户的敏感数据不被外露。除此之外, Google Gears 还提供了 Desktop, Geolocation 等 API 来丰富已有的功能。目前有 Google Docs, Google Reader 和 Zoho 等 Web 应用程序利用 Google Gears 来提供更多的功能。下面将介绍如何利用 Google Gears 提供的各种 API 来增强你的 Web 应用程序。
|
为 了开发 Google Gears 应用程序,你需要先安装 Google Gears 插件到你的浏览器,用你的浏览器访问 http://gears.google.com/,如果你的浏览器没有安装 Google Gears 插件,在显示的网页右上角会出现一个 Install Gears 的按钮,点击它将会开始安装 Google Gears。接下来你还需要到 http://code.google.com/intl/zh-CN/apis/gears/tools.html 下载 gears_init.js 文件,把该文件放到你的 Web 应用程序里面。由于 Google Gears 提供的 API 是基于 JavaScript 语言的,所以你不需要安装额外的 IDE 插件来开发 Google Gears 程序,仅需要普通的文本编辑器就可以。如果你的浏览器是 Firefox, 可以安装 Firebug 来调试 JavaScript。
|
为了使你的 Web 应用程序的某一个页面能用上 Google Gears 的功能,需要把清单 1 所示的代码嵌入到网页的 Html 代码里面 , src 的值是前面下载的 gears_init.js 的相对路径。
清单 1. 为网页增加对 Google Gears 的 JavaScript 库的引用
|
只 有在浏览器安装了 Google Gears 插件的情况下,Google Gears 提供的 JavaScript API 才会生效,所以你的 Web 应用程序需要在一开始就检测用户的浏览器是否安装了 Google Gears, 如果没有就转到 Google Gears 的安装页面。清单 2 所示代码能够帮你做到这些
清单 2. 检测浏览器是否安装了 Google Gears
|
在上面代码中你可以用 message 参数自定义显示在安装页面的消息。
|
虽 然 Google Gears 不仅仅是为了给 Web 应用程序赋予离线功能而生,但离线功能却无疑是 Google Gears 最重要的使命之一。LocalServer, WorkerPool 和 Database 这三个 Gears 最早的,同时也是最核心的功能模块,为完成这一使命提供了必不可少的利器。
LocalServer 的主要功能是将 Web 应用程序的 HTTP 资源缓存到用户的本地硬盘中,并且当用户需要再次访问同样的网络资源的时候,对其进行拦截,转而用本地已存储的 HTTP 资源来代替服务器端的资源,为用户提供服务。这样,不但可以在 Ajax 的基础上进一步提高 Web 应用程序的响应速度,同时也使得用户在没有网络连接,或者网络连接状况不好的情况下依然可以像使用本地应用程序一样的流畅使用 Web 应用程序。
然 而,即使有了 LocalServer 的支持,也并不意味着我们需要一股脑的把所有的 Web 应用程序都拿到本地来执行,很多实时性很强,或者数据量过大的 Web 应用程序,都并不适合进行本地存储和利用本地资源来提供服务。因此,在利用 LocalServer API 对 Web 应用程序提供离线使用的功能之前,更重要的是要根据不同应用程序的应用场景,考量和分析哪些 Web 应用程序,或者某个 Web 应用程序的哪些功能和资源适合放到本地,并且能相对容易放到本地,放到本地之后能有更好的使用效果和用户体验。
在确定了要存储 Web 应用程序的哪些功能和资源之后,我们可以利用 LocalServer 提供的两种缓存方式来获取 HTTP 资源:
- ResrouceStore – 获取指定 URL 的用户数据,PDF 文件,图片,样式表,JavaScript, 和 HTML 页面等。
- ManagedResourceStore – 根据 manifest 文件事先声明的内容,获取一系列版本可控的相关的网络资源。
与 利用 ResourceStore 获取并存储相对独立的网络资源相比,利用 ManagedResourceStore 获取一系列相关联的网络资源会复杂一些。不过 manifest 文件的引入,也让由 ManageResourceStore 方式获取的网络资源变得相对的简单和一目了然。该 manifiest 文件由一个包括版本信息和资源清单在内的 JSON 对象组成,其中的“entries”属性,列出了所有需要被获取和存储的资源的 URL。
此外,以上两种存储方式最主要的区别是 在于对所存资源的更新方式有所不同。ResourceStore 方式存储的资源不会自动被更新。如果需要更新,开发人员要在代码中显示地调用 captrure() 方法。而利用 ManagedResourceStore 存储的资源,由于在 manifest 文件中记录了版本的信息,因此可以同时支持手动和自动两种更新方式。开发人员既可以通过调用 checkForUpdate() 来手动检查并更新本地资源,也可以在 Google Gears 拦截或提供来自 ManagedResourceStore 的请求的同时,自动比较服务器和本地版本的差异,如有不同,注意这里是不同,也就是说即使服务器版本要低于本地版本,也会触发更新。
LocalServer API 提供了三个主要的类来创建和管理由 ResourceStore 和 ManagedResourceStore 两种方式获取的网络资源。
- LocalServer – 创建、打开和删除 ResourceStore 和 ManagedResourceStore 两种存储方式的网络资源。
- ManagedResourceStore – 管理以 ManagedResourceStore 方式存储的网络资源。
- ResourceStore – 管理以 ResourceStore 方式存储的网络资源。
在 使用包括 LocalServer API 在内的 Google Gears 的 API 之前,首先需要利用 Factory API 中的 create() 方法,指定需要用到的接口。正如清单 3 所示的代码,在调用 LocalServer API 的方法之前,需要先调用 create() 方法创建出一个 LocalServer 类型的对象。
清单 3. 在使用 Google Gears 的 API 之前需先创建出相应类型的对象
|
下面我们以将一个最简单的 HTML 页面用 ManagedResourceStore 方式存储到本地为例,来简单的说明 LocalServer API 是怎样将网络资源存储到本地,并且及时更新的。代码和 manifest 文件,分别如清单 4 和清单 5 所示。
清单 4. 以 ManagedResourceStore 方式将普通 HTML 页面存储到本地
|
清单 5. 对应的 manifest 文件
|
首先,将 HTML 页面和 manifest 文件部署到 HTTP 服务器上,之后我们便可以通过浏览器访问该页面。点击 "Create Managed Store" 按钮,会触发一个创建 ManagedResourceStore 类型的本地存储的事件,该事件会将 manifest 文件里列出的所有同源网络资源下载并存储到本地硬盘。如果你想知道它们被存储到了哪里,存放路径因操作系统和浏览器的不同而不同,具体的位置可以参看:
http://code.google.com/intl/zh-CN/apis/gears/api_database.html#directories
接 下来用户便可以通过访问与连网时相同的 URL,或者通过创建的桌面快捷方式(在后面的章节会提到如何为你的离线 Web 应用程序创建桌面快捷方式),在即使没有网络连接的情况下依然可以访问并使用这个页面。这时,如果我们更新了服务器端的资源文件,并且相应的更新了 manifest 文件中的版本信息,你会看到,存储到本地的网页信息,也会跟着自动被更新。当然,如果你等不及自动更新,也可以通过再次点击 "Create Managed Store" 按钮,调用 checkForUpdate() 方法手动更新页面。
|
在 一个页面执行计算量比较大的任务或 I/O 操作时,Web 应用程序经常会慢到不响应,这个时候 WorkerPool 就有了用武之地。WorkerPool 可以用来在父页面的后台独立地执行计算量大的任务,而父页面则继续执行自己的任务。清单 6 演示了如何使用 WorkerPool API
清单 6. 使用 WorkerPool API
|
WorkerPool 不是单例对象,所以可以看到在上面的父页面中可以创建两个 WorkerPool。在父页面的 Javascript 代码相当于父 worker, 你可以用 WorkerPool 对象的 createWorkerFromUrl() 方法创建一个子 worker, 该方法的参数是 JavaScript 文件的 URL, 也可以用 createWorker() 方法创建,参数是一段 JavaScript 代码。创建完子 worker 之后,父 worker 和子 worker 将并行运行 , 子 worker 在后台承担计算量大的任务,而父 worker 在前面继续响应处理用户操作。
另外,由于 WorkerPool 更像是一个进程池,而不是线程池,所以 worker 之间不共享执行状态,他们之间的相互通讯只能通过消息传递,也就是调用 WorkerPool 对象的 sendMessage() 方法,该方法有两个参数:
- 第一个参数是要传递的消息内容。
- 第二个参数是接受该消息的 worker 的 ID,WorkerPool 对象的两个方法 createWorkerFromUrl() 和 createWorker() 返回的就是创建的子 worker 的 ID, 可以用来作为该参数的值。
不管是父 worker 还是子 worker, 要想得到 sendMessage() 方法传过来的消息,都需要事先定义回调函数 onmessage,用于处理接收到的消息。
正 是由于 WorkerPool 之间不共享执行状态,作为结果,子 worker 不能访问父页面的 DOM,以及 document, window 这样的对象,只有父 worker 能访问这些对象。如果子页面要访问这些对象,可以通过给父 worker 发消息让父 worker 来访问。但是,子 worker 还是能调用 JavaScript 的内置函数和大部分 Google Gears API。
|
为 了让 Web 应用程序支持离线操作,首先需要解决的问题是将 Web 应用程序包含的 HTTP 资源缓存到用户本地的文件系统中,这个已经可以通过前面介绍的 LocalServer 来解决,第二个需要解决的问题是要让用户在使用 Web 应用程序的过程中产生的用户数据能够存放到本地,然后在连上网络的时候将本地的数据与服务器端的数据进行同步,这个问题可以用 Google Gears 提供的本地浏览器数据库来解决。之所以叫本地浏览器数据库,是因为数据库是运行在用户本地电脑上的,并且浏览器可以通过 JavaScript 操作该数据库。当然,本地浏览器数据库还可以用于存放用户希望保密的数据在用户自己的文件系统,而不是存放在云端。同时,即使在连上网络的情况下,也可以 通过访问本地数据带来性能上的巨大提升,毕竟这要比访问服务器端的数据快得多。
为了让浏览器上运行的 Web 应用程序能够操作本地浏览器数据库, Google Gears 提供了一套 Database API, 它使得 Web 应用程序可以将用户数据存放到用户自己的电脑中, Google Gears 是用开源的关系数据库系统 SQLite 作为这个本地浏览器数据库,当用户将 Google Gears 安装为浏览器的插件之后,用户实际上就把 SQLite 安装到了他 / 她的电脑中, Web 应用程序则可以调用 Google Gears 提供的 Database API 来操作 SQLite, Database API 允许开发人员在 JavaScript 里面直接用 SQL 语句从 SQLite 读取数据或者写入数据到 SQLite。在这一节我们将介绍如何调用 Database API 来操作 SQLite。
清单 7 所示代码演示了如何用 Database API
- 打开或创建一个新数据库
- 创建表
- 删除表中的记录
- 插入记录到表中
- 查询表中的数据以及遍历返回的结果集
- 更新表中的数据
- 删除表
清单 7. 使用 Database API 操作 SQLite 数据库
|
当调用 db.open('testdb') 方法时, Google Gears 会检测是否已经存在名为 "testdb" 的数据库,如果没有,就创建一个新的名为 "testdb" 的数据库。你也许会想知道这些数据库存放在哪里,存放路径因操作系统和浏览器的不同而不同,具体的位置可以参看 http://code.google.com/intl/zh-CN/apis/gears/api_database.html#directories
。 中文字符 SQLite 默认是用 utf-8 编码来存储数据库文件,因此能支持中文字符。也许你注意到了,清单 7 所示的代码中有一条插入语句,把中文字符“傅飞”插入到表 emp 里面。需要注意的是,SQLite 不会对 SQL 语句中的字符串自动转换为 utf-8 编码,所以开发人员需要确保这些字符串是正确编码的,方法是将代码文件用 utf-8 编码保存即可。
Google Gears 包含了 SQLite 的一个扩展 fts2, 用于支持全文检索功能。fts2 使你能在一个表中类型为 Text 的所有字段上搜索指定的关键字。为了利用 fts2 的全文检索功能,你需要用 fts2 扩展来创建表,如清单 8 所示。
清单 8. 创建支持全文检索的表
|
上面的代码会创建一个能支持全文检索的表,这个表有以下特性
- 依然可以用标准的 Insert, Update 和 Delete 语句操作表中的数据,如清单 9 所示。
- 表中 3 个字段 name, director 和 stars 的类型都是 Text 的。
- 表中隐式包含了一个跟表名相同的字段名,该列会在全文检索的时候用到。由于该列的存在,当你用 Insert 语句插入新记录的时候有必要把你要更新的字段列举出来,同样,用 Select 语句检索的时候也要列举出你期望得到哪些字段的值,如清单 9 所示。如果只是用 Select * from movie 检索 , SQLite 将会抛出异常。
清单 9. 操作支持全文检索的表
|
为了能够全文检索上面创建的表,你需要用 “< 表名 | 字段名 > match < 查询字符串 >” 作为检索条件。如果用表名,实际上是用表中隐含的跟表名同名的字段名,这将会检索表中所有类型为 Text 的字段;如果用字段名,这只在指定的字段上进行检索,这种情况并不是全文检索。我们这里用表名作为例子,假设表 movie 中已经有了如表 1 所示的记录,注意 Stars 字段中的人名是用空格隔开的,空格是 fts2 的分词符, fts2 全文检索的时候是按词来索引的,如果一个字段值没有包含一个空格,该字段值将被视为一个词。
表 1. 表 movie 的所有记录
Name | Director | Stars |
国产凌凌漆 | 李力持 | 周星驰 谷德昭 李力持 |
功夫 | 周星驰 | 周星驰 元华 冯小刚 |
叶问 | 叶伟信 | 甄子丹 任达华 |
非诚勿扰 | 冯小刚 | 葛优 范伟 |
The Dark Knight | Christopher Nolan | Heath Ledger |
Forrest Gump | Robert Zemeckis | Tom Hanks |
我们现在用清单 10 所示的全文检索语句来检索 movie 表,其中查询字符串是“周星驰”。
清单 10. 全文检索的 Select 语句
|
我们将得到如表 2 所示的结果,可以看出,表 movie 中任何一个字段,只要包含“周星驰”, SQLite 都会将该记录返回给结果集。
表 2. 检索任意字段中包含'周星驰'的记录
Name | Director | Stars |
国产凌凌漆 | 李力持 | 周星驰 谷德昭 李力持 |
功夫 | 周星驰 | 周星驰 元华 冯小刚 |
查询字符串本身也有自己的语法,表 3 中列举出了一些例子。
表 3. 查询字符串的语法
语法例子 | 描述 |
movie match '周星驰 冯小刚' | 返回任意一个字段中既包含“周星驰”又包含“冯小刚”的记录 |
movie match '周星驰 OR 冯小刚' | 返回任意一个字段中包含“周星驰”和“冯小刚”两者中的任一个的记录,OR 必须大写。 |
movie match '国产 *' | 返回任意一个字段中包含以“国产”字符起头的词的记录 |
movie match 'stars: 周星驰 国产 *' | 返回的记录必须满足下面两个条件:
|
movie match '"周星驰 元华"' | 返回的记录必须满足以下条件: 任意一个字段中“元华”紧跟着“周星驰” |
movie match '"周星驰 –冯小刚"' | 返回的记录必须满足下面两个条件:
|
有了本地浏览器数据库,你可以尽量多地把数据存放到本地,因为访问本地浏览器数据确实比访问服务器端的数据要快得多。然而不是所有的数据都适合存放到本地,有些情况下你还是需要去服务器端获取数据,下面列举了一些例子。
- 频繁更新的数据,将这种数据保存到本地意义不是很大,因为这种数据很短时间就会过时,比如股票价格,期货价格和等。
- 不常用的数据,比如存储用户偏好信息的数据,这些数据很少使用,如果将这类数据存放到本地,所需的成本也许还大于所带来的收益。
- 太大的数据,由于客户端的硬盘空间有限,不能将这些数据悉数存到本地。
对于支持离线模式的 Web 应用程序来说,数据同步显然是一个很重要的话题,一般以下两种情况会用到数据同步:
- 当应用程序离线时,需要将服务器端的最新数据同步到本地,这样才能确保离线时 Web 应用程序照样能运行。
- 当应用程序离线后,用户使用 Web 应用程序时所产生的数据存放在本地,这部分数据需要在连上网络的时候同步到服务器端。
Google Gears 本身并没有提供数据同步 API, 你需要自己实现数据同步的功能,这涉及到浏览器端代码和服务器端代码。下面我们介绍两种实现数据同步功能的思路。
显式同步
这是一种最常见也是最简单的同步方法,用户可以决定什么时候进行同步,实现上可以显式地放置一个同步按钮在网页上,点击该按钮将触发数据同步,把本地新的数据上传到服务器端,并把服务器端新的数据下载到本地。
显示同步的优点是:
- 实现起来比较简单。
- 比较容易测试。
缺点是:
- 如果同步的数据块比较大,将会一次性消耗很长的时间,而 Web 应用程序在数据同步期间是不可用的,这将让用户等待很长时间。
- 在网络连接时断时续的情况下,用户将会疲于点击同步按钮来同步数据,用户还不得不对网络状况保持敏感以在适当的时候手工同步数据。
后台同步
在后台同步中,Web 应用程序持续地将本地数据与服务器端数据进行同步,同步操作是在后台进行的,不需要用户显式地触发,同步过程中用户依然可以使用 Web 应用程序,实现上可以用 WorkerPool 在后台设定每隔一段时间跟服务器同步一次。
后台同步的优点是:
- 同步操作对用户是透明的,用户不需要做任何操作,也不需要对网络状况保持敏感。
- 同步操作一直在后台进行,即时当网络意外中断的情况下,本地数据和服务器数据都能保持较高的一致性。
缺点是:
- 实现起来比较复杂。
- 同步是在后台进行的,不太容易进行测试。
- 同步的间隔不能太频繁,否则会产生太多的 HTTP 连接,从而降低服务器的响应能力。
|
Desktop API 能帮你在用户桌面上创建你的 Web 应用程序的快捷方式,调用代码如清单 11 所示。
清单 11. 创建桌面快捷方式
|
createShortcut 方法有以下几个参数 :
- name, 这是用户看到的快捷方式的名字,在清单 11 中设为 "Google Gears Desktop API Example"
- url, 当用户启动快捷方式时要访问的 Web 应用程序的 url, 在清单 11 中设为" http://www.testapp.net/gears/DesktopAPI.html "
- icons, 这是一个 JSON 对象,可以设定不同尺寸的图标,在清单 11 中设定了 4 中不同尺寸的图标
- description, 这是一个可选参数,用于描述将要创建的这个快捷方式的更详细信息,当清单 11 的代码在用户浏览器里执行时,会弹出一个确认对话框,description 参数确定的文本将会显示在该确认对话框里面,如图 1 所示。
当清单 11 所示代码在 Window 上的浏览器执行的时候,不管是 Internet Explorer 还是 Firefox, 都会出现如图 1 所示的确认对话框,让用户选择是否需要在桌面,开始菜单和快速启动栏里面创建 Web 应用程序的快捷方式。
图 1. 创建桌面快捷方式
|
Geolocation API 使你的 Web 应用程序能获取用户的当前位置,结合 Google Maps API 可以立刻在 Google 地图上显示用户当前的位置,代码如清单 12 所示。
清单 12. 结合使用 Geolocation API 和 Google Maps API
|
上面代码中的关键点是用 geo.getCurrentPosition(updatePosition, handleError) 来获取用户的当前位置,当 Google Gears 拿到用户的当前位置信息后,将其值传给 updatePosition 函数来处理,该函数调用 Google Maps API 将地图的中心位置设为用户的当前位置。如果 Google Gears 拿不到用户的当前位置,将会调用 handleError 函数来处理错误信息。
|
Google Gears 提供的 HttpRequest API 实现了 W3C XmlHttpRequest specification 的一个子集 , 你可以用 HttpRequest API 发起一个 Ajax 请求。你肯定会问在浏览器中我们已经可以用 XmlHttpRequest 对象来创建 Ajax 请求,为什么 Google Gears 还要提供另外一种方式呢?这是因为在前面提到的子 worker 里面是不能访问浏览器的 XmlHttpRequest 对象,所以 Google Gears 提供了 HttpRequest API 使得子 worker 也能创建 Ajax 请求,当然 , 你依然可以在普通网页里面使用 HttpRequest API,而不是非得到子 worker 里面才能用。为了简单起见,我们只介绍如何在普通网页里面使用 HttpRequest API,如清单 13 所示
清单 13. 使用 HttpRequest API
|
需要注意的是当用 HttpRequest 的 open 方法确定要访问的 Web 资源时必须遵守同源策略,否则浏览器将报错。
|
与 HttpRequest API 相似,由于子 worker 不能访问 window 对象,所以也就不能调用它的两个定时器方法 setTimeout 和 setInterval, Google Gears 专门提供了 Timer API,让子 worker 里面的 JavaScript 代码也可以调用定时器方法。Timer API 的功能和 window 对象的定时器方法是一样的,在此也不再举例。
|
在这一节中,我们将介绍 Googler Gears 用到的安全策略以及如何在开发过程中写出安全的代码。
同源策略不允许一个网站上的 JavaScript 访问另一个网站上的的任何资源。 Google Gears 就是用同源策略作为基本的安全策略,具体体现为:
- 运行在一个网站上的 LocalServer API 只能抓取同一个网站上的 URL 或者用同一个网站上的 manifest 文件。
- 运行在一个网站上的 Database API 只能打开为同一个网站创建的 SQLite 数据库。
为 了保护用户的客户端,当 Google Gears 的 API 尝试访问本地资源的时候,浏览器会弹出一个许可对话框让用户选择是否允许该操作,当用户选择允许该操作的时候, Google Gears 会记住这个决定,以后同样的操作就不会再弹出许可对话框。但用户仍然可以更改被记住的决定,方法是在浏览器的菜单中选择“工具”->“Gears 设置”,在弹出的对话框中进行修改,如图 2 所示。
图 2.Gears 设置对话框
Google Gears 的数据,包括 localServer API 抓取到本地的 Html 文件和 Database API 创建的本地数据库文件,都是存放在操作系统的当前用户目录,操作系统的其他用户是不能访问这些文件的。
为了避免攻击者用 SQL 注入方式攻击你的 Web 应用程序,不要直接把用户的输入作为值传给 SQL 语句,而是通过问号占位符将用户的输入传给 SQL 语句,如清单 14 所示。
清单 14. 防止 SQL 注入
|