java基础之编译运行使用文本编辑器开发的JAVA程序

事件起因

昨天跑通了一个JAVA程序,是基于SpringBoot项目的,本地运行完全没问题,但到了服务器上启动的时候总是报错,提示IdWorker类中的getDataCenterId方法有空指针。IdWorker类一般是基于雪花算法生成ID。仔细查看代码,实际上是获取网卡信息失败了,导致的空指针。 在网上找资料的时候,也有macOS遇到过类似的问题

单元测试

为了对代码进行测试,定位具体代码问题所在,于是想到简单的方法,用文本文档写个程序,进行简单的单元测试,验证代码问题出在哪里。

于是用记事本编写了一个JAVA代码

package com.test;
public class Test {
	public static void main(String[] args) throws Exception{
          System.out.println("hello");
    }
}

保存为Test.java

通过javac 编译

javac Test.java

执行成功,生成了Test.class

再执行java 指令

java Test

此时,发生了错误

找不到或无法加载主类

于是乎上网看文章,发现需要配置JAVA相关的环境变量,于是按下面的参数配置环境变量操作

JAVA_HOME JAVA程序所在主路径,我这里是 E:\jdk,根据你的情况来调整。 CLASSPATH .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar; PATH 包含 %JAVA_HOME%\bin

再重新打开命令行,执行JAVA程序

为了确保环境变量生效,可以通过echo 查看环境变量的值,如果跟设置的一样,那就是已生效了。比如

echo %CLASSPATH%

当我配置完成后,环境变量都已生效了。

再次执行java Test 还是报错

尝试执行java com.test.Test 也报错

找不到或无法加载主类

此时才发现,由于给定了com.test包,而程序并没有在该路径下,因此需要去掉包名。

修改代码如下:


public class Test {
	public static void main(String[] args) throws Exception{
          System.out.println("hello");
    }
}

此时重新编译

javac Test.java

再执行java命令

java Test

执行成功!

再把获取mac地址的代码放进来

import java.net.InetAddress;
import java.net.NetworkInterface;
public class Test {
	public static void main(String[] args) throws Exception{
         
          InetAddress ip = InetAddress.getLocalHost();
           System.out.println("ip:"+ip);
           NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                 System.out.println("network:null");
            } else { 
                    
                byte[] mac = network.getHardwareAddress();
                 System.out.println("mac:"+mac.toString());
                
            }
            
    }
}

再执行编译

javac Test.java

再执行java命令

java Test

本地得到结果

E:\mavenres\com\java2nb\novel-admin\4.3.0>java Test
ip:8RCF9JB/192.168.0.141 
mac:[B@d934260d

服务器得到结果

[root@hcss-ecs-5eee novel]# java Test
ip:hcss-ecs-5eee/127.0.0.1 
Exception in thread "main" java.lang.NullPointerException
	at Test.main(Test.java:15)

最终确认问题出在 getHardwareAddress 这个方法,在服务器上这个方法返回的是空,出现空指针异常。

解决办法

为了解决这个问题,新建了一个IdWorkerUtil类,来替换原来的IdWorker类,所有使用该类的地方,都使用IdWorkerUtil类来代替

在IdWorkerUtil类中,重写getDataCenterId方法,我这里为了省事,让系统先跑起来,只有在能够获取到mac地址的时候才调用getHardwareAddress这个方法,当获取不到时,就不去计算了。

实际上可以使用遍历多个地址的方式,取一个有效值,再去进行运算。后续有时间再去考虑一下怎么优化。

IdWorkerUtil类代码:

package com.java2nb.novel.core.utils;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

public enum IdWorkerUtil {

    INSTANCE;

    private static final long epoch = 1288834974657L;
    private static final long workerIdBits = 5L;
    private static final long datacenterIdBits = 5L;
    private static final long maxWorkerId = 31L;
    private static final long maxDatacenterId = 31L;
    private static final long sequenceBits = 12L;
    private static final long workerIdShift = 12L;
    private static final long datacenterIdShift = 17L;
    private static final long timestampLeftShift = 22L;
    private static final long sequenceMask = 4095L;
    private static long lastTimestamp = -1L;
    private long sequence = 0L;
    private final long workerId;
    private final long datacenterId = this.getDatacenterId();

    private IdWorkerUtil() {
        this.workerId = this.getMaxWorkerId(this.datacenterId);
    }

    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        } else {
            if (lastTimestamp == timestamp) {
                this.sequence = this.sequence + 1L & 4095L;
                if (this.sequence == 0L) {
                    timestamp = this.tilNextMillis(lastTimestamp);
                }
            } else {
                this.sequence = 0L;
            }

            lastTimestamp = timestamp;
            return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
        }
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp;
        for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
            ;
        }

        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    private long getMaxWorkerId(long datacenterId) {
        StringBuilder mPid = new StringBuilder();
        mPid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            mPid.append(name.split("@")[0]);
        }

        return (long)(mPid.toString().hashCode() & '\uffff') % 32L;
    }

    private long getDatacenterId() {
        long id = 0L;
        try {

            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (!network.isUp()||network.isLoopback()) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = (255L & (long)mac[mac.length - 1] | 65280L & (long)mac[mac.length - 2] << 8) >> 6;
                id %= 32L;
            }


        } catch (Exception ex) {
             ex.printStackTrace();
        }
        return id;
    }
}