需求:在tomcat啓動時開啓一個定時任務。
想法:容器啓動時執行方法,最容易想到的就是servlet中可以配置load-on-startup,設置一個正整數也就可以隨容器一起啓動。
問題:上面的方法很好,但是由於定時任務需要去操作數據庫,而項目採用了spring的依賴注入來管理對象,而servlet並不受Spring的管理。若此時在servlet中注入Spring管理的對象,則會報錯:javax.naming.NameNotFoundException: Name com.test.InitServlet is not bound in this Context。
所以想要再servlet中操作數據庫,只能手動去創建一個service,這顯然違背了我們使用Spring的初衷,讓項目看起來不倫不類的。那麼如何才能在啓動WEB容器的時候執行一段初始化代碼,並且可以讓其被Spring管理呢?
解決方案:Spring提供了當一個Bean初始化後執行方法的擴展點:InitializingBean。這裏的初始化指的就是當該Bean的屬性被注入完成後(注意:這裏並不是所有屬性都需要被設置),所以InitializingBean接口提供的方法名稱也很形象:afterPropertiesSet()。
使用的時,將該Bean注入到Spring容器,之後我們便可以獲得Spring容器中的對象了,也就是說,可以得到service方法來執行我們的定時任務了。
具體代碼如下:
- @Component
- public class InitServlet implements InitializingBean {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Resource
- private DispatchesService dispatchesService;
- @Override
- public void afterPropertiesSet() throws Exception {
- dispatchesService.spyDDetails();
- }
- }
另外還有兩種方法也可以實現如上的功能。
1、若採用XML來配置Bean的話,可以指定屬性init-method
2、通過註解@PostConstruct來修改初始化方法
值得注意的是,三者可以同時存在,觸發的順序是先觸發@PostConstruct修飾的方法,再觸發afterPropertiesSet(),最後觸發init-method
其中@PostConstruct是通過註冊一個BeanPostProcessor,在Bean的初始化方法之前調用,而afterPropertiesSet()和init-method都在初始化方法中調用
關於@PostConstruct詳細的介紹可以看這裏:http://blog.csdn.net/yaerfeng/article/details/8447530
下面是Spring中調用Bean的初始化代碼的源代碼:
- protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
- throws Throwable {
- boolean isInitializingBean = (bean instanceof InitializingBean);
- if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
- if (logger.isDebugEnabled()) {
- logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
- }
- if (System.getSecurityManager() != null) {
- try {
- AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
- public Object run() throws Exception {
- ((InitializingBean) bean).afterPropertiesSet();// 這裏觸發afterPropertiesSet
- return null;
- }
- }, getAccessControlContext());
- }
- catch (PrivilegedActionException pae) {
- throw pae.getException();
- }
- }
- else {
- ((InitializingBean) bean).afterPropertiesSet();// 這裏觸發afterPropertiesSet
- }
- }
- if (mbd != null) {
- String initMethodName = mbd.getInitMethodName();// 這裏是觸發init-method
- if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
- !mbd.isExternallyManagedInitMethod(initMethodName)) {
- invokeCustomInitMethod(beanName, bean, mbd);
- }
- }
- }
補充:
還有一種方法,是當Spring將所有的Bean都初始化完成後,會留給我們一個入口,我們可以實現如下接口
- @Component
- public class InstantiationTracingBeanPostProcessor implements
- ApplicationListener<ContextRefreshedEvent> {
- @Override
- public void onApplicationEvent(ContextRefreshedEvent arg0) {
- System.out.println("-----所有Bean載入完成---");
- }
- }