最近想做一个功能,就是把我们编译后的字节码及其资源文件打包成一个可执行的jar包,在装有jre的机器上双击就能运行。
首先是我们需要选择哪些字节码和文件需要打包到文件中,这个我们用JFileChooser来做,让用户选择,我做了一个窗体来让用户选择。
效果如下:
我们让浏览文件系统,并选择需要打包的文件夹,然后计算出可以作为启动类的文件,通过下方的下拉让用户选择。
生成文件路径在确认按钮点击后弹出文件保存框让用户选择就好(也可以弹出输入框)。
代码如下:
Main
1 package org.coderecord.commons.ejarmaker; 2 3 import java.awt.EventQueue; 4 5 import javax.swing.UIManager; 6 import javax.swing.UnsupportedLookAndFeelException; 7 8 public class Main { 9 10 public static void main(String[] args) {11 try {12 UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");13 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {14 e.printStackTrace();15 }16 EventQueue.invokeLater(new Runnable() {17 18 @Override19 public void run() {20 new FrmMain().setVisible(true);21 }22 });23 }24 25 }
FrmMain(只是界面代码,业务代码最后贴出)
1 package org.coderecord.commons.ejarmaker; 2 3 import java.awt.Toolkit; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.io.File; 7 import java.util.ArrayList; 8 import java.util.List; 9 10 import javax.swing.JButton; 11 import javax.swing.JComboBox; 12 import javax.swing.JFileChooser; 13 import javax.swing.JFrame; 14 import javax.swing.JLabel; 15 import javax.swing.JScrollPane; 16 import javax.swing.JTextArea; 17 import javax.swing.filechooser.FileFilter; 18 19 public class FrmMain extends JFrame implements ActionListener { 20 21 private static final long serialVersionUID = 2016913328739206536L; 22 // 选择的文件(用户在文件选择器中选择的) 23 private ListuserSelectedFiles = new ArrayList<>(); 24 // 我们经过分析得到的最终会被打包的文件 25 private List finalFiles = new ArrayList<>(); 26 27 public FrmMain() { 28 setSize(480, 320); 29 setResizable(false); 30 setLocationRelativeTo(null); 31 setTitle("通用可执行Jar包生成工具"); 32 setDefaultCloseOperation(EXIT_ON_CLOSE); 33 setLayout(null); 34 // 在运行时获取资源文件的方式,一定是使用Class.getResource方式 35 // 在jar包中这种方式也行得通 36 // ‘/’代表根路径 37 setIconImage(Toolkit.getDefaultToolkit().getImage(FrmMain.class.getResource("/resources/icon.png"))); 38 initComponents(); 39 } 40 41 // 初始化组件 42 private void initComponents() { 43 // 提示 44 lblTip = new JLabel("选择需要打包的文件并设置启动类"); 45 lblTip.setLocation(20, 10); 46 lblTip.setSize(350, 20); 47 add(lblTip); 48 49 // 浏览按钮 50 btnBrowser = new JButton("浏 览"); 51 btnBrowser.setLocation(380, 10); 52 btnBrowser.setSize(80, 24); 53 btnBrowser.addActionListener(this); 54 add(btnBrowser); 55 56 // 展示已选择文件 57 JScrollPane jspFiles = new JScrollPane(); 58 txtFiles = new JTextArea(); 59 txtFiles.setEditable(false); 60 jspFiles.setSize(440, 160); 61 jspFiles.setLocation(20, 40); 62 txtFiles.setSize(440, 201600); 63 txtFiles.setLocation(20, 40); 64 txtFiles.setFocusable(false); 65 jspFiles.setViewportView(txtFiles); 66 add(jspFiles); 67 68 // 选择启动类 69 cobMainClass = new JComboBox<>(); 70 cobMainClass.setSize(440, 30); 71 cobMainClass.setLocation(20, 210); 72 add(cobMainClass); 73 74 // 清除已选 75 btnCls = new JButton("重 选"); 76 btnCls.setLocation(20, 250); 77 btnCls.setSize(80, 24); 78 btnCls.addActionListener(this); 79 add(btnCls); 80 81 // 确认按钮 82 btnConfirm = new JButton("确认"); 83 btnConfirm.setSize(80, 24); 84 btnConfirm.setLocation(380, 250); 85 btnConfirm.addActionListener(this); 86 add(btnConfirm); 87 88 // 文件选择器 89 jfcSelect = new JFileChooser(); 90 // 可以选择文件和文件夹 91 jfcSelect.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 92 // 可以多选 93 jfcSelect.setMultiSelectionEnabled(true); 94 95 // 文件保存 96 jfcSave = new JFileChooser(); 97 // 设置只接受以“.jar”结尾的文件 98 jfcSave.setAcceptAllFileFilterUsed(false); 99 jfcSave.setFileFilter(new FileFilter() {100 101 @Override102 public String getDescription() {103 return "可执行Jar";104 }105 106 @Override107 public boolean accept(File f) {108 return f.getName().endsWith(".jar");109 }110 });111 }112 113 @Override114 public void actionPerformed(ActionEvent e) {115 116 }117 118 private JLabel lblTip;119 private JButton btnBrowser;120 private JFileChooser jfcSelect;121 private JTextArea txtFiles;122 private JComboBox cobMainClass;123 private JButton btnCls;124 private JButton btnConfirm;125 private JFileChooser jfcSave;126 }
然后开始业务部分,首先是选择文件,我们允许用户选择多个文件和文件夹(甚至可以通过多次选择来选择不同盘符、路径下的文件和文件夹),在选择后可能有重复的地方或两次选择后有包含的项目,我们要去除。
我们为“浏览”按钮事件添加处理,让用户选择文件并处理选中文件:
1 @Override 2 public void actionPerformed(ActionEvent e) { 3 if(e.getSource() == btnBrowser) { 4 // 浏览 5 int result = jfcSelect.showOpenDialog(this); 6 7 // 选择了文件 8 if(result == JFileChooser.APPROVE_OPTION) { 9 for(File file : jfcSelect.getSelectedFiles()) 10 userSelectedFiles.add(file); 11 12 // 整理选择的文件,去除重复项 13 removeDuplicateItems(userSelectedFiles); 14 15 // 重新计算选中文件 16 finalFiles.clear(); 17 for(File file : userSelectedFiles) 18 addFileToList(file, finalFiles); 19 20 // 计算文件展示打包路径及展示路径 21 // 计算可启动类路径 22 // 展示到文本框中 23 cobMainClass.removeAllItems(); 24 txtFiles.setText(""); 25 File file,direc; 26 String filePath,direcPath; 27 Iteratoritd,itf; 28 for(itd = userSelectedFiles.iterator(); itd.hasNext();) { 29 direc = itd.next(); 30 direcPath = direc.getAbsolutePath(); 31 for(itf = finalFiles.iterator(); itf.hasNext();) { 32 file = itf.next(); 33 filePath = file.getAbsolutePath(); 34 if(filePath.equalsIgnoreCase(direcPath)) { 35 txtFiles.append(file.getName() + "\n"); 36 filePaths.put(file.getName(), file); 37 //fileNames.put(file.getName(), file.getName().endsWith(".class")?file.getName().substring(0, file.getName().lastIndexOf('.')):file.getName()); 38 if(file.getName().endsWith(".class")) 39 cobMainClass.addItem(file.getName().endsWith(".class")?file.getName().substring(0, file.getName().lastIndexOf('.')):file.getName()); 40 itf.remove(); 41 } else if(filePath.startsWith(direcPath)) { 42 String nameTmp = filePath.substring(direcPath.lastIndexOf(File.separator) + 1).replace(File.separatorChar, '/'); 43 filePaths.put(nameTmp, file); 44 txtFiles.append(nameTmp + "\n"); 45 //fileNames.put(nameTmp, nameTmp.endsWith(".class")?nameTmp.substring(0, nameTmp.lastIndexOf('.')).replace('/', '.'):nameTmp); 46 if(nameTmp.endsWith(".class") && nameTmp.indexOf('$') == -1) 47 cobMainClass.addItem(nameTmp.substring(0, nameTmp.lastIndexOf('.')).replace('/', '.')); 48 itf.remove(); 49 } 50 } 51 } 52 } 53 } 54 } 55 56 // 添加文件(非文件夹)到集合 57 private void addFileToList(File file, List fileArr) { 58 if(file.isDirectory()) 59 for(File child : file.listFiles()) 60 addFileToList(child, fileArr); 61 else 62 fileArr.add(file); 63 } 64 65 // 去除重复项 66 private void removeDuplicateItems(List fileArr) { 67 // 去重复项 68 Set directories = new HashSet<>(); 69 Set files = new HashSet<>(); 70 for(File file : fileArr) 71 if(file.isDirectory()) 72 directories.add(file.getAbsolutePath()); 73 else 74 files.add(file.getAbsolutePath()); 75 //去包含项(先去文件夹再去文件应该更好) 76 String fpath,dpath; 77 for(Iterator itf = files.iterator(); itf.hasNext();) { 78 fpath = itf.next(); 79 for(Iterator itd = directories.iterator(); itd.hasNext();) { 80 dpath = itd.next(); 81 if(fpath.startsWith(dpath)) 82 itf.remove(); 83 } 84 } 85 String dpath1,dpath2; 86 Set directories1 = new HashSet<>(directories); 87 for(Iterator itd1 = directories.iterator(); itd1.hasNext();) { 88 dpath1 = itd1.next(); 89 for(Iterator itd2 = directories1.iterator(); itd2.hasNext();) { 90 dpath2 = itd2.next(); 91 if(dpath1.equals(dpath2)) 92 continue; 93 else if(dpath2.startsWith(dpath1)) 94 itd2.remove(); 95 else if(dpath1.startsWith(dpath2)) 96 itd1.remove(); 97 } 98 } 99 directories.addAll(directories1);100 101 fileArr.clear();102 for(String file : files)103 fileArr.add(new File(file));104 for(String directory : directories)105 fileArr.add(new File(directory));106 }
“重选”按钮点击后清除已选项,逻辑就先不详细介绍了。
然后是“确定”按钮,我们弹出文件保存框让用户选择保存位置,然后生成可执行的jar包:
1 @Override 2 public void actionPerformed(ActionEvent e) { 3 if(e.getSource() == btnBrowser) { 4 } else if(e.getSource() == btnCls) { 5 if(userSelectedFiles.size() == 0) return; 6 else if(JOptionPane.showConfirmDialog(this, "确定重选吗?将清除所有已选项!") == JOptionPane.OK_OPTION) { 7 userSelectedFiles.clear(); 8 finalFiles.clear(); 9 filePaths.clear();10 cobMainClass.removeAllItems();11 }12 } else if(e.getSource() == btnConfirm) {13 if(filePaths.size() == 0) {14 JOptionPane.showMessageDialog(this, "未选择文件", "错误", JOptionPane.ERROR_MESSAGE);15 return;16 } else if(cobMainClass.getSelectedItem() == null) {17 JOptionPane.showMessageDialog(this, "未选择启动类", "错误", JOptionPane.ERROR_MESSAGE);18 return;19 }20 // 打包21 int result = jfcSave.showSaveDialog(this);22 if(result == JFileChooser.APPROVE_OPTION) {23 try {24 // 清单文件25 Manifest man = new Manifest();26 // 版本和启动类路径必要27 man.getMainAttributes().putValue(Name.MANIFEST_VERSION.toString(), "1.0");28 man.getMainAttributes().putValue(Name.MAIN_CLASS.toString(), cobMainClass.getSelectedItem().toString());29 // Class-Path一定不要,除非能保证将引用类(即import的类)都联合打包了30 JarOutputStream jos = new JarOutputStream(new FileOutputStream(jfcSave.getSelectedFile()), man);31 jos.setLevel(Deflater.BEST_COMPRESSION);32 BufferedInputStream bis = null;33 byte[] cache = new byte[1024];34 StringBuffer config = new StringBuffer();35 for(String name : filePaths.keySet()) {36 bis = new BufferedInputStream(new FileInputStream(filePaths.get(name)), 1024);37 config.append(name).append('=').append(bis.available()).append('\n');38 jos.putNextEntry(new JarEntry(name));39 int count;40 while((count = bis.read(cache, 0, 1024)) != -1)41 jos.write(cache, 0, count);42 jos.closeEntry();43 bis.close();44 }45 jos.flush();46 jos.close();47 JOptionPane.showMessageDialog(this, "导出成功!", "成功", JOptionPane.INFORMATION_MESSAGE);48 System.exit(0);49 } catch(Exception ex) {50 JOptionPane.showMessageDialog(this, ex.getMessage(), "异常", JOptionPane.ERROR_MESSAGE);51 System.exit(1);52 }53 }54 }55 56 }
当然,这里还有一个小问题:选择文件(自己写的文件名就算不加后缀也能保存成功-_-)。
先展示一下结果:
在文件系统中选择:
导出到桌面:
运行一下:
我最后再将完整的源码贴出一份:
1 package org.coderecord.commons.ejarmaker; 2 3 import java.awt.Toolkit; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.io.BufferedInputStream; 7 import java.io.File; 8 import java.io.FileInputStream; 9 import java.io.FileOutputStream; 10 import java.util.ArrayList; 11 import java.util.HashSet; 12 import java.util.Hashtable; 13 import java.util.Iterator; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Set; 17 import java.util.jar.JarEntry; 18 import java.util.jar.JarOutputStream; 19 import java.util.jar.Manifest; 20 import java.util.jar.Attributes.Name; 21 import java.util.zip.Deflater; 22 23 import javax.swing.JButton; 24 import javax.swing.JComboBox; 25 import javax.swing.JFileChooser; 26 import javax.swing.JFrame; 27 import javax.swing.JLabel; 28 import javax.swing.JOptionPane; 29 import javax.swing.JScrollPane; 30 import javax.swing.JTextArea; 31 import javax.swing.filechooser.FileFilter; 32 33 public class FrmMain extends JFrame implements ActionListener { 34 35 private static final long serialVersionUID = 2016913328739206536L; 36 // 选择的文件(用户在文件选择器中选择的) 37 private ListuserSelectedFiles = new ArrayList<>(); 38 // 我们经过分析得到的最终会被打包的文件 39 private List finalFiles = new ArrayList<>(); 40 // 文件打包路径及物理文件 41 private Map filePaths = new Hashtable<>(); 42 43 public FrmMain() { 44 setSize(480, 320); 45 setResizable(false); 46 setLocationRelativeTo(null); 47 setTitle("通用可执行Jar包生成工具"); 48 setDefaultCloseOperation(EXIT_ON_CLOSE); 49 setLayout(null); 50 // 在运行时获取资源文件的方式,一定是使用Class.getResource方式 51 // 在jar包中这种方式也行得通 52 // ‘/’代表根路径 53 setIconImage(Toolkit.getDefaultToolkit().getImage(FrmMain.class.getResource("/resources/icon.png"))); 54 initComponents(); 55 } 56 57 // 初始化组件 58 private void initComponents() { 59 // 提示 60 lblTip = new JLabel("选择需要打包的文件并设置启动类"); 61 lblTip.setLocation(20, 10); 62 lblTip.setSize(350, 20); 63 add(lblTip); 64 65 // 浏览按钮 66 btnBrowser = new JButton("浏 览"); 67 btnBrowser.setLocation(380, 10); 68 btnBrowser.setSize(80, 24); 69 btnBrowser.addActionListener(this); 70 add(btnBrowser); 71 72 // 展示已选择文件 73 JScrollPane jspFiles = new JScrollPane(); 74 txtFiles = new JTextArea(); 75 txtFiles.setEditable(false); 76 jspFiles.setSize(440, 160); 77 jspFiles.setLocation(20, 40); 78 txtFiles.setSize(440, 201600); 79 txtFiles.setLocation(20, 40); 80 txtFiles.setFocusable(false); 81 jspFiles.setViewportView(txtFiles); 82 add(jspFiles); 83 84 // 选择启动类 85 cobMainClass = new JComboBox<>(); 86 cobMainClass.setSize(440, 30); 87 cobMainClass.setLocation(20, 210); 88 add(cobMainClass); 89 90 // 清除已选 91 btnCls = new JButton("重 选"); 92 btnCls.setLocation(20, 250); 93 btnCls.setSize(80, 24); 94 btnCls.addActionListener(this); 95 add(btnCls); 96 97 // 确认按钮 98 btnConfirm = new JButton("确认"); 99 btnConfirm.setSize(80, 24);100 btnConfirm.setLocation(380, 250);101 btnConfirm.addActionListener(this);102 add(btnConfirm);103 104 // 文件选择器105 jfcSelect = new JFileChooser();106 // 可以选择文件和文件夹107 jfcSelect.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);108 // 可以多选109 jfcSelect.setMultiSelectionEnabled(true);110 111 // 文件保存112 jfcSave = new JFileChooser();113 // 设置只接受以“.jar”结尾的文件114 jfcSave.setAcceptAllFileFilterUsed(false);115 jfcSave.setFileFilter(new FileFilter() {116 117 @Override118 public String getDescription() {119 return "可执行Jar";120 }121 122 @Override123 public boolean accept(File f) {124 return f.getName().endsWith(".jar");125 }126 });127 }128 129 @Override130 public void actionPerformed(ActionEvent e) {131 if(e.getSource() == btnBrowser) {132 // 浏览133 int result = jfcSelect.showOpenDialog(this);134 135 // 选择了文件136 if(result == JFileChooser.APPROVE_OPTION) {137 for(File file : jfcSelect.getSelectedFiles())138 userSelectedFiles.add(file);139 140 // 整理选择的文件,去除重复项141 removeDuplicateItems(userSelectedFiles);142 143 // 重新计算选中文件144 finalFiles.clear();145 for(File file : userSelectedFiles)146 addFileToList(file, finalFiles);147 148 // 计算文件展示打包路径及展示路径149 // 计算可启动类路径150 // 展示到文本框中151 cobMainClass.removeAllItems();152 txtFiles.setText("");153 File file,direc;154 String filePath,direcPath;155 Iterator itd,itf;156 for(itd = userSelectedFiles.iterator(); itd.hasNext();) {157 direc = itd.next();158 direcPath = direc.getAbsolutePath();159 for(itf = finalFiles.iterator(); itf.hasNext();) {160 file = itf.next();161 filePath = file.getAbsolutePath();162 if(filePath.equalsIgnoreCase(direcPath)) {163 txtFiles.append(file.getName() + "\n");164 filePaths.put(file.getName(), file);165 if(file.getName().endsWith(".class"))166 cobMainClass.addItem(file.getName().endsWith(".class")?file.getName().substring(0, file.getName().lastIndexOf('.')):file.getName());167 itf.remove();168 } else if(filePath.startsWith(direcPath)) {169 String nameTmp = filePath.substring(direcPath.lastIndexOf(File.separator) + 1).replace(File.separatorChar, '/'); 170 filePaths.put(nameTmp, file);171 txtFiles.append(nameTmp + "\n");172 if(nameTmp.endsWith(".class") && nameTmp.indexOf('$') == -1)173 cobMainClass.addItem(nameTmp.substring(0, nameTmp.lastIndexOf('.')).replace('/', '.'));174 itf.remove();175 }176 }177 }178 }179 } else if(e.getSource() == btnCls) {180 if(userSelectedFiles.size() == 0) return;181 else if(JOptionPane.showConfirmDialog(this, "确定重选吗?将清除所有已选项!") == JOptionPane.OK_OPTION) {182 userSelectedFiles.clear();183 finalFiles.clear();184 filePaths.clear();185 cobMainClass.removeAllItems();186 }187 } else if(e.getSource() == btnConfirm) {188 if(filePaths.size() == 0) {189 JOptionPane.showMessageDialog(this, "未选择文件", "错误", JOptionPane.ERROR_MESSAGE);190 return;191 } else if(cobMainClass.getSelectedItem() == null) {192 JOptionPane.showMessageDialog(this, "未选择启动类", "错误", JOptionPane.ERROR_MESSAGE);193 return;194 }195 // 打包196 int result = jfcSave.showSaveDialog(this);197 if(result == JFileChooser.APPROVE_OPTION) {198 try {199 // 清单文件200 Manifest man = new Manifest();201 // 版本和启动类路径必要202 man.getMainAttributes().putValue(Name.MANIFEST_VERSION.toString(), "1.0");203 man.getMainAttributes().putValue(Name.MAIN_CLASS.toString(), cobMainClass.getSelectedItem().toString());204 // Class-Path一定不要,除非能保证将引用类(即import的类)都联合打包了205 JarOutputStream jos = new JarOutputStream(new FileOutputStream(jfcSave.getSelectedFile()), man);206 jos.setLevel(Deflater.BEST_COMPRESSION);207 BufferedInputStream bis = null;208 byte[] cache = new byte[1024];209 StringBuffer config = new StringBuffer();210 for(String name : filePaths.keySet()) {211 bis = new BufferedInputStream(new FileInputStream(filePaths.get(name)), 1024);212 config.append(name).append('=').append(bis.available()).append('\n');213 jos.putNextEntry(new JarEntry(name));214 int count;215 while((count = bis.read(cache, 0, 1024)) != -1)216 jos.write(cache, 0, count);217 jos.closeEntry();218 bis.close();219 }220 jos.flush();221 jos.close();222 JOptionPane.showMessageDialog(this, "导出成功!", "成功", JOptionPane.INFORMATION_MESSAGE);223 System.exit(0);224 } catch(Exception ex) {225 JOptionPane.showMessageDialog(this, ex.getMessage(), "异常", JOptionPane.ERROR_MESSAGE);226 System.exit(1);227 }228 }229 }230 231 }232 233 // 添加文件(非文件夹)到集合234 private void addFileToList(File file, List fileArr) {235 if(file.isDirectory())236 for(File child : file.listFiles())237 addFileToList(child, fileArr);238 else239 fileArr.add(file);240 }241 242 // 去除重复项243 private void removeDuplicateItems(List fileArr) {244 // 去重复项245 Set directories = new HashSet<>();246 Set files = new HashSet<>();247 for(File file : fileArr)248 if(file.isDirectory())249 directories.add(file.getAbsolutePath());250 else251 files.add(file.getAbsolutePath());252 //去包含项(先去文件夹再去文件应该更好)253 String fpath,dpath;254 for(Iterator itf = files.iterator(); itf.hasNext();) {255 fpath = itf.next();256 for(Iterator itd = directories.iterator(); itd.hasNext();) {257 dpath = itd.next();258 if(fpath.startsWith(dpath))259 itf.remove();260 }261 }262 String dpath1,dpath2;263 Set directories1 = new HashSet<>(directories);264 for(Iterator itd1 = directories.iterator(); itd1.hasNext();) {265 dpath1 = itd1.next();266 for(Iterator itd2 = directories1.iterator(); itd2.hasNext();) {267 dpath2 = itd2.next();268 if(dpath1.equals(dpath2))269 continue;270 else if(dpath2.startsWith(dpath1))271 itd2.remove();272 else if(dpath1.startsWith(dpath2))273 itd1.remove();274 }275 }276 directories.addAll(directories1);277 278 fileArr.clear();279 for(String file : files)280 fileArr.add(new File(file));281 for(String directory : directories)282 fileArr.add(new File(directory));283 }284 285 private JLabel lblTip;286 private JButton btnBrowser;287 private JFileChooser jfcSelect;288 private JTextArea txtFiles;289 private JComboBox cobMainClass;290 private JButton btnCls;291 private JButton btnConfirm;292 private JFileChooser jfcSave;293 }
有我导出的文件(这个是eclipse导出的,它在manifest中加入了classPath没有错误,我有时候加入后有问题)。
欢迎您移步我们的交流群,无聊的时候大家一起打发时间:
或者通过QQ与我联系:
(最后编辑时间2014-03-02 16:12:50)