购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.3 图形化界面开发Java程序

当今Java图形化界面的开发工具有两大霸主:一个是Eclipse(免费)/MyEclipse(收费),另一个是JetBrains公司的IntelliJ IDEA(简称IDEA,收费)。两者都是Java语言的跨平台图形化集成开发环境,可以在Windows、Mac OS和Linux上提供一致的体验,两者的用户群体都不少。一般预算足够的人用IntelliJ IDEA比较多,IntelliJ IDEA在业界被公认为是最好的Java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(Git、SVN等)、JUnit、CVS整合、代码分析、创新的GUI设计等方面的功能可以说是超常的。这两个工具笔者都使用过,感觉IntelliJ IDEA更胜一筹。举一个例子,比如在IntelliJ IDEA开发中,只知道一个类名,但不知道是哪个程序包里的,即不知道import后如何编写,此时把鼠标放在这个类名上就会出现智能提示,如图3-27所示。

076-1

图3-27

PKCS7是要使用的类名,但不知道是哪个程序包里的,此时可以单击蓝色的Import class,就会自动帮助添加好包,我们在源文件开头可以看到新增了import sun.security.pkcs.PKCS7;这一句,这就是IDEA帮助添加的,它识别出了PKCS7,非常智能。别小看这个功能,只知道类名但不知道包名这种情况经常会发生,IDEA可以最大限度地帮我们节省时间。这里笔者推荐IntelliJ IDEA,它提供了30天的免费使用期限,读者可以去官网下载,地址是https://www.jetbrains.com/idea/download/。笔者使用的是IntelliJ IDEA 2021.1.3版本,在Windows 7下使用(用Windows 10与此类似)。

在图形化的Linux下使用这两款开发工具非常简单,都是傻瓜式操作。在企业一线开发中,很多Linux系统都是不带图形界面的,而且Linux主机都是锁在机柜里的,开发人员只能在自己办公桌上用计算机进行远程的Linux开发。因此,我们要学会在计算机上进行远程Linux开发,而且要使用集成开发环境。值得庆幸的是,IDEA已经完全考虑到了这一点,并且提供了周到的支持,让我们远程开发起来非常舒心。

3.3.1 第一个IDEA开发的Java应用程序

笔者在Windows 7上安装了IntelliJ IDEA 2021.1.3,然后用VMware安装了CentOS 7.6来模拟远程Linux,这样只需要一台计算机就够了。笔者的Windows 7的IP是192.168.11.2,虚拟机CentOS 7.6的IP是192.168.11.129,两者已经能互相ping通。下载安装IntelliJ IDEA的过程这里就不赘述了,相信读者都是Windows安装高手。另外,要在Windows 7下使用IDEA,必须先在Windows 7下安装好JDK,我们在上一章已经介绍过其安装方法,这里不再赘述。下面直接进入实战。

【例3.2】 在IDEA下远程开发Linux程序

1)在Windows下打开IntelliJ IDEA 2021.1.3,新建一个Java工程,如图3-28所示。

077-1

图3-28

单击Next按钮,然后指定工程名和路径,如图3-29所示。

077-2

图3-29

用户也可以根据自己的习惯设置工程名和路径。单击Finish按钮进入IDEA主界面。如果磁盘上没有E:\ex\myjava路径,则会提示我们是否要自动建立,选择“是”,系统会帮助我们建立E:\ex\myjava路径,非常贴心。

2)添加一个源文件。在IDEA的project视图下右击src,然后在快捷菜单中选择New→Java Class,再输入类名helloworld,随后编辑窗口就被打开了,在其中输入如下代码:

public class helloworld {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

3)进行运行与调试配置。单击菜单Run→Edit Configurations...,此时出现Run/Debug Configurations对话框,单击Add New Configuration,出现一个菜单,选择Application菜单项,也就是为应用程序添加一个运行配置(包括编译所在的主机、工作文件夹等信息),如图3-30所示。

077-3

图3-30

在该对话框的Name框中输入自定义的配置名,比如myconfig,随后在Run on右边的下拉列表框中选择SSH,将出现New Target:SSH对话框,如图3-31所示。

078-1

图3-31

在Host框中输入192.168.11.129,在Username框中输入root,其中192.168.11.129是虚拟机Linux的IP地址,root是虚拟机Linux的账号,通过SSH连接,我们可以把本地编辑的Java源码文件安全传输到虚拟机Linux上去编译和运行。单击Next按钮,稍等片刻,如果连接成功,则会出现root账号与密码的对话框,如图3-32所示。

078-2

图3-32

输入root账号的密码123456,单击Next按钮,稍等片刻,如果认证成功,则自动解析Linux上的Java安装成功,如图3-33所示。

078-3

图3-33

单击Next按钮,出现工程目录在Linux上的配置对话框,这里保持默认设置,如图3-34所示。

078-4

图3-34

默认的/root/test/路径就是在Linux上的工程目录的路径。单击Finish按钮,再次回到Run/Debug Configurations对话框,可以发现对话框的下方出现了一句错误提示信息Error:No main class specified,如图3-35所示。

078-5

图3-35

这是经常令初学者头疼的问题,也是IDEA有点“弱智”的地方。在对话框中找到Main class编辑框,在该编辑框的右边末端单击一个矩形图标,随后出现Choose Main Class对话框,如图3-36所示。

079-1

图3-36

选中helloworld,并单击OK按钮,回到Run/Debug Configurations对话框,这是就会发现错误提示信息没有了,如图3-37所示。

079-2

图3-37

IDEA会自动识别主类并填好,单击OK按钮,配置结束。此时回到IDEA主界面上,单击右上方的绿色箭头就可以开始编译和运行了,而且是在虚拟机Linux上编译和运行,如图3-38所示。

079-3

图3-38

稍等片刻,编译并运行成功,可以看到在IDEA下方显示出运行的结果,如图3-39所示。

079-4

图3-39

可以看到输出了“Hello world!”,说明运行成功。至此,第一个IDEA开发的远程Linux Java程序成功。

3.3.2 使用第三方JAR库

开发服务器程序免不了要与第三方库打交道,尤其是为了保障服务器程序的安全,肯定要在服务端程序中加入安全机制,比如加密、签名等措施。为了增加这些安全功能,通常会加入提供安全机制的算法库,所以我们要学会如何在工程中添加第三方库,并且使用库中的函数。

在Java领域,BouncyCastle可谓是大名鼎鼎的安全算法库,尤其是实现了JDK不曾支持的国密算法,比如SM2等。现在要设计安全的服务端程序,自主可控是基本要求,而自主可控的基本要求就是使用国家密码管理局认可的国密算法。下面我们在工程中使用BouncyCastle库,当然本书不专门介绍安全编程,只是让读者学会如何导入和使用第三方安全算法库。这是每个服务端程序开发者必须要学会的。

我们可以到BouncyCastle官网下载JAR文件,根据自己的JDK版本进行选择,笔者的JDK是1.8,所以找到这个链接进行下载,如图3-40所示。

080-1

图3-40

单击bcprov-jdk15to18-169.jar,稍等片刻下载完成,下载下来的文件是bcprov-jdk15to18-169.jar。下面进入实战,导入bcprov-jdk15to18-169.jar并使用它。

【例3.3】 调用SM3算法计算摘要

1)打开IDEA,新建一个Java应用程序工程,工程名是test。路径笔者设为e:\ex\test。

2)在工程中新建一个文件夹,用来存放第三方库。在Project视图中,右击test,然后在快捷菜单上选择New→Directory,输入文件夹名为lib,此时Project视图中就多了一个lib文件夹。如果到磁盘上的工程目录(笔者的是e:\ex\test)去看,可以看到多了一个空的lib文件夹,我们把下载下来的bcprov-jdk15to18-169.jar文件复制到lib下。

3)导入库。在Project视图中,用鼠标右击lib,在快捷菜单上选择Add as Library,注意,如果有时不出现该菜单项,那么可以确认bcprov-jdk15to18-169.jar文件已经在lib文件夹中了,然后在出现的Create Library对话框的Name框中输入lib,接着单击OK按钮,如图3-41所示。

080-2

图3-41

接着添加该项目的Library,也就是指定该lib文件夹作为项目的一个Library。然后检查是否添加成功,单击菜单File→Project Structure,在Project Structure对话框左侧的Project Settings下选中Libraries,可以看到中间有一个lib了,如图3-42所示。

080-3

图3-42

下面添加Dependence。在Project Settings下选中Modules,然后在右边的Dependencies下勾选lib,如图3-43所示。

080-4

图3-43

最后单击OK按钮。至此,第三方库添加成功。下面开始添加源码。

4)在IDES中的Project视图下右击src,然后选择菜单New→Java Class,输入类名test,然后在test.java中输入如下代码:

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;

public class test {
    public static void main(String[] args)
    {
        sm3();
       printProvider();
        sm2();

    }
    static int sm2()
    {
        System.out.println("----------生成密钥对start----------------");
        //引入BC库
        Security.addProvider(new BouncyCastleProvider());
        //获取SM2椭圆曲线的参数
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        try {
            //获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            //使用SM2参数初始化生成器
            kpg.initialize(sm2Spec);
            //使用SM2算法使用规范初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            //获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            PublicKey pk = keyPair.getPublic();
            PrivateKey privk = keyPair.getPrivate();
        System.out.println("公钥串:"+new BASE64Encoder().encode(keyPair.getPublic().getEncoded()));
        System.out.println("私钥串:"+new BASE64Encoder().encode(keyPair.getPrivate().getEncoded()));
        System.out.println("公钥对象:"+pk);
        System.out.println("私钥对象:"+privk);
        }
        catch (NoSuchAlgorithmException e)
        {
        }
        catch (InvalidAlgorithmParameterException e)
        {
        }
        System.out.println("----------生成密钥对end----------------");
        return 0;
    }

    static int sm3()
    {
        try {
            //注册BouncyCastle
            Security.addProvider(new BouncyCastleProvider());
            //按名称正常调用
            MessageDigest md = MessageDigest.getInstance("Sm3");
            md.update("abc".getBytes("UTF-8"));
            byte[] result = md.digest();
            System.out.println(new BigInteger(1, result).toString(16));

            System.out.println("Hello world!");
        }
        catch(NoSuchAlgorithmException e)
        {
        }
        catch(UnsupportedEncodingException e)
        {
        }
        return 0;
    }

    //打印支持的算法
    private static void printProvider()
    {
        Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
        for (Provider.Service service : provider.getServices()) {
            System.out.println(service.getType() + ": "
                    + service.getAlgorithm());
        }
    }

    /**
     * byte[] 转换为十六进制字符串
     */
    private final static char[] HEX_CHAR = "0123456789ABCDEF".toCharArray();
    public static String hex16(byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (byte value : b) {
            sb.append(HEX_CHAR[value >> 4 & 0xf])
                    .append(HEX_CHAR[value & 0xf]);
        }
        return sb.toString();
    }
}

上面的程序主要实现了3个功能,对“abc”计算SM3摘要,生成SM2密钥对,打印BouncyCastle所支持的算法。至于SM2与SM3的原理这里就不介绍了,读者可以参考密码相关的图书。建议网络编程开发人员了解和掌握这些常用的国密安全算法。记住,服务器编程不单是编写一个网络程序,更要考虑应用过程的安全性。笔者一直认为,一个优秀的网络程序员肯定也是一个安全应用开发的高手。举一个简单的例子,如果要开发网络游戏的服务端程序,那样一定要考虑如何防止逆向和外挂,如何有效地认证真实的客户,不加密肯定不行。

5)保存工程并运行,运行结果如下:

66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0
Hello world!
MessageDigest: GOST3411
Mac: HMACGOST3411
KeyGenerator: HMACGOST3411
MessageDigest: GOST3411-2012-256
Mac: HMACGOST3411-2012-256
KeyGenerator: HMACGOST3411-2012-256
...

3.3.3 使用Linux中的SO库

在一线开发Java服务器程序的过程中,经常为了加入安全性机制,要跟主机上插着的各种安全硬件打交道,比如加密卡、USBKEY等,这些硬件通常都提供了Linux下的C语言版本的共享库(.so),然后我们的Java服务器程序需要和这些C语言版本的SO库联合编程,也就是在Java程序中要调用C语言库。这也是Java网络程序员必须要掌握的一项技能,而且工作中肯定会碰到。

在Java中使用C语言库的传统做法是使用JNI编程。现在有了更好的替代者,即JNA(Java Native Access)。下面我们来看一下IntelliJ IDEA平台的JNA编程。JNA是一个开源的Java框架,是Sun公司推出的一种调用本地方法的技术,是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。使用JNI的朋友可以考虑升级了,但JNI编程依旧要掌握,因为很多需要维护的老系统依旧使用的是JNI。

JNA只需要我们编写Java代码,而不用编写JNI或本地代码(适配用的.dll/.so),只需要在Java中编写一个接口和一些代码,作为.dll/.so的代理,就可以在Java程序中调用DLL/SO。JNA的功能相当于Windows的Platform/Invoke和Python的ctypes。

首先要下载jna.jar包,到JNA官网下载新版本的jna.jar,链接为https://github.com/twall/jna。

下载的文件是jna-master.zip,解压后搜索出文件jna.jar,把它单独复制出来,先找一个临时目录保存好,以后要导入工程中。jna.jar准备好了,下面可以实战。首先开发一个Linux SO库出来。

【例3.4】 在Java中调用C语言共享库

1)准备一个Linux的SO库(共享库)。在Windows下编辑一个C语言源文件,代码如下:

void PrintBuf(unsigned char *buf, int    buflen)
{
    int i;
    printf("\n");
    printf("len = %d\n", buflen);
    for (i = 0; i < buflen; i++)
    {
        if (i % 32 != 31)
            printf("%02x", buf[i]);
        else
            printf("%02x\n", buf[i]);
    }
    printf("\n");
    return;
}

int dosth(unsigned char *in, int inlen, unsigned char *out, int * poutlen)
{
    printf("in:");
    PrintBuf(in, inlen);

    memset(out, '1', 32);
    *poutlen = 32;

    puts("-------bye------");

    return 0;
}

上述代码把传进来的参数in打印出来,然后用“1”填充out,并更新*poutlen。把该源文件上传到Linux,然后用命令编译:

[root@localhost ex]# gcc test.c -shared -fPIC -o libmy.so
[root@localhost ex]#

在同一个目录下将生成文件libmy.so,我们把这个libmy.so文件复制到/usr/lib64系统路径下,这样Java调用时就能找到该库文件了。

2)开发Java调用程序。在Windows下打开IDEA,新建一个Java工程并新建一个类,类名是test,然后在test.java中输入如下代码:

import java.io.IOException;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Memory;

public class test {
    CLibrary lib;

    public interface CLibrary extends Library
    {
        int dosth(byte[]in,int inlen,byte []out,Pointer poutlen);
    }

    test() //构造方法,加载库
    {
        lib = (CLibrary) Native.load("my", CLibrary.class);
    }

    public static void main(String[] args) throws IOException
    {
        test t1 = new test();
        t1.test_myfunc();
    }

    public void test_myfunc()  //测试功能函数
    {
        byte[] in= new byte[3];
        String strIn="abc";
        System.arraycopy(strIn.getBytes(), 0, in, 0 , 3);
        StringBuffer signValue = new StringBuffer("");
        myfunc(in,signValue);
        System.out.println(signValue);
    }
    //功能函数,indata是输入数据,signValue是返回参数,存放最终结果
    public int myfunc(byte[]indata,StringBuffer  signValue)
    {
        byte[] out=new byte[512];
        int size=Native.getNativeSize(Integer.class);
        Pointer poutlen = new Memory(size);
        int r = lib.dosth(indata,indata.length,out,poutlen);
        int outlen = poutlen.getInt(0); //得到最终结果的长度

        System.out.println("outlen="+outlen);
        System.out.println("r:"+Integer.toHexString(r));
         for(int i=0;i<outlen;i++)
            System.out.print(Integer.toHexString(out[i]&0xff)+",");
        System.out.println("");
        String str = new String(out,0,outlen); //把字节数组中的有效数据转为字符串
        signValue.delete(0,signValue.capacity()).append(str);

         return r;
    }
}

为了让Java认识SO库中的函数dosth,我们在代码中以Java的形式声明了dosth,4个参数分别是in(输入参数字节数组)、inlen(字节数组的长度)、out(输出参数,也是字节数组,存放函数的最终结果)以及poutlen(类似于指针,存放最终结果的长度)。以上4个参数的类型基本覆盖了实战环境中的常见情况。

3)在工程目录下新建一个lib文件夹,把jna.jar文件放到lib文件夹下。在IDEA中单击菜单File→Project Structure...,打开Project Structure对话框,在该对话框中选择左边的Modules,在右边单击加号,选择JARs or Directories...,随后选择lib文件夹下的jna.jar文件,此时jna.ja出现在列表框中,然后勾选该复选框,如图3-44所示。

086-1

图3-44

最后单击OK按钮。

4)保存工程并运行,运行结果如下:

in:
len = 3
616263
-------bye------
outlen=32
r:0
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,
11111111111111111111111111111111

其中,“-------bye------”及其之前的内容都是dosth内部打印的,这是为了让我们知道,IDEA最终也能输出库函数中的打印,这样方便查找库中代码的问题。另外,31是字符“1”的ASCII码的十六进制形式,r旁边的0表示库函数的返回值。

至此,Java调用C语言共享库成功了。在以后的工作中,我们开发Java服务器程序时调用硬件厂家的共享库就不怕了。一个网络程序员必须是多面手。 8y0pcaXgpJJYjb5Zb3S10nHsDGM1pfjNNdGIlS5yydGEGgapv1M264VhdjLoX+bT

点击中间区域
呼出菜单
上一章
目录
下一章
×