这听起来像是你永远都不需要的东西,但是有时候,当你发布最终用户软件时,你可能需要安装一个java程序作为Windows服务。我不得不这么做,因自动转换并将其Excel文件推送到我的国家的opendata门户网站。该工具必须定期运行,因此它是一项服务的主要候选对象(即使公务员完全忘记这项任务,这也将使上传成为可能,此外,重复的手动上传是浪费时间)。
尽管有许多关于这个主题的帖子和stackoverflow答案,但我仍然花了很多时间,因为一些小的警告和一个很少有人拥有的重要先决条件——拥有一个捆绑的JRE,这样就没有人需要下载和安装JRE(这会不必要地使安装过程变得复杂,并且目标受众不一定精通技术)。
所以,有了带jar打包的maven项目,我首先想到的是打包一个e将其注册为服务。这样做的问题是,java程序使用一个预定的执行器,所以它永远不会退出,这使得它不可能作为一个进程启动。
所以我不得不“妖魔化”它普罗克伦。在此之前,我必须将所需的每个组件组装到一个目标文件夹中fat jar(包括所有依赖项)、JRE、commons-daemon二进制文件和配置文件。
你可以看到完整${installer.dir}
存在${project.basedir}/target/installer}
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-compiler-plugin</ artifactId > < version >2.3.2</ version > < configuration > < source >1.8</ source > < target >1.8</ target > </ configuration > </ plugin > < plugin > < artifactId >maven-assembly-plugin</ artifactId > < executions > < execution > < id >assembly</ id > < phase >package</ phase > < goals > < goal >single</ goal > </ goals > < configuration > < descriptorRefs > < descriptorRef >jar-with-dependencies</ descriptorRef > </ descriptorRefs > < finalName >opendata-ckan-pusher</ finalName > < appendAssemblyId >false</ appendAssemblyId > </ configuration > </ execution > </ executions > </ plugin > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-antrun-plugin</ artifactId > < version >1.7</ version > < executions > < execution > < id >default-cli</ id > < phase >package</ phase > < goals > < goal >run</ goal > </ goals > < configuration > < target > < copy todir = "${installer.dir}/jre1.8.0_91" > < fileset dir = "${project.basedir}/jre1.8.0_91" /> </ copy > < copy todir = "${installer.dir}/commons-daemon" > < fileset dir = "${project.basedir}/commons-daemon" /> </ copy > < copy file = "${project.build.directory}/opendata-ckan-pusher.jar" todir = "${installer.dir}" /> < copy file = "${project.basedir}/install.bat" todir = "${installer.dir}" /> < copy file = "${project.basedir}/uninstall.bat" todir = "${installer.dir}" /> < copy file = "${project.basedir}/config/pusher.yml" todir = "${installer.dir}" /> < copy file = "${project.basedir}/LICENSE" todir = "${installer.dir}" /> </ target > </ configuration > </ execution > </ executions > </ plugin > |
您会注意到installer.bat和uninstaller.bat这两个文件使用commons-daemon来管理服务。安装程序创建服务。Commons-daemon有三种模式:exe(允许你包装任意可执行文件)、java(类似exe,但针对java应用)和jvm(在同一个进程中运行Java应用;虽然我不知道具体如何)。
我可以使用所有三个选项(包括launch4j created exe),但是jvm允许您有一个指定的方法来控制您正在运行的应用程序。start class/start method/stop class/stop method参数就是为此准备的。这是完整的安装程序。蝙蝠:
https://movie.douban.com/review/14637559/
1 2 3 4 5 6 7 8 9 | commons-daemon\prunsrv //IS//OpenDataPusher --DisplayName="OpenData Pusher" --Description="OpenData Pusher"^ --Install="%cd%\commons-daemon\prunsrv.exe" --Jvm="%cd%\jre1.8.0_91\bin\client\jvm.dll" --StartMode=jvm --StopMode=jvm^ --Startup=auto --StartClass=bg.government.opendatapusher.Pusher --StopClass=bg.government.opendatapusher.Pusher^ --StartParams=start --StopParams=stop --StartMethod=windowsService --StopMethod=windowsService^ --Classpath="%cd%\opendata-ckan-pusher.jar" --LogLevel=DEBUG^ --LogPath="%cd%\logs" --LogPrefix=procrun.log^ --StdOutput="%cd%\logs\stdout.log" --StdError="%cd%\logs\stderr.log" commons-daemon\prunsrv //ES//OpenDataPusher |
一些澄清:
- Jvm参数指向jvm dll
- start class/start method/stop class/stop method指向用于控制正在运行的应用程序的指定方法。在这种情况下,启动将只调用main方法,停止将关闭预定的执行器,以便应用程序可以退出
- 类路径参数指向胖罐子
- 使用%cd%来确定当前目录的路径是有风险的,但是由于最终用户总是从它所在的目录启动它,所以在这种情况下是安全的。
这windowsService
看起来是这样的:
https://baa.yiche.com/DEUSdiandongpaoche/thread-42947806.html
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static void windowsService(String args[]) throws Exception { String cmd = "start" ; if (args.length > 0 ) { cmd = args[ 0 ]; } if ( "start" .equals(cmd)) { Pusher.main( new String[]{}); } else { executor.shutdownNow(); System.exit( 0 ); } } |
这里需要注意的一点是您可能遇到的32位/64位问题。这就是为什么捆绑32位JRE并使用32位(默认)prunsrv.exe更安全的原因。
然后,我有一个“安装程序”文件夹,其中有jre和commons-daemon文件夹、两个bat文件和一个fat jar。然后,我可以将它打包成一个可自解压的归档文件,并分发它(当然,附带一个手册)。我调查但是找不到如何捆绑一个JRE(也许你可以)。
这是一个非常小的场景——通常我们开发部署到Linux服务器上,但是为一个使用Java的大组织提供本地工具可能不时需要。在我的例子中,长时间运行的部分是一个预定的执行器,但是它也可以运行一个服务于web接口的jetty服务。为什么要这样做,而不是提供一个URL——在访问本地机器很重要的情况下。它甚至可以是分布式搜索个你想用Java写的p2p软件。
Hibernate提供事件监听器作为其SPI的一部分。您可以将侦听器与许多事件挂钩,包括预插入、后插入、预删除、刷新等。
但是有时在这些监听器中,你想要使用spring依赖。hibernate已经升级了,现在有了更好的方法(由于缺少类,旧方法在最新版本中不起作用)。
这次更简单。你只需要一颗像这样的豆子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Component public class HibernateListenerConfigurer { @PersistenceUnit private EntityManagerFactory emf; @Inject private YourEventListener listener; @PostConstruct protected void init() { SessionFactoryImpl sessionFactory = emf.unwrap(SessionFactoryImpl. class ); EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry. class ); registry.getEventListenerGroup(EventType.POST_INSERT).appendListener(listener); registry.getEventListenerGroup(EventType.POST_UPDATE).appendListener(listener); registry.getEventListenerGroup(EventType.POST_DELETE).appendListener(listener); } } |
它然而,这是行不通的,因为它也依赖于过时的类。
你也可以注射一个List<..>
侦听器(尽管它们没有共享一个公共接口,但您可以定义自己的接口)。
正如SO回答中所指出的,您不能在监听器中存储新的