Springboot启动时开启一个或多个线程

需求背景

最近项目基于springboot做微信机器人,从网上看了一下,一般都是单个用户登录,然后一个线程死循环,类似监听器,如果有信息就处理信息。但是,我们的要求是需要可以多个用户登录,所以,每次登录一个用户,开启一个线程,用户意外退出或者接口响应错误的情况下,该线程回收。

具体流程图如下:
微信机器人流程图

设计思路很简单,但是问题真多。具体遇到的问题,在另一篇中详细讲解吧。此处,就想解决线程池争取资源时间太长的问题,想放弃前面的思路,直接在项目启动的时候开启一个监听器线程,循环处理。也就是上面流程图中async的监听器,原来是每个登录请求都会创建,现在设计为程序启动后直接开启一个线程监听,从队列中获取信息,新来的机器人加入到队列末尾,依次遍历,同微信服务器保持心跳。虽然两者都有很多弊端,能支持4-8个机器人同时在线即可。

开启线程

方法1

首先想到方法是在Application的main方法中创建一个线程:

public static void main(String[] args) {
    System.setProperty("https.protocols", "TLSv1");
    System.setProperty("jsse.enableSNIExtension", "false");
    SpringApplication application = new SpringApplication(WxbotApplication.class);
    application.setBannerMode(Banner.Mode.CONSOLE);
    application.addListeners(new PropertiesListener("classpath*:*.properties"));
    application.run(args);
    // 开启监听线程
    Thread listener = new Thread(new RobotListenJob());
    listener.start();
}

但是如果我的RobotListenJob里面有些service需要注入的时候,这时候,虽然spring给我留了接口可获取容器中的bean,但是不太优雅,甚至是有些部署环境还会无法启动该线程,如果是单独的jar包服务,方法1方法2均是可行的,但是web中不一定可行

// RobotListenJob.java

public class RobotListenJob implements Runnable {
    private WxbotService wxbotService;

    public RobotListenJob() {
        wxbotService = getWxbotService();
    }
    @Override
    public void run() {
        if (wxbotService != null) {
            wxbotService.listen();
        }
    }

    /**
    * 从spring上下文容器中获取wxbotService实例
    */
    private WxbotService getWxbotService() {
        return SpringContextUtils.getBean(WxbotService.class);
    }
}

这种方式,可能就是wxbotService对象还没有实例化,同样还有一种就是在Application中获取wxbotService实例,传给RobotListenJob的构造函数,代码如方法2

方法2

public static void main(String[] args) {
    System.setProperty("https.protocols", "TLSv1");
    System.setProperty("jsse.enableSNIExtension", "false");
    ApplicationContext ctx = SpringApplication.run(WxbotApplication.class, args);

    // 开启监听线程
    Thread listener = new Thread(new RobotListenJob(ctx.getBean(WxbotService.class)));
    listener.start();
}

RobotListenJob增加有参构造函数:

public class RobotListenJob implements Runnable {
    private WxbotService wxbotService;

    public RobotListenJob(WxbotService wxbotService) {
        this.wxbotService = wxbotService;
    }
    @Override
    public void run() {
        if (wxbotService != null) {
            wxbotService.listen();
        }
    }
}

上述方法1方法2,如果你的web项目继承了SpringBootServletInitializer,且在protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)该方法中构建了Application.class,是没有问题的,如下。

public class SpringBootStartApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的WxbotApplication启动类
        // 此处的WxbotApplication就是文章中所说的Application,里面有一个main方法
        return builder.sources(WxbotApplication.class);
    }
}

方法3

下面我们看看终极大招一,使用@Component的注解方式来开启一个线程,还是利用了spring注解的原理以及spring初始化bean的方式(无参构造器),其他没什么新意。直接通过构造函数,开启一个线程。

// RobotListenJob.java
@Component
public class RobotListenJob implements DisposableBean, Runnable {
    @Resource
    private WxbotService wxbotService;
    private Thread thread;
    // private boolean someCondition;

    public RobotListenJob() {
        thread = new Thread(this);
    }
    @Override
    public void run() {
        if (wxbotService != null) {
            // listen方法是一个带条件的死循环
            wxbotService.listen();
        }

        // 也可以根据条件在该方法中开启一个
        //while(someCondition) {
        // doStuff();
        //}
    }

    @Override
    public void destroy(){
        // someCondition = false
    }
}

方法4

springboot启动过程产生的6种事件,包括ApplicationStartingEventApplicationEnvironmentPreparedEventApplicationPreparedEventApplicationStartedEventApplicationReadyEvent、“和ApplicationFailedEvent,通过ApplicationReadyEvent事件可以实现系统启动完后做一些系统初始化的操作。

  • ApplicationStartingEvent: springboot应用启动且未作任何处理(除listener注册和初始化)的时候发送ApplicationStartingEvent
  • ApplicationEnvironmentPreparedEvent: 确定springboot应用使用的Environment且context创建之前发送这个事件
  • ApplicationPreparedEvent: context已经创建且没有refresh发送个事件
  • ApplicationStartedEvent: context已经refresh且application and command-line runners(如果有) 调用之前发送这个事件
  • ApplicationReadyEvent: application and command-line runners (如果有)执行完后发送这个事件,此时应用已经启动完毕
  • ApplicationFailedEvent: 应用启动失败后产生这个事件

创建RoboJobListener事件监听器,实现onApplicationEvent方法,在ApplicationReadyEvent就绪时,调用WxbotService服务。

public class RoboJobListener implements ApplicationListener<ApplicationEvent>{
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartingEvent){
            return;
        }
        if (event instanceof ApplicationEnvironmentPreparedEvent){
            return;
        }
        if (event instanceof ApplicationPreparedEvent){
            return;
        }
        if (event instanceof ApplicationStartedEvent){
            return;
        }
        if (event instanceof ApplicationReadyEvent){
            ApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext();
            WxbotService wxbotService = context.getBean(WxbotService.class);
            wxbotService.listen();
            return;
        }
        if (event instanceof ApplicationFailedEvent){
            return;
        }
    }
}

注册监听器listener,在SpringApplication初始化的时候添加进去。

@SpringBootApplication
public class WxbotApplication {
public static void  main(String[]args){
    new SpringApplicationBuilder().sources(WxbotApplication.class)
            .listeners(new RoboJobListener()).run(args);
    }
}

方法5

除了springboot启动过程产生的6种事件中通过ApplicationReadyEvent事件可以实现系统启动完后做一些系统初始化的操作外,springboot应用还可以通过ApplicationRunner(CommandLineRunner也类似)这种方式也可以实现同样的功能。

@Component
public class RobotListenJob implements ApplicationRunner {
    @Resource
    private WxbotService wxbotService;

    @Override
    public void run(ApplicationArguments args) {
        Thread thread = new Thread(() -> {
            if (wxbotService != null) {
                // listen方法是一个带条件的死循环
                wxbotService.listen();
            }
            // 也可以根据条件在该方法中开启一个
            //while(someCondition) {
            // doStuff();
            //}
        });
        thread.start();
    }
}

点击量:21

发表评论