Java托盘以及打包exe

Java干货

 JavaFx是从JDK1.8开始加入JDK,Java11单独分离,相对于之前的Java桌面程序友好了很多
 主要讲一下SpringBoot集成JavaFx的干货
 1.右下角生成托盘利于关闭(windows再也不用cmd查端口杀8080了)
 2.打成exe方便部分win的情况下实施

Java托盘

1.启动类增加代码

Application.launch(TestApplication.class, args);

如图
在这里插入图片描述

2.JVM增加参数

-Djava.awt.headless=false -Dfile.encoding=GBK

-Djava.awt.headless=false 是防止awt报错的
-Dfile.encoding=GBK是方式托盘中文乱码

3.加3个类1张图

SystemTrayUtil(必须有的托盘类)
import javafx.application.Platform;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.net.URL;


/**
 * 自定义系统托盘(单例模式)
 */
@Slf4j
@Component
public class SystemTrayUtil {

	private static SystemTrayUtil instance;
//	private static MenuItem showItem;
	private static MenuItem exitItem;
	private static TrayIcon trayIcon;
	private static ActionListener showListener;
	private static ActionListener exitListener;
	private static MouseListener mouseListener;
	//图片默认路径
	private static String png;
	//服务名
	private static String appName;

	@Value("${javaFx.png:/icon.png}")
	public void setPng(String png) {
		SystemTrayUtil.png = png;
	}

	@Value("${spring.application.name:default}")
	public void setAppName(String appName) {
		SystemTrayUtil.appName = appName;
	}

	static{
		//执行stage.close()方法,窗口不直接退出
		Platform.setImplicitExit(false);
		//菜单项(打开)中文乱码的问题是编译器的锅,如果使用IDEA,需要在Run-Edit Configuration在LoginApplication中的VM Options中添加-Dfile.encoding=GBK
		//如果使用Eclipse,需要右键Run as-选择Run Configuration,在第二栏Arguments选项中的VM Options中添加-Dfile.encoding=GBK
//		showItem = new MenuItem("打开");
		//菜单项(退出)
		exitItem = new MenuItem("退出");
		//初始化监听事件(空)
		showListener = e -> Platform.runLater(() -> {});
		exitListener = e -> {};
		mouseListener = new MouseAdapter() {};
	}

	public synchronized static SystemTrayUtil getInstance(){
		if(Strings.isEmpty(instance)){
			instance = new SystemTrayUtil();
		}
		return instance;
	}

	private SystemTrayUtil(){
		try {
			if(Strings.isEmpty(SystemTrayUtil.png) || Strings.isEmpty(SystemTrayUtil.appName)) return;
			//检查系统是否支持托盘
			if (!SystemTray.isSupported()) {
				//系统托盘不支持
				log.info(Thread.currentThread().getStackTrace()[ 1 ].getClassName() + ":系统托盘不支持");
				return;
			}
			//此处不能选择ico格式的图片,要使用16*16的png格式的图片
			URL url = SystemTrayUtil.class.getResource(SystemTrayUtil.png);
			Image image = Toolkit.getDefaultToolkit().getImage(url);
			//系统托盘图标
			trayIcon = new TrayIcon(image);
			//设置图标尺寸自动适应
			trayIcon.setImageAutoSize(true);
			//系统托盘
			SystemTray tray = SystemTray.getSystemTray();
			//弹出式菜单组件
			final PopupMenu popup = new PopupMenu();
//			popup.add(showItem);
			popup.add(exitItem);
			trayIcon.setPopupMenu(popup);
			//鼠标移到系统托盘,会显示提示文本
			trayIcon.setToolTip(SystemTrayUtil.appName);
			tray.add(trayIcon);
		} catch (Exception e) {
			//系统托盘添加失败
			log.error(Thread.currentThread().getStackTrace()[ 1 ].getClassName() + ":系统添加失败", e);
		}
	}

	/**
	 * 更改系统托盘所监听的Stage
	 */
	public void listen(Stage stage){
		//防止报空指针异常
		if(Strings.isEmpty(showListener)
				|| Strings.isEmpty(exitListener)
				|| Strings.isEmpty(mouseListener)
//				|| Strings.isEmpty(showItem)
				|| Strings.isEmpty(exitItem)
				|| Strings.isEmpty(trayIcon)
		) return;

		//移除原来的事件
//		showItem.removeActionListener(showListener);
		exitItem.removeActionListener(exitListener);
		trayIcon.removeMouseListener(mouseListener);
		//行为事件: 点击"打开"按钮,显示窗口
		showListener = e -> Platform.runLater(() -> showStage(stage));
		//行为事件: 点击"退出"按钮, 就退出系统
		exitListener = e -> {
			System.exit(0);
		};
		//鼠标行为事件: 单机显示stage
//		mouseListener = new MouseAdapter() {
//			@Override
//			public void mouseClicked(MouseEvent e) {
//				//鼠标左键
//				if (e.getButton() == MouseEvent.BUTTON1) {
//					showStage(stage);
//				}
//			}
//		};
		//给菜单项添加事件
//		showItem.addActionListener(showListener);
		exitItem.addActionListener(exitListener);
		//给系统托盘添加鼠标响应事件
		trayIcon.addMouseListener(mouseListener);
	}

	/**
	 * 关闭窗口
	 */
	public void hide(Stage stage){
		Platform.runLater(() -> {
			//如果支持系统托盘,就隐藏到托盘,不支持就直接退出
			if (SystemTray.isSupported()) {
				//stage.hide()与stage.close()等价
				stage.hide();
			} else {
				System.exit(0);
			}
		});
	}

	/**
	 * 点击系统托盘,显示界面(并且显示在最前面,将最小化的状态设为false)
	 */
	private void showStage(Stage stage){
		//点击系统托盘,
		Platform.runLater(() -> {
			if(stage.isIconified()){ stage.setIconified(false);}
			if(!stage.isShowing()){ stage.show(); }
			stage.toFront();
		});
	}
}
TestApplication(名字可以随便改,启动类调用对就行,这是用于界面的类,也可以没有,用其他代替)
import javafx.application.Application;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class TestApplication extends Application {


    @Override
    public void start(Stage primaryStage) throws Exception {
	    SystemTrayUtil.getInstance().listen(primaryStage);
    }

    public void stop() throws Exception {
        System.exit(0);
        log.debug("正在clone()。。。。");
        super.stop();
    }
}

Strings(用于判断空 可以不要或者用其他类代替,CharsetUtil可能会报错,使用来转utf-8的,可以其他代替)

可能需要的pom

 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.6.Final</version>
        </dependency>
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.Charset;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Slf4j
public class Strings extends StringUtils {
	/**
	 * 下划线
	 */
	private static final char SEPARATOR = '_';

    /**
     * 去掉字符串指定的前缀
     * @param str 字符串名称
     * @param prefix 前缀数组
     * @return
     */
    public static String removePrefix(String str, String[] prefix) {
        if (StringUtils.isEmpty(str)) {
            return "";
        } else {
            if (null != prefix) {
                String[] prefixArray = prefix;

                for(int i = 0; i < prefix.length; ++i) {
                    String pf = prefixArray[i];
                    if (str.toLowerCase().matches("^" + pf.toLowerCase() + ".*")) {
                        return str.substring(pf.length());//截取前缀后面的字符串
                    }
                }
            }

            return str;
        }
    }

    public static String getStringByEnter(int length, String string) {
        if (isEmpty(string)) return EMPTY;
        for (int i = 1; i <= string.length(); i++) {
            if (string.substring(0, i).getBytes(Charset.defaultCharset()).length > length) {
                return string.substring(0, i - 1) + "\n" +
                        getStringByEnter(length, string.substring(i - 1));
            }
        }
        return string;
    }

	/**
	 *返回去掉-的  16位Id
	 * @return
	 */
	public static String UUId16() {
        String uuId = Strings.UUId();
        uuId = uuId.replaceAll("-", "");
        return uuId.substring(0, 16);
    }

	/**
	 * 返回UUId
	 * @return
	 */
	public static String UUId() {
		return UUID.randomUUID().toString();
	}


	/**
	 * 返回当前字符串毫秒
	 * @return
	 */
    public static String currentTimeMillis() {
        return String.valueOf(System.currentTimeMillis());
    }



    /**
     * min-max之间的随机数
     *
     * @param min
     * @param max
     * @return
     */
    public static int Random(Integer min, Integer max) {
        return (int) ((max - min) * Math.random()) + min;
    }

    /**
     * 字符串转int类型
     *
     * @param str
     * @return
     */
    public static Integer StrToInteger(String str) {
        if (Strings.isEmpty(str)) {
            return 0;
        }
        try {
            return Integer.valueOf(str);
        } catch (NumberFormatException e) {
            log.error(e.toString());
        }

        return 0;
    }

    public static boolean isEmpty(List<Object> value) {
        if (value == null || value.isEmpty()) {
            return true;
        }
        return false;
    }

    public static boolean isEmpty(Object[] value) {
        if (value == null || value.length == 0) {
            return true;
        }
        return false;
    }

	public static boolean isNotEmpty(Long value) {
		return !isEmpty(value);
	}

	public static boolean isEmpty(Long value) {
		if (value == null || EMPTY.equals(value) || 0L == value) {
			return true;
		}
		return false;
	}

    public static boolean isNotEmpty(Object object) {
        return !isEmpty(object);
    }

    public static boolean isEmpty(Object object) {
        if (object == null || EMPTY.equals(object)) {
            return true;
        }
        return false;
    }

    public static boolean isNotEmpty(Object[] value) {
        return !isEmpty(value);
    }

    public static boolean isNotEmpty(List<Object> value) {
        return !isEmpty(value);
    }

    public static boolean isNotEmpty(String value) {
        return !isEmpty(value);
    }

    public static boolean isNotEmpty(CharSequence value) {
        return !isEmpty(value);
    }

    /**
     * @param strs     字符串数组
     * @param splitStr 连接数组的字符串
     * @return
     * @Title: join
     * @Description: 用指定字符串数组相连接,并返回
     * @return: String
     */
    public static String join(String[] strs, String splitStr) {
        if (strs != null) {
            if (strs.length == 1) {
                return strs[0];
            }
            StringBuffer sb = new StringBuffer();
            for (String str : strs) {
                sb.append(str).append(splitStr);
            }
            if (sb.length() > 0) {
                sb.delete(sb.length() - splitStr.length(), sb.length());
            }
            return sb.toString();
        }
        return null;
    }

    /**
     * @param str 字符串
     * @param end 结束位置
     * @return
     * @Title: subStrStart
     * @Description: 从头开始截取
     * @return: String
     */
    public static String subStrStart(String str, int end) {
        return subStr(str, 0, end);
    }

    /**
     * @param str   字符串
     * @param start 开始位置
     * @return
     * @Title: subStrEnd
     * @Description: 从尾开始截取
     * @return: String
     */
    public static String subStrEnd(String str, int start) {
        return subStr(str, str.length() - start, str.length());
    }

    /**
     * @param str    待截取的字符串
     * @param length 长度 ,>=0时,从头开始向后截取length长度的字符串;<0时,从尾开始向前截取length长度的字符串
     * @return
     * @throws RuntimeException
     * @Title: subStr
     * @Description: 截取字符串 (支持正向、反向截取)
     * @return: String
     */
    public static String subStr(String str, int length) throws RuntimeException {
        if (str == null) {
            throw new NullPointerException("字符串为null");
        }
        int len = str.length();
        if (len < Math.abs(length)) {
            throw new StringIndexOutOfBoundsException("最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(length)));
        }
        if (length >= 0) {
            return subStr(str, 0, length);
        } else {
            return subStr(str, len - Math.abs(length), len);
        }
    }


    /**
     * 截取字符串 (支持正向、反向选择)
     *
     * @param str   待截取的字符串
     * @param start 起始索引 ,>=0时,从start开始截取;<0时,从length-|start|开始截取
     * @param end   结束索引 ,>=0时,从end结束截取;<0时,从length-|end|结束截取
     * @return 返回截取的字符串
     * @throws RuntimeException
     */
    public static String subStr(String str, int start, int end) throws RuntimeException {
        if (str == null) {
            throw new NullPointerException("");
        }
        int len = str.length();
        int s = 0;//记录起始索引
        int e = 0;//记录结尾索引
        if (len < Math.abs(start)) {
            throw new StringIndexOutOfBoundsException("最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(start)));
        } else if (start < 0) {
            s = len - Math.abs(start);
        } else if (start < 0) {
            s = 0;
        } else {//>=0
            s = start;
        }
        if (len < Math.abs(end)) {
            throw new StringIndexOutOfBoundsException("最大长度为" + len + ",索引超出范围为:" + (len - Math.abs(end)));
        } else if (end < 0) {
            e = len - Math.abs(end);
        } else if (end == 0) {
            e = len;
        } else {//>=0
            e = end;
        }
        if (e < s) {
            throw new StringIndexOutOfBoundsException("截至索引小于起始索引:" + (e - s));
        }

        return str.substring(s, e);
    }

    /**
     * 获取参数不为空值
     *
     * @param value defaultValue 要判断的value
     * @return value 返回值
     */
    public static <T> T nvl(T value, T defaultValue) {
        return value != null ? value : defaultValue;
    }

    /**
     * * 判断一个Collection是否为空, 包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Collection<?> coll) {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * * 判断一个Collection是否非空,包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Collection<?> coll) {
        return !isEmpty(coll);
    }


    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Map<?, ?> map) {
        return isNull(map) || map.isEmpty();
    }

    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    /**
     * * 判断一个字符串是否为空串
     *
     * @param str String
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(String str) {
        return isNull(str) || EMPTY.equals(str.trim());
    }

    /**
     * * 判断一个对象是否为空
     *
     * @param object Object
     * @return true:为空 false:非空
     */
    public static boolean isNull(Object object) {
        return object == null;
    }

    /**
     * * 判断一个对象是否非空
     *
     * @param object Object
     * @return true:非空 false:空
     */
    public static boolean isNotNull(Object object) {
        return !isNull(object);
    }

    /**
     * * 判断一个对象是否是数组类型(Java基本型别的数组)
     *
     * @param object 对象
     * @return true:是数组 false:不是数组
     */
    public static boolean isArray(Object object) {
        return isNotNull(object) && object.getClass().isArray();
    }

    /**
     * 去空格
     */
    public static String trim(String str) {
        return (str == null ? "" : str.trim());
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @return 结果
     */
    public static String substring(final String str, int start) {
        if (str == null) {
            return EMPTY;
        }

        if (start < 0) {
            start = str.length() + start;
        }

        if (start < 0) {
            start = 0;
        }
        if (start > str.length()) {
            return EMPTY;
        }

        return str.substring(start);
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @param end   结束
     * @return 结果
     */
    public static String substring(final String str, int start, int end) {
        if (str == null) {
            return EMPTY;
        }

        if (end < 0) {
            end = str.length() + end;
        }
        if (start < 0) {
            start = str.length() + start;
        }

        if (end > str.length()) {
            end = str.length();
        }

        if (start > end) {
            return EMPTY;
        }

        if (start < 0) {
            start = 0;
        }
        if (end < 0) {
            end = 0;
        }

        return str.substring(start, end);
    }

    /**
     * 下划线转驼峰命名
     */
    public static String toUnderScoreCase(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        // 前置字符是否大写
        boolean preCharIsUpperCase = true;
        // 当前字符是否大写
        boolean curreCharIsUpperCase = true;
        // 下一字符是否大写
        boolean nexteCharIsUpperCase = true;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (i > 0) {
                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
            } else {
                preCharIsUpperCase = false;
            }

            curreCharIsUpperCase = Character.isUpperCase(c);

            if (i < (str.length() - 1)) {
                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
            }

            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
                sb.append(SEPARATOR);
            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
                sb.append(SEPARATOR);
            }
            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 是否包含字符串
     *
     * @param str  验证字符串
     * @param strs 字符串组
     * @return 包含返回true
     */
    public static boolean inStringIgnoreCase(String str, String... strs) {
        if (str != null && strs != null) {
            for (String s : strs) {
                if (str.equalsIgnoreCase(trim(s))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
     *
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        // 快速检查
        if (name == null || name.isEmpty()) {
            // 没必要转换
            return "";
        } else if (!name.contains("_")) {
            // 不含下划线,仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        // 用下划线将原始字符串分割
        String[] camels = name.split("_");
        for (String camel : camels) {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 驼峰式命名法 例如:user_name->userName
     */
    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 把null值转为“”
     */
    public static String nullToString(Object obj) {
        return ObjectUtils.toString(obj).trim();
    }

	/**
	 * 转换成string
	 * @param bytes
	 * @return
	 */
	public static String toString(byte[] bytes) {
    	if(Strings.isEmpty(bytes)) return Strings.EMPTY;
    	return new String(bytes, CharsetUtil.UTF_8);
	}
}

一张图(随便找个16*16的png 放Resources目录下即可,名字改成icon.png)

在这里插入图片描述

4.完结(默认情况如图)

在这里插入图片描述
图片默认路径 /icon.png,可以利用javaFx.png配置自己想要的
名称配置页类似,默认使用的服务名,根据自己的喜好设置

Java打包exe

1.先下载exe4j打包工具

前文

1.Idea其实在带了打包exe,但是经过试用并不好用,打包出来exe少很多东西
2.github上也有很多开源的打包,也是各种问题
3.最终一共找了4种,其中exe4j打包步骤虽然繁琐了些,但是最传统实用
4.4种打包方式的链接
链接:https://pan.baidu.com/s/1tSUYJ7H3RLvNygyuqBm-og
提取码:h7w1

介绍
1.先下载官网https://exe4j.apponic.com/

安装好如图
在这里插入图片描述
在这里插入图片描述

必须激活,不激活打包出来也不能使用,虽然网上激活码免费的很多,建议购买,支持正版!

2.打好jar包后,确认激活后,直接开始打包,第二步选择JAR

在这里插入图片描述

3.声明服务名,配合打包出来的exe存放位置

在这里插入图片描述

4.选择打包好后的类型,名字以及图标

在这里插入图片描述日志处理
在这里插入图片描述
JDK如果是64位的记得打钩,32位记得把打钩去掉
在这里插入图片描述
在这里插入图片描述

4.选好启动类以及JVM参数的处理

这里的JVM参数分别是G1垃圾回收器,以及托盘需要的两个JVM参数,当然如果你需要限制java内存大小或娶她之类的,也可以加上相应的一些参数

-XX:+UseG1GC -Djava.awt.headless=false -Dfile.encoding=GBK

在这里插入图片描述
选好后
在这里插入图片描述
这里是加lib,正常情况下都打到jar包内了,应该不用加,特殊情况处理
在这里插入图片描述

5.JRE是一个很关键的点

在这里插入图片描述
这里1.8就选相对路径的JRE,尽量吧JDK复制出来放在exe同级目录
在这里插入图片描述
在这里插入图片描述

这里其实有很大的优化空间
因为1.8打好exe后,由于JDK很大,打一个项目,就感觉不太划算,打多个,带一个200M的JDK,哈哈哈哈哈

1.9的话有单独的JRE,加上内部打包有模块划分,体积会小很多
当JRE出问题时,把JDK中的bin和lib分别覆盖JRE的bin与lib就可以解决问题
Java11和Java14可能找不到JRE了,不要慌,以管理员身份打开cmd,进入JDK目录,制作min版JER,会直接瘦身到几十MB,这个这里就不写了
打包min JRE命令,记得一定要用管理员身份

\bin\jlink.exe --module-path jmods --add-modules java.base,java.desktop,java.xml,java.scripting,java.logging,jdk.unsupported --output minijre
6.然后下一步,下一步就打包好了

在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值