命令行 java 編譯腳本從入門到放棄

前天客服羣有一個用戶反饋,說是某個用戶進入直播間老是進不去。然後就查了查這塊的代碼。和接口業務使用 PHP不同,聊天業務用 Java 開發,而且是老年版本(不知道啥時候寫的,反正給人很古老的感覺)。然後就發現了一些問題,嘗試着做了一些解決方案,在此記錄一下😂。
錯誤簡述

繼續查,發現底層錯誤堆棧中有這麼一個 exception。

java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:504)
	at java.lang.Integer.parseInt(Integer.java:527)
	at newtv.chatserver.model.json.UserInfo.<init>(UserInfo.java:56)
	at newtv.chatserver.utils.RedisHelper.getUserInfo(RedisHelper.java:475)
	at newtv.chatserver.WebSocketMessageHandler.onOpen(WebSocketMessageHandler.java:521)

原來是這樣,可以看出是chattvserver.utils.RedisHelper.getUserInfo 方法裏面報了錯了,具體的錯誤內容爲NumberFormatException 。看了下里面的代碼

Map<String, String> info = jedis.hgetAll(NEWTV_USERINFO_HSET_PREFIX + uid);
if( info != null && info.containsKey("userid") && !Strings.isNullOrEmpty(info.get("userid"))){
	userinfo = new UserInfo(info);
	Map<String, String> vip = jedis.hgetAll(VIP_INFO + uid);
	if(vip != null && vip.containsKey("id") && !Strings.isNullOrEmpty(vip.get("id"))) {
		int vipid = Integer.parseInt(vip.get("id"));

而這最後一行就是定位到出問題的代碼了。因爲聊天服務器是 netty 寫的,所以之前的調試方法一般有兩種:

  • 硬調,靜態檢查(就是直接看代碼,不能保證健壯性,需要很通順的思路)
  • 打日誌,上測試環境,看輸出,再回歸(流程較長,步驟繁瑣)

哪種我都不想碰,聯想到大一的時候導師(姜國海),一直在強調

  • 少用 IDE,用 notepad++寫Java
  • 用命令行編譯執行Java 程序

老薑號稱“東北 Java 第一人”,是我的本科導師,不知道他這個稱號是不是自封的,但是他本人技術水平那確實是沒的說,牛!哈哈,有點跑偏,回到今天的問題上:完善代碼測試流程

說到這裏,不得不提一下 Java 的 classpath.
Java classpath

這張圖先做下鋪墊,待會還會用得到它。接着一點點來循序漸進,一步步完成一個 “差強人意” 的自動化腳本。

  • hello world 版本
//MyTest.java
public class MyTest{
    public static void main(String[] args){
        System.out.println("Hello world!");
    }
}

在 IDEA 或者 Eclipse 中直接 run 就能看到Hello world!的輸出,但是在命令行裏,想必大家也都知道😜

javac MyTest.java
java MyTest
  • 使用第三方包

光是輸出 helloworld,只能算開了個頭,涉及 Redis 的操作,所以不可避免的要引入一些第三方的 jar 包,這裏我用了 jedis。

// MyTest.java
import redis.clients.jedis.Jedis;
public class MyTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.32.103", 6379);
        System.out.println(jedis.ping());
        System.out.println("hello world");
    }
}

依樣畫葫蘆,咱還按剛纔的步驟來編譯執行。

javac MyTest.java
MyTest.java:2: 錯誤: 程序包redis.clients.jedis不存在
import redis.clients.jedis.Jedis;
                          ^
MyTest.java:5: 錯誤: 找不到符號
        Jedis jedis = new Jedis("192.168.32.103", 6379);
        ^
  符號:   類 Jedis
  位置: 類 MyTest
MyTest.java:5: 錯誤: 找不到符號
        Jedis jedis = new Jedis("192.168.32.103", 6379);
                          ^
  符號:   類 Jedis
  位置: 類 MyTest
3 個錯誤

哈哈,編譯都通不過,看給出的錯誤原因,應該是找不到Redis的jar包了,回頭看看eclipse,依稀記得有把某個jar包Add to Build Path 的行爲,那是不是說eclipse幫我們做好了添加classpath的工作,所以IDE使用起來就方便很多,命令行下就得手動添加了呢?

root@ktv322:/home/wwwroot/chattvserver/src# javac -classpath /home/wwwroot/chattvserver/libs/jedis-2.1.0.jar MyTest.java
root@ktv322:/home/wwwroot/chattvserver/src#

發現編譯沒報錯,沒報錯說明就是個好消息。接着看下執行結果:

root@ktv322:/home/wwwroot/chattvserver/src# java MyTest
Exception in thread "main" java.lang.NoClassDefFoundError: redis/clients/jedis/Jedis
    at MyTest.main(MyTest.java:5)
Caused by: java.lang.ClassNotFoundException: redis.clients.jedis.Jedis
    at java.net.URLClassLoader$1.run(URLClassLoader.java:359)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:348)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:347)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:312)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 1 more

執行的結果不是很樂觀。但是提示的也很明顯,沒找到類的路徑,應該和編譯的時候是一樣的問題, 那就手動加上去再試試

root@ktv322:/home/wwwroot/chattvserver/src# java -classpath /home/wwwroot/chattvserver/libs/jedis-2.1.0.jar MyTest
錯誤: 找不到或無法加載主類 MyTest
root@ktv322:/home/wwwroot/chattvserver/src#

這裏我注意到一個問題,MyTest.class文件是剛纔手動編譯出來的,明明就在當前路徑下面,但是按下tab鍵就是不出來,是因爲路徑變掉了嗎?然後嘗試着把運行時的classpath改了一下,Windows上路徑之間的分隔符是; 而Linux上的分隔符是:

root@ktv322:/home/wwwroot/chattvserver/src# java -classpath /home/wwwroot/chattvserver/libs/jedis-2.1.0.jar:. MyTest
PONG
hello world
root@ktv322:/home/wwwroot/chattvserver/src#

回過頭看看剛纔那個圖,人家也說classpath 路徑之間用 : 進行分隔。

root@ktv322:/home/wwwroot/chattvserver/src# cat MyTest.java
import java.util.Map;
import redis.clients.jedis.Jedis;
public class MyTest {
    public static void main(String[] args) {
        String key = "uid:2614677";
        Jedis jedis = new Jedis("192.168.32.103", 6379);
        Map<String, String> userinfo = jedis.hgetAll(key);
        System.out.println(userinfo);
    }
}
root@ktv322:/home/wwwroot/chattvserver/src# javac -classpath /home/wwwroot/chattvserver/libs/jedis-2.1.0.jar:. MyTest.java
root@ktv322:/home/wwwroot/chattvserver/src# java -classpath /home/wwwroot/chattvserver/libs/jedis-2.1.0.jar:. MyTest
{valid=0, birthday=, phone=110, lastlivetime=1548064849, memberid=, truename=嘻嘻嘻, registertime=2017-11-09 10:16:57, audience_cnt=38390, userid=2614677, md5pwd=, level=7, lastlogintime=2019-01-23 14:23:19, invalid=0, lastsessionid=10120290, gender=1, experience=654168, popularcount=69891, signature=拒絕評論啦啦啦, userlevel=7, headphoto=http://aliimg.changbalive.com/photo/35/05c78d9124a78310.jpg, accesstoken=, nickname_blob=太陽宮李榮浩, nickname_md5=fc6af45fd4ca8d10135ad5acce50ce42, audience_cnt_inner=60, starlevel=, userexp=4983, coins=10674, anchorexp=69891, experiencelevel=4983, realname=2}
  • 自動化腳本
#!/usr/bin bash
# 目標: 自配置classpath
ORIGIN_PATH=`pwd`
#echo $ORIGIN_PATH
# 項目的根目錄 一般不會怎麼變化,就每次都改着用吧,或者通過export的形式動態添加,會話內有效
echo "###########################################"
echo "# 基本上每個項目都只有一個依賴包目錄,所以#"
echo "# 所以有必要的話單獨設置下SEARCH_PATH即可 #"
echo "# TODO runtime_class_path 的自發現實現    #"
echo "###########################################"

SEARCH_PATH=/home/wwwroot/chattvserver/libs/
cd $SEARCH_PATH
RUNTIME_CLASS_PATH=`find ./ -name "*.jar" | sed "s#./#$SEARCH_PATH#" | tr "\n" ":"`.
cd $ORIGIN_PATH
#echo PWD:$ORIGIN_PATH
#echo "--------------------"
NAME=$1
PREFIX=`echo $NAME| echo ${NAME%.*}`
#echo name: $NAME
#echo prefix: $PREFIX
RUBBISH_FILE=$PWD/$PREFIX.class
javac -classpath $RUNTIME_CLASS_PATH $PWD/$1
java -classpath $RUNTIME_CLASS_PATH $PREFIX

rm $RUBBISH_FILE
cd $ORIGIN_PATH

配到alias別名裏面,然後source下,運行,完事,如果想再靈活點,可以(通過export會話內)修改下SEARCH_PATH,這樣更高效。

  • 新的問題
    本身一個類怎麼寫,無非是調類內方法,或者第三方庫。但是如果調用其他包的方法,就還是會失敗。但是可以猜測到依舊是 classpath 的問題。那麼解決方法應該還是類似的,

模擬一個目錄結構

- workspace
  - libs
    - fastjson-1.2.61.jar
    - jedis-2.1.0.jar
  - src
    - defaultpackage
      - MyTest.java
    - utils
      - IPUsage.java

核心代碼

// src/defaultpackage/MyTest.java
import redis.clients.jedis.Jedis;
import utils.IPUsage;

public class MyTest {
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1");
		String ipaddress = jedis.get("ipaddress");
		System.out.println(IPUsage.getIPString(ipaddress));
	}
}


// src/utils/IPUsage.java
package utils;

public class IPUsage {
	// 測試外部 class 調用
	public static String getIPString(String ipaddress) {
		if ("192.168.1.1".equals(ipaddress)) {
			return "1.1";
		}else if("192.168.0.1".equals(ipaddress)) {
			return "0.1";
		}
		return "0.0";
	}
}

彙總 classpath 版本

javac -cp /Users/biao/eclipse-workspace/MyTest/libs/fastjson-1.2.61.jar:/Users/biao/eclipse-workspace/MyTest/libs/jedis-3.2.0.jar:/Users/biao/eclipse-workspace/MyTest/src:/Users/biao/eclipse-workspace/MyTest/src/utils MyTest.java
java -cp /Users/biao/eclipse-workspace/MyTest/libs/fastjson-1.2.61.jar:/Users/biao/eclipse-workspace/MyTest/libs/jedis-3.2.0.jar:/Users/biao/eclipse-workspace/MyTest/src:/Users/biao/eclipse-workspace/MyTest/src/utils MyTest
0.0
# 修改 Redis 中的內容
redis-cli
127.0.0.1:6379> set ipaddress 192.168.1.1
OK
127.0.0.1:6379> exit
# 重新執行
java -cp /Users/biao/eclipse-workspace/MyTest/libs/fastjson-1.2.61.jar:/Users/biao/eclipse-workspace/MyTest/libs/jedis-3.2.0.jar:/Users/biao/eclipse-workspace/MyTest/src:/Users/biao/eclipse-workspace/MyTest/src/utils MyTest
1.1

可以看出啊,這個命令行已經很臃腫了。而且這還是隻涉及幾個 java 文件,想想日常你見到的Java 項目,還是放棄命令行模式編譯執行吧。選擇 ant,gradle,換新框架去吧😂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章