在項目中,會遇到以下狀況,即須要在 Tomcat 啓動時去執行一些操做,首先咱們想到的是繼承 ServletContextListener,而後在 contextInitialized 加入須要執行的操做,這是一種方法;那麼對於 Spring 項目來講,也能夠繼承 InitialzationBean 來實現,在初始化 bean 和銷燬 bean 的時候執行某個方法,因爲 ServletContextListener 須要在 web.xml 中進行配置,並且可能要注入其餘 bean,因此筆者選擇了繼承 InitialzationBean 來實現。java
新建一個類,繼承 InitialzationBean,代碼以下:web
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class DoOnStart implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("xxxxxxxx");
}
}
複製代碼
本覺得這樣就 OK 了,啓動 Tomcat 後發現,afterPropertiesSet 方法被執行了兩次,奇怪,難道 Spring 會初始化兩次 Bean?帶着這種猜想,又進行了以下驗證:spring
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class DoOnStart implements InitializingBean, ApplicationContextAware {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("xxxxxxxx");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("xxxxxxxx");
}
}
複製代碼
經過 Debug 發現,setApplicationContext 方法確實執行了兩次,也就是說,有兩個容器被初始化了,經過查看 applicationContext 發現,第一次是 Root WebApplicationContext,第二次是 WebApplicationContext for namespace spring-servlet,看到這裏,茅塞頓開:express
第一次是 Spring 對 Bean 進行了初始化,第二次是 Spring MVC 又對 Bean 進行了初始化mvc
那麼如何解決加載兩次對問題呢?那就是讓 Spring MVC 只掃描 @Controller 註解,配置以下:app
<!-- spring 配置文件-->
<context:component-scan base-package="com.xxx.xxx">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- spring mvc -->
<context:component-scan base-package="com.xxx.xxx.web" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
複製代碼
爲何要將 Spring 的配置文件和 Spring MVC 的配置文件分開呢?ide
咱們用如下代碼進行測試:源碼分析
@Service
public class DoOnStart implements InitializingBean {
@Autowired
private XXXController xxxController;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("xxxxxxxx");
}
}
複製代碼
有以下狀況:測試
原來 Spring 是父容器, Spring MVC 是子容器, 子容器能夠訪問父容器的 bean,父容器不能訪問子容器的 beanspa
初始化兩次,Spring 容器先初始化 bean,MVC 容器再初始化 bean,因此應該是兩個 bean
缺點是不利於擴展
經過查看 Spring 的加載 bean 的源碼類 AbstractAutowireCapableBeanFactory 可看出其中奧妙,AbstractAutowireCapableBeanFactory 類中的 invokeInitMethods 講解的很是清楚,源碼以下:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
//判斷該bean是否實現了實現了InitializingBean接口,若是實現了InitializingBean接口,則只掉調用bean的afterPropertiesSet方法
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 {
//直接調用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
return null;
}
},getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//直接調用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
//判斷是否指定了init-method方法,若是指定了init-method方法,則再調用制定的init-method
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
//進一步查看該方法的源碼,能夠發現init-method方法中指定的方法是經過反射實現
invokeCustomInitMethod(beanName, bean, mbd);
}
}
複製代碼
Spring 爲 bean 提供了兩種初始化 bean 的方式,實現 InitializingBean 接口,實現 afterPropertiesSet 方法,或者在配置文件中經過 init-method 指定,兩種方式能夠同時使用
實現 InitializingBean 接口是直接調用 afterPropertiesSet 方法,比經過反射調用 init-method 指定的方法效率相對來講要高點。可是 init-method 方式消除了對 Spring 的依賴
若是調用 afterPropertiesSet 方法時出錯,則不調用 init-method 指定的方法
要將 Spring 的配置文件和 Spring MVC 的配置文件分開