Java进阶篇——springboot2源码探究
1.@EnableAutoConfiguration
除了元注解之外,EnableAutoConfiguration包含了两大重要部分:
1)@AutoConfigurationPackage注解
该注解只导入了一个内部类:AutoConfigurationPackages.Registrar.class
类中有两个方法
从名字上看,registerBeanDefinitions方法注册了定义好的一些Bean,determineImports方法决定这些要不要导入
registerBeanDefinitions调用了register方法,并传入了registry参数和一个元数据名字数组。registry参数是一个接口,
实际场景中必然是使用的实现类,可以在该方法打断点debug
发现实际上他的实现类是一个名为DefaultListableBeanFactory,并且可以清晰的看到该类的一些基本属性的值
可以看到,registry中存储的是一些关于项目程序的基本配置和bean实例名。比如一些网页支持组件和springContext以及一些加载器
还有用户自定义的类,这些类组件中存储了bean名字、作用域、懒加载等等
再来看另一个参数:new PackageImports(metadata).getPackageNames().toArray(new String[0])
看起来是将元数据中的包名提取成数组,我们打开看看该类在初始化的时候具体干了什么,可以看到实际上在初始化时调用了一个ClassUtils.getPackageName,
传入了一个什么呢,metadata.getclassname,他是什么呢,打断点!
,实际上就是启动类的全名,com.***.application,而这个方法则是将classname的前缀提取出来,即提取出我们的包名com.&&&
即这个AutoConfigurationPackages.register方法传入了我们的一大堆初始bean的名字、配置和总包名com.**,我们研究研究他做了什么
首先是判断定义的bean中有没有Bean,这个bean是类的属性,存着当前类的全名:org.springframework.boot.autoconfigure.AutoConfigurationPackages
大概想处理的是用户自定义了AutoConfigurationPackages类的情况。当前是没有定义的,所以直接走else逻辑
new了一个GenericBeanDefinition,暂且叫他通用的bean定义工具,set一大堆东西。之后调用了注册方法。把工具丢了进去。
这个注册方法里大概是将AutoConfigurationPackages类同样注册进了定义bean的map中,然后将map中的所有值加入到了这个默认的bean工厂中,之后这个类就走完了。
因此该注解的作用即是将自定义的bean以及一些基本类型、原始组件注册到bean工厂中
2)AutoConfigurationImportSelector类
该类中点进去映入眼帘的就是selectImports方法。听起来名字是选择导入,也就是该方法决定了要加载什么依赖组件
而该方法调用了并且只调用了getAutoConfigurationEntry方法来获取要加载的组件。
我们来看看这个方法,先是调用getAttributes获取了某个东西,打断点发现是
这样就很熟悉了,是EnableAutoConfiguration注解的两个属性值,虽然默认值为null。点进方法体发现确实是这样。
getCandidateConfigurations(annotationMetadata, attributes)方法调用了一大串,最终调用了这个方法loadSpringFactories,
该方法的大致内容是,从当前所有的依赖包中加载META-INF/目录下的spring.factories文件中寻找一些组件。
可以看到他先尝试从缓存中拿,如果为空,则去依赖包中的META-INF/目录下的spring.factories中加载。
拿到这个组件列表之后,还要进行一层过滤,抽取含有factoryTypeName的组件列表。
这个name即是org.springframework.boot.autoconfigure.EnableAutoConfiguration自动配置注解
再回到getEntry方法,之后对获取到的组件列表进行去重,然后试图从列表中拿出排除项,
也就是attributes中获取到的EnableAutoConfiguration注解的两个属性值的内容。很容易理解,属性值中配置了要排除的内容将在这里进行排除。
而后调用filter方法对列表进行筛选,而筛选使用的是autoConfigurationMetadata这个类,
由此可见,这个类是某种筛选规则,它里面存储了501个properties,所以筛选规则可能就是逐一比对,筛选出Metadata中有的组件。
筛选出来的结果即是最终要加载的组件bean。
也就是说,@EnableAutoConfiguration的两个重要成员,一个决定了要加载默认的哪些组件(用户自定义bean、数值包装类、字符串类等等)
和配置,另一个决定了要加载哪些外部依赖类,即通过starter等通过pom引入的组件。
2.请求处理
如何知道spring是怎么、在哪处理请求的呢,有个很简单的方法,在properties中将日志级别设置为debug,即dubug=true。而后运行程序,发送任意一个请求
就会发现spring打印出了关于处理该请求的一些细节,比如处理请求是从初始化DispatcherServlet开始的,
由此可见请求处理最重要的便是DispatcherServlet。而后AbstractHandlerMapping识别到了处理该请求的具体方法。
我们进入到DispatcherServlet中,查看他的方法。
学过mvc原生web开发的都知道,servlet有两大重要方法,doGet和doPost。而DispatcherServlet继承了FrameworkServlet继承了HttpServletBean,
HttpServletBean继承了HttpServlet,由此可知,DispatcherServlet也是一个httpServlet。那么我们就有思路了,从他的do**方法开始探究。
1)doDispatch
从名字看,这个方法似乎是为了做转发。并且他的参数几乎和doGet方法是一模一样的
最开始是初始化了一大堆东西,包括处理异步请求的异步管理器以及检查是否是文件上传的请求巴拉巴拉,而后这个getHandler方法,直接获取到了处理这个请求的具体方法。
它是如何处理的呢,他遍历了一个handlerMappings的集合,这个mapping里面存储的是spring一些专处理映射的类
,比如欢迎页的映射,以及我们使用requestMapping标注的url。这样就打通了,他会在requestMappingHandlerMapping中查找到/hello请求并且找到他映射的方法。
具体查找调用了HandlerMapping的gethandler,由此就和日志中的对上了,gethandler方法中就是根据url在映射中找方法,先抛开这个细节不看。
拿到这个处理器(方法)后,将其丢入到了HandlerAdapter请求适配器中,这个在mvc架构中熟悉的身影。
之后并不是立即执行该方法而是先判断方法的种类,如果是get或者head方法,则执行逻辑。
这个逻辑会衡返回一个-1,也就是写死的,我不理解为什么是这样,在网上搜了Last-Modified,发现这是一种缓存机制,
这也就理解了为什么必须是get方法,而他实现需要实现LastModified接口,我并没有实现这个,所以spring这个判断逻辑会衡false。
之后是一个applyPreHandle的方法,点进去就会发现是使用当前spring的拦截器组件对请求进行拦截,
能发现就是一些请求方法拦截器、token拦截器、资源拦截器等等,也就是一些坏的请求会在这条被拦截
之后就是请求适配器执行自己的处理逻辑了,可以看到他返回的是一个modelAndView类型的实例,那么就意味着,此时方法已经被执行了。
但是实际上并没有使用modelAndView作为返回值,而是直接返回的string。所以mv是一个空值,但是可以在响应体中观察到,
已经有19字节的东西被写进了响应体中,页印证了方法已经被执行。
紧接着是判断请求是否是异步请求,如果是异步请求可能会做First响应之类的处理。
再往下是一个名叫应用默认的视图名的方法,将请求体中或者默认的视图名加入model中。
因为如果应用了modelandview,此时才只是一个model,必然要添加对应的view。可以简单测试一下。我们在处理方法中new一个modelandview对象,设置一个model值并返回。
可以看到,经过此方法之后,我们并没有设置view值,系统默认将hello当作了view加入model,这个默认值即是请求路径去掉前面的/。
这个方法之后,又是一个类似于拦截器,有点类似于在方法执行前后各执行一次拦截。拦截器执行完毕后,方法结束。
后面就是一些兜底处理,比如如果是文件相关之类的请求,要关闭对应的流。如果是异步请求执行怎样的逻辑。
2)handle方法
我们知道,适配器的handle方法里面执行了方法逻辑,具体是怎么执行的呢。实际上是调用了super的AbstractHandlerMethodAdapter的handle方法。
这个handle方法又会调用自己(RequestMappingHandlerAdapter)的handleInternal方法。该方法内对session做了一些处理,
而后调用invokeHandlerMethod方法,这个方法内做了很多的处理。比如WebDataBinderFactory binderFactory对象,他是对参数之中的数据格式做转换的,他里面初始化了128种对象转换的方式;
又或者初始化一些参数解析器和返回值处理器:里面对各种参数和返回值做对应的解析和处理,总之就是把这些丢入到一个mavContainer容器中。
然后调用invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0])方法,同时传入的还有webRequest,
这个就是将请求体和响应体包装到了一起。这个方法里面第一行就直接执行了方法,可以看到此时已经有返回值了。
这个方法就比较简单了:获取参数、执行方法
①getMethodArgumentValues
该方法最开始是获取映射方法的参数类型以及参数名
之后做的事大概可以猜到,就是从请求中找出这些参数名对应的参数并且转化成对应的类型。可以看看具体的
首先定义了一个接收数组,用来存储获取到的参数,而后尝试从providedArgs中拿对应的参数,但实际上这个参数传的是空值。
所以执行后面的逻辑,从请求中拿,并且同时传入了mav容器,这个容器中就有格式转换器和返回值处理器等。
该方法里面有两个重要的构成,getArgumentResolver获取参数解析器和resolveArgument解析参数。获取参数解析器方法中,
循环目前已经存进ioc容器的解析器组件,和参数进行一一比对,找到可以解析对应参数的解析器。
解析参数方法中,就是获取参数名容器→获取方法参数容器→获取参数名→解析参数。之后会做很多的后续处理,比如格式转换之类的。
②doInvoke
这个方法比较简单,利用之前已经存入InvocableHandlerMethod中的反射方法public java.lang.String
com.glodon.controller.HelloController.home(java.lang.String),实际上,该对象实例也是专门存储方法处理的相关组件的。
获取到方法后,利用spring反射机制执行该方法。
3)请求响应
前面将方法执行完之后,封装完modelAndView,在执行完处理后拦截器后,还要执行一个兜底方法对结果进行处理,从名字来看,他是用来处理结果转发的。
开始是判断方法执行是否有异常,如果有则走异常处理逻辑。而后如果modelandview不为空,则执行一个render方法render(mv, request, response)
render方法首先从请求中拿到语言标识,并加入到响应体中,而后拿出modelandview中的视图名,即重定向或者其他视图。
然后调用resolveViewName方法对视图名进行处理,对当前容器中的所有解析器进行遍历,哪个可以解析这个视图名,就直接返回解析结果。
解析器的解析过程,以ContentNegotiatingViewResolver举例。
这个解析器获取了请求的Attributes,然后传入getMediaTypes方法中并调用来获取返回数据类型。
getMediaTypes里面其实就是一个双重循环匹配,格式化网页请求→获取浏览器请求头中的可接受媒体类型→获取系统可生产的媒体类型→初始化匹配的媒体类型。
然后进行一个双重循环,如果匹配成功,则把对应的媒体类型加入到compatibleMediaTypes中。
排序完成后就是一个set→List,然后进行一个排序,排序的依据就是媒体类型的权重。
浏览器在发送请求的时候会给服务器一个accept,里面明确表示了浏览器可以接收的返回类型以及他的权重,而这里的排序就是按照这个权重进行排序的。
该方法返回后,在接着看解析方法,之后调用了getCandidateViews获取候选视图。
getCandidateViews方法内同样是个双层循环。外层是除了当前解析器外的其他三个视图解析器,内层是对匹配到的媒体类型。
通过调试,发现最终是被InternalResourceViewResolver成功处理,我们只看他的细节。
同样还是resolveViewname方法,该方法先尝试去缓存中拿,拿不到了才执行createView方法创建视图。而这个方法就很清晰明了了
判断是重定向还是转发来生成对应的视图。最终返回合适的视图,添加缓存巴拉巴拉。
我们测试的刚好是一个重定向视图,所以返回的结果就是bean为redirect,url为/helloWorld的视图。
Java进阶篇——springboot2源码探究的更多相关文章
- 基于Python接口自动化测试框架+数据与代码分离(进阶篇)附源码
引言 在上一篇<基于Python接口自动化测试框架(初级篇)附源码>讲过了接口自动化测试框架的搭建,最核心的模块功能就是测试数据库初始化,再来看看之前的框架结构: 可以看出testcase ...
- spring-boot-2.0.3之quartz集成,数据源问题,源码探究
前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...
- Java中常用的七个阻塞队列第二篇DelayQueue源码介绍
Java中常用的七个阻塞队列第二篇DelayQueue源码介绍 通过前面两篇文章,我们对队列有了了解及已经认识了常用阻塞队列中的三个了.本篇我们继续介绍剩下的几个队列. 本文主要内容:通过源码学习De ...
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- ConcurrentHashMap源码探究 (JDK 1.8)
很早就知道在多线程环境中,HashMap不安全,应该使用ConcurrentHashMap等并发安全的容器代替,对于ConcurrentHashMap也有一定的了解,但是由于没有深入到源码层面,很多理 ...
- Java IO 之 OutputStream源码
Writer :BYSocket(泥沙砖瓦浆木匠) 微 博:BYSocket 豆 瓣:BYSocket FaceBook:BYSocket Twitter ...
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- java多线程----线程池源码分析
http://www.cnblogs.com/skywang12345/p/3509954.html 线程池示例 在分析线程池之前,先看一个简单的线程池示例. 1 import java.util.c ...
随机推荐
- 1.2.2 musl pwn
1.2.2 musl pwn 几个结构 __malloc_context(与glibc中的main_arena类似) struct malloc_context { uint64_t secret; ...
- 经典排序算法之-----选择排序(Java实现)
其他的经典排序算法链接地址:https://blog.csdn.net/weixin_43304253/article/details/121209905 选择排序思想: 思路: 1.从整个数据中挑选 ...
- 什么是subsignature和return-type-substitutable
subsignature 什么是签名(signature) 方法签名组成:方法名+参数列表(参数的类型.个数.顺序) Java语言层面规定的签名是不包含返回值类型的: JVM层面规定的签名是包含返回值 ...
- 研发效能|DevOps 已死平台工程永存带来的焦虑
最近某位大神在推特上发了一个帖子,结果引来了国内众多卖课机构.培训机构的狂欢,开始贩卖焦虑,其实「平台工程」也不是什么特别高深莫测的东西.闲得无聊,把这位大神的几个帖子薅了下来,你看过之后就会觉得没啥 ...
- pod(五):pod hook(pod钩子)和优雅的关闭nginx pod
目录 一.系统环境 二.前言 三.pod hook(pod钩子) 四.如何优雅的关闭nginx pod 一.系统环境 服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架 ...
- Vue3的新特性
总概 1) 性能提升 打包大小减少 41% 初次渲染快 55%,更新渲染快 133% 内存减少 54% 使用 Proxy 代替 defineProperty 实现数据响应式 重写虚拟 DOM 的实现和 ...
- 云实例初始化工具cloud-init简介
项目简介 cloud-init是一款用于初始化云服务器的工具,它拥有丰富的模块,能够为云服务器提供的能力有:初始化密码.扩容根分区.设置主机名.注入公钥.执行自定义脚本等等,功能十分强大. 目前为止c ...
- Emgu实现图像分割
C#通过Emgu这个图像处理库,可以很方便的将一幅单通道图像分割为R.G.B三个单通道图像. Image<Bgr, Byte> ImageBGR = null; Image<Bgr, ...
- css文字垂直展示的方法
一.使用writing-mode(推荐使用) writing-mode:翻译过来是"写字 - 模式",文本在水平或垂直方向上如何排布 有以下几个属性值: horizontal-tb ...
- 【云原生 · Kubernetes】Kubernetes容器云平台部署与运维
[题目1]Deployment管理 在master节点/root目录下编写yaml文件nginx-deployment.yaml,具体要求如下: (1)Deployment名称:nginx-deplo ...