需求: 在tomcat启动时开启一个定时任务,或初始化slor索引
想法:容器启动时执行方法,最容易想到的就是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管理呢?
解决方案:
1)InitializingBean 初始化bean类里的具体方法
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 { <span style="white-space:pre" > </span>dispatchesService.spyDDetails(); } }
2)spring XML文件直接配置
若采用XML来配置Bean的话,可以指定属性init-method
<bean id="testInitializingBean" class="com.TestInitializingBean" init-method="testInit"></bean>
3)通过注解@PostConstruct来修改初始化方法
值得注意的是,三者可以同时存在,触发的顺序是先触发@PostConstruct修饰的方法,再触发afterPropertiesSet(),最后触发init-method
其中@PostConstruct是通过注册一个BeanPostProcessor,在Bean的初始化方法之前调用,而afterPropertiesSet()和init-method都在初始化方法中调用
关于@PostConstruct详细的介绍可以看这里:http://blog.youkuaiyun.com/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();<span style="white-space:pre" > </span><span style= "font-family: Arial, Helvetica, sans-serif;" > </span> return null ; } }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ((InitializingBean) bean).afterPropertiesSet();<span style="white-space:pre" > </span> } } if (mbd != null ) { String initMethodName = mbd.getInitMethodName(); if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet" .equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }
4)Bean都初始化完成后,实现ApplicationListener<ContextRefreshedEvent>接口
还有一种方法,是当Spring将所有的Bean都初始化完成后,会留给我们一个入口,我们可以实现如下接口
@Component public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent arg0) { System.out.println("-----所有Bean载入完成---" ); } }