您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關SpringBoot留給開發者的7個啟動擴展點分別是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
我們經常需要在容器啟動的時候做一些鉤子動作,比如注冊消息消費者,監聽配置等,今天就總結下SpringBoot留給開發者的7個啟動擴展點。
基本用法
熟悉Spring的同學一定知道,容器刷新成功意味著所有的Bean初始化已經完成,當容器刷新之后Spring將會調用容器內所有實現了ApplicationListener<ContextRefreshedEvent>的Bean的onApplicationEvent方法,應用程序可以以此達到監聽容器初始化完成事件的目的。
@Component public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> { private static final Logger LOG = Logger.getLogger(StartupApplicationListenerExample.class); public static int counter; @Override public void onApplicationEvent(ContextRefreshedEvent event) { LOG.info("Increment counter"); counter++; } }
易錯的點
這個擴展點用在web容器中的時候需要額外注意,在web 項目中(例如spring mvc),系統會存在兩個容器,一個是root application context,另一個就是我們自己的context(作為root application context的子容器)。如果按照上面這種寫法,就會造成onApplicationEvent方法被執行兩次。解決此問題的方法如下:
@Component public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> { private static final Logger LOG = Logger.getLogger(StartupApplicationListenerExample.class); public static int counter; @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext().getParent() == null) { // root application context 沒有parent LOG.info("Increment counter"); counter++; } } }
高階玩法
當然這個擴展還可以有更高階的玩法:自定義事件,可以借助Spring以最小成本實現一個觀察者模式:
先自定義一個事件:
public class NotifyEvent extends ApplicationEvent { private String email; private String content; public NotifyEvent(Object source) { super(source); } public NotifyEvent(Object source, String email, String content) { super(source); this.email = email; this.content = content; } // 省略getter/setter方法 }
注冊一個事件監聽器
@Component public class NotifyListener implements ApplicationListener<NotifyEvent> { @Override public void onApplicationEvent(NotifyEvent event) { System.out.println("郵件地址:" + event.getEmail()); System.out.println("郵件內容:" + event.getContent()); } }
發布事件
@RunWith(SpringRunner.class) @SpringBootTest public class ListenerTest { @Autowired private WebApplicationContext webApplicationContext; @Test public void testListener() { NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content"); webApplicationContext.publishEvent(event); } }
執行單元測試可以看到郵件的地址和內容都被打印出來了
當容器上下文初始化完成之后,SpringBoot也會調用所有實現了CommandLineRunner接口的run方法,下面這段代碼可起到和上文同樣的作用:
@Component public class CommandLineAppStartupRunner implements CommandLineRunner { private static final Logger LOG = LoggerFactory.getLogger(CommandLineAppStartupRunner.class); public static int counter; @Override public void run(String...args) throws Exception { LOG.info("Increment counter"); counter++; } }
對于這個擴展點的使用有額外兩點需要注意:
多個實現了CommandLineRunner的Bean的執行順序可以根據Bean上的@Order注解調整
其run方法可以接受從控制臺輸入的參數,跟ApplicationListener<ContextRefreshedEvent>這種擴展相比,更加靈活
// 從控制臺輸入參數示例 java -jar CommandLineAppStartupRunner.jar abc abcd
這個擴展和SpringBoot的CommandLineRunner接口的擴展類似,只不過接受的參數是一個ApplicationArguments類,對控制臺輸入的參數提供了更好的封裝,以--開頭的被視為帶選項的參數,否則是普通的參數
@Component public class AppStartupRunner implements ApplicationRunner { private static final Logger LOG = LoggerFactory.getLogger(AppStartupRunner.class); public static int counter; @Override public void run(ApplicationArguments args) throws Exception { LOG.info("Application started with option names : {}", args.getOptionNames()); LOG.info("Increment counter"); counter++; } }
比如:
java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose
前面的內容總結了針對容器初始化的擴展點,在有些場景,比如監聽消息的時候,我們希望Bean初始化完成之后立刻注冊監聽器,而不是等到整個容器刷新完成,Spring針對這種場景同樣留足了擴展點:
@PostConstruct注解一般放在Bean的方法上,被@PostConstruct修飾的方法會在Bean初始化后馬上調用: @Component public class PostConstructExampleBean { private static final Logger LOG = Logger.getLogger(PostConstructExampleBean.class); @Autowired private Environment environment; @PostConstruct public void init() { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
InitializingBean的用法基本上與@PostConstruct一致,只不過相應的Bean需要實現afterPropertiesSet方法
@Component public class InitializingBeanExampleBean implements InitializingBean { private static final Logger LOG = Logger.getLogger(InitializingBeanExampleBean.class); @Autowired private Environment environment; @Override public void afterPropertiesSet() throws Exception { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
通過@Bean注入Bean的時候可以指定初始化方法:
Bean的定義
public class InitMethodExampleBean { private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class); @Autowired private Environment environment; public void init() { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
Bean注入
@Bean(initMethod="init") public InitMethodExampleBean initMethodExampleBean() { return new InitMethodExampleBean(); }
Spring也支持通過構造函數注入,我們可以把搞事情的代碼寫在構造函數中,同樣能達到目的
@Component public class LogicInConstructorExampleBean { private static final Logger LOG = Logger.getLogger(LogicInConstructorExampleBean.class); private final Environment environment; @Autowired public LogicInConstructorExampleBean(Environment environment) { this.environment = environment; LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
可以用一個簡單的測試:
@Component @Scope(value = "prototype") public class AllStrategiesExampleBean implements InitializingBean { private static final Logger LOG = Logger.getLogger(AllStrategiesExampleBean.class); public AllStrategiesExampleBean() { LOG.info("Constructor"); } @Override public void afterPropertiesSet() throws Exception { LOG.info("InitializingBean"); } @PostConstruct public void postConstruct() { LOG.info("PostConstruct"); } public void init() { LOG.info("init-method"); } }
實例化這個Bean后輸出:
[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor [main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct [main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean [main] INFO o.b.startup.AllStrategiesExampleBean - init-method
關于SpringBoot留給開發者的7個啟動擴展點分別是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。