Spring Boot 启动输出一句话


placeholder image
admin 发布于:2022-05-09 10:35:57
阅读:loading

案例实践

在Spring Boot启动过程中做一些程序初始化的动作是一个比较常规的事情,这里就以程序启动输出一句话作为实际监听启动过程中的业务逻辑。总共整理了几种常见的实现方式来分别实现(未使用Web环境依赖的Servlet、Filter),主要以Java和Spring环境相关的实现方式,参考实现方式如下:

(1)启动类输出,这种输出方式拥有绝对的最先触发和最后触发,实现参考如下:

/**
 * 启动类
 *
 * @author chendd
 * @date 2022/5/8 20:09
 */
@SpringBootApplication
@Import(value = ContextConfiguration.class)
public class Bootstrap {

    /**
     * 服务器启动
     * @param args 启动参数
     */
    public static void main(String[] args) {
        SoutCounter.sign("Bootstrap.main###启动类启动前输出一句话");
        SpringApplication.run(Bootstrap.class , args);
        SoutCounter.sign("Bootstrap.main###启动类启动后输出一句话");
    }

}

(2)Spring组件的构造函数触发,构造函数的方式优先于其它各种触发逻辑;

(3)@PostConstruct注解触发,注意该注解是JDK内置的并非Spring提供,触发时机为当前组件类初始化完成后触发,实现参考如下:

package cn.chendd.modules.sout;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * 普通组件
 *
 * @author chendd
 * @date 2022/5/25 21:48
 */
@Component
public class SoutComponent {

    public SoutComponent() {
        SoutCounter.sign("SoutComponent###使用构造函数输出");
    }

    @PostConstruct
    public void postVonstruct() {
        SoutCounter.sign("SoutComponent.postVonstruct###使用@PostConstruct输出");
    }

}

(4)实现InitializingBean接口注册Bean组件,并重写afterPropertiesSet方法执行输出,实现参考如下:

package cn.chendd.modules.sout;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * 使用InitializingBean
 *
 * @author chendd
 * @date 2022/5/25 22:46
 */
@Component
public class SoutInitializingBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        SoutCounter.sign("SoutInitializingBean使用InitializingBean输出");
    }
}

(5)使用监听器Listener来监控服务器启动进度实现输出,在ApplicationListener的泛型支持的实现类中支持多种启动事件的监听,本文逻辑其中三种事件监听来验证实现,分别是:ApplicationStartedEvent、ApplicationReadyEvent、ContextRefreshedEvent,分别是:服务器启动后触发监听、服务器环境启动好触发监听、Spring容器上下文刷新触发监听,请注意上下文刷新事件ContextRefreshedEvent可能会执行多次,若使用该事件作为初始化的功能时需要处理重复执行的场景,实现参考如下:

package cn.chendd.modules.sout;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * 启动监听器
 *
 * @author chendd
 * @date 2022/5/25 22:16
 */
@Configuration
public class SoutApplicationListener {

    @Component
    public static class SoutApplicationStartingListener implements ApplicationListener<ApplicationStartedEvent> {

        @Override
        public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
            SoutCounter.sign("SoutApplicationListener$ApplicationStartedEvent###使用ApplicationStartedEvent监听器输出");
        }
    }

    @Component
    public static class SoutApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {

        @Override
        public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
            SoutCounter.sign("SoutApplicationListener$SoutApplicationReadyListener###使用ApplicationReadyEvent监听器输出");
        }
    }

    @Component
    public static class SoutContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
            SoutCounter.sign("SoutApplicationListener$SoutContextRefreshedEventListener###使用ContextRefreshedEvent监听器输出");
        }
    }

}

(6)使用CommandLineRunner接口实现run方法实现输出,最初发现该种实现方式还是在翻阅Spring官网提供的子项目示例中看到的这种初始化方式,借着本次实践特整理出来,run方法的args参数等同于启动类的指定的args,实现参考如下:

package cn.chendd.modules.sout;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 使用CommandLineRunner输出
 *
 * @author chendd
 * @date 2022/5/25 22:29
 */
@Component
@Order(101)
public class SoutCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        //此处args等同于启动类main的参数
        SoutCounter.sign("SoutCommandLineRunner使用CommandLineRunner输出");
    }
}

(7)使用SoutApplicationRunner接口实现run方法实现输出,这种实现与上述CommandLineRunner接口的实现比较相似,推荐使用这种方式,因为在它的run方法参数类型为ApplicationArguments,除了可以拿到应用程序main函数启动时的args外,还可以获取到sourceArgs、optionNames、optionValues等启动参数,实现参考如下:

package cn.chendd.modules.sout;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 使用ApplicationRunner
 *
 * @author chendd
 * @date 2022/5/25 22:52
 */
@Component
@Order(102)
public class SoutApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        SoutCounter.sign("SoutApplicationRunner使用ApplicationRunner输出");
    }
}

程序技术器

package cn.chendd.modules.sout;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 启动时运行方法的计数器
 *
 * @author chendd
 * @date 2022/5/25 21:50
 */
public class SoutCounter {

    /**
     * 默认构造计数器
     */
    private static final AtomicLong COUNTER = new AtomicLong();

    /**
     * 加1后返回
     * @return 计数器的值
     */
    private static Long add() {
        return COUNTER.addAndGet(1);
    }

    public static void sign(String location) {
        String message = String.format("%1$s,执行顺序:%2$d" , location , add());
        System.err.println(message);
    }

}

程序输出结果

image.png

其它注意

(1)一些同类型的组件的初始化执行顺序默认以扫描包的优先级触发,可使用@Order注解设置优先顺序;

(2)未使用BeanPostProcessor这种所有初始化均触发的启动实现;

源码下载

源码工程下载:源码下载.zip


 点赞


 发表评论

当前回复:作者

 评论列表


留言区