spring执行流程,spring mvc 的工作流程是什么
spring执行流程,spring mvc 的工作流程是什么详细介绍
本文目录一览: Spring mvc执行流程图-1
1.1 Spring mvc的执行流程图
Spring mvc的执行流程大致如下: 1)所有请求被DispatcherServlet控制器拦截。 2)被拦截的请求去handlerMappings中寻找对应的HandlerMapping对象并得到请求对应的Handler对象。 3)把获取到的Handler对象以及根据请求查找跟请求对应的拦截器作为入参,封装成一个HandlerExectionChain对象,返回到DispatcherServlet控制器。 4)把HandlerExecutionChain对象作为入参,去handlerAdapters集合中寻找对应的HandlerAdapter对象。 5)把HandlerExectionChain对象作为入参调用HandlerAdapter对象的handle方法,执行完毕将返回ModelAndView对象到DispatcherServlet控制器中。 6)从viewResolvers集合中查找对应的View对象,并返回给到DispatcherServlet控制器。 7)View视图渲染成具体的文件格式并返回给客户端。
View视图渲染
render具体源码
View的类型如下图(ctrl+h可查看类的所有子类)
spring mvc 的工作流程是什么
MVC(Model-View-Controller)三元组的概念:
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据) 和 服务层(行为)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作,。
说简单了就是 Controller--》Service --》 Repository
复杂点说 就涉及到它的设计思想
spring mvc 的工作流程:
1、用户发送请求至前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView。
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。
SpringMVC是一种web层的mvc框架,用于替代servlet(处理响应请求,获取表单参数,表单验证等)。
MVC即Model-View-Controller,将应用按照Model(模型)、View(视图)、Controller(控制)这样的方式分离。
视图(View):代表用户交互界面,对于Web应用来说,可以是HTML,也可能是jsp、XML和Applet等。
一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。
模型(Model):是业务的处理以及业务规则的制定。模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计是MVC最主要的核心。
MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,抽象与具体不能隔得太远,也不能太近。
MVC并没有提供模型的设计方法,而只是组织管理这些模型,以便于模型的重构和提高重用性。
控制(Controller):可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。
划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。
springmvc工作流程
springmvc工作流程:
1、 用户向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。
2、DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器。由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知)。
3、DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller。
4、HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet。
5、DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后返回真正的视图。
6、DispatcherServlet将模型数据填充到视图中。
7、DispatcherServlet将结果响应给用户。
组件说明:
DispatcherServlet:前端控制器,也称为中央控制器,它是整个请求响应的控制中心,组件的调用由它统一调度。
HandlerMapping:处理器映射器,它根据用户访问的 URL 映射到对应的后端处理器 Handler。也就是说它知道处理用户请求的后端处理器,但是它并不执行后端处理器,而是将处理器告诉给中央处理器。
HandlerAdapter:处理器适配器,它调用后端处理器中的方法,返回逻辑视图 ModelAndView 对象。
ViewResolver:视图解析器,将 ModelAndView 逻辑视图解析为具体的视图(如 JSP)。
Handler:后端处理器,对用户具体请求进行处理,也就是我们编写的 Controller 类。
springboot随笔5.0:run方法执行流程
初始化:
1.1 调 SpringFactoriesLoader#getSpringFactoriesInstances 方法,key为:SpringApplicationRunListener.class,并创建对应的实例。 SpringApplicationRunListener负责在springboot启动的不同阶段,广播出不同的消息,传递给ApplicationListener实现类。 1.2 把1.1中获取的监听器对象遍历.starting()。
应用上下文环境:就是指一个环境的集合,包含多部分的环境信息。例如:系统信息、jdk环境信息、自定义信息等。 把所有的环境信息进行加载封装到environment对象中,使用时候直接取。 2.1 创建并配置相应的环境; 根据应用不同,创建需要的应用环境。 2.2 根据用户配置,配置environment系统环境; 例如:开发环境/生产环境/测试环境有不同的配置文件,加载配置文件,封装成 SimpleCommandLinePropertySource 加入到环境中。 2.3 启动相应的监听器,其中有一个重要的监听器 configFileApplicationListener (项目配置文件的监听器) configFileApplicationListener 监听器是run方法初始化中第二滴根据ApplicationListener.class获取的监听器中的一个
应用上下文:当前环境的属性集合 ; 可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一 些高级功能。 应用上下文对IoC容器是持有的关系。他的一个属性beanFactory就是IoC容器 (DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系。 返回值赋值给开始定义的ConfigurableApplicationContext
在createApplicationContext()方法中的,BeanUtils.instantiateClass(contextClass) 这个方法中,不但初始化了AnnotationConfigServletWebServerApplicationContext类,也就是我们的上下文context,同样 也触发了GenericApplicationContext类的构造函数,从而IoC容器也创建了。 看他的构造函数,发现一个很熟悉的类DefaultListableBeanFactory(是IoC容器)
这步的核心就是对第三步获得的上下文对象进行属性的设置和一些bean对象的创建。例如我们的核心启动类。
SpringBoot中有三种实现定位,
所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成: classpath:com/lidongz/**/.class这样的形式,然后一个叫做 xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition.
通过基础路径的扫描,确定@Import注解需要加载的类,调用类中的方法 从 META-INF/spring.factories 中获取的全路径类名,完成BeanDefinition的加载和注册。 获取到的权限定类名中有标注的@bean的方法也会执行,从而再创建一些bean对象存到容器中。
怎么扫描启动类上的解析、 compenentScan不配置路径为什么会是核心类所在的包以及子包 解析compenentScan注解的时候 如果值为null 给一个值就是核心类所在的包,然后doscan扫描就是以这个为基础路径
springboot启动前执行方法的几种方式
配置bean的源,就是bean的来源,就是注解了SpringBootApplication的那个类。推断,推断应用类型,有webflux,webservlet,none推断主类,这个是通过线程堆栈实现,构造一个运行时异常,找异常堆栈里面找mian所在的那个类。
网上大多数的解决方案是通过添加spring-boot-starter-tomcat依赖来解决,但实测证明此方法不可行。
首先贴一张很不错的图,SpringBoot启动结构图,图片出自SpringBoot启动流程解析。本文的分析基于SpringBoot5,非Spring的代码只有下面这个启。提供大量优秀的Web框架方便开发等等。
在了解SpringBoot的启动流程的时候,我们先看一下一个SpringBoot应用是如何启动的,如下是一个简单的SpringBoot程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。
执行核心run方法初始化initialize方法执行完之后,会调用run方法,开始启动SpringBoot。
「Spring 」「AOP 容器」不看源码就带你认识核心流程以及运作原理
前一篇文章主要介绍了 spring 核心特性机制的 IOC 容器机制和核心运作原理,接下来我们去介绍另外一个较为核心的功能,那就是 AOP 容器机制,主要负责承接前一篇代理模式机制中动态代理:JDKProxy 和 CglibProxy 的功能机制之后,我们开始研究一下如何实现一下相关的 AOP 容器代理机制的。
实现的基本实现原理就是后置处理器:BeanPostProcessor 机制,实现动态化植入机制。
bean 在初始化的时候会进行调用对应的 BeanPostProcessor 的对应的方法会进行织入。
主要取决于 wrapIfNecessary 方法:
如果是基础设施类型,则直接回进行返回该 bean 对象,不会进行相关的初始化对应的 aspectj 的动态织入机制。
会进行寻找相关的 Bean 对应的何时的加强通知类。
则会对该 bean 对象,额外进行增强操作生成相关的代理对象,并返回该执行之后的对象,否则会直接返回该对象即可。
getAdvicesAndAdvisorsForBean 方法是我们筛选 Advice 增强类的核心方法,主要用于过滤和筛选对应该 bean 的何时的增强器数组信息。
主要用于调用 AnnotationAwareAspectJAutoProxyCreator 的**findCandidateAdvisors()**方法,其内部会进行先关的核心构建相关的 Aspectj 的类的相关实现操作
advisorsFactory.getAdvisors 获取通知器
切点类处理操作到此为止,还不完整接下来才是构建动态代理对象的真正执行操作,
扩展相关的筛选出的通知器列表,extendAdvisors 方法,通知器列表首部添加一个 DefaultPointcutAposr 类型的通知器,也就是 ExposeInvocationInterceptor.ADVISOR 的实现机制。
proxy-target-class 的属性值,代表是否可以支持代理实现类,默认采用的 false 代表着,当 bean 有实现接口的时候,会直接采用 jdk 的动态代理机制生成代理对象,如果是 true,则代表着使用 cglib 进行生成代理对象。
复制代码
前提是必须要配置相关的 expose-proxy 属性配置值为 true,才会进行暴露对应的代理机制。
为了解决目标方法调用同对象中的其他方法,其他方法的切面逻辑是无法实现,因为会涉及到相关的 this 操作而不是 proxy 对象机制。
可以实现使用 AopContext.currentProxy()强制转换为当前的代理对象。
获取相关的对应方法的拦截器栈链路,如果没有获取到相关的缓存链路,则会直接调用相关的 getInterceptorsAndDynamicInterceptorAdvice 获取先关的拦截器链。
会进行先关的 PointcutAdvisor 类型通知器,这里会调用相关的通知器所持有的切点(Pointcut)对类和方法进行匹配,匹配冲过这说明相关的向当前的方法进行织入逻辑控制。此外还会通过 geIntercptors()方法对非 MethodIntercptor 类型的通知进行转换。返回相关的拦截器数组,并且随后存入缓存中。
则会直接通过代理机制的反射控制进行调用执行即可。
则例如 jdkDynamicAutoProxy 对象进行调用构建 ReflectiveMethodInvocation 对象,例如它的 process 方法启动拦截器栈的 invoke 方法。
处理返回值,并且返回该值。
java 急求SpringMVC的工作原理的解释和它的流程图
Spring工作流程描述
1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
7. ViewResolver 结合Model和View,来渲染视图
8. 将渲染结果返回给客户端。
Spring工作流程描述
为什么Spring只使用一个Servlet(DispatcherServlet)来处理所有请求?
详细见J2EE设计模式-前端控制模式
Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?
符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler。
最近面试问我用过什么框架,我说spring springmvc,经常被问到什么是springmvc
SpringMvc是spring的一个模块 基于MVC的一个框架 无需中间整合层来整合
SpringMvc整个的执行流程:
1、发起请求到前端控制器(DispatcherServlet )
2、前端控制器请求HandlerMapping查找Handler(可以根据xml、注解进行查找)
3、处理器映射器HandlerMapping向前端控制器DispatcherServlet 返回Handler
4、前端控制器DispatcherServlet 调用处理器适配器HandlerAdapter 执行Handler
5、处理器适配器HandlerAdapter 执行Handler
6、Handler执行完给处理器适配器返回ModelAndView
7、处理器适配器向前端控制器返回ModelAndView (ModelAndView 是SpringMvc的底层对象 包括model和view)
8、前端控制器请求视图解析器去解析视图
根据逻辑视图名解析成真正的视图(jsp)
9、视图解析器向前端控制器返回view
10、前端控制器进行视图渲染
视图渲染将模型数据(模型数据在ModelAndView对象中)填充到request域
11、前端控制器向用户响应结果
图文并茂,揭秘 Spring 的 Bean 的加载过程
目录
Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。
我们对 Spring 的工作流进行一个粗略的概括,主要为两大环节:
我们假设所有的配置和扩展类都已经装载到了 ApplicationContext 中,然后具体的分析一下 Bean 的加载流程。
思考一个问题,抛开 Spring 框架的实现,假设我们手头上已经有一套完整的 Bean Definition Map,然后指定一个 beanName 要进行实例化,需要关心什么?即使我们没有 Spring 框架,也需要了解这两方面的知识:
Spring 进行了抽象和封装,使得作用域和依赖关系的配置对开发者透明,我们只需要知道当初在配置里已经明确指定了它的生命周期和依赖了谁,至于是怎么实现的,依赖如何注入,托付给了 Spring 工厂来管理。
Spring 只暴露了很简单的接口给调用者,比如 getBean :
那我们就从 getBean 方法作为入口,去理解 Spring 加载的流程是怎样的,以及内部对创建信息、作用域、依赖关系等等的处理细节。
上面是跟踪了 getBean 的调用链创建的流程图,为了能够很好地理解 Bean 加载流程,省略一些异常、日志和分支处理和一些特殊条件的判断。
从上面的流程图中,可以看到一个 Bean 加载会经历这么几个阶段(用绿色标记):
整个流程最为复杂的是对循环依赖的解决方案,后续会进行重点分析。
而在我们解析完配置后创建的 Map,使用的是 beanName 作为 key。见 DefaultListableBeanFactory:
BeanFactory.getBean 中传入的 name,有可能是这几种情况:
为了能够获取到正确的 BeanDefinition,需要先对 name 做一个转换,得到 beanName。
见 AbstractBeanFactory.doGetBean :
如果是 alias name ,在解析阶段,alias name 和 bean name 的映射关系被注册到 SimpleAliasRegistry 中。从该注册器中取到 beanName。见 SimpleAliasRegistry.canonicalName :
如果是 factorybean name ,表示这是个工厂 bean,有携带前缀修饰符 & 的,直接把前缀去掉。见 BeanFactoryUtils.transformedBeanName :
我们从配置文件读取到的 BeanDefinition 是 GenericBeanDefinition 。它的记录了一些当前类声明的属性或构造参数,但是对于父类只用了一个 parentName 来记录。
接下来会发现一个问题,在后续实例化 Bean 的时候,使用的 BeanDefinition 是 RootBeanDefinition 类型而非 GenericBeanDefinition 。这是为什么?
答案很明显,GenericBeanDefinition 在有继承关系的情况下,定义的信息不足:
为了能够正确初始化对象,需要完整的信息才行 。需要递归 合并父类的定义 :
见 AbstractBeanFactory.doGetBean :
在判断 parentName 存在的情况下,说明存在父类定义,启动合并。如果父类还有父类怎么办?递归调用,继续合并。
见 AbstractBeanFactory.getMergedBeanDefinition 方法:
每次合并完父类定义后,都会调用 RootBeanDefinition.overrideFrom 对父类的定义进行覆盖,获取到当前类能够正确实例化的 全量信息 。
什么是循环依赖?
举个例子,这里有三个类 A、B、C,然后 A 关联 B,B 关联 C,C 又关联 A,这就形成了一个循环依赖。如果是方法调用是不算循环依赖的,循环依赖必须要持有引用。
循环依赖根据注入的时机分成两种类型:
如果是构造器循环依赖,本质上是无法解决的 。比如我们准调用 A 的构造器,发现依赖 B,于是去调用 B 的构造器进行实例化,发现又依赖 C,于是调用 C 的构造器去初始化,结果依赖 A,整个形成一个死结,导致 A 无法创建。
如果是设值循环依赖,Spring 框架只支持单例下的设值循环依赖 。Spring 通过对还在创建过程中的单例,缓存并提前暴露该单例,使得其他实例可以引用该依赖。
Spring 不支持原型模式的任何循环依赖 。检测到循环依赖会直接抛出 BeanCurrentlyInCreationException 异常。
使用了一个 ThreadLocal 变量 prototypesCurrentlyInCreation 来记录当前线程正在创建中的 Bean 对象,见 AbtractBeanFactory#prototypesCurrentlyInCreation :
在 Bean 创建前进行记录,在 Bean 创建后删除记录。见 AbstractBeanFactory.doGetBean :
见 AbtractBeanFactory.beforePrototypeCreation 的记录操作:
见 AbtractBeanFactory.beforePrototypeCreation 的删除操作:
为了节省内存空间,在单个元素时 prototypesCurrentlyInCreation 只记录 String 对象,在多个依赖元素后改用 Set 集合。这里是 Spring 使用的一个节约内存的小技巧。
了解了记录的写入和删除过程好了,再来看看读取以及判断循环的方式。这里要分两种情况讨论。
这两个地方的实现略有不同。
如果是构造函数依赖的,比如 A 的构造函数依赖了 B,会有这样的情况。实例化 A 的阶段中,匹配到要使用的构造函数,发现构造函数有参数 B,会使用 BeanDefinitionValueResolver 来检索 B 的实例。见 BeanDefinitionValueResolver.resolveReference :
我们发现这里继续调用 beanFactory.getBean 去加载 B。
如果是设值循环依赖的的,比如我们这里不提供构造函数,并且使用了 @Autowire 的方式注解依赖(还有其他方式不举例了):
加载过程中,找到无参数构造函数,不需要检索构造参数的引用,实例化成功。接着执行下去,进入到属性填充阶段 AbtractBeanFactory.populateBean ,在这里会进行 B 的依赖注入。
为了能够获取到 B 的实例化后的引用,最终会通过检索类 DependencyDescriptor 中去把依赖读取出来,见 DependencyDescriptor.resolveCandidate :
发现 beanFactory.getBean 方法又被调用到了。
在这里,两种循环依赖达成了同一 。无论是构造函数的循环依赖还是设置循环依赖,在需要注入依赖的对象时,会继续调用 beanFactory.getBean 去加载对象,形成一个递归操作。
而每次调用 beanFactory.getBean 进行实例化前后,都使用了 prototypesCurrentlyInCreation 这个变量做记录。按照这里的思路走,整体效果等同于 建立依赖对象的构造链 。
prototypesCurrentlyInCreation 中的值的变化如下:
调用判定的地方在 AbstractBeanFactory.doGetBean 中,所有对象的实例化均会从这里启动。
判定的实现方法为 AbstractBeanFactory.isPrototypeCurrentlyInCreation :
所以在原型模式下,构造函数循环依赖和设值循环依赖,本质上使用同一种方式检测出来。Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。
Spring 也不支持单例模式的构造循环依赖 。检测到构造循环依赖也会抛出 BeanCurrentlyInCreationException 异常。
和原型模式相似,单例模式也用了一个数据结构来记录正在创建中的 beanName。见 DefaultSingletonBeanRegistry :
会在创建前进行记录,创建化后删除记录。
见 DefaultSingletonBeanRegistry.getSingleton
记录和判定的方式见 DefaultSingletonBeanRegistry.beforeSingletonCreation :
这里会尝试往 singletonsCurrentlyInCreation 记录当前实例化的 bean。我们知道 singletonsCurrentlyInCreation 的数据结构是 Set,是不允许重复元素的, 所以一旦前面记录了,这里的 add 操作将会返回失败 。
比如加载 A 的单例,和原型模式类似,单例模式也会调用匹配到要使用的构造函数,发现构造函数有参数 B,然后使用 BeanDefinitionValueResolver 来检索 B 的实例,根据上面的分析,继续调用 beanFactory.getBean 方法。
所以拿 A,B,C 的例子来举例 singletonsCurrentlyInCreation 的变化,这里可以看到和原型模式的循环依赖判断方式的算法是一样:
单例模式下,构造函数的循环依赖无法解决,但设值循环依赖是可以解决的 。
这里有一个重要的设计: 提前暴露创建中的单例 。
我们理解一下为什么要这么做。
还是拿上面的 A、B、C 的的设值依赖做分析,
=> 1. A 创建 -> A 构造完成,开始注入属性,发现依赖 B,启动 B 的实例化
=> 2. B 创建 -> B 构造完成,开始注入属性,发现依赖 C,启动 C 的实例化
=> 3. C 创建 -> C 构造完成,开始注入属性,发现依赖 A
重点来了,在我们的阶段 1中, A 已经构造完成,Bean 对象在堆中也分配好内存了,即使后续往 A 中填充属性(比如填充依赖的 B 对象),也不会修改到 A 的引用地址。
所以,这个时候是否可以 提前拿 A 实例的引用来先注入到 C ,去完成 C 的实例化,于是流程变成这样。
=> 3. C 创建 -> C 构造完成,开始注入依赖,发现依赖 A,发现 A 已经构造完成,直接引用,完成 C 的实例化。
=> 4. C 完成实例化后,B 注入 C 也完成实例化,A 注入 B 也完成实例化。
这就是 Spring 解决单例模式设值循环依赖应用的技巧。流程图为:
为了能够实现单例的提前暴露。Spring 使用了三级缓存,见 DefaultSingletonBeanRegistry :
这三个缓存的区别如下:
从 getBean("a") 开始,添加的 SingletonFactory 具体实现如下:
可以看到如果使用该 SingletonFactory 获取实例,使用的是 getEarlyBeanReference 方法,返回一个未初始化的引用。
读取缓存的地方见 DefaultSingletonBeanRegistry :
先尝试从 singletonObjects 和 singletonFactory 读取,没有数据,然后尝试 singletonFactories 读取 singletonFactory,执行 getEarlyBeanReference 获取到引用后,存储到 earlySingletonObjects 中。
这个 earlySingletonObjects 的好处是,如果此时又有其他地方尝试获取未初始化的单例,可以从 earlySingletonObjects 直接取出而不需要再调用 getEarlyBeanReference 。
从流程图上看,实际上注入 C 的 A 实例,还在填充属性阶段,并没有完全地初始化。等递归回溯回去,A 顺利拿到依赖 B,才会真实地完成 A 的加载。
获取到完整的 RootBeanDefintion 后,就可以拿这份定义信息来实例具体的 Bean。
具体实例创建见 AbstractAutowireCapableBeanFactory.createBeanInstance ,返回 Bean 的包装类 BeanWrapper,一共有三种策略:
使用工厂方法创建,会先使用 getBean 获取工厂类,然后通过参数找到匹配的工厂方法,调用实例化方法实现实例化,具体见 ConstructorResolver.instantiateUsingFactoryMethod :
使用有参构造函数创建,整个过程比较复杂,涉及到参数和构造器的匹配。为了找到匹配的构造器,Spring 花了大量的工作,见 ConstructorResolver.autowireConstructor :
使用无参构造函数创建是最简单的方式,见 AbstractAutowireCapableBeanFactory.instantiateBean :
我们发现这三个实例化方式,最后都会走 getInstantiationStrategy().instantiate(...) ,见实现类 SimpleInstantiationStrategy.instantiate :
虽然拿到了构造函数,并没有立即实例化。因为用户使用了 replace 和 lookup 的配置方法,用到了动态代理加入对应的逻辑。如果没有的话,直接使用反射来创建实例。
创建实例后,就可以开始注入属性和初始化等操作。
但这里的 Bean 还不是最终的 Bean。返回给调用方使用时,如果是 FactoryBean 的话需要使用 getObject 方法来创建实例。见 AbstractBeanFactory.getObjectFromBeanInstance ,会执行到 doGetObjectFromFactoryBean :
实例创建完后开始进行属性的注入,如果涉及到外部依赖的实例,会自动检索并关联到该当前实例。
Ioc 思想体现出来了。正是有了这一步操作,Spring 降低了各个类之间的耦合。
属性填充的入口方法在 AbstractAutowireCapableBeanFactory.populateBean 。
可以看到主要的处理环节有:
如果我们的 Bean 需要容器的一些资源该怎么办?比如需要获取到 BeanFactory、ApplicationContext 等等。
Spring 提供了 Aware 系列接口来解决这个问题。比如有这样的 Aware:
Spring 在初始化阶段,如果判断 Bean 实现了这几个接口之一,就会往 Bean 中注入它关心的资源。
见 AbstractAutowireCapableBeanFactory.invokeAwareMethos :
在 Bean 的初始化前或者初始化后,我们如果需要进行一些增强操作怎么办?
这些增强操作比如打日志、做校验、属性修改、耗时检测等等。Spring 框架提供了 BeanPostProcessor 来达成这个目标。比如我们使用注解 @Autowire 来声明依赖,就是使用 AutowiredAnnotationBeanPostProcessor 来实现依赖的查询和注入的。接口定义如下:
实现该接口的 Bean 都会被 Spring 注册到 beanPostProcessors 中, 见 AbstractBeanFactory :
只要 Bean 实现了 BeanPostProcessor 接口,加载的时候会被 Spring 自动识别这些 Bean,自动注册,非常方便。
然后在 Bean 实例化前后,Spring 会去调用我们已经注册的 beanPostProcessors 把处理器都执行一遍。
这里使用了责任链模式,Bean 会在处理器链中进行传递和处理。当我们调用 BeanFactory.getBean 的后,执行到 Bean 的初始化方法 AbstractAutowireCapableBeanFactory.initializeBean 会启动这些处理器。
自定义初始化有两种方式可以选择:
见 AbstractAutowireCapableBeanFactory.invokeInitMethods :
Bean 已经加载完毕,属性也填充好了,初始化也完成了。
在返回给调用者之前,还留有一个机会对 Bean 实例进行类型的转换。见 AbstractBeanFactory.doGetBean :
抛开一些细节处理和扩展功能,一个 Bean 的创建过程无非是:
获取完整定义 -> 实例化 -> 依赖注入 -> 初始化 -> 类型转换。
作为一个完善的框架,Spring 需要考虑到各种可能性,还需要考虑到接入的扩展性。
所以有了复杂的循环依赖的解决,复杂的有参数构造器的匹配过程,有了 BeanPostProcessor 来对实例化或初始化的 Bean 进行扩展修改。
先有个整体设计的思维,再逐步击破针对这些特殊场景的设计,整个 Bean 加载流程迎刃而解。