Spring高级
Spring高级
Ⅰ 容器与bean
一 容器接口
1.1 BeanFactory
- 简介
- 是ApplicationContext的父接口
- 它才是Spring的核心容器,主要的ApplicationContext的实现都组合了他的功能
- 功能
- 表面只有getBean
- 实际上控制反转、基本的依赖注入,直至Bean的声明周期的各种功能,都由它的实现类提供
1.2 ApplicationContext
比BeanFactory多的一些容器接口
国际化
context.getMessage("hi", null, Locale.ENGLISH)
根据通配符获取资源
context.getResource(...)
获取配置信息
context.getEnvironment().getProperty()
发布事件(实现组件解耦)
context.getBean(..).register(...)
二 容器实现
2.1 BeanFactory实现特点
DefaultListableBeanFactory
是 BeanFactory 最重要的实现,像控制反转和依赖注入功能,都是它来实现
- bean的定义
- 注册bean
不能解析bean内部的bean
解决:给bean工厂添加后处理器并将其加入与bean工厂的联系(
addBeanPostProcessor
)针对bean的生命周期的各个阶段提供 扩展,如
@Autowired
@Resource
…
特点
- 不会主动对beanFactory后处理器
- 不会主动添加Bean后处理器
- 不会主动实例化单例
- 不会解析beanFactory 还不会解析
${}
#{}
后处理器的排序
- 先添加进去的后处理器的优先级高,如果一个bean同时加了
@Autowired
和@Resource
,则看这两个后处理器谁先被加入bean容器谁就优先执行 - 也可以通过比较器来改变后处理器的优先级
- 先添加进去的后处理器的优先级高,如果一个bean同时加了
2.2 ApplicationContext常见实现
ClassPathXmlApplicationContext
基于类路径下读取xml配置文件加载bean
FileSystemXmlApplicationContext
- 基于文件路径读取xml
读取xml的原理
XmlBeanDefinitionReader
读取xml中的xml然后加入到DefaultListableBeanFactory
中
AnnotationApplicationContext
基于注解,通过配置类加载bean
- 其中Config类需要
@Configuration
修饰
- 其中Config类需要
这里加了
@Autowired
和@Resource
等后处理器
AnnotationConfigServletWebServerApplicationContext
用于web环境,基于java配置类加载bean
配置类中需要定义
ServletWebServerFactory
:web服务器容器(tomcat)DispatcherServlet
:前控制器,所有请求先经过它DispatcherServletRegistrationBean
:将前控制器注册到服务器中- controller
三 bean生命周期
3.1 生命周期
创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
初始化:回调各种 Aware 接口,调用对象的各种初始化方法
可用
销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
- prototype 对象也能够销毁,不过需要容器这边主动调用
3.2 生命周期增强方法
创建前后的增强
postProcessBeforeInstantiation
- 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
postProcessAfterInstantiation
- 这里如果返回 false 会跳过依赖注入阶段
依赖注入前的增强
postProcessProperties
- 如
@Autowired
、@Value
、@Resource
- 如
初始化前后的增强
postProcessBeforeInitialization
- 这里返回的对象会替换掉原本的 bean
- 如
@PostConstruct
、@ConfigurationProperties
postProcessAfterInitialization
- 这里返回的对象会替换掉原本的 bean
- 如代理增强
销毁之前的增强
postProcessBeforeDestruction
- 如
@PreDestroy
- 如
3.3 模板方法设计模式
- 后处理器原理
- 通过实现
BeanPostProcessor
接口,可以实现在不改变getBean
方法的前提下扩展功能
四 Bean后处理器
作用:为Bean生命周期各个阶段提供扩展
GenericApplicationContext
:一个比较干净的容器(没有定义后处理器)registerBean()
:注册bean,后处理器refresh()
:初始化容器close()
:销毁容器
常见bean后处理器
AutowiredAnnotationBeanPostProcessor
- 解析
@Autowired
@Value
- 解析
CommonAnnotationBeanPostProcessor
- 解析
@Resource
@PostConstruct
@PreDestroy
- 解析
ConfigurationPropertiesBindingPostProcessor.register()
- 解析
@configurationProperties
- 解析
五 BeanFactory后处理器
5.1 介绍
- 作用:为BeanFactory提供扩展
常见BeanFactory后处理器
ConfigurationClassPostProcessor
- 解析
@ComponentScan
@Bean
@Import
@ImportResource
…
- 解析
MapperScannerConfigurer
- 扫描mapper接口
- 基本方式是创建MapperFactoryBean,然后通过Mapper接口类创建对应对象
- 在此基础上扫描包,读取Mapper接口,然后再创建
使用注册
5.2 常见BeanFactory后处理器1
ConfigurationClassPostProcessor
原理以
@ComponentScan
为例判断当前类是否有
@ComponentScan
如果有则获取
@ComponentScan
扫描的包下的所有class文件是否加了@Component
及其衍生注解如果加了
@Component
及其衍生注解,则获取到这个bean,起名字并加入到bean工厂BeanDefinitionBuilder
(获取bean)
5.3 常见BeanFactory后处理器2
MapperScannerConfigurer
Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
原理
扫描Mapper包
判断是不是Interface
根据接口创建MapperFactoryBean注入到bean工厂中
上面的bd2的作用是生成name,最终不会添加到容器
不然的话添加到容器的就是
MapperFactoryBean
了
六 Aware 接口
6.1 Aware 接口
用于注入一些与容器相关的信息
BeanNameAware
注入bean的名字BeanFactoryAware
注入BeanFactory
容器ApplicationContextAware
注入ApplicationContext
容器EmbeddedValueResolverAware
注入${}
解析器
6.2 InitializingBean 接口
InitializingBean 接口提供了一种【内置】的初始化手段
对比
内置的注入(ApplicationContextAware)和初始化(InitializingBean )不受扩展功能的影响,总会被执行
而扩展功能受某些情况影响可能会失效
因此 Spring 框架内部的类常用内置注入和初始化
1 | sequenceDiagram |
七 初始化与销毁
7.1 初始化
初始化手段
@PostConstruct
@Bean(initMethod)
- 执行Bean1内定义的init3方法
实现
InitializingBean
接口
三种初始化手段的执行顺序
@PostConstruct
标注的初始化方法InitializingBean
接口的初始化方法@Bean(initMethod)
指定的初始化方法
7.2 销毁
- 顺序为
@PreDestroy
标注的销毁方法DisposableBean
接口的销毁方法@Bean(destroyMethod)
指定的销毁方法
八 Scope
8.1 Scope类型
singleton
,容器启动时创建(未设置延迟),容器关闭时销毁prototype
,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁request
,每次请求用到此 bean 时创建,请求结束时销毁session
,每个会话用到此 bean 时创建,会话结束时销毁application
,web 容器用到此 bean 时创建,容器停止时销毁
8.2 在singleton中使用其他几种scope的Bean
单例对象,依赖注入仅发生一次,后续用的始终是第一次依赖注入的对象
- e是单例,其中有一个多例的bean f
解决
@Lazy
生成代理代理对象还是同一个,但是每次使用代理对象的任意方法时,由代理创建新的f对象
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
ObjectFactory
ApplicationContext.getBean
解决的理念都是:推迟其他scope bean的获取
Ⅱ AOP
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
- 作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
aspectj
在编译和加载时,修改目标字节码,性能较高,并没有用springaspectj
因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强- 但
aspectj
侵入性较强,且需要学习新的aspectj
特有语法,因此没有广泛流行
一 ajc增强
- 编译器也能修改 class 实现增强
- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
二 agent增强
- 类加载时可以通过 agent 修改 class 实现增强
三 proxy增强
3.1 jdk 动态代理
- jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
3.2 cglib代理
1 | public class CglibProxyDemo { |
- cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
- 父类以及父类的方法不能是final修饰的(本质是方法的重写)
四 jdk代理原理
1 | public class A12 { |
模拟代理实现
1 | import java.lang.reflect.InvocationHandler; |
- 优化:反射创建性能低
- 前 16 次反射性能较低
- 第 17 次调用会生成代理类,优化为非反射调用
- 会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类
五 cglib代理原理
- 和JDK动态代理原理差不多
- 回调的接口换了一下,
InvocationHandler
改成了MethodInterceptor
- 调用目标时有所改进,见下面代码片段
- method.invoke 是反射调用,必须调用到足够次数才会进行优化
- methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
- methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
- 回调的接口换了一下,
1 | public class A14Application { |
六 cglib如何避免发射调用
6.1 MethodProxy实现
- 当调用
MethodProxy
的 invoke 或 invokeSuper 方法时, 会动态生成两个类ProxyFastClass
配合代理对象一起使用, 避免反射TargetFastClass
配合目标对象一起使用, 避免反射 (Spring 用的这种)
TargetFastClass
记录了 Target 中方法与编号的对应关系- save(long) 编号 2
- save(int) 编号 1
- save() 编号 0
- 首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号
- 然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射
ProxyFastClass
记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法- saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)
- saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)
- saveSuper() 编号 0,不增强, 仅是调用 super.save()
- 查找方式与 TargetFastClass 类似
- 为什么有这么麻烦的一套东西呢?
- 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
- 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
七 jdk和cglib的统一
7.1 Spring的代理选择规则
Spring 中对切点、通知、切面的抽象如下
- 切点:接口 Pointcut,典型实现
AspectJExpressionPointcut
- 通知:典型接口为
MethodInterceptor
代表环绕通知 - 切面:Advisor,包含一个 Advice 通知,
PointcutAdvisor
包含一个 Advice 通知和一个 Pointcut
- 切点:接口 Pointcut,典型实现
1 | classDiagram |
代理相关类图
- AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
- AopProxy 通过 getProxy 创建代理对象
- 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
- 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
1 | classDiagram |
ProxyFactory 用来创建代理
如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
- 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy
7.2 底层切点实现
切点的匹配
常见 aspectj 切点用法
aspectj 切点的局限性,实际的
@Transactional
切点实现不能处理加在类上的注解或者加在接口上的注解(加在类上表示类中所有方法都匹配,接口类似)
实际的实现(多加了一些判断)
7.3 底层切面实现
高级的
@Aspect
切面低级的Advisor切面
一些方法
AnnotationAwareAspectJAutoProxyCreator
的作用- 将高级 @Aspect 切面统一为低级 Advisor 切面
- 在合适的时机创建代理
findEligibleAdvisors
找到有【资格】的 Advisors- 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如本例 A17 中的 advisor3
- 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得
wrapIfNecessary
- 它内部调用
findEligibleAdvisors
, 只要返回集合不空, 则表示需要创建代理 - 它的调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
- 它内部调用
高级转换未低级切面的时机及代理生成时机
- 代理的创建时机
- 初始化之后 (无循环依赖时)
- 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存
- 依赖注入与初始化不应该被增强, 仍应被施加于原始对象
- 代理的创建时机
切面的控制顺序
@Order
高级切面解析为低级切面的过程
拿到类中所有的方法
解析方法是否有注解(
@Before
,@After
…)拿到注解,并获取到切点表达式,然后创建切点对象
创建通知类,参数是方法、切点以及切面实例对象
创建切面
Advisor
并放到一个集合中
7.4 底层通知实现
静态通知
通过
proxyFactory
的getInterceptorsAndDynamicInterceptionAdvice()
将其他通知统一转换为MethodInterceptor
环绕通知MethodBeforeAdviceAdapter
将@Before
AspectJMethodBeforeAdvice
适配为MethodBeforeAdviceInterceptor
AfterReturningAdviceAdapter
将@AfterReturning
AspectJAfterReturningAdvice
适配为AfterReturningAdviceInterceptor
- 这体现的是适配器设计模式
所谓静态通知,体现在上面方法的
Interceptors
部分,这些通知调用时无需再次检查切点,直接调用即可结合目标与环绕通知链,创建
MethodInvocation
对象,通过它完成整个调用(递归)
动态通知
- 动态通知:
- 需要参数的绑定(性能稍微低一些,因为需要处理参数)
- 需要切点。静态执行时不需要切点
- 过程
- 通过
proxyFactory
的getInterceptorsAndDynamicInterceptionAdvice()
将其他通知统一转换为MethodInterceptor
环绕通知 - 所谓动态通知,体现在上面方法的
DynamicInterceptionAdvice
部分,这些通知调用时因为要为通知方法绑定参数,还需再次利用切点表达式 - 动态通知调用复杂程度高,性能较低
- 通过
- 动态通知:
Ⅲ Web MVC
一 Controller解析请求
1.1 DispatcherServlet 初始化
创建WebConfig配置类
- 三个必须组件:web容器工厂、DispatcherServlet 、注册DispatcherServlet (Spring MVC入口)
创建
AnnotationConfigServletWebServerApplicationContext
,导入配置类DispatcherServlet
初始化时间- 默认在首次使用的时候才会初始化
- 也可以修改为tomcat初始化的时候就初始化
DispatcherServlet
初始化过程- 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如
HandlerMapping
、HandlerAdapter
等,并逐一调用它们的初始化
- 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如
1.2 RequestMappingHandlerMapping初始化
RequestMappingHandlerMapping
初始化时,会收集所有@RequestMapping
映射信息,封装为 Map,其中- key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
- value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
- 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
1.3 RequestMappingHandlerAdapter初始化
RequestMappingHandlerAdapter
初始化时,会准备HandlerMethod
调用时需要的各个组件,如:- HandlerMethodArgumentResolver 解析控制器方法参数
- HandlerMethodReturnValueHandler 处理控制器方法返回值
- 一般是将返回值转为json发送到响应体中
自定义参数与自定义返回值处理器
继承
HandlerMethodArgumentResolver
并重写相关方法e要在配置类中在
RequestMappingHandlerAdapter
中添加这个处理器
继承
HandlerMethodReturnValueHandler
并重写相关方法- 同样的也需要在配置类中的
RequestMappingHandlerAdapter
中添加这个处理器(略)
- 同样的也需要在配置类中的
二 参数解析器
常见参数解析器
@RequesParam
、@PathVariable
、@RquestHeader
、@CookieValue
、@Value
、@ModelAttribute
、@RequestBody
组合模式在Spring中的应用
组合解析器
获取参数名
- 如果编译时添加了
-parameters
可以生成参数表, 反射时就可以拿到参数名 - 如果编译时添加了
-g
可以生成调试信息, 但分为两种情况- 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
- 接口, 不会包含局部变量表, 无法获得参数名
- 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
- 如果编译时添加了
三 对象绑定和类型转换
3.1 底层第一套转换接口与实现
1 | classDiagram |
- Printer 把其它类型转为 String
- Parser 把 String 转为其它类型
- Formatter 综合 Printer 与 Parser 功能
- Converter 把类型 S 转为类型 T
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
- FormattingConversionService 利用其它们实现转换
3.2 底层第二套转换接口
1 | classDiagram |
- PropertyEditor 把 String 与其它类型相互转换
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
3.3 高层接口与实现
1 | classDiagram |
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能
3.4 示例
SimpleTypeConverter
BeanWrapperImpl
DirectFieldAccessor
ServletRequesrDataBinder
3.5 绑定器工厂
将创建DataBinder的过程放到工厂里面(没有功能),通过一些方式可以自定义转换功能
ServletRequestDataBinderFactory
扩展转换功能
@initBinder
ConversionService
- 同时用了以上两者
- 默认使用
ConversionService
3.6 如何获取泛型参数
- 开发框架中用
- java api获取泛型参数
- Spring api获取
四 @ControllerAdvice
和AOP的代理没关系,只是借鉴了Advice这个名字
@ExceptionHandler
:处理异常的@ModelAttribute
:补充模型数据@InitBinder
:类型转换器
4.1 @InitBinder
加在ControllerAdivce中的是全局的,加在Controller中的是局部的
RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
- RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
- 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
- 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂
4.2 @ModelAttribute
加在参数上
将模型数据加到modelandview中
没有指定名字(图中 的“u”)则以类名首字母小写存
加载类方法上
将方法返回结果加到modelandview中
没有指定名字则以方法返回值首字母小写存
4.3 控制器方法执行流程
1 | classDiagram |
HandlerMethod 需要
- bean 即是哪个 Controller
- method 即是 Controller 中的哪个方法
ServletInvocableHandlerMethod 需要
- WebDataBinderFactory 负责对象绑定、类型转换
- ParameterNameDiscoverer 负责参数名解析
- HandlerMethodArgumentResolverComposite 负责解析参数
- HandlerMethodReturnValueHandlerComposite 负责处理返回值
1 | sequenceDiagram |
1 | sequenceDiagram |
五 返回值处理器
- 返回值类型
ModelAndView
String
void
Object
- 常见返回值处理器
- 返回值类型为ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
- 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
- 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值类型为 ResponseEntity 时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
- 返回值类型为 HttpHeaders 时
- 会设置 ModelAndViewContainer.requestHandled 为 true
- 返回值添加了 @ResponseBody 注解时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
六 异常处理
ExceptionHandlerExceptionResolver
- 它能够重用参数解析器、返回值处理器,实现组件重用
- 它能够支持嵌套异常
@ExceptionHandler
只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内
6.1 tomcat异常处理
- 比ExceptionHandler更高级,处理的异常等级更高,比如Filter的异常
- 在 Spring Boot 中,是这么实现的:
- 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
- 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为
/error
也可以通过${server.error.path}
进行配置 - 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至
/error
这个地址- 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
- Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为
/error
,所以处理异常的职责就又回到了 Spring - 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
- 具体异常信息会由 DefaultErrorAttributes 封装好
- BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
- 如果要的不是 text/html,走 MessageConverter 流程
- 如果需要 text/html,走 mvc 流程,此时又分两种情况
- 配置了 ErrorViewResolver,根据状态码去找 View
- 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView
七 其他的Handler
@BeanNameUrlHandlerMapping
:请求路径不找Controller方法而是对应的bean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
// /c3的请求将访问这个bean作为控制器,bean需要实现Controller接口
public Controller controller3() {
return (request, response) -> {
response.getWriter().print("this is c3");
return null;
};
}- BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径
- 这些 bean 本身当作 handler,要求实现 Controller 接口
- SimpleControllerHandlerAdapter,调用 handler
- 模拟实现这组映射器和适配器
总结
HandlerMapping
负责建立请求与控制器之间的映射关系- RequestMappingHandlerMapping (与 @RequestMapping 匹配)
- WelcomePageHandlerMapping (/)
- BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
- RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
- SimpleUrlHandlerMapping (静态资源 通配符 / /img/)
- 之间也会有顺序问题, boot 中默认顺序如上
HandlerAdapter
负责实现对各种各样的 handler 的适配调用- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
- 参数解析器、返回值处理器体现了组合模式
- SimpleControllerHandlerAdapter 处理:Controller 接口
- HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
- HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)
- 这也是典型适配器模式体现
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
八 MVC处理流程
- 当浏览器发送一个请求
http://localhost:8080/hello
后,请求到达服务器,其处理流程是: 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器- jsp 不会匹配到 DispatcherServlet
- 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
HandlerMapping
,初始化时记录映射关系HandlerAdapter
,初始化时准备参数解析器、返回值处理器、消息转换器HandlerExceptionResolver
,初始化时准备参数解析器、返回值处理器、消息转换器- ViewResolver
- 路径:默认映射路径为
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice 全局增强点1️⃣:补充模型数据
- @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
- 根据 ModelAndViewContainer 获取 ModelAndView
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 调用拦截器的 postHandle 方法
- 处理异常或视图渲染
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
- 正常,走视图解析及渲染流程
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法
Ⅳ Spring boot
一 Boot启动过程
1.1 构造分析
springApplication
构造- 记录 BeanDefinition 源
- 推断应用类型
- 记录 ApplicationContext 初始化器
- 记录监听器
- 推断主启动类
1.2 run分析
执行run方法
得到
SpringApplicationRunListeners
,名字取得不好,实际是事件发布器- 发布 application starting 事件1️⃣
- 封装启动 args
- 准备 Environment 添加命令行参数(*)
- ApplicationEnvironment
ConfigurationPropertySources 处理(*)
- 发布 application environment 已准备事件2️⃣
- 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)
- application.properties,由 StandardConfigDataLocationResolver 解析
- spring.application.json
- 绑定 spring.main 到 SpringApplication 对象(*)
- 打印 banner(*)
- 创建容器
准备容器
- 发布 application context 已初始化事件3️⃣
加载 bean 定义
- 发布 application prepared 事件4️⃣
refresh 容器
- 发布 application started 事件5️⃣
执行 runner
发布 application ready 事件6️⃣
这其中有异常,发布 application failed 事件7️⃣
二 tomcat内嵌容器
tomcat基本结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Server
└───Service
├───Connector (协议, 端口)
└───Engine
└───Host(虚拟主机 localhost)
├───Context1(应用1, 可以设置虚拟路径, / 即 url 起始路径; 项目磁盘路径, 即 docBase)
│ │ index.html
│ └───WEB-INF
│ │ web.xml (servlet, filter, listener) 3.0
│ ├───classes (servlet, controller, service ...)
│ ├───jsp
│ └───lib (第三方 jar 包)
└───Context2 (应用2)
│ index.html
└───WEB-INF
web.xmltomcat内嵌容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public static void main(String[] args) throws LifecycleException, IOException {
// 1.创建 Tomcat 对象
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("tomcat");
// 2.创建项目文件夹, 即 docBase 文件夹
File docBase = Files.createTempDirectory("boot.").toFile();
docBase.deleteOnExit();
// 3.创建 Tomcat 项目, 在 Tomcat 中称为 Context
Context context = tomcat.addContext("", docBase.getAbsolutePath());
// 4.编程添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
HelloServlet helloServlet = new HelloServlet();//这个是自定义的继承自HttpServlet的类
ctx.addServlet("aaa", helloServlet).addMapping("/hello");
}
}, Collections.emptySet());
// 5.启动 Tomcat
tomcat.start();
// 6.创建连接器, 设置监听端口
Connector connector = new Connector(new Http11Nio2Protocol());
connector.setPort(8080);
tomcat.setConnector(connector);
}集成Spring容器
1
2
3
4
5
6
7
8
9
10
11
12
13WebApplicationContext springContext = getApplicationContext();
// 4.编程添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
// ⬇️通过 ServletRegistrationBean 添加 DispatcherServlet 等
for (ServletRegistrationBean registrationBean :
springContext.getBeansOfType(ServletRegistrationBean.class).values()) {
registrationBean.onStartup(ctx);
}
}
}, Collections.emptySet());
三 自动配置
- 自动配置类
EnableAutoConfiguration
中提供了常用的一些bean - 如果冲突了怎么办
- 解析时机 @Import先解析,本项目中的bean后解析
- 默认会同名覆盖,springboot中默认不能覆盖,会报错
3.0 原理
假设已有第三方的两个自动配置类
1 | // ⬅️第三方的配置类 |
提供一个配置文件 META-INF/spring.factories,key 为导入器类名,值为多个自动配置类名,用逗号分隔
1 | MyImportSelector=/ |
引入自动配置
1 | // ⬅️本项目的配置类 |
- 自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦
- @Enable 打头的注解本质是利用了 @Import
- @Import 配合 DeferredImportSelector 即可实现导入,selectImports 方法的返回值即为要导入的配置类名
- DeferredImportSelector 的导入会在最后执行,为的是让其它配置优先解析
3.1 AOP自动配置
- AOP 自动配置类为
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
- 可以通过
spring.aop.auto=false
禁用 aop 自动配置 - AOP 自动配置的本质是通过
@EnableAspectJAutoProxy
来开启了自动代理,如果在引导类上自己添加了@EnableAspectJAutoProxy
那么以自己添加的为准 @EnableAspectJAutoProxy
的本质是向容器中添加了AnnotationAwareAspectJAutoProxyCreator
这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的
3.2 DataSource自动配置
- 对应的自动配置类为:
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效
简单说明一下,Spring Boot 支持两大类数据源:
EmbeddedDatabase - 内嵌数据库连接池
PooledDataSource - 非内嵌数据库连接池
hikari 提供的 HikariDataSource
tomcat-jdbc 提供的 DataSource
dbcp2 提供的 BasicDataSource
oracle 提供的 PoolDataSourceImpl
如果知道数据源的实现类类型,即指定了 spring.datasource.type
,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)
3.3 MyBatis自动配置
- MyBatis 自动配置类为
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 它主要配置了两个 bean
- SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession
- SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定
- 用 ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口
- 用 AutoConfigurationPackages 来确定扫描的包
- 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带
mybatis.
前缀的配置项进行定制配置
@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别
- @MapperScan 扫描具体包(当然也可以配置关注哪个注解)
- @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口
- MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口
3.4 事务自动配置
事务自动配置类有两个:
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作
- 后者功能上对标 @EnableTransactionManagement,包含以下三个 bean
- BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点
- TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作
- AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能
- 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准
3.5 MVC自动配置
ServletWebServerFactoryAutoConfiguration
- 提供 ServletWebServerFactory
DispatcherServletAutoConfiguration
- 提供 DispatcherServlet
- 提供 DispatcherServletRegistrationBean
WebMvcAutoConfiguration
- 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有
- 多项 HandlerMapping
- 多项 HandlerAdapter
- HandlerExceptionResolver
- 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有
ErrorMvcAutoConfiguration
- 供的 bean 有 BasicErrorController
四 条件装配类
@Conditional()
确定要不要判断实现了
Condition
接口的类来做具体的判断1
2
3
4
5
6static class MyCondition1 implements Condition {
// ⬇️如果存在 Druid 依赖,条件成立
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
}
}具体使用
1
2
3
4
5
6
7
8// 第三方的配置类
// ⬅️加入条件
static class AutoConfiguration1 {
public Bean1 bean1() {
return new Bean1();
}
}本质:
- 一种特殊的if-else
五 FactoryBean
- 它的作用是用制造创建过程较为复杂的产品, 如 SqlSessionFactory, 但 @Bean 已具备等价功能
- 使用上较为古怪, 一不留神就会用错
- 被 FactoryBean 创建的产品
- 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责, 这些流程都不会走
- 唯有后初始化的流程会走, 也就是产品可以被代理增强
- 单例的产品不会存储于 BeanFactory 的 singletonObjects 成员中, 而是另一个 factoryBeanObjectCache 成员中
- 按名字去获取时, 拿到的是产品对象, 名字前面加 & 获取的是工厂对象
- 被 FactoryBean 创建的产品
六 @index
- 在编译时就根据
@Indexed
生成 META-INF/spring.components 文件- @Component注解中就间接使用了@Index
- 扫描时
- 如果发现 META-INF/spring.components 存在, 以它为准加载 bean definition
- 否则, 会遍历包下所有 class 资源 (包括 jar 内的)
- 解决的问题,在编译期就找到 @Component 组件,节省运行期间扫描 @Component 的时间
七 代理
spring 代理的设计特点
依赖注入和初始化影响的是原始对象
- 因此 cglib 不能用 MethodProxy.invokeSuper()
代理与目标是两个对象,二者成员变量并不共用数据
static 方法、final 方法、private 方法均无法增强
- 进一步理解代理增强基于方法重写
八 @Value
${}
#{}
- 类型转换:
TypeConverter
九 @Autowired
@Autowired
本质上是根据成员变量或方法参数的类型进行装配beanfactory.doResolveDependency()
要点- 结果包装为Optional
ObjectProvider - 如果待装配类型是 Optional,需要根据 Optional 泛型找到 bean,再封装为 Optional 对象装配
- 如果待装配的类型是 ObjectFactory,需要根据 ObjectFactory 泛型创建 ObjectFactory 对象装配
- 此方法可以延迟真实 bean 的获取
对@Lazy的处理
- 如果待装配的成员变量或方法参数上用 @Lazy 标注,会创建代理对象装配
- 此方法可以延迟真实 bean 的获取
- 被装配的代理不作为 bean
- 如果待装配的成员变量或方法参数上用 @Lazy 标注,会创建代理对象装配
其他
- 如果待装配类型是数组,需要获取数组元素类型,根据此类型找到多个 bean 进行装配
- 如果待装配类型是 Collection 或其子接口,需要获取 Collection 泛型,根据此类型找到多个 bean
- 如果待装配类型是 ApplicationContext 等特殊类型
- 会在 BeanFactory 的 resolvableDependencies 成员按类型查找装配
- resolvableDependencies 是 map 集合,key 是特殊类型,value 是其对应对象
- 不能直接根据 key 进行查找,而是用 isAssignableFrom 逐一尝试右边类型是否可以被赋值给左边的 key 类型
- 如果待装配类型有泛型参数
- 需要利用 ContextAnnotationAutowireCandidateResolver 按泛型参数类型筛选
- 如果待装配类型有 @Qualifier
- 需要利用 ContextAnnotationAutowireCandidateResolver 按注解提供的 bean 名称筛选
- 结果包装为Optional
十 事件监听器
10.1 ApplicationListener
- 实现 ApplicationListener 接口
- 根据接口泛型确定事件类型
10.2 @EventListener
- 根据监听器方法参数确定事件类型
- 解析时机:在 SmartInitializingSingleton(所有单例初始化完成后),解析每个单例 bean
10.3 事件发布器
addApplicationListenerBean
负责收集容器中的监听器- 监听器会统一转换为 GenericApplicationListener 对象,以支持判断事件类型
multicastEvent
遍历监听器集合,发布事件- 发布前先通过 GenericApplicationListener.supportsEventType 判断支持该事件类型才发事件
- 可以利用线程池进行异步发事件优化
- 如果发送的事件对象不是 ApplicationEvent 类型,Spring 会把它包装为 PayloadApplicationEvent 并用泛型技术解析事件对象的原始类型
- 视频中未讲解