О ямах и аналитическом мышлении, с которыми столкнулся Spring InitialzationBean

задняя часть Spring MVC контейнер

задний план

В проекте мы столкнемся со следующими ситуациями, то есть нам нужно выполнить некоторые операции при запуске Tomcat.Первое, о чем мы думаем, это наследовать ServletContextListener, а затем добавить операции, которые будут выполняться в contextInitialized.Это метод ; затем для проекта Spring вы также можете наследовать InitialzationBean для реализации, выполнения метода при инициализации и уничтожении bean-компонентов, потому что ServletContextListener необходимо настроить в web.xml, и он может внедрять другие bean-компоненты, поэтому я решил наследовать InitialzationBean для достижения.

яма столкнулась

Создайте новый класс, наследуйте InitialzationBean, код выглядит следующим образом:

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");
    }
}

Я думал, что это нормально. После запуска Tomcat я обнаружил, что метод afterPropertiesSet был выполнен дважды. Странно, Spring инициализирует bean-компоненты дважды? С этим предположением была проведена следующая проверка:

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 действительно выполняется дважды, то есть были инициализированы два контейнера. пространство имен spring-servlet, см. здесь, Мао Саид:

В первый раз Spring инициализирует bean-компонент, а во второй раз Spring MVC снова инициализирует bean-компонент.

Так как же решить проблему загрузки двух пар? То есть, чтобы Spring MVC сканировал только аннотацию @Controller, конфигурация выглядит следующим образом:

<!-- 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?

Мы тестируем с помощью следующего кода:

@Service  
public class DoOnStart implements InitializingBean {   
    @Autowired  
    private XXXController xxxController;  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("xxxxxxxx");  
    }  
}  

Бывают следующие ситуации:

  • Spring загружает все компоненты, MVC загружает контроллер
    • Может
  • Spring загружает все bean-компоненты, контейнер MVC ничего не загружает
    • Может
  • Spring загружает все bean-компоненты, кроме контроллера, MVC загружает только контроллер
    • Нет, родительский контейнер не может получить доступ к компонентам дочернего контейнера.
  • Spring не загружает бины, MVC загружает все бины
    • Может

Получается, что Spring — это родительский контейнер, а Spring MVC — дочерний контейнер, дочерний контейнер может получить доступ к bean-компонентам родительского контейнера, но родительский контейнер не может получить доступ к bean-компонентам дочернего контейнера.

  • Существуют ли одноэлементные компоненты в одном или двух экземплярах в родительско-дочернем контейнере?

Инициализированный дважды, контейнер Spring сначала инициализирует bean-компонент, а контейнер MVC инициализирует bean-компонент, поэтому должно быть два bean-компонента.

  • Почему бы не сканировать все bean-компоненты в подконтейнерах?

Недостатком является то, что он не способствует расширению.

Анализ исходного кода

Взглянув на исходный класс загруженного компонента Spring AbstractAutowireCapableBeanFactory, мы можем увидеть загадку. Метод invokeInitMethods в классе AbstractAutowireCapableBeanFactory объясняется очень четко. Исходный код выглядит следующим образом:

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-компонентов, реализовать интерфейс initializingBean, реализовать метод instialPropertiesset или указать в файле конфигурации два способа, которые можно использовать в обоих случаях.

  • Реализация интерфейса InitializingBean заключается в непосредственном вызове метода afterPropertiesSet, что относительно более эффективно, чем вызов метода, указанного init-методом, через отражение. Но метод init удаляет зависимость от Spring

  • Если при вызове метода afterPropertiesSet возникает ошибка, метод, указанный init-методом, не вызывается

  • Чтобы отделить файлы конфигурации Spring от файлов конфигурации Spring MVC