Spring Boot 打包成windows服务的生命周期

springboot buildwindows 服务,使用的是官方推荐的 sample , 代码有点老,依赖改了改,贴在下面

  properties部分
  
      ${project.build.directory}/dist
      ${project.artifactId}
      Demo
      com.steve.MasterApplication  
      
        Master service.
      
      30001 
      1.8
      UTF-8
  
    
  新增依赖 部分
  
  
      com.sun.winsw
      winsw
      2.2.0
      bin
      exe
  
  
      commons-daemon
      commons-daemon
      1.0.15
  
  
  build 部分
  
  
      
          
              org.apache.maven.plugins
              maven-dependency-plugin
              2.10
              
                  
                      copy
                      package
                      
                        copy
                      
                      
                          
                              
                                  com.sun.winsw
                                  winsw
                                  bin
                                  exe
                                  service.exe
                              
                          
                          ${dist.dir}
                      
                  
              
          
          
              org.apache.maven.plugins
              maven-resources-plugin
              2.7
              
                  
                  copy-resources
                  process-resources
                  
                    copy-resources
                  
                  
                      ${dist.dir}
                      
                          
                              src/main/dist
                              true
                          
                      
                  
                  
              
          
          
              org.apache.maven.plugins
              maven-assembly-plugin
              2.5.5
              
              
                  src/main/assembly/unix.xml
                  src/main/assembly/windows.xml
              
              
              
                  
                      assembly
                      package
                      
                        single
                      
                  
              
          
      
  
  
repository (不可少)
   
       
           alimaven
           aliyun maven
           http://maven.aliyun.com/nexus/content/groups/public/
           
            true
           
           
            false
           
       
       
           jenkins
           Jenkins Repository
           http://repo.jenkins-ci.org/releases
           
            false
           
       
   

这个项目是 windows 服务和 linux 服务都帮我们做了。所以需要引入 commons-daemon 这个依赖,改造我们的入口类.

@SpringBootApplication
public class MasterApplication implements Daemon{

    public static void main(String[] args) {
        SpringApplication sp = new SpringApplication(AgentApplication.class);
        Environment environment = sp.run(args).getEnvironment();
    }


    private Class springBootApp;

    private ConfigurableApplicationContext content;

    @Override
    public void init(DaemonContext context) throws DaemonInitException, Exception {
        this.springBootApp = ClassUtils.resolveClassName(context.getArguments()[0],
                AgentApplication.class.getClassLoader());
    }

    @Override
    public void start() throws Exception {
        this.content = SpringApplication.run(springBootApp);
    }

    @Override
    public void stop() throws Exception {
        this.content.close();
    }

    @Override
    public void destroy() {

    }
}

src/mian 下面添加sample对应的文件,添加完成后项目结构图

1555666436049.png

添加关闭的执行方法,这里就不用官方样例的那个shutdown方法了,官方的关闭方法在SpringApplicationAdminMXBeanRegistrar#SpringApplicationAdminshutdown方法里面,但是实际测试发现当程序运行时间过久,就会出现关闭不了的情况,于是开始寻找另一种程序化的关闭springboot 项目方法,发现有个通过http post调用actuator/shutdown方法去关闭项目的方法,看源码(在ShutdownEndpoint类中)

@WriteOperation
public Map shutdown() {
    if (this.context == null) {
        return NO_CONTEXT_MESSAGE;
    }
    try {
        return SHUTDOWN_MESSAGE;
    }
    finally {
        Thread thread = new Thread(this::performShutdown);  // 调用 performShutdown 方法
        thread.setContextClassLoader(getClass().getClassLoader());
        thread.start();
    }
}

private void performShutdown() {
    try {
        Thread.sleep(500L);   // 这个 sleep 在这里不知道有什么用,懂的评论区留言,谢谢指点。
    }
    catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
    this.context.close();
}

再来对比一下官方样例的SpringApplicationAdminshutdown方法

private class SpringApplicationAdmin implements SpringApplicationAdminMXBean {
    
    // ... 

    @Override
    public void shutdown() {
        logger.info("Application shutdown requested.");
        SpringApplicationAdminMXBeanRegistrar.this.applicationContext.close();
    }

}

可以发现最终都是通过关闭context来关闭应用,但是第一个方法就不会出现关闭不了程序的问题,即使项目中有线程池的存在,然后我们就自然而然想到了手写一个 MBean 来实现第一个方法的作用,开干

Shutdown.java

@Slf4j
public class Shutdown implements ApplicationContextAware {

    private ConfigurableApplicationContext applicationContext;
    public static final String DEFAULT_OBJECT_NAME="com.steve:type=Demo,name=Shutdown";  // MBean name

    public void shutdown() {
        Thread thread = new Thread(() -> performShutdown(applicationContext));
        thread.setContextClassLoader(LifeCycleService.class.getClassLoader());
        thread.start();
    }

    private void performShutdown(ConfigurableApplicationContext applicationContext) {
        try {
            Thread.sleep(500L);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
        if(applicationContext == null){
            log.error("shutdown failed, application context is null");
        }
        applicationContext.close();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(applicationContext instanceof ConfigurableApplicationContext) {
            this.applicationContext = (ConfigurableApplicationContext) applicationContext;
        }
    }

}


MBean (样例,非官方的xxxMBean结尾命名)我们已经下好了,接下来要借助Spring的帮助将这个类实例转化为MBean

DemoApplicationConfiguration.java

@Configuration
public class DemoApplicationConfiguration {
     @Bean
    public Shutdown lifeCycleConfiguration(){
        return new Shutdown();
    }

    /*
     * 由MBeanExporte 管理需要转换成 JMX的 MBean
     */
    @Bean
    public MBeanExporter mBeanExporter(Shutdown shutdown){
        MBeanExporter mBeanExporter = new MBeanExporter();
        Map beans = new HashMap<>();
        beans.put(Shutdown.DEFAULT_OBJECT_NAME, shutdown);
        mBeanExporter.setBeans(beans);
        return mBeanExporter;
    }
}

更多关于 SpringJMX 的支持看这篇 博文, 这里我们的关闭方法就已经准备好了,然后就是调用了,我们这里就直接套用样例代码的部分就可以了
SpringApplicationAdminClient.java

public class SpringApplicationAdminClient {

    public static final String DEFAULT_OBJECT_NAME = Shutdown.DEFAULT_OBJECT_NAME; //这里只需要改变object_name的值就行了,其他代码不做改动
    
    // .....
    
}

另外三个类 [ SpringBootService.class, StartSpringbootService.class, StopSpringbootService.class ] 就不需要改动了,来看一下windows服务的启动的文件service.xml内容改动


    @dist.project.id@
    @dist.project.name@
    @dist.project.description@
    %BASE%\
    %DATAMESH_HOME%/logs/@dist.project.id@
    rotate

    %JAVA_PATH%/java 
    [email protected]@
    -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false 
    -cp
    lib/*
    com.steve.StartSpringbootService  
    @dist.start.class@ 

    %JAVA_PATH%/java
    -cp
    lib/*
    com.steve.StopSpringbootService
    @dist.jmx.port@



然后就可以正常使用了,下面看一下最终打包的项目结构和操作指令

打包后target目录生成三个包,可以修改 assembly 文件夹中对应系统的xml文件更改对应系统的打包生成的压缩文件格式。建议linux改为tar.gz。 下面为打包好的文件图

1555666436049.png

一共有三个,一个是jar, 一个是linux下的,一个是windows下的。解压缩 windows系统的zip包,使用系统管理员打开cmd窗口

xxx.exe install    // 安装服务
net start xxx.exe  // 启动服务
net stop xxx.exe   // 关闭服务

sc delete xxx   // xxx 为安装的服务名, 删除对应的服务
sc query xxx    // 查询对应服务状态

踩坑:

  1. 这种启动方式当项目里引用了 spring-boot-devtools 这个依赖的时候启动会报异常:
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)

去除这个依赖就好了,暂时还没找到解决方案。目测是 class loader 的问题

你可能感兴趣的:(Spring Boot 打包成windows服务的生命周期)