Java 中文官方教程 2022 版(四十二)(2)https://developer.aliyun.com/article/1488229
最高分条目
策略文件中的最后一个条目授予HighScore
类权限。更具体地说,它授予由"chris"
签名的代码权限,他创建并签署了这个类。要求类由"chris"
签名确保当ExampleGame
调用这个类来更新用户的高分时,ExampleGame
确切知道它正在使用由"chris"
实现的原始类。
要更新调用它的任何游戏的用户高分值,HighScore
类需要三个权限:
1. 读取"user.home"
属性值的权限。
HighScore
类将用户的高分值存储在用户主目录中的.highscore
文件中。因此,这个类需要一个java.util.PropertyPermission
,允许它读取"user.home"
属性值,以确定用户主目录的确切位置:
permission java.util.PropertyPermission "user.home", "read";
2. 读写高分文件本身的权限。
这个权限是为了让HighScore
的getHighScore
和 setHighScore
方法可以访问用户的.highscore
文件,分别获取或设置当前游戏的当前高分。
这是所需的权限:
permission java.io.FilePermission "${user.home}${/}.highscore", "read,write";
注意:${propName}
表示属性的值。因此,${user.home}
将被"user.home"
属性的值替换。${/}
表示文件分隔符的平台无关方式。
3. 所有 HighScorePermissions(即任何名称的 HighScorePermissions)。
这个权限是为了确保HighScore
检查调用游戏是否被授予了一个名为游戏名称的HighScorePermission
。也就是说,HighScore
类必须同样被授予权限,因为权限检查要求堆栈上的所有代码都具有指定的权限。
这是所需的权限:
permission com.scoredev.scores.HighScorePermission "*", signedBy "chris";
与以前一样,HighScorePermission
本身需要由实际实现权限的"chris"
签名。
将所有内容整合在一起
原文:
docs.oracle.com/javase/tutorial/security/userperm/together.html
在这里,我们模拟依次成为HighScore
开发者(克里斯),ExampleGame
开发者(特里),以及运行游戏的用户(金)。
您可以执行所有指定的步骤,然后(作为金的最后一步)运行ExampleGame
。
这些步骤没有解释。关于代码签名者(如克里斯和特里)和接收此类代码的人(如金)需要采取的进一步信息,请参阅签署代码并授予权限课程。
这里是步骤:
- HighScore 开发者(克里斯)的步骤
- ExampleGame 开发者(特里)的步骤
- 运行 ExampleGame 的用户(金)的步骤
高分开发者(克里斯)的步骤
原文:
docs.oracle.com/javase/tutorial/security/userperm/chris.html
克里斯在创建HighScore
和HighScorePermission
类之后将采取的步骤是:
编译这些类
javac HighScore*.java -d .
将类文件放入一个 JAR 文件中
jar cvf hs.jar com/scoredev/scores/HighScore*.class
创建用于签名的密钥库和密钥
keytool -genkey -keystore chris.keystore -alias signJars
指定密码和显著名称信息
签署 JAR 文件
jarsigner -keystore chris.keystore hs.jar signJars
导出公钥证书
keytool -export -keystore chris.keystore -alias signJars -file Chris.cer
提供游戏开发人员和用户所需的文件和信息
也就是说,提供它们
- 签名的 JAR 文件
hs.jar
, - 公钥证书文件
Chris.cer
, HighScore
和HighScorePermission
类在策略文件中必须被授予的权限信息,以便能够正常工作。对于这一点,克里斯可以提供所需的确切授权条目。
示例游戏开发者(Terry)的步骤
原文:
docs.oracle.com/javase/tutorial/security/userperm/terry.html
Terry 创建一个调用 HighScore
的 getHighScore
和 setHighScore
方法来获取和设置用户高分的游戏(ExampleGame
)后,Terry 需要采取的步骤是:
编译游戏类
javac ExampleGame.java -classpath hs.jar -d .
将其类文件放入一个 JAR 文件中
jar cvf terry.jar com/gamedev/games/ExampleGame.class
创建用于签名的密钥库和密钥
keytool -genkey -keystore terry.keystore -alias signTJars
为密码和区分名称信息指定任何你想要的内容。
签署 JAR 文件
jarsigner -keystore terry.keystore terry.jar signTJars
导出公钥证书
keytool -export -keystore terry.keystore -alias signTJars -file Terry.cer
为用户提供所需的文件和信息
也就是说,向他们提供
- 签名的 JAR 文件
terry.jar,
- 公钥证书文件
Terry.cer
, 和 ExampleGame
类所需权限的信息。对于这一点,Terry 可能会提供所需的确切授权条目。
游戏用户还需要来自 Chris 的文件和信息。为了方便他们,Terry 可能会将这些信息转发给他们:
- 签名的 JAR 文件
hs.jar
, - 公钥证书文件
Chris.cer
, 和 - 有关
HighScore
和HighScorePermission
类在策略文件中必须被授予的权限的信息,以便其正常工作。这可能是所需的确切授权条目。
运行 ExampleGame(Kim)的用户步骤
原文:
docs.oracle.com/javase/tutorial/security/userperm/kim.html
用户(比如 Kim)需要执行的步骤包括:
将证书导入为受信任的证书
keytool -import -alias chris -file Chris.cer -keystore kim.keystore keytool -import -alias terry -file Terry.cer -keystore kim.keystore
设置具有所需权限的策略文件
这里是完整的kim.policy
策略文件,如 A Sample Policy File 中所述。
运行 ExampleGame
设置高分:
java -Djava.security.manager -Djava.security.policy=kim.policy -classpath hs.jar;terry.jar com.gamedev.games.ExampleGame set 456
获取高分:
java -Djava.security.manager -Djava.security.policy=kim.policy -classpath hs.jar;terry.jar com.gamedev.games.ExampleGame get
注意:
- 如果不指定
-Djava.security.manager
,应用程序将无限制地运行(策略文件和权限不会被检查)。 -Djava.security.policy=kim.policy
指定了策略文件的位置。注意:还有其他指定策略文件的方法。例如,你可以在安全属性文件中添加一个条目,指定包含kim.policy
,如在查看策略文件效果课程末尾讨论的那样。-classpath hs.jar;terry.jar
指定了包含所需类文件的 JAR 文件。对于 Windows,使用分号(“;”)分隔 JAR 文件;对于 UNIX,使用冒号(“:”)。- 策略文件
kim.policy
指定了密钥库kim.keystore
。由于未提供密钥库的绝对 URL 位置,因此假定密钥库与策略文件位于同一目录中。
教程:扩展机制
扩展机制提供了一种标准、可扩展的方式,使自定义 API 对在 Java 平台上运行的所有应用程序可用。Java 扩展也被称为可选包。本教程可能会交替使用这两个术语。
扩展是通过扩展机制增强 Java 平台的一组包和类。扩展机制使运行时环境能够找到并加载扩展类,而无需在类路径上命名扩展类。在这方面,扩展类类似于 Java 平台的核心类。这也是扩展得名的原因–它们实际上扩展了平台的核心 API。
由于此机制扩展了平台的核心 API,应谨慎使用。最常见的用途是用于由 Java 社区流程定义的标准化接口,尽管也可能适用于站点范围接口。
如图所示,扩展充当 Java 平台的“附加”模块。它们的类和公共 API 自动对在平台上运行的任何应用程序可用。
扩展机制还提供了一种从远程位置下载扩展类供 applets 使用的方法。
扩展被打包为 Java 存档(JAR)文件,本教程假定您熟悉 JAR 文件格式。如果您对 JAR 文件不熟悉,您可能需要在继续本教程的课程之前查阅一些 JAR 文件文档:
- 本教程中的在 JAR 文件中打包程序课程。
- JDK™文档中的JAR 指南。
本教程有两个课程:
创建和使用扩展
这一部分向您展示了如何向您的 Java 平台添加扩展,并且 applets 如何通过下载远程扩展类从扩展机制中受益。
使扩展安全
本节描述了在您的平台上授予扩展的安全特权和权限。如果您正在编写自己的扩展类,您将了解如何使用 Java 平台的安全架构。
附加文档
您可以在 JDK 文档的Java 扩展机制部分找到有关扩展的更多信息。
教程:创建和使用扩展
任何一组包或类都可以轻松地扮演扩展的角色。将一组类转变为扩展的第一步是将它们打包在一个 JAR 文件中。完成这一步后,您可以通过两种方式将软件转变为扩展:
- 通过将 JAR 文件放置在 Java 运行时环境目录结构的特定位置,这种情况下称为已安装扩展。
- 通过以特定方式从另一个 JAR 文件的清单中引用 JAR 文件,这种情况下称为下载扩展。
本课将通过使用一个简单的“玩具”扩展作为示例来展示扩展机制的工作原理。
已安装扩展
在本节中,您将创建一个简单的已安装扩展,并看到扩展软件如何被运行时环境视为平台的一部分。
下载扩展
本节将向您展示如何修改 JAR 文件的清单,以便 JAR 打包的软件可以利用下载扩展。
理解扩展类加载
本节是一个简短的插曲,总结了 Java 平台的类加载委托模型,并展示了它与扩展中类加载的关系。
创建可扩展应用程序
本节讨论了用于扩展应用程序的机制,通过插件或模块,而无需修改其原始代码库。
下一课,使扩展安全 使用相同的扩展来展示 Java 平台如何控制授予扩展的安全权限。
已安装的扩展
已安装的扩展是 JRE™软件的lib/ext
目录中的 JAR 文件。顾名思义,JRE 是 Java 开发工具包的运行时部分,包含平台的核心 API,但不包括编译器和调试器等开发工具。JRE 可以单独使用,也可以作为 Java 开发工具包的一部分使用。
JRE 是 JDK 软件的严格子集。JDK 软件目录树的子集如下所示:
JRE 由图中突出显示的目录组成。无论您的 JRE 是独立的还是作为 JDK 软件的一部分,JRE 目录中的lib/ext
中的任何 JAR 文件都会被运行时环境自动视为扩展。
由于安装的扩展会扩展平台的核心 API,请谨慎使用。它们很少适用于仅由单个或少量应用程序使用的接口。
此外,由于安装的扩展定义的符号将在所有 Java 进程中可见,因此应注意确保所有可见符号遵循适当的“反向域名”和“类层次结构”约定。例如,com.mycompany.MyClass
。
从 Java 6 开始,扩展 JAR 文件也可以放置在与任何特定 JRE 无关的位置,以便扩展可以被安装在系统上安装的所有 JRE 共享。在 Java 6 之前,java.ext.dirs
的值指的是单个目录,但是从 Java 6 开始,它是一个目录列表(类似于CLASSPATH
),指定扩展被搜索的位置。路径的第一个元素始终是 JRE 的lib/ext
目录。第二个元素是 JRE 之外的目录。这个其他位置允许扩展 JAR 文件只安装一次,并被安装在该系统上安装的几个 JRE 使用。位置因操作系统而异:
- Solaris™操作系统:
/usr/jdk/packages/lib/ext
- Linux:
/usr/java/packages/lib/ext
- Microsoft Windows:
%SystemRoot%\Sun\Java\lib\ext
请注意,放置在上述任一目录中的安装扩展会扩展该系统上每个JRE(Java 6 或更高版本)的平台。
一个简单的例子
让我们创建一个简单的已安装扩展。我们的扩展由一个类RectangleArea
组成,用于计算矩形的面积:
public final class RectangleArea { public static int area(java.awt.Rectangle r) { return r.width * r.height; } }
此类有一个名为area
的方法,该方法接受一个java.awt.Rectangle
的实例并返回矩形的面积。
假设你想要使用名为AreaApp
的应用程序测试RectangleArea
:
import java.awt.*; public class AreaApp { public static void main(String[] args) { int width = 10; int height = 5; Rectangle r = new Rectangle(width, height); System.out.println("The rectangle's area is " + RectangleArea.area(r)); } }
此应用程序实例化一个 10 x
5 的矩形,然后使用RectangleArea.area
方法打印出矩形的面积。
在没有扩展机制的情况下运行 AreaApp
让我们首先回顾一下如何在不使用扩展机制的情况下运行AreaApp
应用程序。我们假设RectangleArea
类被捆绑在名为area.jar
的 JAR 文件中。
当然,RectangleArea
类不是 Java 平台的一部分,因此您需要将area.jar
文件放在类路径上才能运行AreaApp
而不会出现运行时异常。例如,如果area.jar
在目录/home/user
中,您可以使用以下命令:
java -classpath .:/home/user/area.jar AreaApp
此命令中指定的类路径包含当前目录,其中包含AreaApp.class
,以及包含RectangleArea
包的 JAR 文件的路径。通过运行此命令,您将获得所需的输出:
The rectangle's area is 50
使用扩展机制运行 AreaApp
现在让我们看看如何通过将RectangleArea
类作为扩展来运行AreaApp
。
要将RectangleArea
类变成一个扩展,您需要将文件area.jar
放在 JRE 的lib/ext
目录中。这样做会自动将RectangleArea
赋予已安装扩展的状态。
将area.jar
安装为扩展后,您可以运行AreaApp
而无需指定类路径:
java AreaApp
因为您正在使用area.jar
作为已安装的扩展,运行时环境将能够找到并加载RectangleArea
类,即使您没有在类路径上指定它。同样,任何用户在您的系统上运行的小程序或应用程序都可以找到并使用RectangleArea
类。
如果系统上安装了多个 JRE(Java 6 或更高版本),并且希望RectangleArea
类作为所有 JRE 的扩展可用,而不是将其安装在特定 JRE 的lib/ext
目录中,请将其安装在系统范围的位置。例如,在运行 Linux 的系统上,将area.jar
安装在目录/usr/java/packages/lib/ext
中。然后AreaApp
可以在安装在该系统上的不同 JRE 上运行,例如,如果不同的浏览器配置为使用不同的 JRE。
下载扩展
下载扩展是 JAR 文件中的一组类(和相关资源)。JAR 文件的清单可以包含引用一个或多个下载扩展的头部。这些扩展可以通过以下两种方式引用:
- 通过
Class-Path
头部 - 通过
Extension-List
头部
请注意,清单中最多只允许一个。通过Class-Path
头部指示的下载扩展仅在下载它们的应用程序(如 Web 浏览器)的生命周期内下载。它们的优点是客户端上没有安装任何内容;缺点是每次需要时都会下载它们。通过Extension-List
头部下载的下载扩展将安装到下载它们的 JRE 的/lib/ext
目录中。它们的优点是第一次需要时下载,随后可以在不下载的情况下使用。但是,正如本教程后面所示,它们部署起来更加复杂。
由于使用Class-Path
头部的下载扩展更简单,让我们先考虑它们。例如假设a.jar
和b.jar
是同一目录中的两个 JAR 文件,并且a.jar
的清单包含了这个头部:
Class-Path: b.jar
那么b.jar
中的类将作为a.jar
中的类的扩展类。a.jar
中的类可以调用b.jar
中的类,而无需将b.jar
中的类命名在类路径中。a.jar
本身可能是扩展,也可能不是。如果b.jar
不在与a.jar
相同的目录中,那么Class-Path
头部的值应设置为b.jar
的相对路径名。
扮演下载扩展角色的类没有任何特殊之处。它们之所以被视为扩展,仅仅是因为它们被某个其他 JAR 文件的清单引用。
为了更好地理解下载扩展的工作原理,让我们创建一个并投入使用。
一个示例
假设你想要创建一个小程序,其中使用了前一节中的RectangleArea
类:
public final class RectangleArea { public static int area(java.awt.Rectangle r) { return r.width * r.height; } }
在前一节中,你将RectangleArea
类放入 JRE 的lib/ext
目录中,将其转换为已安装扩展。通过将其转换为已安装扩展,任何应用程序都可以使用RectangleArea
类,就好像它是 Java 平台的一部分。
如果你想要在小程序中使用RectangleArea
类,情况会有些不同。例如,假设你有一个名为AreaApplet
的小程序,其中使用了RectangleArea
类:
import java.applet.Applet; import java.awt.*; public class AreaApplet extends Applet { Rectangle r; public void init() { int width = 10; int height = 5; r = new Rectangle(width, height); } public void paint(Graphics g) { g.drawString("The rectangle's area is " + RectangleArea.area(r), 10, 10); } }
此小程序实例化一个 10 x
5 的矩形,然后使用RectangleArea.area
方法显示矩形的面积。
然而,你不能假设每个下载并使用你的小程序的人都会在他们的系统上有RectangleArea
类可用,作为已安装的扩展或其他方式。解决这个问题的一种方法是从服务器端提供RectangleArea
类,并且你可以通过将其作为下载扩展来实现。
要了解如何做到这一点,让我们假设你已经将AreaApplet
捆绑在名为AreaApplet.jar
的 JAR 文件中,并且类RectangleArea
捆绑在RectangleArea.jar
中。为了使RectangleArea.jar
被视为下载扩展,RectangleArea.jar
必须在AreaApplet.jar
的清单中的Class-Path
头中列出。例如,AreaApplet.jar
的清单可能如下所示:
Manifest-Version: 1.0 Class-Path: RectangleArea.jar
这个清单中Class-Path
头的值是RectangleArea.jar
,没有指定路径,表示RectangleArea.jar
位于与小程序的 JAR 文件相同的目录中。
关于Class-Path
头的更多信息
如果一个小程序或应用程序使用多个扩展,你可以在清单中列出多个 URL。例如,以下是一个有效的头部:
Class-Path: area.jar servlet.jar images/
在Class-Path
头中,列出的任何不以’/
‘结尾的 URL 都被假定为 JAR 文件。以’/
'结尾的 URL 表示目录。在上面的例子中,images/
可能是一个包含小程序或应用程序所需资源的目录。
请注意,清单文件中只允许一个Class-Path
头,并且清单中的每一行不能超过 72 个字符。如果需要指定的类路径条目超过一行的空间,可以将它们延伸到后续的续行上。每个续行都以两个空格开头。例如:
Class-Path: area.jar servlet.jar monitor.jar datasource.jar provider.jar gui.jar
未来的版本可能会取消每个标题只能有一个实例的限制,以及将行限制为仅有 72 个字符。
下载扩展可以“串联”,意味着一个下载扩展的清单可以有一个引用第二个扩展的Class-Path
头,第二个扩展可以引用第三个扩展,依此类推。
安装下载扩展
在上面的例子中,小程序下载的扩展仅在加载小程序的浏览器仍在运行时可用。然而,如果在小程序和扩展的清单中包含了额外的信息,小程序可以触发扩展的安装。
由于这种机制扩展了平台的核心 API,其使用应谨慎。它很少适用于仅由单个或少量应用程序使用的接口。所有可见的符号应遵循反向域名和类层次结构约定。
基本要求是小程序和它使用的扩展在它们的清单中提供版本信息,并且它们被签名。版本信息允许 Java 插件确保扩展代码具有小程序期望的版本。例如,AreaApplet
可以在其清单中指定一个areatest
扩展:
Manifest-Version: 1.0 Extension-List: areatest areatest-Extension-Name: area areatest-Specification-Version: 1.1 areatest-Implementation-Version: 1.1.2 areatest-Implementation-Vendor-Id: com.example areatest-Implementation-URL: http://www.example.com/test/area.jar
area.jar
中的清单将提供相应的信息:
Manifest-Version: 1.0 Extension-Name: area Specification-Vendor: Example Tech, Inc Specification-Version: 1.1 Implementation-Vendor-Id: com.example Implementation-Vendor: Example Tech, Inc Implementation-Version: 1.1.2
应用程序和扩展程序都必须由相同的签名者签名。对 jar 文件进行签名会直接修改它们,在清单文件中提供更多信息。签名有助于确保只有可信任的代码被安装。签署 jar 文件的简单方法是首先创建一个密钥库,然后使用该密钥库保存用于应用程序和扩展程序的证书。例如:
keytool -genkey -dname "cn=Fred" -alias test -validity 180
您将被要求输入密钥库和密钥密码。生成密钥后,jar 文件可以被签名:
jarsigner AreaApplet.jar test jarsigner area.jar test
您将被要求输入密钥库和密钥密码。有关keytool
、jarsigner
和其他安全工具的更多信息,请参阅Java 2 平台安全工具概述。
这里是AreaDemo.html
,它加载应用程序并导致扩展程序代码被下载并安装:
<html> <body> <applet code="AreaApplet.class" archive="AreaApplet.jar"/> </body> </html>
当页面首次加载时,用户会被告知该应用程序需要安装扩展程序。随后的对话框会通知用户有关已签名的应用程序。接受两者会将扩展程序安装在 JRE 的lib/ext
文件夹中并运行应用程序。
重新启动 Web 浏览器并加载相同的网页后,只会显示有关应用程序签名者的对话框,因为area.jar
已经安装。如果在不同的 Web 浏览器中打开AreaDemo.html
(假设两个浏览器都使用相同的 JRE),情况也是如此。
理解扩展类加载
扩展框架利用了类加载委托机制。当运行时环境需要为应用程序加载新类时,它按照以下顺序在以下位置查找类:
- 引导类:
rt.jar
中的运行时类,i18n.jar
中的国际化类等。 - 已安装扩展:JRE 的
lib/ext
目录中的 JAR 文件中的类,以及系统范围内的特定于平台的扩展目录(例如在 Solaris™操作系统上的/usr/jdk/packages/lib/ext
,但请注意,此目录仅适用于 Java™ 6 及更高版本)。 - 类路径:包括系统属性
java.class.path
指定路径上的类,包括 JAR 文件中的类。如果类路径上的 JAR 文件具有带有Class-Path
属性的清单,则还将搜索Class-Path
属性指定的 JAR 文件。默认情况下,java.class.path
属性的值为.
,即当前目录。您可以通过使用-classpath
或-cp
命令行选项或设置CLASSPATH
环境变量来更改该值。命令行选项会覆盖CLASSPATH
环境变量的设置。
优先级列表告诉您,例如,只有在要加载的类在rt.jar
、i18n.jar
或已安装扩展中的类中未找到时,才会搜索类路径。
除非您的软件为特殊目的实例化自己的类加载器,否则您实际上不需要了解比记住这个优先级列表更多的内容。特别是,您应该注意可能存在的任何类名冲突。例如,如果您在类路径上列出一个类,如果运行时环境代替加载了安装的扩展中找到的同名另一个类,您将得到意外的结果。
Java 类加载机制
Java 平台使用委托模型来加载类。基本思想是每个类加载器都有一个“父”类加载器。在加载类时,类加载器首先将类的搜索委托给其父类加载器,然后再尝试找到类本身。
以下是类加载 API 的一些亮点:
java.lang.ClassLoader
及其子类中的构造函数允许您在实例化新类加载器时指定一个父类加载器。如果您没有明确指定父类加载器,则虚拟机的系统类加载器将被分配为默认父类加载器。- 当调用
ClassLoader
中的loadClass
方法加载类时,它按顺序执行以下任务:
- 如果类已经被加载,它会返回该类。
- 否则,它将搜索新类的任务委托给父类加载器。
- 如果父类加载器未找到类,
loadClass
调用findClass
方法来查找和加载类。
- 如果父类加载器未找到类,则
ClassLoader
的findClass
方法将在当前类加载器中搜索该类。当您在应用程序中实例化类加载器子类时,可能需要重写此方法。 - 类
java.net.URLClassLoader
用作扩展和其他 JAR 文件的基本类加载器,覆盖了java.lang.ClassLoader
的findClass
方法,以在一个或多个指定的 URL 中搜索类和资源。
要查看一个使用与 JAR 文件相关的 API 的示例应用程序,请参阅本教程中的使用与 JAR 相关的 API 课程。
类加载和java
命令
Java 平台的类加载机制体现在java
命令中。
- 在
java
工具中,-classpath
选项是设置java.class.path
属性的简便方式。 -cp
和-classpath
选项是等效的。-jar
选项用于运行打包在 JAR 文件中的应用程序。有关此选项的描述和示例,请参阅本教程中的运行 JAR 打包软件课程。
创建可扩展应用程序
下面涵盖了以下主题:
- 介绍
- 字典服务示例
- 运行 DictionaryServiceDemo 示例
- 编译和运行 DictionaryServiceDemo 示例
- 理解 DictionaryServiceDemo 示例
- 定义服务提供者接口
- 定义检索服务提供者实现的服务
- 单例设计模式
- 实现服务提供者
- 注册服务提供者
- 创建使用服务和服务提供者的客户端
- 将服务提供者、服务和服务客户端打包在 JAR 文件中
- 将服务提供者打包在 JAR 文件中
- 将字典 SPI 和字典服务打包在 JAR 文件中
- 将客户端打包在 JAR 文件中
- 运行客户端
- ServiceLoader 类
- ServiceLoader API 的限制
- 摘要
Java 中文官方教程 2022 版(四十二)(4)https://developer.aliyun.com/article/1488235