当前位置: 代码网 > it编程>编程语言>Java > SpringBoot初始化加载配置的八种方式总结

SpringBoot初始化加载配置的八种方式总结

2024年12月07日 Java 我要评论
@postconstruct 注解initializingbean 接口@bean initmethod方法构造器注入applicationlistenercommandlinerunnerappli
  • @postconstruct 注解
  • initializingbean 接口
  • @bean initmethod方法
  • 构造器注入
  • applicationlistener
  • commandlinerunner
  • applicationrunner
  • smartlifecycle 

序号

初始化加载方式

执行时机

1

@postconstruct 注解(在方法加注解)

bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 

2

构造器注入(构造方法加  @autowired注解 

bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 

3

initializingbean 接口(继承 initializingbean接口,并实现 afterpropertiesset()这个方法)

bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 ) 3和4是一样的,实现方式不同

4

@bean initmethod方法

bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行  3和4是一样的,实现方式不同

5

smartlifecycle 接口(继承smartlifecycle 接口),并实现start() 方法

smartlifecycle 的执行时机是在 spring 应用上下文刷新完成之后,即所有的 bean 都已经被实例化和初始化之后。

6

applicationlistener(继承  applicationlistener接口,并实现onapplicationevent()方法  与方法上加 @eventlistener的效果一样 

所有bean初始化完成后才会执行方法

7

commandlinerunner 继承 commandlinerunner,继承 run() 方法 

应用启动后执行

8

applicationrunner( 继承 commandlinerunner,继承 run() 方法 

应用启动后执行

背景

在日常开发时,我们常常需要 在springboot 应用启动时执行某一段逻辑,如下面的场景:

  • 获取一些当前环境的配置或变量

  • 向缓存数据库写入一些初始数据

  • 连接某些第三方系统,确认对方可以工作

在实现这些功能时,我们可能会遇到一些"坑"。 为了利用springboot框架的便利性,我们不得不将整个应用的执行控制权交给容器,于是造成了大家对于细节是一无所知的。那么在实现初始化逻辑代码时就需要小心了,比如,我们并不能简单的将初始化逻辑在bean类的构造方法中实现,类似下面的代码:

@component 
public class invalidinitexamplebean { 
    @autowired 
     private environment env; 
    
    public invalidinitexamplebean() { 
    
        env.getactiveprofiles(); 
    } 
}

注意:这里,我们在invalidinitexamplebean的构造方法中试图访问一个自动注入的env字段,当真正执行时,你一定会得到一个空指针异常(nullpointerexception)。原因在于,当构造方法被调用时,spring上下文中的environment这个bean很可能还没有被实例化,同时也仍未注入到当前对象,所以并不能这样进行调用。

下面,我们来看看在springboot中实现"安全初始化"的一些方法:

一、@postconstruct 注解

@postconstruct 注解其实是来自于 javax的扩展包中(大多数人的印象中是来自于spring框架),它的作用在于声明一个bean对象初始化完成后执行的方法。

来看看它的原始定义:

the postconstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.

也就是说,该方法会在所有依赖字段注入后才执行,当然这一动作也是由spring框架执行的。 

示例:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.env.environment;
import org.springframework.stereotype.component;
import javax.annotation.postconstruct;
import java.util.arrays;
 
/**
 * @description todo
 * @date 2024/12/5 11:19
 * @version 1.0
 * @author gezongyang
 */
@component
@slf4j
public class invalidinitexamplebean {
 
    @autowired
    private environment environment;
 
    /**
     * 该方法会在所有依赖字段(environment)注入后才执行,当然这一动作也是由spring框架执行的。
     */
    @postconstruct
    public void init() {
        //environment 已经注入
        log.info("@postconstruct execute:{}",arrays.aslist(environment.getdefaultprofiles()));
    }
}

二、实现 initializingbean 接口

initializingbean 是由spring框架提供的接口,其与@postconstruct注解的工作原理非常类似。

如果不使用注解的话,你需要让bean实例继承 initializingbean接口,并实现afterpropertiesset()这个方法。

示例:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.initializingbean;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.env.environment;
import org.springframework.stereotype.component;
import java.util.arrays;
 
/**
 * @description 继承 initializingbean接口,并实现afterpropertiesset()
 *  afterpropertiesset() 会在所有依赖的字段(environment)注入后才执行
 * @date 2024/12/5 11:37
 * @version 1.0
 * @author gezongyang
 */
@component
@slf4j
public class initializingbeanexamplebean implements initializingbean {
 
    @autowired
    private environment environment;
 
    /**
      * 这个方法会在environment注入后执行
     */
    @override
    public void afterpropertiesset() {
        //environment 已经注入
        log.info("initializingbean execute:{}", arrays.aslist(environment.getdefaultprofiles()));
    }
}

三、@bean initmethod方法

我们在声明一个bean的时候,可以同时指定一个initmethod属性,该属性会指向bean的一个方法,表示在初始化后执行。

示例:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.bean;
import org.springframework.core.env.environment;
import org.springframework.stereotype.component;
import java.util.arrays;
 
/**
 * @description 指定一个initmethod属性,该属性会指向bean的一个方法,表示在初始化后执行。
 * @date 2024/12/5 11:37
 * @version 1.0
 * @author gezongyang
 */
@component
@slf4j
public class initializingbean {
 
    @autowired
    private environment environment;
 
    /**
     * 这个方法会在environment注入后执行
     */
    public void init() {
        //environment 已经注入
        log.info("@bean initmethod execute:{}", arrays.aslist(environment.getdefaultprofiles()));
    }
 
    /**
     * 这里将initmethod指向init方法,相应的我们也需要在bean中实现这个方法:
     * @return
     */
    @bean(initmethod="init")
    public initializingbean exbean() {
        return new initializingbean();
    }
}

四、构造器注入

如果依赖的字段在bean的构造方法中声明,那么spring框架会先实例这些字段对应的bean,再调用当前的构造方法。

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.env.environment;
import org.springframework.stereotype.component;
import java.util.arrays;
 
/**
 * @description 如果依赖的字段在bean的构造方法中声明,那么spring框架会先实例这些字段对应的bean,
 * 再调用当前的构造方法。
 * @date 2024/12/5 14:11
 * @version 1.0
 * @author gezongyang
 */
@slf4j
@component
public class logicinconstructorexamplebean {
    private final environment environment;
 
    /**
     * logicinconstructorexamplebean(environment environment) 调用在
     * environment 对象实例化之后
     * @param environment
     */
    @autowired
    public logicinconstructorexamplebean(environment environment) {
        //environment实例已初始化
        this.environment = environment;
        log.info("logicinconstructor:{}", arrays.aslist(environment.getdefaultprofiles()));
    }
}

五、实现applicationlistener 接口

applicationlistener 是由 spring-context组件提供的一个接口,主要是用来监听 “容器上下文的生命周期事件”。它的定义如下:

public interface applicationlistener<e extends applicationevent> extends eventlistener {
    /**
     * handle an application event.
     * @param event the event to respond to
     */
    void onapplicationevent(e event);
}

这里的event可以是任何一个继承于applicationevent的事件对象。 对于初始化工作来说,我们可以通过监听contextrefreshedevent这个事件来捕捉上下文初始化的时机。

示例:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.context.applicationlistener;
import org.springframework.context.event.contextrefreshedevent;
import org.springframework.stereotype.component;
/**
 * @description 当所有的bean都初始化完成后,执行onapplicationevent 方法
 * @date 2024/12/5 14:19
 * @version 1.0
 * @author gezongyang
 */
@slf4j
@component
public class startupapplicationlistenerexample  implements applicationlistener<contextrefreshedevent> {
    public static int counter;
 
    /**
     * 这里的event可以是任何一个继承于applicationevent的事件对象。
     * 对于初始化工作来说,我们可以通过监听contextrefreshedevent这个事件来捕捉上下文初始化的时机。
     * @param event
     */
    @override
    public void onapplicationevent(contextrefreshedevent event) {
        log.info("applicationlistener run!");
        counter++;
    }
}

在spring上下文初始化完成后,这里定义的方法将会被执行。 与前面的 initializingbean 不同的是,通过 applicationlistener 监听的方式是全局性的,也就是当 所有 的 bean 都 初始化完成 后才会执行方法。
spring 4.2 之后引入了新的 @eventlistener注解,可以实现同样的效果:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.context.event.contextrefreshedevent;
import org.springframework.context.event.eventlistener;
import org.springframework.stereotype.component;
 
/**
 * @description todo
 * @date 2024/12/5 14:24
 * @version 1.0
 * @author gezongyang
 */
@slf4j
@component
public class eventlistenerexamplebean {
    public static int counter;
 
    /**
     * 所有的bean都初始化完成后,执行onapplicationevent方法。
     * @param event
     */
    @eventlistener
    public void onapplicationevent(contextrefreshedevent event) {
        log.info("@eventlistener execute", event.tostring());
        counter++;
    }
}

六、实现 commandlinerunner 接口

springboot 提供了一个commanlinerunner接口,用来实现在应用启动后的逻辑控制,其定义如下:

public interface commandlinerunner {
    /**
     * callback used to run the bean.
     * @param args incoming main method arguments
     * @throws exception on error
     */
    void run(string... args) throws exception;
}

此外,对于多个commandlinerunner的情况下可以使用@order注解来控制它们的顺序。

案例:

1 定义一个commanddemo实现commandlinerunner,并纳入到spring容器

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.boot.commandlinerunner;
import org.springframework.stereotype.component;
/**
 * @description 应用启动后的回调函数,在应用启动后执行此方法
 * @date 2024/12/5 14:32
 * @version 1.0
 * @author gezongyang
 */
@component
@slf4j
public class commanddemo implements commandlinerunner {
    /**
     * 此方法在应用启动后执行
     * @param args
     */
    @override
    public void run(string... args) {
        log.info("commandlinerunner run!");
    }
}

2 配置参数,然后执行启动类

3 打印结果:

=====应用已经启动成功======[aaa,bbb]

七、实现 applicationrunner 接口

package org.springframework.boot;
 
 
import org.springframework.core.ordered;
import org.springframework.core.annotation.order;
 
 
/**
* interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link springapplication}. multiple {@link applicationrunner} beans can be defined
* within the same application context and can be ordered using the {@link ordered}
* interface or {@link order @order} annotation.
*
* @author phillip webb
* @since 1.3.0
* @see commandlinerunner
*/
@functionalinterface
public interface applicationrunner {
 
   /**
    * callback used to run the bean.
    * @param args incoming application arguments
    * @throws exception on error
    */
   void run(applicationarguments args) throws exception;
}

与 commandlinerunner接口类似, spring boot 还提供另一个applicationrunner 接口来实现初始化逻辑。不同的地方在于 applicationrunner.run()方法接受的是封装好的applicationarguments参数对象,而不是简单的字符串参数。applicationarguments是对参数做了进一步的处理,可以解析key=value形式,我们可以通过name来获取value(而commandlinerunner只是获取key=value整体)

package org.springframework.boot;
 
import java.util.list;
import java.util.set;
/**
* provides access to the arguments that were used to run a {@link springapplication}.
*
* @author phillip webb
* @since 1.3.0
*/
public interface applicationarguments {
 
   /**
    * return the raw unprocessed arguments that were passed to the application.
    * @return the arguments
    */
   string[] getsourceargs();
 
   /**
    * return the names of all option arguments. for example, if the arguments were
    * "--foo=bar --debug" would return the values {@code ["foo", "debug"]}.
    * @return the option names or an empty set
    */
   set<string> getoptionnames();
 
   /**
    * return whether the set of option arguments parsed from the arguments contains an
    * option with the given name.
    * @param name the name to check
    * @return {@code true} if the arguments contain an option with the given name
    */
   boolean containsoption(string name);
 
   /**
    * return the collection of values associated with the arguments option having the
    * given name.
    * <ul>
    * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
    * collection ({@code []})</li>
    * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
    * collection having one element ({@code ["bar"]})</li>
    * <li>if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"),
    * return a collection having elements for each value ({@code ["bar", "baz"]})</li>
    * <li>if the option is not present, return {@code null}</li>
    * </ul>
    * @param name the name of the option
    * @return a list of option values for the given name
    */
   list<string> getoptionvalues(string name);
 
   /**
    * return the collection of non-option arguments parsed.
    * @return the non-option arguments or an empty list
    */
   list<string> getnonoptionargs();
}

applicationarguments可以接收key=value这样的参数,getoptionnames()方法可以得到key的集合,getoptionvalues(string name)方法可以得到value这样的集合

示例:

import org.springframework.boot.applicationarguments;
import org.springframework.boot.applicationrunner;
import org.springframework.stereotype.component;
 
/**
* @description todo
* @date 2022/6/23 9:55
* @version 1.0
* @author gezongyang
*/
@component
public class applicationrunnerdemo implements applicationrunner {
 
    @override
    public void run(applicationarguments args) throws exception {
        system.out.println("====getoptionnames======"+args.getoptionnames());
        system.out.println("====getoptionvalues====="+args.getoptionvalues("key"));
    }
}

配置参数启动

打印结果:

====getoptionnames======[key]
====getoptionvalues=====[value]

八、实现smartlifecycle 接口

package org.springframework.context;
 
public interface smartlifecycle extends lifecycle, phased {
    boolean isautostartup();
    void stop(runnable var1);
}

smartlifecycle 是 spring framework 中的一个接口,它扩展了 lifecycle 接口,并提供了更灵活的生命周期管理功能。使用 smartlifecycle 可以更精细地控制应用组件的启动和关闭过程。以下是 smartlifecycle 的一些主要特性:

特性:

isautostartup(): 返回一个布尔值,指示该组件是否应该在容器启动时自动启动。

getphase(): 返回一个整数值,表示组件的启动顺序(阶段)。较低的数字表示较早的启动顺序。

start(): 启动组件,当返回时,组件应该已经启动完毕。

stop(runnable callback): 停止组件,并且可以在停止完成后执行提供的回调函数。

isrunning(): 返回一个布尔值,指示组件当前是否处于运行状态。

案例:

package com.cfcc.teis.load;
 
import lombok.extern.slf4j.slf4j;
import org.springframework.context.smartlifecycle;
import org.springframework.stereotype.component;
 
/**
 * @description smartlifecycle 可以更精细地控制应用组件的启动和关闭过程
 * @date 2024/12/5 15:10
 * @version 1.0
 * @author gezongyang
 */
@slf4j
@component
public class smartlifecycledemo implements smartlifecycle {
 
    private volatile boolean running = false;
 
    /**
     * smartlifecycle run
     */
    @override
    public void start() {
        log.info("smartlifecycle run!");
        this.running = true;
    }
 
    @override
    public void stop() {
 
    }
 
    /**
     * 在这里放置停止逻辑
     * @param callback
     */
    @override
    public void stop(runnable callback) {
        this.running = false;
        callback.run();  // 确保回调被执行
    }
 
    @override
    public boolean isrunning() {
        return this.running;
    }
 
    /**
     * 组件将在spring上下文加载后自动启动
     * @return
     */
    @override
    public boolean isautostartup() {
        return true;
    }
 
    /**
     * 定义启动顺序,数字越小越先启动
     * @return
     */
    @override
    public int getphase() {
        return 0;
    }
}

注意事项

当你实现 smartlifecycle 时,确保 start() 方法不会阻塞,因为它会延迟整个应用程序的启动。如果需要长时间运行的任务,考虑将其放入单独的线程中。

stop(runnable callback) 方法中的回调是重要的,必须调用它来通知框架组件已经停止。如果你不打算使用某些方法,可以不重写它们;默认实现将提供合理的行为。

通过使用 smartlifecycle,你可以更好地控制应用程序中不同组件的生命周期行为,这对于那些对启动和关闭有特殊需求的应用特别有用。

smartlifecycle 的执行时机:

是在 spring 应用上下文刷新完成之后,即所有的 bean 都已经被实例化和初始化之后。具体来说,spring 容器在调用 finishrefresh() 方法时会触发所有实现了 smartlifecycle 接口的 bean 的启动过程。这是通过容器中的 lifecycleprocessor 来管理的,默认使用的是 defaultlifecycleprocessor。

smartlifecycle 执行的具体流程

上下文刷新完成后:当 spring 应用上下文完成刷新(finishrefresh),意味着所有的 bean 已经被加载并初始化完毕。

初始化生命周期处理器:spring 会初始化 lifecycleprocessor,如果没有显式定义,则创建一个默认的 defaultlifecycleprocessor。

调用 onrefresh():接着,spring 会调用 lifecycleprocessor 的 onrefresh() 方法,这将导致调用 startbeans() 方法。

根据 phase 分组并排序:startbeans() 方法会获取所有实现了 lifecycle 接口的 bean,并根据它们的 phase 属性进行分组和排序。phase 值越小的 bean 将越早启动。

检查 isautostartup():对于每个 smartlifecycle bean,如果它的 isautostartup() 方法返回 true,那么它的 start() 方法就会被自动调用。

执行 start() 方法:按照 phase 排序后的顺序,依次调用每个 smartlifecycle bean 的 start() 方法来启动组件。

设置运行状态:start() 方法应该确保组件处于运行状态,并且 isrunning() 方法应返回 true。

同样的逻辑也适用于停止组件的过程,但是是以相反的顺序执行的,即 phase 值较大的 bean 会先停止。

因此,如果你希望在 spring 容器完全准备好之后执行某些任务,比如开启消息监听、启动定时任务等,你可以实现 smartlifecycle 接口并在 start() 方法中放置这些逻辑。同时,通过调整 getphase() 返回的值,可以控制多个 smartlifecycle bean 之间的启动顺序。

以上就是springboot初始化加载配置的八种方式总结的详细内容,更多关于springboot初始化加载配置的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com