有时由于项目需要,要将开发的一个程序打成JAR包运行,就像springBoot一样。

运行时,直接使用命令java -jar命令运行即可。

可运行JAR包打包

但如果那个项目不是一个springboot的项目该怎么办呢?

能实现的方式有很多,比如可以通过写一个sh脚本来实现

今天在看presto源码的时候,看到presto-cli(项目地址:https://github.com/prestodb/presto/tree/master/presto-cli)项目中有相关的代码,这里贴出来下,以便以后用到时,能快速地进行开发。

presto-cli可直接运行JAR的实现方法是通过maven插件来实现的(其实现原理目前还没研究,以后再慢慢研究下)

在maven的build下加入以下代码即可:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>executable</shadedClassifierName>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Main-Class>${main-class}</Main-Class>
                                    </manifestEntries>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.skife.maven</groupId>
                <artifactId>really-executable-jar-maven-plugin</artifactId>
                <configuration>
                    <flags>-Xmx1G</flags>
                    <classifier>executable</classifier>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>really-executable-jar</goal>
                        </goals>
                    </execution>
                </executions>
</plugin>
其中的main-class指的是将要运行JAR包主类的绝对路径。如我这里的:
com.haiyang.jar.HelloMain
如此操作之后,以后打包时,就会在target目录下生成一个可以直接运行的jar包了,是不是很方便!!!

运行时,可以通过java命令运行,亦可以直接运行这个JAR包,presto-cli的使用说明对于这一点也有相应的说明(https://prestodb.io/docs/current/installation/cli.html):

The CLI is a self-executing JAR file, which means it acts like a normal UNIX executable.

Download presto-cli-0.196-executable.jar, rename it to presto, make it executable with chmod +x, then run it:

./presto --server localhost:8080 --catalog hive --schema default

将jar包改名,然后再赋予可执行的权限即可。

为JAR包结束时添加监听方法

另外,在看presto-cli源码的时候,也找到了一个以前项目中没有解决问题的解决办法。

问题如下:在JAR包执行的过程中,由于服务操作人员手动通过ctrl+c命令或kill进程ID的方式将这个程序给KILL掉了,导致正在处理的操作还没有处理完而导致数据对不上号的问题。

比如有这样的一个程序:通过JAR包运行去爬取某个网站(如:www.haiyang.com)下所有关联页面的所有内容。在开始爬取这个网站时,就在数据库中记录一下正在爬取这个网站(www.haiyang.com),并将爬取状态设为【正在爬取】,待完成这个网站的爬取后将这条记录的爬取状态设为【爬取完成】。

考虑这种情况:www.haiyang.com网站下共存在10个页面,爬取JAR包程序目前正在运行,且已经完成了3个网页的爬取了,那么www.haiyang.com的当前状态为【正在爬取】。这时,来了一个运维大哥登录了服务器,PS了一下进程,出于某种原因将这个JAR包给KILL掉了,然后导致爬取JAR包停止了工作,而www.haiyang.com这条记录的状态也一直现实为【正在爬取】。那么问题就来了,实际上爬取程序已经挂了,并没有正在继续爬取网站了,而现显示的记录信息为正在爬取。

如果能在程序被KILL掉时记录下,就可以解决这个问题了。

那时自己在网上搜了下,没有找到解决办法。今天有幸在看到了presto-cli源码的Console类中中找到了解决此功能的代码。

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            exiting.set(true);
            interruptor.interrupt();
            awaitUninterruptibly(exited, EXIT_DELAY.toMillis(), MILLISECONDS);
        }));

没错,就是addShutdownHook这个方法。其JDK的说明如下:

java.lang.Runtime
public void addShutdownHook(@NotNull Thread hook)
Inferred annotations available: 
@org.jetbrains.annotations.NotNull
Registers a new virtual-machine shutdown hook.
The Java virtual machine shuts down in response to two kinds of events:
The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or
The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.

A shutdown hook is simply an initialized but unstarted thread. When the virtual machine begins its shutdown sequence it will start all registered shutdown hooks in some unspecified order and let them run concurrently. When all the hooks have finished it will then run all uninvoked finalizers if finalization-on-exit has been enabled. Finally, the virtual machine will halt. Note that daemon threads will continue to run during the shutdown sequence, as will non-daemon threads if shutdown was initiated by invoking the exit method.
Once the shutdown sequence has begun it can be stopped only by invoking the halt method, which forcibly terminates the virtual machine.
Once the shutdown sequence has begun it is impossible to register a new shutdown hook or de-register a previously-registered hook. Attempting either of these operations will cause an IllegalStateException to be thrown.
Shutdown hooks run at a delicate time in the life cycle of a virtual machine and should therefore be coded defensively. They should, in particular, be written to be thread-safe and to avoid deadlocks insofar as possible. They should also not rely blindly upon services that may have registered their own shutdown hooks and therefore may themselves in the process of shutting down. Attempts to use other thread-based services such as the AWT event-dispatch thread, for example, may lead to deadlocks.
Shutdown hooks should also finish their work quickly. When a program invokes exit the expectation is that the virtual machine will promptly shut down and exit. When the virtual machine is terminated due to user logoff or system shutdown the underlying operating system may only allow a fixed amount of time in which to shut down and exit. It is therefore inadvisable to attempt any user interaction or to perform a long-running computation in a shutdown hook.
Uncaught exceptions are handled in shutdown hooks just as in any other thread, by invoking the uncaughtException method of the thread's ThreadGroup object. The default implementation of this method prints the exception's stack trace to System.err and terminates the thread; it does not cause the virtual machine to exit or halt.
In rare circumstances the virtual machine may abort, that is, stop running without shutting down cleanly. This occurs when the virtual machine is terminated externally, for example with the SIGKILL signal on Unix or the TerminateProcess call on Microsoft Windows. The virtual machine may also abort if a native method goes awry by, for example, corrupting internal data structures or attempting to access nonexistent memory. If the virtual machine aborts then no guarantee can be made about whether or not any shutdown hooks will be run.

主要是说通过addShutdownHook注册一个钩子方法后,能在JVM退出时去调用。
为了测试,我写了如下代码:
public class HelloMain {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("-----TODO SOME THING ABOUT SHUTDOWN-----");
            }
        }));
        int i = 60;
        while (i > 0) {
            --i;
            try {
                Thread.sleep(1000);
                System.out.println("--------->>>>>>>>>" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果为:

--------->>>>>>>>>59
--------->>>>>>>>>58
………此处省略N行………
--------->>>>>>>>>2
--------->>>>>>>>>1
--------->>>>>>>>>0
-----TODO SOME THING ABOUT SHUTDOWN----- 

即hook方法会在JVM完全退出前执行。


当这个JAR包放在linux上执行被中断又会是怎么样的结果呢?

root@ubuntu:/home/haiyang/Desktop/temp# ./can-excute 
--------->>>>>>>>>59
--------->>>>>>>>>58
--------->>>>>>>>>57
--------->>>>>>>>>56
--------->>>>>>>>>55
--------->>>>>>>>>54
^C-----TODO SOME THING ABOUT SHUTDOWN-----
root@ubuntu:/home/haiyang/Desktop/temp# 

当按下ctrl+c退出时,也成功执行了hook中的方法。


当此JAR被KILL时呢?

KILL掉:

root@ubuntu:~# jps
85303 can-excute
85389 Jps
2029 jenkins.war
root@ubuntu:~# kill 85303
root@ubuntu:~# 

运行结果:

--------->>>>>>>>>59
--------->>>>>>>>>58
--------->>>>>>>>>57
--------->>>>>>>>>56
--------->>>>>>>>>55
--------->>>>>>>>>54
--------->>>>>>>>>53
--------->>>>>>>>>52
--------->>>>>>>>>51
--------->>>>>>>>>50
--------->>>>>>>>>49
--------->>>>>>>>>48
--------->>>>>>>>>47
--------->>>>>>>>>46
-----TODO SOME THING ABOUT SHUTDOWN-----


当此JAR包被KILL -9呢?

kill -9 

root@ubuntu:~# jps
88554 can-excute
88925 Jps
2029 jenkins.war
root@ubuntu:~# kill -9 88554
root@ubuntu:~#  

运行结果:

root@ubuntu:/home/haiyang/Desktop/temp# ./can-excute 
--------->>>>>>>>>59
--------->>>>>>>>>58
--------->>>>>>>>>57
--------->>>>>>>>>56
--------->>>>>>>>>55
--------->>>>>>>>>54
--------->>>>>>>>>53
--------->>>>>>>>>52
--------->>>>>>>>>51
--------->>>>>>>>>50
--------->>>>>>>>>49
--------->>>>>>>>>48
--------->>>>>>>>>47
--------->>>>>>>>>46
--------->>>>>>>>>45
--------->>>>>>>>>44
Killed
root@ubuntu:/home/haiyang/Desktop/temp# 

由此看出,hook里的方法在JVM正常关闭时会被执行,如KILL或ctrl+c命令。而被强制KILL时,不会执行。


Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐