关于springboot:了解这些你就可以在Spring启动时为所欲为了

6次阅读

共计 6338 个字符,预计需要花费 16 分钟才能阅读完成。

八仙过海,各显神通

Spring 是一个管制反转依赖治理的容器,作为 Java Web 的开发人员,根本没有不相熟 Spring 技术栈的,只管在依赖注入畛域,Java Web 畛域不乏其余优良的框架,如 google 开源的依赖治理框架 guice,如 Jersey web 框架等。但 Spring 曾经是 Java Web 畛域应用最多,利用最宽泛的 Java 框架。

此文将专一解说如何在 Spring 容器启动时实现咱们本人想要实现的逻辑。咱们时常会遇到在 Spring 启动的时候必须实现一些初始化的操作,如创立定时工作,创立连接池等。

如果没有 Spring 容器,不依赖于 Spring 的实现,回归 Java 类实现自身,咱们能够在动态代码块,在类构造函数中实现相应的逻辑,Java 类的初始化程序顺次是 动态变量 > 动态代码块 > 全局变量 > 初始化代码块 > 结构器

比方,Log4j 的初始化,就是在 LogManager 的动态代码块中实现的:

static {Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
    repositorySelector = new DefaultRepositorySelector(h);

    String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);

    if(override == null || "false".equalsIgnoreCase(override)) {String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
          String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);

          URL url = null;

          if(configurationOptionStr == null) {url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
            if(url == null) {url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
            }
          } else {
            try {url = new URL(configurationOptionStr);
            } catch (MalformedURLException ex) {url = Loader.getResource(configurationOptionStr);
            }
          }

          if(url != null) {LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
            try {OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());
            } catch (NoClassDefFoundError e) {LogLog.warn("Error during default initialization", e);
            }
          } else {LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
          }
    } else {LogLog.debug("Default initialization of overridden by" +  DEFAULT_INIT_OVERRIDE_KEY + "property.");
    }
}

比方在构造函数中实现相应的逻辑:

@Component
public class CustomBean {

    @Autowired
    private Environment env;

    public CustomBean() {env.getActiveProfiles();
    }
}

这里考验一下各位,下面的代码是否能够失常运行。—— 不行,构造函数中的 env 将会产生 NullPointException 异样。这是因为在 Spring 中将先初始化 Bean,也就是会先调用类的构造函数,而后才注入成员变量依赖的 Bean(@Autowired@Resource 注解润饰的成员变量),留神 @Value 等注解的配置的注入也是在构造函数之后。

@PostConstruct

在 Spring 中,咱们能够应用 @PostConstruct 在 Bean 初始化之后实现相应的初始化逻辑,@PostConstruct润饰的办法将在 Bean 初始化实现之后执行,此时 Bean 的依赖也曾经注入实现,因而能够在办法中调用注入的依赖 Bean。

@Component
public class CustomBean {

    @Autowired
    private Environment env;

    @PostConstruce
    public void init() {env.getActiveProfiles();
    }
}

@PostConstruct 绝对应的,如果想在 Bean 登记时实现一些打扫工作,如敞开线程池等,能够应用 @PreDestroy 注解:

@Component
public class CustomBean {

    @Autowired
    private ExecutorService executor = Executors.newFixedThreadPool(1)

    @PreDestroy
    public void destroy() {env.getActiveProfiles();
    }
}

InitializingBean

实现 Spring 的 InitializingBean 接口同样能够实现以上在 Bean 初始化实现之后执行相应逻辑的性能,实现 InitializingBean 接口,在 afterPropertiesSet 办法中实现逻辑:

@Component
public class CustomBean implements InitializingBean {

    private static final Logger LOG
      = Logger.getLogger(InitializingBeanExampleBean.class);

    @Autowired
    private Environment environment;

    @Override
    public void afterPropertiesSet() throws Exception {LOG.info(environment.getDefaultProfiles());
    }
}

ApplicationListener

咱们能够在 Spring 容器初始化的时候实现咱们想要的初始化逻辑。这时咱们就能够应用到 Spring 的初始化事件。Spring 有一套残缺的事件机制,在 Spring 启动的时候,Spring 容器自身预设了很多事件,在 Spring 初始化的整个过程中在相应的节点触发相应的事件,咱们能够通过监听这些事件来实现咱们的初始化逻辑。Spring 的事件实现如下:

  • ApplicationEvent,事件对象,由 ApplicationContext 公布,不同的实现类代表不同的事件类型。
  • ApplicationListener,监听对象,任何实现了此接口的 Bean 都会收到相应的事件告诉。实现了 ApplicationListener 接口之后,须要实现办法 onApplicationEvent(),在容器将所有的 Bean 都初始化实现之后,就会执行该办法。

与 Spring Context 生命周期相干的几个事件有以下几个:

  • ApplicationStartingEvent: 这个事件在 Spring Boot 利用运行开始时,且进行任何解决之前发送(除了监听器和初始化器注册之外)。
  • ContextRefreshedEvent: ApplicationContext 被初始化或刷新时,该事件被公布。这也能够在 ConfigurableApplicationContext 接口中应用 refresh() 办法来产生。
  • ContextStartedEvent: 当应用 ConfigurableApplicationContext 接口中的 start() 办法启动 ApplicationContext 时,该事件被触发。你能够查问你的数据库,或者你能够在承受到这个事件后重启任何进行的应用程序。
  • ApplicationReadyEvent: 这个事件在任何 application/ command-line runners 调用之后发送。
  • ContextClosedEvent: 当应用 ConfigurableApplicationContext 接口中的 close() 办法敞开 ApplicationContext 时,该事件被触发。一个已敞开的上下文达到生命周期末端;它不能被刷新或重启。
  • ContextStoppedEvent: Spring 最初实现的事件。

因而,如果咱们想在 Spring 启动的时候实现一些相应的逻辑,能够找到 Spring 启动过程中合乎咱们须要的事件,通过监听相应的事件来实现咱们的逻辑:

@Component
@Slf4j
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {log.info("Subject ContextRefreshedEvent");
    }
}

除了通过实现 ApplicationListener 接口来监听相应的事件,Spring 的事件机制也实现了通过 @EventListener 注解来监听绝对应事件:

@Component
@Slf4j
public class StartupApplicationListenerExample {

    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {log.info("Subject ContextRefreshedEvent");
    }
}

Spring Event 是一套欠缺的过程内事件公布订阅机制,咱们除了用来监听 Spring 内置的事件,也能够应用 Spring Event 实现自定义的事件公布订阅性能。

Constructor 注入

在学习 Spring 的注入机制的时候,咱们都晓得 Spring 能够通过构造函数、Setter 和反射成员变量注入等形式。下面咱们在成员变量上通过 @Autoware 注解注入依赖 Bean,然而在 Bean 的构造函数函数中却无奈应用到注入的 Bean(因为 Bean 还未注入),其实咱们也是应用 Spring 的构造函数注入形式,这也是 Spring 举荐的注入机制(在咱们应用 IDEA 的时候,如果没有敞开相应的代码 Warning 机制,会发现在成员变量上的 @Autoware 是黄色的,也就是 idea 不倡议的代码)。Spring 更举荐构造函数注入的形式:

@Component
@Slf4j
public class ConstructorBean {

    private final Environment environment;

    @Autowired
    public LogicInConstructorExampleBean(Environment environment) {
        this.environment = environment;
        log.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

CommandLineRunner

如果咱们的我的项目应用的是 Spring Boot,那么能够应用 Spring Boot 提供的 CommandLineRunner 接口来实现初始化逻辑,Spring Boot 将在启动初始化实现之后调用实现了 CommandLineRunner 的接口的 run 办法:

@Component
@Slf4j
public class CommandLineAppStartupRunner implements CommandLineRunner {

    @Override
    public void run(String...args) throws Exception {log.info("Increment counter");
    }
}

并且,多个 CommandLineRunner 实现,能够通过 @Order 来管制它们的执行程序。

SmartLifecycle

还有一种更高级的办法来实现咱们的逻辑。这能够 Spring 高级开发必备技能哦。SmartLifecycle 不仅仅能在初始化后执行一个逻辑,还能再敞开前执行一个逻辑,并且也能够管制多个 SmartLifecycle 的执行程序,就像这个类名示意的一样,这是一个智能的生命周期治理接口。

  • start():bean 初始化结束后,该办法会被执行。
  • stop():容器敞开后,spring 容器发现以后对象实现了 SmartLifecycle,就调用 stop(Runnable),如果只是实现了 Lifecycle,就调用 stop()。
  • isRunning:以后状态,用来判你的断组件是否在运行。
  • getPhase:管制多个 SmartLifecycle 的回调程序的,返回值越小越靠前执行 start() 办法,越靠后执行 stop() 办法。
  • isAutoStartup():start 办法被执行前先看此办法返回值,返回 false 就不执行 start 办法了。
  • stop(Runnable):容器敞开后,spring 容器发现以后对象实现了 SmartLifecycle,就调用 stop(Runnable),如果只是实现了 Lifecycle,就调用 stop()。
@Component
public class SmartLifecycleExample implements SmartLifecycle {

    private boolean isRunning = false;

    @Override
    public void start() {System.out.println("start");
        isRunning = true;
    }

    @Override
    public int getPhase() {
        // 默认为 0
        return 0;
    }

    @Override
    public boolean isAutoStartup() {
        // 默认为 false
        return true;
    }

    @Override
    public boolean isRunning() {
        // 默认返回 false
        return isRunning;
    }

    @Override
    public void stop(Runnable callback) {System.out.println("stop(Runnable)");
        callback.run();
        isRunning = false;
    }

    @Override
    public void stop() {System.out.println("stop");

        isRunning = false;
    }

}

往期举荐:

从面试角度一文学完 Kafka
Tomcat 架构原理解析到架构设计借鉴
终极解密输出网址按回车到底产生了什么

读者敌人能够加我微信备注「加群」退出「码哥字节」专属技术读者群,一起成长。群里还会分享「阿里」和「腾讯」内推,欢送大神到碗里来。

正文完
 0