Spring容器是如何實現 Bean 自動注入(xml)

入口web.xml

  • web.xml 配置文件
<!-- Spring Config -->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring/spring-config.xml</param-value>
</context-param>
 
<!-- SpringMvc Config -->
<servlet>
  <servlet-name>springMvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/spring-mvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Spring 容器 Bean 加載流程

從 Spring 配置部分能夠看出,ContextLoaderListener 監聽器是 Spring 容器的入口,進入該文件web

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

ContextLoaderListener 監聽器一共有四個方法,能夠很容易地判斷出來,進入該監聽器後,會進入初始化方法:contextInitialized。繼而進入 initWebApplicationContext 方法,方法註釋中 「Initialize Spring’s web application context for the given servlet context」,明確代表了該方法的目的是初始化 Spring Web 應用。這段代碼中有兩句話比較關鍵:spring

// 建立 Web 應用容器,即建立了 Spring 容器;
this.context = createWebApplicationContext(servletContext);

// 配置並刷新Spring容器。後續發生的全部事,都是從它開始的。進入,裏面的重點代碼是:
configureAndRefreshWebApplicationContext(cwac, servletContext);
wac.refresh();

wac.refresh();

refresh() 方法是spring容器注入bean的核心方法,每一行代碼都很重要。這裏也是真正加載spring-*.xml配置的開始的地方
spring-mvc

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

獲取 Bean 工廠,把你配置文件中的內容,放在 Bean 工廠中,留着後面建立 Bean 時用。mvc

finishBeanFactoryInitialization(beanFactory);

開始建立 Bean,即實現 Spring 中的自動注入功能。進入該方法後,末尾有這麼一句話:app

beanFactory.preInstantiateSingletons();

繼續跟進,貼出該方法中的重點代碼:ide

getBean(beanName);

咱們在 preInstantiateSingletons() 方法中,會發現有多個地方出現了 getBean() 方法,究竟我們貼出來的是哪一句?可有可無。跟進去以後,post

@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

這裏調用了 doGetBean() 方法,Spring 中只要以 do 命名的方法,都是真正幹活的。重點代碼分段貼出分析:ui

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
    if (logger.isDebugEnabled()) {
        if (isSingletonCurrentlyInCreation(beanName)) {
            logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                    "' that is not fully initialized yet - a consequence of a circular reference");
        }
        else {
            logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
        }
    }
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

直接獲取單例 Bean,若沒有取到,繼續往下走:this

// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if (args != null) {
        // Delegation to parent with explicit args.
        return (T) parentBeanFactory.getBean(nameToLookup, args);
    }
    else {
        // No args -> delegate to standard getBean method.
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    }
}

// 這一段代碼單獨看,不知所云,裏面提到了一個詞:Parent。暫且跳過,後續會回來分析這一段。繼續:
// Create bean instance.
if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
            @Override
             public Object getObject() throws BeansException {
                 try {
                     return createBean(beanName, mbd, args);
                  }
                  catch (BeansException ex) {
                      // Explicitly remove instance from singleton cache: It might have been put there
                      // eagerly by the creation process, to allow for circular reference resolution.
                      // Also remove any beans that received a temporary reference to the bean.
                      destroySingleton(beanName);
                      throw ex;
                 }
                }
         });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

這段代碼中有 createBean,我們的目的是分析 Bean 的建立過程,此處出現了 create,堅決果斷地跟進,進入實現類中的方法,有這麼一句:url

Object beanInstance = doCreateBean(beanName, mbdToUse, args);

剛纔我們提了,Spring 中有 do 命名的方法,是真正幹活的。跟進:

instanceWrapper = createBeanInstance(beanName, mbd, args);

這句話是初始化 Bean,即建立了 Bean,等價於調用了一個類的空構造方法。此時,已經成功地建立了對象,下文須要作的是,給該對象注入須要的屬性;

populateBean(beanName, mbd, instanceWrapper);

填充 Bean 屬性,就是剛纔我們提的,初始化一個對象後,只是一個空對象,須要給它填充屬性。跟進,看 Spring 是如何爲對象注入屬性的,或者說,看一下 Spring 是如何實現 Bean 屬性的自動注入:

pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);

繼續進入 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 方法:

metadata.inject(bean, beanName, pvs);

這句話中,出現了 inject,這個詞的意思是「注入」。我們能夠判定,Spring 的自動注入,八成跟它有關了。進入該方法:

element.inject(target, beanName, pvs);

與上一句同樣,只是作了一些參數處理,並無開始注入。繼續跟進看:InjectionMetadata類

Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));

看到這裏,大概明白了 Spring 是如何自動注入了。Java 反射相關的代碼,經過反射的方式給 field 賦值。這裏的 field 是 Bean 中的某一個屬性

getResourceToInject,獲取須要賦予的值了,其實這裏會從新進入 getBean 方法,獲取 Bean 值(例如 UserController 對象中須要注入 userService。),而後賦予 field。至此,Spring容器已經初始化完成,Spring Bean注入的大概流程

回到開始初始化 Spring 容器的地方,ContextLoader 類 initWebApplicationContext 方法,

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

初始化 Spring 容器以後,將其放入了 servletContext 中。

繼續看 Spring MVC 容器 Bean 加載流程

從 web.xml 中的 SpringMVC 配置出發,裏面有 DispatcherServlet,這是 SpringMVC 的入口.
DispatcherServlet 本質上是一個 Servlet.看一下 Servlet 的接口

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}

從 Servlet 接口方法中能夠看出,Servlet 的入口是 init 方法,層層跟進(必定要根據 DispatcherServlet 繼承圖跟進),進入到了 FrameworkServlet 的 initServletBean() 方法,進入方法,貼出重點代碼:

this.webApplicationContext = this.initWebApplicationContext();

字面理解,初始化 SpringMVC Web容器,進入探究:

WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

前面我們提到,Spring 容器初始化完成以後,放入了 servletContext 中。這裏又從 servletContext 獲取到了 Spring 容器;

wac = this.createWebApplicationContext(rootContext);

字面理解建立 Web 應用容器,且參數是 Spring 容器。跟進方法:

ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);

建立web應用容器,即我們所理解的 SpringMVC 容器在此建立了;

wac.setParent(parent);

這裏是重點,SpringMVC 容器將 Spring 容器設置成了本身的父容器。

configureAndRefreshWebApplicationContext(wac);

這個方法剛纔在分析 Spring Bean 加載流程時,分析過了。其中有一段,前面說,「暫且跳過,後續會回來分析這一段」。如今開始分析:

在 AbstractBeanFactory 類 doGetBean 方法,有這麼一段:

// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if (args != null) {
            // Delegation to parent with explicit args.
        return (T) parentBeanFactory.getBean(nameToLookup, args);
    }
    else {
        // No args -> delegate to standard getBean method.
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    }
}

這裏實際上是在獲取父容器中的 Bean,若獲取到,直接拿到 Bean,這個方法就結束了。

結論:子容器可使用父容器裏的 Bean,反之則不行。

參考

[spring 源碼相關解析](https://juejin.im/post/5a3f5b43f265da432e5c37ea)

[spring 源碼相關解析](https://juejin.im/user/58fcc0768d6d810058965a06/posts )

https://mp.weixin.qq.com/s/hJX9-lc4q2Uoc3eJNPHFdw

相關文章
相關標籤/搜索