Spring系列(一):Spring MVC bean 解析、注册、实例化流程源码剖析

背景

最近在使用Spring MVC过程中遇到了一些问题,网上搜索不少帖子后虽然找到了答案和解决方法,但这些答案大部分都只是给了结论,并没有说明具体原因,感觉总是有点不太满意。

更重要的是这些所谓的结论大多是抄来抄去,基本源自一家,真实性也有待考证。

要成为一名优秀的码农,不仅能熟练的复制粘贴,更要有打破砂锅问到底的精神,达到知其然也知其所以然的境界。

那作为程序员怎么能知其所以然呢?

答案就是阅读源代码!

此处请大家内心默读三遍。

用过Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring机制就得先从这两点入手,本文主要通过对IOC部分的机制进行介绍。

2. 实验环境

在开始阅读之前,先准备好以下实验材料。

  • Spring 5.0源码(https://github.com/spring-projects/spring-framework.git)
  • IDE:Intellij IDEA

IDEA 是一个优秀的开发工具,如果还在用Eclipse的建议切换到此工具进行。

IDEA有很多的快捷键,在分析过程中建议大家多用Ctrl+Alt+B快捷键,可以快速定位到实现函数。

3. Spring Bean 解析注册

Spring bean的加载主要分为以下6步:

  • (1)读取XML配置文件
  • (2)XML文件解析为document文档
  • (3)解析bean
  • (4)注册bean
  • (5)实例化bean
  • (6)获取bean

3.1 读取XML配置文件

查看源码第一步是找到程序入口,再以入口为突破口,一步步进行源码跟踪。

Java Web应用中的入口就是web.xml。

在web.xml找到ContextLoaderListener ,此Listener负责初始化Spring IOC。

contextConfigLocation参数设置了bean定义文件地址。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring.xml</param-value>
</context-param>

下面是ContextLoaderListener的官方定义:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener
Bootstrap listener to start up and shut down Spring's root WebApplicationContext. Simply delegates to ContextLoader as well as to ContextCleanupListener.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/ContextLoaderListener.html

翻译过来ContextLoaderListener作用就是负责启动和关闭Spring root WebApplicationContext。

具体WebApplicationContext是什么?开始看源码。

package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    //servletContext初始化时候调用
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext();
    }
    //servletContext销毁时候调用
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
    }
}

从源码看出此Listener主要有两个函数,一个负责初始化WebApplicationContext,一个负责销毁。

继续看initWebApplicationContext函数。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//初始化Spring容器时如果发现servlet 容器中已存在根Spring容根器则抛出异常,证明rootWebApplicationContext只能有一个。
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }
   if (this.context == null) {
	//1.创建webApplicationContext实例
        this.context = createWebApplicationContext(servletContext);
   }
   if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
	 //2.配置WebApplicationContext
        configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
    //把生成的webApplicationContext 设置为root webApplicationContext。
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    return this.context; 

}

在上面的代码中主要有两个功能:

  • (1)创建WebApplicationContext实例。
  • (2)配置生成WebApplicationContext实例。

3.1.1 创建WebApplicationContext实例

进入
CreateWebAPPlicationContext函数

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   //得到ContextClass类,默认实例化的是XmlWebApplicationContext类
   Class<?> contextClass = determineContextClass(sc);
   //实例化Context类
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

进入determineContextClass函数。

protected Class<?> determineContextClass(ServletContext servletContext) {
   // 此处CONTEXT_CLASS_PARAM = "contextClass"String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
         //若设置了contextClass则使用定义好的ContextClass。
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
   else {
      //此处获取的是在Spring源码中ContextLoader.properties中配置的org.springframework.web.context.support.XmlWebApplicationContext类。
      contextClassName = defaultStrategies.get
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值