Java基础知识:部署Java应用程序

本文详细解析了Java归档(JAR)文件的创建、执行与资源管理机制,包括清单文件的作用、可执行JAR文件的生成、资源的获取途径、包密封的重要性以及应用首选项的存储方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值