- JAR文件
- 将应用程序打包时,使用者希望仅提供一个单独的文件。Java归档(JAR)文件就是为此目的而设计的。
- 一个JAR文件即可以包含类文件,也可以包含诸如图像和声音这些其他类型的文件。
- JAR文件是压缩的,ZIP压缩格式。
- 创建JAR文件:
- 通常命令格式如下: jar options File1 File2 . . .
- jar程序的可选项:
- c 创建新或空的存档文件并加入文件。create
- C 暂时改变目录。
- e 清单文件中创建一个条目
- f 将JAR文件名指定为第二个命令行参数
- i 建立索引文件 index
- m 将一个清单文件添加到JAR文件中 menifest
- M 不为条目创建清单文件
- t 显示内容表 table
- u 更新一个已有的JAR文件 update
- v 生成详细的输出结果
- x 解压文件
- 0 存储,不进行ZIP压缩
- 清单文件
- 被命名为 MANIFEST.MF,它位于JAR文件的一个特殊 META-INF子目录 中。
- 复杂清单包含很多条目,被分为多个节。第一节 为 主节(作用于整个JAR文件)。随后的条目用来指定 已命名条目的属性(起始于名为Name的条目)。
- 节与节之间空行分隔。
- 可执行JAR文件
- 使用jar命令中的e选项指定入口点,即通常需要在调用java程序加载器时指定的类:
- jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add
- 或者,在清单中指定应用程序的主类:
- Main-Class: com.mycompany.mypkg.MainAppClass
- 上面两种方式取其一,通过下列的命令即可启动应用程序
- java -jar MyProgram.jar
- Windows平台中,Java运行时安装器将建立一个 扩展名为.jar 的文件和与 javaw -jar命令 相关联来启动文件(与java命令不同, javaw不打开shell窗口)。
- 在Windows平台中,可以使用第三方的包装器工具将JAR文件转换成Windows可执行文件。包装器是扩展名为 .exe的Windows程序,可以查找和加载Java虚拟机。如:Launch4J 和 IzPack 。
- 资源:
- applet和应用程序使用的类通常需要一些相关的数据文件,如:
- 图像和声音文件
- 带有消息字符串和按钮标签的文本文件
- 二进制数据文件,如描述地图布局的文件
- 获取资源的必要步骤:
- 获得具有资源的Class对象,如:AboutPanel.class
- 如果资源是一个图像或声音文件,就需要调用 getresource(filename) 获得作为URL的资源位置,然后利用getImage或getAudioClip方法进行读取。(Uniform Resource Locator)
- 其他资源,可以直接使用 getResourceAsStream方法读取文件中的数据。
- 重点:类加载器可以记住如何定位类,然后在同一位置查找关联的资源。
- URL url = ResourceTest.class.getResource("about.gif")
- InputStream stream = ResourceTest.class.getResourceAsStream("about.text")
- 层级资源名(相对资源名): data/text/about.txt (相对于加载这个资源的类所在的包)。(必须使用“/”作为分隔符)
- 一个以“/”开头的资源名为绝对资源名:/corejava/title.txt(定位于corejava目录下)。(定位方式与类在包中的定位方式一样)
- 创建JAR文件和执行这个程序的命令:(假定已经定位到正确的目录)
javac resource/ResourceTest.java
jar cvfm ResourceTest.jar resource/ResourceTest.mf resource/*.class resource/*.gif resource/*.txt
java -jar ResourceTest.jar
- 密封
- 可以将Java包密封(seal)以保证不会有其他的类加入到其中。
- 默认情况下,JAR文件中的包是没有密封的。
- 清单文件的主节加入一行:Sealed: true 。可以改变全局的默认设定。
- 对于每个单独的包,可以单独在某节设定是否想要密封。
- 要想密封一个包,需要创建一个包含清单指令的文本文件。然后用常规的方式运行命令:
- jar cvfm MyArchive.jar manifest.mf files to add
- Package Sealing的益处:Package Sealing所能带来的好处主要是版本一致性. 我们知道Java 在运行时是严格按照classpath中定义的顺序进行装载和检查,尤其是现在Java开源包满天飞, 很有可能你的Java应用程序或者中间件的classpath中会在不同的Jar文件中包含同一个Package的不同版本。这会使得程序运行产生不一致性结果,很难发现。
- 应用首选项的存储:
- 应用用户通常希望能够保存他们的首选项和定制信息,以后再次启动应用时再恢复这些配置。
- Java应用的传统做法:将配置信息保存在属性文件中。首选项API:更加健壮的解决方案。
- 属性映射(property map):
- 一种存储 键/值 对的数据结构。通常用来存储配置信息。
- 三个特性:
- 键和值都是字符串
- 映射可以很容易存入文件以及从文件加载
- 有一个二级表保存默认值
- 可以使用store方法,将属性映射列表保存到一个文件中。
- 习惯上,会把程序属性存储在用户主目录的一个子目录中。目录名通常以一个点号开头(约定说明:这是一个对用户隐藏的系统目录)。
- 可以为程序属性提供默认值
- 如果存储复杂的配置信息,就应当使用Preferences类。
- java.util.Properties
- Properties()
- Properties(Properties defaults)
- String getProperty(String key)
- String getProperty(String key, string defaultValue)
- Object setProperty(String key, String value)
- void load(InputStream in) throws IOException
- void store(OutputStream out, String header) //header存储文件第一行的标题
- java.lang.System
- Properties getProperties()
- String getProperty(String key) //以上两个方法都要求应用必须有相应的 权限,否则会抛出一个安全异常
- 示例程序:
package properties;
import javax.swing.JFrame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
public class PropertiesFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
PropertiesFrame frame = new PropertiesFrame();
frame.setVisible(true);
});
}
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
private Properties settings;
private File propertiesFile;
public PropertiesFrame() {
String userDir = System.getProperty("user.home");
File propertiesDir = new File(userDir, ".corejavatest");
if( !propertiesDir.exists() ) propertiesDir.mkdir();
propertiesFile = new File(propertiesDir, "program.properties");
Properties defaultProperties = new Properties();
defaultProperties.setProperty("left", "0");
defaultProperties.setProperty("top", "0");
defaultProperties.setProperty("width", String.valueOf(DEFAULT_WIDTH));
defaultProperties.setProperty("height", String.valueOf(DEFAULT_HEIGHT));
defaultProperties.setProperty("title", "");
settings = new Properties(defaultProperties);
if( propertiesFile.exists() ) {
try(InputStream in = new FileInputStream(propertiesFile)){
settings.load(in);
}catch(IOException e) {
e.printStackTrace();
}
}
int left = Integer.parseInt(settings.getProperty("left"));
int top = Integer.parseInt(settings.getProperty("top"));
int width = Integer.parseInt(settings.getProperty("width"));
int height = Integer.parseInt(settings.getProperty("height"));
String title = settings.getProperty("title");
setBounds(left, top, width, height);
if( title.equals("")) {
title = JOptionPane.showInputDialog("Please input a title for frame: ");
}
if(title == null ) title = "";
setTitle(title);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
settings.setProperty("left", "" + getX());
settings.setProperty("top", ""+ getY());
settings.setProperty("width", "" + getWidth());
settings.setProperty("height", "" + getHeight());
settings.setProperty("title", getTitle());
try(OutputStream out = new FileOutputStream(propertiesFile)){
settings.store(out, "Program Properties");
}catch(IOException e ) {
e.printStackTrace();
}
System.exit(0);
}
});
}
}
- 首选项API
- 使用属性文件的缺点:
- 有些操作系统没有 主目录 的概念,因此难以统一配置文件的位置。
- 配置文件的命名没有标准约定,容易发生命名冲突。
- 有些操作系统有一个存储配置信息的中心存储库(如,Windows的注册表)。Preference类 以一种 平台无关 的方式提供了这样一个中心存储库。
- Preferences存储库有一个树状结构,节点路径名类似于 /com/mycompany/myapp (类似于 包名)。实际上,API的设计者就 建议 配置节点路径 要与程序中的 包名 一致。
- 存储库的各个节点分别有一个单独的 键/值对表,可以用来存储数值、字符串或字节数组,但不能存储可串行化的对象。
- 如果节点的路径名等于包名,就有便捷的方式来获得这个节点: Preferences.userNodeForPackage(obj.getClass()) 。
- 需要说明的是,读取信息时必须指定一个默认值。以防止没有可用的存储库数据。
- java.util.Preferences
- void exportSubtree(OutputStream out)
- void importPreferences(InputStream in)
- 示例程序:
package preferences;
import javax.swing.JFrame;
import java.util.prefs.*;
import javax.swing.*;
import java.io.*;
import javax.swing.filechooser.*;
import java.awt.EventQueue;
import java.awt.event.*;
public class PreferencesFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
PreferencesFrame frame = new PreferencesFrame();
frame.setVisible(true);
});
}
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
private Preferences root = Preferences.userRoot();
private Preferences node = root.node("/com/horstmann/corejava");
public PreferencesFrame() {
int left = node.getInt("left", 0);
int top = node.getInt("top",0);
int width = node.getInt("width", DEFAULT_WIDTH);
int height = node.getInt("height", DEFAULT_HEIGHT);
setBounds(left, top, width, height);
String title = node.get("title", "");
if(title.equals("")) {
title = JOptionPane.showInputDialog("Please input a title for the frame!");
}
if(title == null ) title = "";
setTitle(title);
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("."));
chooser.setFileFilter( new FileNameExtensionFilter("XML files", "xml") );
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenuItem exportItem = new JMenuItem("Export preferences");
fileMenu.add(exportItem);
exportItem.addActionListener(event->{
if( chooser.showSaveDialog(PreferencesFrame.this) == JFileChooser.APPROVE_OPTION ) {
try {
savePreferences();
OutputStream out = new FileOutputStream( chooser.getSelectedFile() );
node.exportSubtree(out);
out.close();
}catch(Exception e ) {
e.printStackTrace();
}
}
});
JMenuItem importItem = new JMenuItem("Import preferences");
fileMenu.add(importItem);
importItem.addActionListener(event->{
if( chooser.showOpenDialog(PreferencesFrame.this) == JFileChooser.APPROVE_OPTION ) {
try {
InputStream in = new FileInputStream(chooser.getSelectedFile());
Preferences.importPreferences(in);
updateFrame();
in.close();
}catch( Exception e ) {
e.printStackTrace();
}
}
});
JMenuItem exitItem = new JMenuItem("Exit");
fileMenu.add(exitItem);
exitItem.addActionListener(event->{
savePreferences();
System.exit(0);
});
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
savePreferences();
System.exit(0);
}
});
}
public void updateFrame() {
setVisible(false);
int left = node.getInt("left", 0);
int top = node.getInt("top",0);
int width = node.getInt("width", DEFAULT_WIDTH);
int height = node.getInt("height", DEFAULT_HEIGHT);
setBounds(left, top, width, height);
String title = node.get("title", "");
setTitle(title);
setVisible(true);
}
public void savePreferences() {
node.putInt("left", getX());
node.putInt("top", getY());
node.putInt("width", getWidth());
node.putInt("height", getHeight());
node.put("title", getTitle());
}
}
- 服务加载器:
- 有时会采用插件体系结构的应用。通常提供一个插件时,程序希望插件设计者能有一些自由来确定如何实现插件的特性,另外还可以有多个实现以供选择。利用 ServiceLoader类 可以很容易地加载符合一个公共接口的插件。
- 步骤:
- 定义一个接口(也可以是超类):服务
- 服务提供者可以提供一个或多个实现这个服务的类(实现类可以放在任意的包中,每个实现类必须有一个无参构造器)
- META-INF/services 目录下建立一个UTF-8的文本文件,文件名 必须 与 完全限定类名 (服务的全名)一致。文本文件中再添加所有实现的全名(一行一个)。
- 完成以上步骤后就可以初始化一个服务加载器(只在程序中完成一次)。服务加载器实现了Iterator接口,可以迭代提供的所有服务。
- java.util.ServiceLoader<S>
- static <S> ServiceLoader<S> load(Class<S> service)
- Iterator<S> iterator()