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 容器的入口,进入该文件
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 应用。这段代码中有两句话比较关键:
// 创建 Web 应用容器,即创建了 Spring 容器;
this.context = createWebApplicationContext(servletContext);
// 配置并刷新Spring容器。后续发生的所有事,都是从它开始的。进入,里面的重点代码是:
configureAndRefreshWebApplicationContext(cwac, servletContext);
wac.refresh();
wac.refresh();
refresh() 方法是spring容器注入bean的核心方法,每一行代码都很重要。这里也是真正加载spring-*.xml配置的开始的地方

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
获取 Bean 工厂,把你配置文件中的内容,放在 Bean 工厂中,留着后面创建 Bean 时用。
finishBeanFactoryInitialization(beanFactory);
开始创建 Bean,即实现 Spring 中的自动注入功能。进入该方法后,末尾有这么一句话:
beanFactory.preInstantiateSingletons();
继续跟进,贴出该方法中的重点代码:
getBean(beanName);
我们在 preInstantiateSingletons() 方法中,会发现有多个地方出现了 getBean() 方法,究竟咱们贴出来的是哪一句?无关紧要。跟进去之后,
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
这里调用了 doGetBean() 方法,Spring 中只要以 do 命名的方法,都是真正干活的。重点代码分段贴出分析:
// 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,若没有取到,继续往下走:
// 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,毫不犹豫地跟进,进入实现类中的方法,有这么一句:
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
Spring容器是如何实现 Bean 自动注入(xml)的更多相关文章
- Spring实战2:装配bean—依赖注入的本质
主要内容 Spring的配置方法概览 自动装配bean 基于Java配置文件装配bean 控制bean的创建和销毁 任何一个成功的应用都是由多个为了实现某个业务目标而相互协作的组件构成的,这些组件必须 ...
- Spring之使用注解实例化Bean并注入属性
1.准备工作 (1)导入jar包 除了上篇文章使用到的基本jar包外,还得加入aop的jar包,所有jar包如下 所需jar包 (2)配置xml <?xml version="1.0& ...
- Spring Ioc源码分析系列--自动注入循环依赖的处理
Spring Ioc源码分析系列--自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到Spring创建bean出现循环依赖的时候并没有深入去分 ...
- spring boot 中@Autowired注解无法自动注入的错误
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/huihuilovei/article/de ...
- 获取并打印Spring容器中所有的Bean名称
思路: 1.实现Spring的ApplicationContextAware接口,重写setApplicationContext方法,将得到的ApplicationContext对象保存到一个静态变量 ...
- 如何获得Spring容器里管理的Bean,。不论是Service层,还是实体Dao层
如何获得Spring容器里管理的Bean,.不论是Service层,还是实体Dao层, 下面的这个必须配置,否则必出错,空指针 下面的这个是代码 而获得bean代码如下: serviceManager ...
- spring bean自动注入
使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的 ...
- Spring 源码分析之 bean 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
- Spring初学笔记(二):Bean的注入
关于Bean的注入 在上一篇中,已经说到虽然注入确实可以降低类与类之间的耦合,但并没有解决调用者必须知道类的创建方法的问题,也可以说是没有实现调用者与类实现的解耦,我们也提到,为了实现两者的解耦,可以 ...
随机推荐
- Book Review 《构建之法》
-首先浏览了一遍<构建之法>这本书的前言,其中通过客观的描述性介绍了学生与学习.老师与教学.以及学习的环境.方法等等.但是对于书中前言包括正文都频繁出现的一个词语 “文档” 深表疑问.何为 ...
- 小学四则运算APP 第一个冲刺阶段 第三天
团队成员:陈淑筠.杨家安.陈曦 团队选题:小学四则运算APP 第一次冲刺阶段时间:11.17~11.27 本次发布是在与团队成员解决了昨天问题的基础上,再增加了几个界面,增加了一些功能,修改与增加的代 ...
- HTTP协议(1)
HTTP 协议笔记 1. HTTP 传输机制 HTTP 是一个应用层协议,位于 TCP 的层次之上,并且是基于 TCP 协议进行通信的,也就是说 HTTP 在每一次通信之前都要先建立 TCP 连接来保 ...
- 四则运算生成程序——GUI支持和部分功能改进
项目成员:张金生 张政 工程地址: https://coding.net/u/jx8zjs/p/paperOne/git ssh://git@git.coding.net:jx8zjs/pap ...
- ci test
下载ci 版本 3.1.9 下载地址 https://www.codeigniter.com/ 怎么查看CI的版本信息?想看某个项目中使用的CI具体是哪个版本,怎么查看?system\core\cod ...
- C++ STL 整理
一.一般介绍 STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库.它被容纳于C++标准程序库(C++ Standard Library ...
- Redis Cluster机器内存充爆处理
机器配置 系统:CentOS6.7 配置:4C8G 应用:Redis Cluster,实例化 现象 1.无法启动redis,启动后系统OOM,直接杀死 2.Redis: OOM command not ...
- Http建立连接的方式
1.协议简介 Http 协议:应用层协议 TCP 协议:传输层协议,主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出的顺序一致,TCP具有可靠,面向连接 ...
- Spring之jdbcTemplate:增删改
JdbcTemplate增删改数据操作步骤:1.导入jar包:2.设置数据库信息:3.设置数据源:4.调用jdbcTemplate对象中的方法实现操作 package helloworld.jdbcT ...
- robotframework常用的几个快捷键
robotframework常用的几个快捷键 重命名(F2) 搜索关键字(F5) 执行用例(F8) 创建新工程(ctrl+n) 创建新测试套(ctrl+shift+f) 创建新用例(ctrl+shif ...