最近想做一个功能,就是把我们编译后的字节码及其资源文件打包成一个可执行的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 @Override 19 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 List<File> userSelectedFiles = new ArrayList<>(); 24 // 我们经过分析得到的最终会被打包的文件 25 private List<File> 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 @Override 102 public String getDescription() { 103 return "可执行Jar"; 104 } 105 106 @Override 107 public boolean accept(File f) { 108 return f.getName().endsWith(".jar"); 109 } 110 }); 111 } 112 113 @Override 114 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<String> 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 Iterator<File> itd,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<File> 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<File> fileArr) { 67 // 去重复项 68 Set<String> directories = new HashSet<>(); 69 Set<String> 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<String> itf = files.iterator(); itf.hasNext();) { 78 fpath = itf.next(); 79 for(Iterator<String> 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<String> directories1 = new HashSet<>(directories); 87 for(Iterator<String> itd1 = directories.iterator(); itd1.hasNext();) { 88 dpath1 = itd1.next(); 89 for(Iterator<String> 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 List<File> userSelectedFiles = new ArrayList<>(); 38 // 我们经过分析得到的最终会被打包的文件 39 private List<File> finalFiles = new ArrayList<>(); 40 // 文件打包路径及物理文件 41 private Map<String, File> 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 @Override 118 public String getDescription() { 119 return "可执行Jar"; 120 } 121 122 @Override 123 public boolean accept(File f) { 124 return f.getName().endsWith(".jar"); 125 } 126 }); 127 } 128 129 @Override 130 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<File> 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<File> fileArr) { 235 if(file.isDirectory()) 236 for(File child : file.listFiles()) 237 addFileToList(child, fileArr); 238 else 239 fileArr.add(file); 240 } 241 242 // 去除重复项 243 private void removeDuplicateItems(List<File> fileArr) { 244 // 去重复项 245 Set<String> directories = new HashSet<>(); 246 Set<String> files = new HashSet<>(); 247 for(File file : fileArr) 248 if(file.isDirectory()) 249 directories.add(file.getAbsolutePath()); 250 else 251 files.add(file.getAbsolutePath()); 252 //去包含项(先去文件夹再去文件应该更好) 253 String fpath,dpath; 254 for(Iterator<String> itf = files.iterator(); itf.hasNext();) { 255 fpath = itf.next(); 256 for(Iterator<String> 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<String> directories1 = new HashSet<>(directories); 264 for(Iterator<String> itd1 = directories.iterator(); itd1.hasNext();) { 265 dpath1 = itd1.next(); 266 for(Iterator<String> 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<String> cobMainClass; 290 private JButton btnCls; 291 private JButton btnConfirm; 292 private JFileChooser jfcSave; 293 }
这里有我导出的文件(这个是eclipse导出的,它在manifest中加入了classPath没有错误,我有时候加入后有问题)。
(最后编辑时间2014-03-02 16:12:50)
来源:https://www.cnblogs.com/Johness/p/3576920.html