开涛springmvc源码
Ⅰ 从源码理解总结web容器、spring容器、spring mvc容器三者关系
本篇,我打算从springMVC项目的web.xml的配置文件入手,通过部分源码逐步去理解解释三个容器的关系以及调用顺序,因为是基于我个人的理解,可能有所不足。
一般web.xml文件里会有如下两段配置信息:
我们先了解下web.xml,以下引用自 《web.xml文件是什么?有什么用?--详解》 :
然后结合我们上面的web.xml中关于spring和spring mvc的配置信息来进入话题:
首先,启动web容器的时,会先生成对应项目的ServelContent对象,这个是每个项目的上下文,这个ServelContent可以管理所有的servlet,并将我们web.xml中设置的<context-param>内容作为键值对交给这个对象。
然后加载<listener>标签内容,这个时候就会产生org.springframework.web.context.ContextLoaderListener。
spring的这个 ContextLoaderListener 在接下来的过程中很重要,我们来看一下源码
首先,可以看出它继承了ContextLoader类,并实现了ServletContextListener接口。
这里再直接引用他人的结论 《Spring中ContextLoaderListener作用》
好了,人家说法中回到我们的起点了,我们基本都被人告知“ContextLoaderListener的作用是创建并初始化spring容器”
那我们就可以深入进去看看,到底哪里做了这一步:
首先,我们知道了ServletContextListene是ServletContext的监听者,监听器的响应动作就是在服务器启动时contextInitialized会被调用,关闭的时候contextDestroyed被调用,这个好理解,那我们就来看一下ContextLoaderListener重写的contextInitialized方法到底做了什么。
我们再进入观察initWebApplicationContext方法细看
我因为自己消化过一遍,直接给出关键位置的方法说明——
1、首先是278行:创建了WebApplicationContext,我们可以理解为spring容器的壳子有了
2、其次是288和289行:对ApplicationContext加载了配置文件,并设置servletContext为WebApplicationContext的parent,到这一步,可以理解为我们的spring容器也就差不多成型了
3、接下来是294行:把ApplicationContext对象以键值对的形式存到servletContext中,这一步很关键,就是因为servletContext中存在这个键值对,所以其他内部成员可以通过servletContext访问到ApplicationContext,当然也能使用其管理的bean,而spring mvc则没有这样存在servletContext,所以我觉得正是这一步决定了子容器springmvc可以取用父容器内的bean,反着则不然。
接下来直到轮到我们的springmvc容器<servlet>标签内容
会生成控制org.springframework.web.servlet.DispatcherServlet,这是一个前端控制器,主要的内容我之前也有一篇文章做过自我记录
《Spring MVC的工作机制简单理解》
我们可以看到设置的
<load-on-startup>1</load-on-startup>
这个标签大概意思就是:
1、load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
2、它的值必须是一个整数,表示servlet被加载的先后顺序。
3、如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
4、如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
在DispatcherServlet的时候就根据springMVC容器容器的配置文件生成。
比如我这边就是
那顺序确定了,我们再看一下spring和spring mvc的父子关系哪里确定:
我们可以从下面3个截图看到dispatcherServlet的继承关系,同时,init方法用的是dispatcherServlet父类的父类的方法。
重点在于initServletBean()方法,经过追踪,我们找到该方法的最终实现又是在dispatcherServlet的父类FrameworkServlet中
其中涉及父子关系的实际是在219行的initWebApplicationContext()方法
initWebApplicationContext()方法主要用于创建或刷新WebApplicationContext实例,并对Servlet功能所使用的变量进行初始化。
从238行源码就可以看到,它获得ContextLoaderListener中初始化的rootContext,
在246行设置了父子关系的引用,也就是从这一点我们看到了spring和springMVC的父子关系!
并且,可以看到这只是一条单向的引用,spring中没有引用直接指向springMVC,也就是子类能找到父类,然而父类都不知道这个子类,父子容器之间内部对象调用关系更明了。
再通过构造函数和Servlet的contextAttribute属性查找ServletContext来进行webApplicationContext实例的初始化,最终。
这个方法内263行源码onRefresh(wac)方法是FrameworkServlet提供的模板方法,在子类,也就是我们的DispatcherServlet的onRefresh()方法中进行了重写。而在onRefresh()方法中调用了initStrategies()方法来完成初始化工作,初始化Spring MVC的9个组件。
1、Tomcat在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供环境。
2、Tomcat在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。
3、Tomcat在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。
4、Servlet会在容器启动时加载或延迟加载(根据启动级别设置数字)。延迟加载时,当第一个请求达到时,serlet容器发现对应Servlet还没有被实例化,就调用Servlet的init方法。
在spring MVC里
DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean。同时,Spring MVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean, 也就是说Spring根容器不能访问SpringMVC容器里的Bean。
说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。
Ⅱ SpringMVC+Spring+Mybatis实践总结
本文源码见 https://github.com/peng66cheng/ssmDemo ,请参考。
最近开发一个后台系统,使用ssm从头搭建。这次开发,有几点值得总闷搭结,以便后续开发提高效率。 (此处只提供方法,具体做法还需自行搜索)
1)利用mybatis-generator-core自动生成domain、接口、**Mapper.xml。
2)使用org.mybatis.spring.mapper.MapperScannerConfigurer 和 @MapperScan注解,自动扫描接口注册。无需开发具体蚂樱拿的实现类。
3)接口 同 **Mapper.xml 参数映射,使用@Param注解。如果参数多于4个,则建议定义参数对象。
参考: http://www.2cto.com/kf/201501/368973.html
http://www.cnblogs.com/wangmingshun/p/5674633.html
1)前端String类型转为后台Date类型,借助,编写自动转换类。
2)后台Date类型转为前端String类型,借助@JsonFormat完成。
参考颂握: http://www.cnblogs.com/lcngu/p/5785805.html
使用maven的profile特性,完成不同配置的打包。
(其他问题待续)
Ⅲ 《看透SpringMVC源代码分析与实践》pdf下载在线阅读全文,求百度网盘云资源
《看透SpringMVC源代码分析与实践》(韩路彪)电子书网盘下载免费在线阅读
链接: https://pan..com/s/1vSy3Wd53qe91ak602kSGqw
书名:看透SpringMVC源代码分析与实践
作者:韩路彪
出版社:机械工业出版社
副标题:源代码分析与实践
原作名:韩路彪
出版年:2016-1-1
页数:309
内容简介
国内资深Web开发专家根据Spring MVC全新技术撰写,基于实际生产环境,从基础知识、源代码和实战3个维度对Spring MVC的结构和实现进行详细讲解
全面介绍Spring MVC的架构、原理、核心概念和操作,通过案例完整呈现Tomcat的实现,系统总结Spring MVC九大组件的处理以及常用的技巧和实践
在大型网站和复杂系统的开发中,java具有天然的优势,而在Java的Web框架中Spring MVC以其强大的功能以及简单且灵活的用法受到越来越多开发者的青睐。本书不仅详细地分析Spring MVC的结构及其实现细节,而且讲解网站的不同架构及其演变的过程,以及网络底层协议的概念及其实现方法,帮助读者开发更高效的网站。
作者简介
韩路彪当代知名作家。
Ⅳ 怎么找到spring注解解析器的源码
下面用的是4.2.5的源码。
从这个文件开始看:META-INF/spring.handlers
文件里的内容是http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
MvcNamespaceHandler源码:
Ⅳ 详解Spring mvc工作原理及源码分析
Model 模型层 (javaBean组件 = 领域模型(javaBean) + 业务层 + 持久层)
View 视图层( html、jsp…)
Controller 控制层(委托模型层进行数据处理)
springmvc是一个web层mvc框架,类似struts2。
springmvc是spring的部分,其实就是spring在原有基础上,又提供了web应用的mvc模块。
实现机制:
struts2是基于过滤器实现的。
springmvc是基于servlet实现的。
运行速度:
因为过滤器底层是servlet,所以springmvc的运行速度会稍微比structs2快。
struts2是多例的
springmvc单例的
参数封装:
struts2参数封装是基于属性进行封装。
springmvc是基于方法封装。颗粒度更细。
⑴ 用户发送请求至DispatcherServlet。
⑵ DispatcherServlet收到请求调用HandlerMapping查询具体的Handler。
⑶ HandlerMapping找到具体的处理器(具体配置的是哪个处理器的实现类),生成处理器对象及处理器拦截器(HandlerExcutorChain包含了Handler以及拦截器集合)返回给DispatcherServlet。
⑷ DispatcherServlet接收到HandlerMapping返回的HandlerExcutorChain后,调用HandlerAdapter请求执行具体的Handler(Controller)。
⑸ HandlerAdapter经过适配调用具体的Handler(Controller即后端控制器)。
⑹ Controller执行完成返回ModelAndView(其中包含逻辑视图和数据)给HandlerAdaptor。
⑺ HandlerAdaptor再将ModelAndView返回给DispatcherServlet。
⑻ DispatcherServlet请求视图解析器ViewReslover解析ModelAndView。
⑼ ViewReslover解析后返回具体View(物理视图)到DispatcherServlet。
⑽ DispatcherServlet请求渲染视图(即将模型数据填充至视图中) 根据View进行渲染视图。
⑾ 将渲染后的视图返回给DispatcherServlet。
⑿ DispatcherServlet将响应结果返回给用户。
(1)前端控制器DispatcherServlet(配置即可)
功能:中央处理器,接收请求,自己不做任何处理,而是将请求发送给其他组件进行处理。DispatcherServlet 是整个流程的控制中心。
(2)处理器映射器HandlerMapping(配置即可)
功能:根据DispatcherServlet发送的url请求路径查找Handler
常见的处理器映射器:BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping,
,(不建议使用)
(3)处理器适配器HandlerAdapter(配置即可)
功能:按照特定规则(HandlerAdapter要求的规则)去执行Handler。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展多个适配器对更多类型的处理器进行执行。
常见的处理器适配器:HttpRequestHandlerAdapter,,
(4)处理器Handler即Controller(程序猿编写)
功能:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
(5)视图解析器ViewReslover(配置即可)
功能:进行视图解析,根据逻辑视图名解析成真正的视图。
ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
springmvc框架提供了多种View视图类型,如:jstlView、freemarkerView、pdfView...
(6)视图View(程序猿编写)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)
引入相关依赖:spring的基本包、springmvc需要的spring-webmvc,日志相关的slf4j-log4j12,jsp相关的jstl、servlet-api、jsp-api。
因为DispatcherServlet本身就是一个Servlet,所以需要在web.xml配置。
一、使用默认加载springmvc配置文件的方式,必须按照以下规范:
①命名规则:-servlet.xml ====> springmvc-servlet.xml
②路径规则:-servlet.xml必须放在WEB-INF下边
二、如果要不按照默认加载位置,则需要在web.xml中通过标签来指定springmvc配置文件的加载路径,如上图所示。
将自定义的 Controller 处理器配置到 spring 容器中交由 spring 容器来管理,因为这里的 springmvc.xml 配置文件中处理器映射器配置的是 BeanNameUrlHandlerMapping ,根据名字可知这个处理器映射器是根据 bean (自定义Controller) 的 name 属性值url去寻找执行类 Handler(Controller) , 所以bean的name属性值即是要和用户发送的请求路径匹配的 url 。
根据视图解析路径:WEB-INF/jsps/index.jsp
功能:根据bean(自定义Controller)的name属性的url去寻找执行类Controller。
功能:自定义的处理器(Controller)实现了Controller接口时,适配器就会执行Controller的具体方法。
会自动判断自定义的处理器(Controller)是否实现了Controller接口,如果是,它将会自动调用处理器的handleRequest方法。
Controller接口中有一个方法叫handleRequest,也就是处理器方法。
因此,自定义的Controller要想被调用就必须实现Controller接口,重写Controller接口中的处理器方法。
Ⅵ SpringMVC
一、SpringMVC应用
1.springmvc是一个表现层的框架
经典三层架构:表现层, service层, 层。
Spring MVC和Struts2一样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模
式的。而这些表现层框架的主要职责就是处理前端HTTP请求。
Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发
2.工作流程:
开发过程:
请求处理流程:
九大组件:
3.请求参数绑定,说白了SpringMVC是如何接受参数的:
原生servlet接收一个整型参数:
SpringMVC框架对Servlet的封装,简化了servlet的很多操作,SpringMVC框架对Servlet的封装,简化了servlet的很多操作
参数绑定:取出参数值绑定到handler⽅法的形参上
默认支持 Servlet API 作为方法参数:
绑定简单类型参数:
绑定Pojo类型参数:
绑定Pojo包装对象参数:
绑定日期类型参数(需要配置自定义类型转换器):
4.Restful风格
什么是Restful:
什么是rest:
Restful的优点:
Restful的特性:
Restful的示例:
SpringMVC如何支持Restful风格的请求:
5.Ajax Json交互
交互:两个方向
什么是Json:
@ResponseBody注解:
分析SpringMVC使用Json交互:
二、SpringMVC高级技术
1.拦截器(Inteceptor)使用
监听器、过滤器、拦截器的对比:
拦截器的执行流程:
多个拦截器的执行流程:
自定义SpringMVC拦截器:
2.处理multipart形式的数据
文件上传:
3.在控制器中处理异常:
4.基于Flash属性的跨重定向请求数据传递:
三、手写SpringMVC框架:
四、SpringMVC源码剖析
1.前端控制器 DispatcherServlet 继承结构:
2.重要时机点分析
SpringMVC处理请求的流程即为:
3.核心步骤getHandler方法剖析:
4.核心步骤getHandlerAdapter方法剖析:
5.核心步骤ha.handle方法剖析:
6.核心步骤processDispatchResult方法剖析:
7.SpringMVC九大组件初始化:
五、SSM整合
1.整合策略:先整合Spring + Mybatis,然后再整合SpringMVC
2.Mybatis整合Spring
3.整合SpringMVC
未完待补充完整。。。
Ⅶ Spring事件监听机制源码解析
1.Spring事件监听体系包括三个组件:事件、事件监听器,事件广播器。
事件:定义事件类型和事件源,需要继承ApplicationEvent。
事件监听器:用来监听某一类的事件,并且执行具体业务逻辑,需要实现ApplicationListener 接口或者需要用@ListenerEvent(T)注解。好比观察者模式中的观察者。
事件多播器:负责广播通知所有监听器,所有的事件监听器都注册在了事件多播器中。好比观察者模式中的被观察者。Spring容器默认生成的是同步事件多播器。可以自定义事件多播器,定义为异步方式。
创建 的过程中,会执行refresh()中的()方法。该方法先获取bean工厂,然后判断工厂是否包含了beanName 为 applicationEventMulticaster的bean。如果包含了,则获取该bean,赋值给applicationEventMulticaster 属性。如果没有,则创建一个 对象,并且赋值给 applicationEventMulticaster 。实现了源码如下:
监听器的注册有两种,通过实现 ApplicationListener接口或者添加@EventListener注解。
注册的逻辑实现在refresh()中的registerListeners()方法里面。第一步,先获取当前ApplicationContext中已经添加的 applicationListeners(SpringMVC源码中有用到),遍历添加到多播器中。第二步,获取实现了ApplicationListener接口的listenerBeanNames集合,添加至多播器中。第三步,判断是否有早期事件,如果有则发起广播。
思考一下,上面的代码中第二步为啥添加的是listenerBeanName?
如果监听器是懒加载的话(即有@Lazy 注解)。那么在这个时候创建监听器显然是不对的,这个时候不能创建监听器。所以添加监听器到多播器的具体逻辑放在初始化具体的监听器之后。通过 BeanPostProcessor 的接口实现。具体的实现类是 ApplicationListenerDetector 。这个类是在 refreah()中prepareBeanFactory()方法中添加的。代码如下:
在创建 的构造方法中,会执行org.springframework.context.annotation.AnnotationConfigUtils#(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object) 方法。这个方法中会添加两个 beanDefs, 代码如下:
EventListenerMethodProcessor:事件监听器的BeanFactory后置处理器,在前期会创建 DefaultEventListenerFactory ,后期在创建好Bean之后,根据 EventListener 属性,调用DefaultEventListenerFactory创建具体的 。
DefaultEventListenerFactory:监听器的创建工厂,用来创建 。
EventListenerMethodProcessor 的类继承图如下:
在refreash的()中会调用 org.springframework.context.event.EventListenerMethodProcessor#postProcessBeanFactory方法,获取EventListenerFactory 类型的 Bean。代码如下:
在 org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 方法中,创建完所有的单例Bean 之后,会遍历所有Bean是否实现了 SmartInitializingSingleton 接口。如果实现接口会执行该 Bean 的 afterSingletonsInstantiated() 方法。代码如下:
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated 中会调用私有方法 processBean()进行 ApplicationEventAdatper 的创建。代码如下:
可以通过调用 org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType) 方法进行事件的调用。代码如下:
中的 multicasEvent,invokeListener,doInvokeListener 三个方法代码如下:
SpringMVC中就是通过Spring的事件机制进行九大组件的初始化。
监听器定义在FrameworkServlet类中,作为内部类。代码如下:
监听器的添加在org.springframework.web.servlet.FrameworkServlet# 中进行。通过SourceFilteringListener进行包装。添加代码如下:
在refresh中的registerListeners方法进行添加,代码如下:
在refresh中的finishRefresh()方法中,会调用publishEvnet(new ContextRefreshedEvent(this))发布事件。进行多播器广播,代码如下
最终会调到FrameworkServlet.this.onApplicationEvent(event)。
Ⅷ [Spring boot源码解析] 2 启动流程分析
在了解 Spring Boot 的启动流程的时候,我们先看一下一个Spring Boot 应用是如何启动的,如下是一个简单的 SpringBoot 程序,非常的简洁,他是如何做到的呢,我们接下来就将一步步分解。
我们追踪 SpringApplication.run() 方法,其实最终它主要的逻辑是新建一个 SpringApplication ,然后调用他的 run 方法,如下:
我们先来看一下创建 SpringApplication 的方法:
在将Main class 设置 primarySources 后,调用了 WebApplicationType.deceFromClasspath() 方法,该方法是为了检查当前的应用类型,并设置给 webApplicationType 。 我们进入 deceFromClasspath 方法 :
这里主要是通过类加载器判断是否存在 REACTIVE 相关的类信息,假如有就代表是一个 REACTIVE 的应用,假如不是就检查是否存在 Servelt 和 ,假如都没有,就代表应用为非 WEB 类应用,返回 NONE ,默认返回 SERVLET 类型,我们这期以我们目前最常使用的 SERVLET 类型进行讲解,所以我们在应用中引入了 spring-boot-starter-web 作为依赖:
他会包含 Spring-mvc 的依赖,所以就包含了内嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 类型。
回到刚才创建 SpringApplication 的构建方法中,我们设置完成应用类型后,就寻找所有的 Initializer 实现类,并设置到 SpringApplication 的 Initializers 中,这里先说一下 getSpringFactoriesInstances 方法,我们知道在我们使用 SpringBoot 程序中,会经常在 META-INF/spring.factories 目录下看到一些 EnableAutoConfiguration ,来出发 config 类注入到容器中,我们知道一般一个 config 类要想被 SpringBoot 扫描到需要使用 @CompnentScan 来扫描具体的路径,对于 jar 包来说这无疑是非常不方便的,所以 SpringBoot 提供了另外一种方式来实现,就是使用 spring.factories ,比如下面这个,我们从 Springboot-test 中找到的例子,这里先定义了一个ExampleAutoConfiguration,并加上了 Configuration 注解:
然后在 spring.factories 中定义如下:
那这种方式是怎么实现的你,这就要回到我们刚才的方法 getSpringFactoriesInstances :
我们先来看一下传入参数,这里需要注意的是 args,这个是初始化对应 type 的时候传入的构造参数,我们先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是会先检查缓存,假如缓存中存在就直接返回,假如没有就调用 classLoader#getResources 方法,传入 META-INF/spring.factories ,即获取所有 jar 包下的对应文件,并封装成 UrlResource ,然后使用 PropertiesLoaderUtils 将这些信息读取成一个对一对的 properties,我们观察一下 spring.factories 都是按 properties 格式排版的,假如有多个就用逗号隔开,所以这里还需要将逗号的多个类分隔开来,并加到 result 中,由于 result 是一个 LinkedMultiValueMap 类型,支持多个值插入,最后放回缓存中。最终完成加载 META-INF/spring.factories 中的配置,如下:
我们可以看一下我们找到的 initializer 有多少个:
在获取到所有的 Initializer 后接下来是调用 方法进行初始化。
这里的 names 就是我们上面通过类加载器加载到的类名,到这里会先通过反射生成 class 对象,然后判断该类是否继承与 ApplicationContextInitializer ,最后通过发射的方式获取这个类的构造方法,并调用该构造方法,传入已经定义好的构造参数,对于 ApplicationContextInitializer 是无参的构造方法,然后初始化实例并返回,回到原来的方法,这里会先对所有的 ApplicationContextInitializer 进行排序,调用 #sort(instances) 方法,这里就是根据 @Order 中的顺序进行排序。
接下来是设置 ApplicationListener ,我们跟进去就会发现这里和上面获取 ApplicationContextInitializer 的方法如出一辙,最终会加载到如图的 15 个 listener (这里除了 外,其他都是 SpringBoot 内部的 Listener):
在完成 SpringApplication 对象的初始化后,我们进入了他的 run 方法,这个方法几乎涵盖了 SpringBoot 生命周期的所有内容,主要分为九个步骤,每一个步骤这里都使用注解进行标识:
主要步骤如下:
第一步:获取 SpringApplicationRunListener, 然后调用他的 staring 方法启动监听器。
第二步:根据 SpringApplicationRunListeners以及参数来准备环境。
第三步:创建 Spring 容器。
第四步:Spring 容器的前置处理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的后置处理器。
第七步:通知所有 listener 结束启动。
第八步:调用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我们接下来一一讲解这些内容。
我们首先看一下第一步,获取 SpringApplicationRunListener :
这里和上面获取 initializer 和 listener 的方式基本一致,都是通过 getSpringFactoriesInstances , 最终只找到一个类就是: org.springframework.boot.context.event.EventPublishingRunListener ,然后调用其构造方法并传入产生 args , 和 SpringApplication 本身:
我们先看一下构造函数,首先将我们获取到的 ApplicationListener 集合添加到initialMulticaster 中, 最后都是通过操作 来进行广播,我,他继承于 ,我们先看一下他的 addApplicationListener 方法:
我们可以看出,最后是放到了 applicationListenters 这个容器中。他是 defaultRetriever 的成员属性, defaultRetriever 则是 的私有类,我们简单看一下这个类:
我们只需要看一下这里的 getApplicationListeners 方法,它主要是到 beanFactory 中检查是否存在多的 ApplicationListener 和旧的 applicationListeners 组合并返回,接着执行 listener 的 start 方法,最后也是调用了 的 multicastEvent 查找支持对应的 ApplicationEvent 类型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,这里除了会:
筛选的方法如下,都是调用了对应类型的 supportsEventType 方法 :
如图,我们可以看到对 org.springframework.boot.context.event.ApplicationStartingEvent 感兴趣的有5个 Listener
环境准备的具体方法如下:
首先是调用 getOrCreateEnvironment 方法来创建 environment ,我们跟进去可以发现这里是根据我们上面设置的环境的类型来进行选择的,当前环境会创建 StandardServletEnvironment
我们先来看一下 StandardServletEnvironment 的类继承关系图,我们可以看出他是继承了 AbstractEnvironment :
他会调用子类的 customizePropertySources 方法实现,首先是 StandardServletEnvironment 的实现如下,他会添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三种 properties,当前调试环境没有配置 jndi properties,所以这里不会添加。接着调用父类的 customizePropertySources 方法,即调用到了 StandardEnvironment 。
我们看一下 StandardEnvironment#customizePropertySources 方法,与上面的三个 properties 创建不同,这两个是会进行赋值的,包括系统环境变量放入 systemEnvironment 中,jvm 先关参数放到 systemProperties 中:
这里会添加 systemEnvironment 和 systemProperties 这两个 properties,最终拿到的 properties 数量如下 4个:
在创建完成 Environment 后,接下来就到了调用 configureEnvironment 方法:
我们先看一下 configurePropertySources 方法,这里主要分两部分,首先是查询当前是否存在 defaultProperties ,假如不为空就会添加到 environment 的 propertySources 中,接着是处理命令行参数,将命令行参数作为一个 CompositePropertySource 或则 添加到 environment 的 propertySources 里面,
接着调用 ConfigurationPropertySources#attach 方法,他会先去 environment 中查找 configurationProperties , 假如寻找到了,先检查 configurationProperties 和当前 environment 是否匹配,假如不相等,就先去除,最后添加 configurationProperties 并将其 sources 属性设置进去。
回到我们的 prepareEnvironment 逻辑,下一步是通知观察者,发送 事件,调用的是 SpringApplicationRunListeners#environmentPrepared 方法,最终回到了 #multicastEvent 方法,我们通过 debug 找到最后对这个时间感兴趣的 Listener 如下:
其主要逻辑如下:
这个方法最后加载了 PropertySourceLoader , 这里主要是两种,一个是用于 Properties 的,一个是用于 YAML 的如下:
其中 apply 方法主要是加载 defaultProperties ,假如已经存在,就进行替换,而替换的目标 PropertySource 就是 load 这里最后的一个 consumer 函数加载出来的,这里列一下主要做的事情:
1、加载系统中设置的所有的 Profile 。
2、遍历所有的 Profile ,假如是默认的 Profile , 就将这个 Profile 加到 environment 中。
3、调用load 方法,加载配置,我们深入看一下这个方法:
他会先调用 getSearchLocations 方法,加载所有的需要加载的路径,最终有如下路径:
其核心方法是遍历所有的 propertySourceLoader ,也就是上面加载到两种 propertySourceLoader ,最红 loadForFileExtension 方法,加载配置文件,这里就不展开分析了,说一下主要的作用,因为每个 propertySourceLoader 都有自己可以加载的扩展名,默认扩展名有如下四个 properties, xml, yml, yaml,所以最终拿到文件名字,然后通过 - 拼接所有的真实的名字,然后加上路径一起加载。
接下来,我们分析 BackgroundPreinitializer ,这个方法在接收 ApplicationPrepareEnvironment 事件的时候真正调用了这份方法:
1、 ConversionServiceInitializer 主要负责将包括 日期,货币等一些默认的转换器注册到 formatterRegistry 中。
2、 ValidationInitializer 创建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用于生成 xml 转换器的。
接着回到我们将的主体方法, prepareEnvironment 在调用完成 listeners.environmentPrepared(environment) 方法后,调用 bindToSpringApplication(environment) 方法,将 environment 绑定到 SpirngApplication 中。
接着将 enviroment 转化为 StandardEnvironment 对象。
最后将 configurationProperties 加入到 enviroment 中, configurationProperties 其实是将 environment 中其他的 PropertySource 重新包装了一遍,并放到 environment 中,这里主要的作用是方便 进行解析。
它主要是检查是否存在 spring.beaninfo.ignore 配置,这个配置的主要作用是设置 javaBean 的内省模式,所谓内省就是应用程序在 Runtime 的时候能检查对象类型的能力,通常也可以称作运行时类型检查,区别于反射主要用于修改类属性,内省主要用户获取类属性。那么我们什么时候会使用到内省呢,java主要是通过内省工具 Introspector 来完成内省的工作,内省的结果通过一个 Beaninfo 对象返回,主要包括类的一些相关信息,而在 Spring中,主要是 BeanUtils#Properties 会使用到,Spring 对内省机制还进行了改进,有三种内省模式,如下图中红色框框的内容,默认情况下是使用 USE_ALL_BEANINFO。假如设置为true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是检查 Application的类型,然后获取对应的 ApplicationContext 类,我们这里是获取到了 org.springframework.boot.web.servlet.context. 接着调用 BeanUtils.instantiateClass(contextClass); 方法进行对象的初始化。
最终其实是调用了 的默认构造方法。我们看一下这个方法做了什么事情。这里只是简单的设置了一个 reader 和一个 scanner,作用于 bean 的扫描工作。
我们再来看一下这个类的继承关系
这里获取 ExceptionReporter 的方式主要还是和之前 Listener 的方式一致,通过 getSpringFactoriesInstances 来获取所有的 SpringBootExceptionReporter 。
其主要方法执行如下:
Ⅸ springmvc谁讲的好
周瑜老师讲的最好的Spring MVC源码教程|从0开始搭建spring项目剖析底层源码!
奈斯~这是我见过讲的最全的SpringMVC
Java架构技余瞎睁术
帮助您更好的了解Java
一、SpringMVC简介
1.1、SpringMVC引言
为了使Spring有可插入的MVC架构,SpringFrameWork在Spring基础上开发SpringMVC框架,从而在使用Spring进行WEB开发时可竖岁以选择使用Spring的SpringMVC框架作为web开发的控制器框架。
1.2、SpringMVC的优势
SpringMVC是一个典型的轻量级MVC框架,在整个MVC架构中充当控制器框架,相对于之前的struts2框架,SpringMVC运行更快,其注解式神裤开发更高效灵活。
Ⅹ Spring系列(一)Spring MVC bean 解析、注册、实例化流程源码剖析
最近在使用Spring MVC过程中遇到了一些问题,网上搜索不少帖子后虽然找到了答案和解决方法,但这些答案大部分都只是给了结论,并没有说明具体原因,感觉总是有点不太满意。
更重要的是这些所谓的结论大多是抄来抄去,基本源自一家,真实性也有待考证。
那作为程序员怎么能知其所以然呢?
此处请大家内心默读三遍。
用过Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring机制就得先从这两点入手,本文主要通过对IOC部分的机制进行介绍。
在开始阅读之前,先准备好以下实验材料。
IDEA 是一个优秀的开发工具,如果还在用Eclipse的建议切换到此工具进行。
IDEA有很多的快捷键,在分析过程中建议大家多用Ctrl+Alt+B快捷键,可以快速定位到实现函数。
Spring bean的加载主要分为以下6步:
查看源码第一步是找到程序入口,再以入口为突破口,一步步进行源码跟踪。
Java Web应用中的入口就是web.xml。
在web.xml找到ContextLoaderListener ,此Listener负责初始化Spring IOC。
contextConfigLocation参数设置了bean定义文件地址。
下面是ContextLoaderListener的官方定义:
翻译过来ContextLoaderListener作用就是负责启动和关闭Spring root WebApplicationContext。
具体WebApplicationContext是什么?开始看源码。
从源码看出此Listener主要有两个函数,一个负责初始化WebApplicationContext,一个负责销毁。
继续看initWebApplicationContext函数。
在上面的代码中主要有两个功能:
进入CreateWebAPPlicationContext函数
进入determineContextClass函数。
进入函数。
WebApplication Context有很多实现类。 但从上面determineContextClass得知此处wac实际上是XmlWebApplicationContext类,因此进入XmlWebApplication类查看其继承的refresh()方法。
沿方法调用栈一层层看下去。
获取beanFactory。
beanFactory初始化。
加载bean。
读取XML配置文件。
XmlBeanDefinitionReader读取XML文件中的bean定义。
继续查看loadBeanDefinitons函数调用栈,进入到XmlBeanDefinitioReader类的loadBeanDefinitions方法。
最终将XML文件解析成Document文档对象。
上一步完成了XML文件的解析工作,接下来将XML中定义的bean注册到webApplicationContext,继续跟踪函数。
用BeanDefinitionDocumentReader对象来注册bean。
解析XML文档。
循环解析XML文档中的每个元素。
下面是默认命名空间的解析逻辑。
不明白Spring的命名空间的可以网上查一下,其实类似于package,用来区分变量来源,防止变量重名。
这里我们就不一一跟踪,以解析bean元素为例继续展开。
解析bean元素,最后把每个bean解析为一个包含bean所有信息的BeanDefinitionHolder对象。
接下来将解析到的bean注册到webApplicationContext中。接下继续跟踪registerBeanDefinition函数。
跟踪registerBeanDefinition函数,此函数将bean信息保存到到webApplicationContext的beanDefinitionMap变量中,该变量为map类型,保存Spring 容器中所有的bean定义。
Spring 实例化bean的时机有两个。
一个是容器启动时候,另一个是真正调用的时候。
相信用过Spring的同学们都知道以上概念,但是为什么呢?
继续从源码角度进行分析,回到之前XmlWebApplication的refresh()方法。
可以看到获得beanFactory后调用了 ()方法,继续跟踪此方法。
预先实例化单例类逻辑。
获取bean。
doGetBean中处理的逻辑很多,为了减少干扰,下面只显示了创建bean的函数调用栈。
创建bean。
判断哪种动态代理方式实例化bean。
不管哪种方式最终都是通过反射的形式完成了bean的实例化。
我们继续回到doGetBean函数,分析获取bean的逻辑。
上面方法中首先调用getSingleton(beanName)方法来获取单例bean,如果获取到则直接返回该bean。方法调用栈如下:
getSingleton方法先从singletonObjects属性中获取bean 对象,如果不为空则返回该对象,否则返回null。
那 singletonObjects保存的是什么?什么时候保存的呢?
回到doGetBean()函数继续分析。如果singletonObjects没有该bean的对象,进入到创建bean的逻辑。处理逻辑如下:
下面是判断容器中有没有注册bean的逻辑,此处beanDefinitionMap相信大家都不陌生,在注册bean的流程里已经说过所有的bean信息都会保存到该变量中。
如果该容器中已经注册过bean,继续往下走。先获取该bean的依赖bean,如果镩子依赖bean,则先递归获取相应的依赖bean。
依赖bean创建完成后,接下来就是创建自身bean实例了。
获取bean实例的处理逻辑有三种,即Singleton、Prototype、其它(request、session、global session),下面一一说明。
如果bean是单例模式,执行此逻辑。
获取单例bean,如果已经有该bean的对象直接返回。如果没有则创建单例bean对象,并添加到容器的singletonObjects Map中,以后直接从singletonObjects直接获取bean。
把新生成的单例bean加入到类型为MAP 的singletonObjects属性中,这也就是前面singletonObjects()方法中获取单例bean时从此Map中获取的原因。
Prototype是每次获取该bean时候都新建一个bean,因此逻辑比较简单,直接创建一个bean后返回。
从相应scope获取对象实例。
判断scope,获取实例函数逻辑。
在相应scope中设置实例函数逻辑。
以上就是Spring bean从无到有的整个逻辑。
从源码角度分析 bean的实例化流程到此基本接近尾声了。
回到开头的问题,ContextLoaderListener中初始化的WebApplicationContext到底是什么呢?
通过源码的分析我们知道WebApplicationContext负责了bean的创建、保存、获取。其实也就是我们平时所说的IOC容器,只不过名字表述不同而已。
本文主要是讲解了XML配置文件中bean的解析、注册、实例化。对于其它命名空间的解析还没有讲到,后续的文章中会一一介绍。
希望通过本文让大家在以后使用Spring的过程中有“一切尽在掌控之中”的感觉,而不仅仅是稀里糊涂的使用。