• 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()