Spring高级

https://www.bilibili.com/video/BV1P44y1N7QG?p=26&spm_id_from=pageDriver&vd_source=1a39594354c31d775ddc587407a55282

Ⅰ 容器与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(...)

        image-20231127202739234

二 容器实现

2.1 BeanFactory实现特点

  • DefaultListableBeanFactory

    • 是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现

      image-20231127203722980

      • bean的定义
      • 注册bean
    • 不能解析bean内部的bean

      • 解决:给bean工厂添加后处理器并将其加入与bean工厂的联系(addBeanPostProcessor

        image-20231127203924081

        image-20231127204756823

      • 针对bean的生命周期的各个阶段提供 扩展,如@Autowired @Resource

  • 特点

    • 不会主动对beanFactory后处理器
    • 不会主动添加Bean后处理器
    • 不会主动实例化单例
    • 不会解析beanFactory 还不会解析 ${} #{}
  • 后处理器的排序

    • 先添加进去的后处理器的优先级高,如果一个bean同时加了@Autowired@Resource,则看这两个后处理器谁先被加入bean容器谁就优先执行
    • 也可以通过比较器来改变后处理器的优先级

2.2 ApplicationContext常见实现

  • ClassPathXmlApplicationContext

    • 基于类路径下读取xml配置文件加载bean

      image-20231127210244668

  • FileSystemXmlApplicationContext

    • 基于文件路径读取xml
  • 读取xml的原理

    • XmlBeanDefinitionReader读取xml中的xml然后加入到DefaultListableBeanFactory
  • AnnotationApplicationContext

    • 基于注解,通过配置类加载bean

      image-20231127211045533

      • 其中Config类需要@Configuration修饰
    • 这里加了@Autowired@Resource等后处理器

  • AnnotationConfigServletWebServerApplicationContext

    • 用于web环境,基于java配置类加载bean

    • 配置类中需要定义

      • ServletWebServerFactory:web服务器容器(tomcat)
      • DispatcherServlet:前控制器,所有请求先经过它
      • DispatcherServletRegistrationBean:将前控制器注册到服务器中
      • controller

      image-20231127212543543

三 bean生命周期

3.1 生命周期

  1. 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象

  2. 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系

  3. 初始化:回调各种 Aware 接口,调用对象的各种初始化方法

  4. 可用

  5. 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)

    • prototype 对象也能够销毁,不过需要容器这边主动调用

    image-20231127212949420

3.2 生命周期增强方法

  • 创建前后的增强

    • postProcessBeforeInstantiation

      • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
    • postProcessAfterInstantiation

      • 这里如果返回 false 会跳过依赖注入阶段
  • 依赖注入前的增强

    • postProcessProperties
      • @Autowired@Value@Resource
  • 初始化前后的增强

    • postProcessBeforeInitialization
      • 这里返回的对象会替换掉原本的 bean
      • @PostConstruct@ConfigurationProperties
    • postProcessAfterInitialization
      • 这里返回的对象会替换掉原本的 bean
      • 如代理增强
  • 销毁之前的增强

    • postProcessBeforeDestruction
      • @PreDestroy

3.3 模板方法设计模式

  • 后处理器原理

image-20231127213721710

  • 通过实现BeanPostProcessor接口,可以实现在不改变getBean方法的前提下扩展功能

四 Bean后处理器

  • 作用:为Bean生命周期各个阶段提供扩展

  • GenericApplicationContext:一个比较干净的容器(没有定义后处理器)

    • registerBean():注册bean,后处理器
    • refresh():初始化容器
    • close():销毁容器
  • 常见bean后处理器

    • AutowiredAnnotationBeanPostProcessor
      • 解析@Autowired @Value

    image-20231127215306236

    • CommonAnnotationBeanPostProcessor
      • 解析@Resource @PostConstruct @PreDestroy
    • ConfigurationPropertiesBindingPostProcessor.register()
      • 解析@configurationProperties

五 BeanFactory后处理器

5.1 介绍

  • 作用:为BeanFactory提供扩展
  • 常见BeanFactory后处理器

    • ConfigurationClassPostProcessor
      • 解析@ComponentScan @Bean @Import @ImportResource
    • MapperScannerConfigurer
      • 扫描mapper接口
      • 基本方式是创建MapperFactoryBean,然后通过Mapper接口类创建对应对象
      • 在此基础上扫描包,读取Mapper接口,然后再创建
  • 使用注册

    image-20231127220822227

5.2 常见BeanFactory后处理器1

  • ConfigurationClassPostProcessor原理

  • @ComponentScan为例

    • 判断当前类是否有@ComponentScan

    • 如果有则获取@ComponentScan扫描的包下的所有class文件是否加了@Component及其衍生注解

      image-20231127221432603

    • 如果加了@Component及其衍生注解,则获取到这个bean,起名字并加入到bean工厂

      • BeanDefinitionBuilder(获取bean)

      image-20231127221719123

5.3 常见BeanFactory后处理器2

  • MapperScannerConfigurer

  • Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中

  • 原理

    • 扫描Mapper包

    • 判断是不是Interface

    • 根据接口创建MapperFactoryBean注入到bean工厂中

      image-20231127223637027

      • 上面的bd2的作用是生成name,最终不会添加到容器

        不然的话添加到容器的就是MapperFactoryBean

六 Aware 接口

6.1 Aware 接口

  • 用于注入一些与容器相关的信息

    • BeanNameAware注入bean的名字

      image-20231128201148264

    • BeanFactoryAware注入BeanFactory 容器

    • ApplicationContextAware 注入 ApplicationContext 容器

      image-20231128201236286

    • EmbeddedValueResolverAware 注入 ${} 解析器

6.2 InitializingBean 接口

  • InitializingBean 接口提供了一种【内置】的初始化手段

  • 对比

    • 内置的注入(ApplicationContextAware)和初始化(InitializingBean )不受扩展功能的影响,总会被执行

    • 而扩展功能受某些情况影响可能会失效

      image-20231128202809671

    • 因此 Spring 框架内部的类常用内置注入和初始化

1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram 
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor(bean工厂后处理器)
ac ->> bpp : 2. 注册 BeanPostProcessor(bean后处理器)
ac ->> +config : 3. 创建和初始化
bpp ->> config : 3.1 依赖注入扩展(如 @Value 和 @Autowired)
bpp ->> config : 3.2 初始化扩展(如 @PostConstruct)
ac ->> config : 3.3 执行 Aware 及 InitializingBean
config -->> -ac : 3.4 创建成功

七 初始化与销毁

7.1 初始化

  • 初始化手段

    • @PostConstruct

      image-20231128211206737

    • @Bean(initMethod)

      image-20231128211002711

      • 执行Bean1内定义的init3方法
    • 实现 InitializingBean 接口

      image-20231128211218810

  • 三种初始化手段的执行顺序

    1. @PostConstruct 标注的初始化方法
    2. InitializingBean 接口的初始化方法
    3. @Bean(initMethod) 指定的初始化方法

7.2 销毁

  • 顺序为
    1. @PreDestroy 标注的销毁方法
    2. DisposableBean 接口的销毁方法
    3. @Bean(destroyMethod) 指定的销毁方法

八 Scope

8.1 Scope类型

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

8.2 在singleton中使用其他几种scope的Bean

  • 单例对象,依赖注入仅发生一次,后续用的始终是第一次依赖注入的对象

    image-20231128215002221

    • e是单例,其中有一个多例的bean f
  • 解决

    • @Lazy生成代理

      • 代理对象还是同一个,但是每次使用代理对象的任意方法时,由代理创建新的f对象

        image-20231128215138340

    • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

    • ObjectFactory

    • ApplicationContext.getBean

  • 解决的理念都是:推迟其他scope bean的获取

Ⅱ AOP

AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能

除此以外,aspectj 提供了两种另外的 AOP 底层实现:

  • 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中

  • 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能

  • 作为对比,之前学习的代理是运行时生成新的字节码

简单比较的话:

  • aspectj 在编译和加载时,修改目标字节码,性能较高,并没有用spring
  • aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
  • aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

一 ajc增强

  • 编译器也能修改 class 实现增强
  • 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

二 agent增强

  • 类加载时可以通过 agent 修改 class 实现增强

三 proxy增强

3.1 jdk 动态代理

image-20231128223224135

  • jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系

3.2 cglib代理

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
public class CglibProxyDemo {

static class Target {
public void foo() {
System.out.println("target foo");
}
}

public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Target proxy = (Target) Enhancer.create(Target.class,
(MethodInterceptor) (p, method, args, methodProxy) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);//用反射调用目标,性能稍弱
//Object result = methodProxy.invoke(target, args); //避免反射的调用,需要目标
//Object result = methodProxy.invokeSuper(target, args); //同上但是需要代理
//spring用的第二种方法
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
  • cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
  • 父类以及父类的方法不能是final修饰的(本质是方法的重写)

四 jdk代理原理

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
29
30
31
32
33
34
public class A12 {

interface Foo {
void foo();
int bar();
}

static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}

public int bar() {
System.out.println("target bar");
return 100;
}
}

public static void main(String[] param) {
// ⬇️1. 创建代理,这时传入 InvocationHandler
Foo proxy = new $Proxy0(new InvocationHandler() {
// ⬇️5. 进入 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// ⬇️6. 功能增强
System.out.println("before...");
// ⬇️7. 反射调用目标方法
return method.invoke(new Target(), args);
}
});
// ⬇️2. 调用代理方法
proxy.foo();
proxy.bar();
}
}

模拟代理实现

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {

public $Proxy0(InvocationHandler h) {
super(h);
}
// ⬇️3. 进入代理方法
public void foo() {
try {
// ⬇️4. 回调 InvocationHandler
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

static Method foo;
static Method bar;
static {
try {
foo = A12.Foo.class.getMethod("foo");
bar = A12.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}
  • 优化:反射创建性能低
    • 前 16 次反射性能较低
    • 第 17 次调用会生成代理类,优化为非反射调用
    • 会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类

五 cglib代理原理

  • 和JDK动态代理原理差不多
    • 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
    • 调用目标时有所改进,见下面代码片段
      1. method.invoke 是反射调用,必须调用到足够次数才会进行优化
      2. methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
      3. methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class A14Application {
public static void main(String[] args) throws InvocationTargetException {

Target target = new Target();
Proxy proxy = new Proxy();

proxy.setCallbacks(new Callback[]{(MethodInterceptor) (p, m, a, mp) -> {
System.out.println("proxy before..." + mp.getSignature());
// ⬇️调用目标方法(三种)
// Object result = m.invoke(target, a); // ⬅️反射调用(m是Method对象)
//mp是MethodProxy对象
// Object result = mp.invoke(target, a); // ⬅️非反射调用, 结合目标用
Object result = mp.invokeSuper(p, a); // ⬅️非反射调用, 结合代理用
System.out.println("proxy after..." + mp.getSignature());
return result;
}});

// ⬇️调用代理方法
proxy.save();
}
}

六 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classDiagram

class Advice
class MethodInterceptor
class Advisor
class PointcutAdvisor

Pointcut <|-- AspectJExpressionPointcut
Advice <|-- MethodInterceptor
Advisor <|-- PointcutAdvisor
PointcutAdvisor o-- "一" Pointcut
PointcutAdvisor o-- "一" Advice

<<interface>> Advice
<<interface>> MethodInterceptor
<<interface>> Pointcut
<<interface>> Advisor
<<interface>> PointcutAdvisor
  • 代理相关类图

    • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
    • AopProxy 通过 getProxy 创建代理对象
    • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
    • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
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
29
30
31
32
33
34
classDiagram

Advised <|-- ProxyFactory
ProxyFactory o-- Target
ProxyFactory o-- "多" Advisor

ProxyFactory --> AopProxyFactory : 使用
AopProxyFactory --> AopProxy
Advised <|-- 基于CGLIB的Proxy
基于CGLIB的Proxy <-- ObjenesisCglibAopProxy : 创建
AopProxy <|-- ObjenesisCglibAopProxy
AopProxy <|-- JdkDynamicAopProxy
基于JDK的Proxy <-- JdkDynamicAopProxy : 创建
Advised <|-- 基于JDK的Proxy

class AopProxy {
+getProxy() Object
}

class ProxyFactory {
proxyTargetClass : boolean
}

class ObjenesisCglibAopProxy {
advised : ProxyFactory
}

class JdkDynamicAopProxy {
advised : ProxyFactory
}

<<interface>> Advised
<<interface>> AopProxyFactory
<<interface>> AopProxy
  • ProxyFactory 用来创建代理

    • 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy

    • 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy

      • 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy

      image-20231130221021934

7.2 底层切点实现

  • 切点的匹配

    • 常见 aspectj 切点用法

      image-20231203130810802

    • aspectj 切点的局限性,实际的 @Transactional 切点实现

      • 不能处理加在类上的注解或者加在接口上的注解(加在类上表示类中所有方法都匹配,接口类似)

      • 实际的实现(多加了一些判断)

        image-20231203131333433

7.3 底层切面实现

  • 高级的@Aspect切面

    image-20231203132443723

  • 低级的Advisor切面

    image-20231203132454991

  • 一些方法

    • AnnotationAwareAspectJAutoProxyCreator 的作用
      • 将高级 @Aspect 切面统一为低级 Advisor 切面
      • 在合适的时机创建代理
    • findEligibleAdvisors 找到有【资格】的 Advisors
      • 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如本例 A17 中的 advisor3
      • 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得
    • wrapIfNecessary
      • 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理
      • 它的调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
  • 高级转换未低级切面的时机及代理生成时机

    • 代理的创建时机
      • 初始化之后 (无循环依赖时)
      • 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存
    • 依赖注入与初始化不应该被增强, 仍应被施加于原始对象
  • 切面的控制顺序

    • @Order
  • 高级切面解析为低级切面的过程

    • 拿到类中所有的方法

    • 解析方法是否有注解(@Before@After…)

    • 拿到注解,并获取到切点表达式,然后创建切点对象

    • 创建通知类,参数是方法、切点以及切面实例对象

    • 创建切面Advisor并放到一个集合中

      image-20231203140403263

7.4 底层通知实现

  • 静态通知

    • 通过 proxyFactorygetInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知

      • MethodBeforeAdviceAdapter@Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
      • AfterReturningAdviceAdapter@AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
      • 这体现的是适配器设计模式
    • 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可

    • 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用(递归)

      image-20231203144733359

  • 动态通知

    • image-20231203145127731
    • 动态通知:
      • 需要参数的绑定(性能稍微低一些,因为需要处理参数)
      • 需要切点。静态执行时不需要切点
    • 过程
      • 通过 proxyFactorygetInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
      • 所谓动态通知,体现在上面方法的 DynamicInterceptionAdvice 部分,这些通知调用时因为要为通知方法绑定参数,还需再次利用切点表达式
      • 动态通知调用复杂程度高,性能较低

Ⅲ Web MVC

image-20231207153421513

image-20231207154139758

一 Controller解析请求

1.1 DispatcherServlet 初始化

  • 创建WebConfig配置类

    • 三个必须组件:web容器工厂、DispatcherServlet 、注册DispatcherServlet (Spring MVC入口)

    image-20231203153928889

  • 创建AnnotationConfigServletWebServerApplicationContext,导入配置类

    image-20231203153855015

  • DispatcherServlet初始化时间

    • 默认在首次使用的时候才会初始化
    • 也可以修改为tomcat初始化的时候就初始化
  • DispatcherServlet初始化过程

    • 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMappingHandlerAdapter 等,并逐一调用它们的初始化

1.2 RequestMappingHandlerMapping初始化

  • RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet

1.3 RequestMappingHandlerAdapter初始化

  • RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:

    • HandlerMethodArgumentResolver 解析控制器方法参数
    • HandlerMethodReturnValueHandler 处理控制器方法返回值
      • 一般是将返回值转为json发送到响应体中
  • 自定义参数与自定义返回值处理器

    • 继承HandlerMethodArgumentResolver并重写相关方法

      image-20231206203820252

      • e要在配置类中在RequestMappingHandlerAdapter中添加这个处理器

        image-20231206203939295

    • 继承HandlerMethodReturnValueHandler并重写相关方法

      image-20231206204616764

      • 同样的也需要在配置类中的RequestMappingHandlerAdapter中添加这个处理器(略)

二 参数解析器

  • 常见参数解析器

    • @RequesParam@PathVariable@RquestHeader@CookieValue@Value@ModelAttribute@RequestBody

      image-20231206205750880

  • 组合模式在Spring中的应用

    • 组合解析器

      image-20231206212857993

  • 获取参数名

    • 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
    • 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
      • 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
      • 接口, 不会包含局部变量表, 无法获得参数名
        • 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名

三 对象绑定和类型转换

3.1 底层第一套转换接口与实现

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
classDiagram

Formatter --|> Printer
Formatter --|> Parser

class Converters {
Set~GenericConverter~
}
class Converter

class ConversionService
class FormattingConversionService

ConversionService <|-- FormattingConversionService
FormattingConversionService o-- Converters

Printer --> Adapter1
Adapter1 --> Converters
Parser --> Adapter2
Adapter2 --> Converters
Converter --> Adapter3
Adapter3 --> Converters

<<interface>> Formatter
<<interface>> Printer
<<interface>> Parser
<<interface>> Converter
<<interface>> ConversionService
  • Printer 把其它类型转为 String
  • Parser 把 String 转为其它类型
  • Formatter 综合 Printer 与 Parser 功能
  • Converter 把类型 S 转为类型 T
  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
  • FormattingConversionService 利用其它们实现转换

3.2 底层第二套转换接口

1
2
3
4
5
6
classDiagram

PropertyEditorRegistry o-- "多" PropertyEditor

<<interface>> PropertyEditorRegistry
<<interface>> PropertyEditor
  • PropertyEditor 把 String 与其它类型相互转换
  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

3.3 高层接口与实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classDiagram
TypeConverter <|-- SimpleTypeConverter
TypeConverter <|-- BeanWrapperImpl
TypeConverter <|-- DirectFieldAccessor
TypeConverter <|-- ServletRequestDataBinder

SimpleTypeConverter --> TypeConverterDelegate
BeanWrapperImpl --> TypeConverterDelegate
DirectFieldAccessor --> TypeConverterDelegate
ServletRequestDataBinder --> TypeConverterDelegate

TypeConverterDelegate --> ConversionService
TypeConverterDelegate --> PropertyEditorRegistry

<<interface>> TypeConverter
<<interface>> ConversionService
<<interface>> PropertyEditorRegistry
  • 它们都实现了 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

image-20231206215723463

  • BeanWrapperImpl

    image-20231206215907603

  • DirectFieldAccessor

    image-20231206215936147

  • ServletRequesrDataBinder

    image-20231206220014794

3.5 绑定器工厂

  • 将创建DataBinder的过程放到工厂里面(没有功能),通过一些方式可以自定义转换功能 ServletRequestDataBinderFactory

  • 扩展转换功能

    • @initBinder
    • ConversionService
    • 同时用了以上两者
    • 默认使用ConversionService

3.6 如何获取泛型参数

  • 开发框架中用
  • java api获取泛型参数
  • Spring api获取

@ControllerAdvice

  • 和AOP的代理没关系,只是借鉴了Advice这个名字

  • @ExceptionHandler:处理异常的

  • @ModelAttribute:补充模型数据

  • @InitBinder:类型转换器

4.1 @InitBinder

  • 加在ControllerAdivce中的是全局的,加在Controller中的是局部的

    image-20231207144031836

  • RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法

  • RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
  • 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
  • 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂

4.2 @ModelAttribute

  • 加在参数上

    • 将模型数据加到modelandview中

      image-20231207151201385

    • 没有指定名字(图中 的“u”)则以类名首字母小写存

  • 加载类方法上

    • 将方法返回结果加到modelandview中

      image-20231207151326746

    • 没有指定名字则以方法返回值首字母小写存

4.3 控制器方法执行流程

1
2
3
4
5
6
7
8
9
10
11
classDiagram
class ServletInvocableHandlerMethod {
+invokeAndHandle(ServletWebRequest,ModelAndViewContainer)
}
HandlerMethod <|-- ServletInvocableHandlerMethod
HandlerMethod o-- bean
HandlerMethod o-- method
ServletInvocableHandlerMethod o-- WebDataBinderFactory
ServletInvocableHandlerMethod o-- ParameterNameDiscoverer
ServletInvocableHandlerMethod o-- HandlerMethodArgumentResolverComposite
ServletInvocableHandlerMethod o-- HandlerMethodReturnValueHandlerComposite

HandlerMethod 需要

  • bean 即是哪个 Controller
  • method 即是 Controller 中的哪个方法

ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory 负责对象绑定、类型转换
  • ParameterNameDiscoverer 负责参数名解析
  • HandlerMethodArgumentResolverComposite 负责解析参数
  • HandlerMethodReturnValueHandlerComposite 负责处理返回值
1
2
3
4
5
6
7
8
9
10
11
sequenceDiagram
participant adapter as RequestMappingHandlerAdapter
participant bf as WebDataBinderFactory
participant mf as ModelFactory
participant container as ModelAndViewContainer
adapter ->> +bf: 准备 @InitBinder
bf -->> -adapter:
adapter ->> +mf: 准备 @ModelAttribute
mf ->> +container: 添加Model数据
container -->> -mf:
mf -->> -adapter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sequenceDiagram
participant adapter as RequestMappingHandlerAdapter
participant ihm as ServletInvocableHandlerMethod
participant ar as ArgumentResolvers
participant rh as ReturnValueHandlers
participant container as ModelAndViewContainer

adapter ->> +ihm: invokeAndHandle
ihm ->> +ar: 获取 args
ar ->> ar: 有的解析器涉及 RequestBodyAdvice
ar ->> container: 有的解析器涉及数据绑定生成模型数据
container -->> ar:
ar -->> -ihm: args
ihm ->> ihm: method.invoke(bean,args) 得到 returnValue
ihm ->> +rh: 处理 returnValue
rh ->> rh: 有的处理器涉及 ResponseBodyAdvice
rh ->> +container: 添加Model数据,处理视图名,是否渲染等
container -->> -rh:
rh -->> -ihm:
ihm -->> -adapter:
adapter ->> +container: 获取 ModelAndView
container -->> -adapter:

五 返回值处理器

  • 返回值类型
    • 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 中,是这么实现的:
    1. 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
    2. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置
    3. 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至 /error 这个地址
      • 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
    4. Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为 /error,所以处理异常的职责就又回到了 Spring
    5. 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
    6. 具体异常信息会由 DefaultErrorAttributes 封装好
    7. BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
      • 如果要的不是 text/html,走 MessageConverter 流程
      • 如果需要 text/html,走 mvc 流程,此时又分两种情况
        • 配置了 ErrorViewResolver,根据状态码去找 View
        • 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView

七 其他的Handler

  • @BeanNameUrlHandlerMapping:请求路径不找Controller方法而是对应的bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
    return new BeanNameUrlHandlerMapping();
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
    return new SimpleControllerHandlerAdapter();
    }

    @Bean("/c3")// /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 接口 (静态资源处理)
      • 这也是典型适配器模式体现

八 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 接下来会:

    1. 调用拦截器的 preHandle 方法
    2. 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 步走视图解析及渲染流程
    3. 调用拦截器的 postHandle 方法
    4. 处理异常或视图渲染
      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
      • 正常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法

Ⅳ Spring boot

一 Boot启动过程

1.1 构造分析

  • springApplication构造
    1. 记录 BeanDefinition 源
    2. 推断应用类型
    3. 记录 ApplicationContext 初始化器
    4. 记录监听器
    5. 推断主启动类

1.2 run分析

  • 执行run方法

    1. 得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器

      • 发布 application starting 事件1️⃣
    2. 封装启动 args
    3. 准备 Environment 添加命令行参数(*)
      • ApplicationEnvironment
    4. ConfigurationPropertySources 处理(*)

      • 发布 application environment 已准备事件2️⃣
    5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)
      • application.properties,由 StandardConfigDataLocationResolver 解析
      • spring.application.json
    6. 绑定 spring.main 到 SpringApplication 对象(*)
    7. 打印 banner(*)
    8. 创建容器
    9. 准备容器

      • 发布 application context 已初始化事件3️⃣
    10. 加载 bean 定义

      • 发布 application prepared 事件4️⃣
    11. refresh 容器

      • 发布 application started 事件5️⃣
    12. 执行 runner

      • 发布 application ready 事件6️⃣

      • 这其中有异常,发布 application failed 事件7️⃣

二 tomcat内嵌容器

  • tomcat基本结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Server
    └───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.xml
  • tomcat内嵌容器

    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
    29
    public 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() {
    @Override
    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
    13
    WebApplicationContext springContext = getApplicationContext();

    // 4.编程添加 Servlet
    context.addServletContainerInitializer(new ServletContainerInitializer() {
    @Override
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration // ⬅️第三方的配置类
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}

@Configuration // ⬅️第三方的配置类
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}

提供一个配置文件 META-INF/spring.factories,key 为导入器类名,值为多个自动配置类名,用逗号分隔

1
2
3
MyImportSelector=/
AutoConfiguration1,/
AutoConfiguration2

引入自动配置

1
2
3
4
5
6
7
8
9
10
11
@Configuration // ⬅️本项目的配置类
@Import(MyImportSelector.class)
static class Config { }

static class MyImportSelector implements DeferredImportSelector {
// ⬇️该方法从 META-INF/spring.factories 读取自动配置类名,返回的 String[] 即为要导入的配置类
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return SpringFactoriesLoader
.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);
}
}
  • 自动配置类本质上就是一个配置类而已,只是用 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
  • ErrorMvcAutoConfiguration
    • 供的 bean 有 BasicErrorController

四 条件装配类

  • @Conditional()确定要不要判断

  • 实现了Condition接口的类来做具体的判断

    1
    2
    3
    4
    5
    6
    static 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
    @Configuration // 第三方的配置类
    @Conditional(MyCondition1.class) // ⬅️加入条件
    static class AutoConfiguration1 {
    @Bean
    public Bean1 bean1() {
    return new Bean1();
    }
    }
  • 本质:

    • 一种特殊的if-else

    五 FactoryBean

image-20231210123453030

  • 它的作用是用制造创建过程较为复杂的产品, 如 SqlSessionFactory, 但 @Bean 已具备等价功能
  • 使用上较为古怪, 一不留神就会用错
    1. 被 FactoryBean 创建的产品
      • 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责, 这些流程都不会走
      • 唯有后初始化的流程会走, 也就是产品可以被代理增强
      • 单例的产品不会存储于 BeanFactory 的 singletonObjects 成员中, 而是另一个 factoryBeanObjectCache 成员中
    2. 按名字去获取时, 拿到的是产品对象, 名字前面加 & 获取的是工厂对象

@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
    • 其他

      • 如果待装配类型是数组,需要获取数组元素类型,根据此类型找到多个 bean 进行装配
      • 如果待装配类型是 Collection 或其子接口,需要获取 Collection 泛型,根据此类型找到多个 bean
      • 如果待装配类型是 ApplicationContext 等特殊类型
        • 会在 BeanFactory 的 resolvableDependencies 成员按类型查找装配
        • resolvableDependencies 是 map 集合,key 是特殊类型,value 是其对应对象
        • 不能直接根据 key 进行查找,而是用 isAssignableFrom 逐一尝试右边类型是否可以被赋值给左边的 key 类型
      • 如果待装配类型有泛型参数
        • 需要利用 ContextAnnotationAutowireCandidateResolver 按泛型参数类型筛选
      • 如果待装配类型有 @Qualifier
        • 需要利用 ContextAnnotationAutowireCandidateResolver 按注解提供的 bean 名称筛选

十 事件监听器

10.1 ApplicationListener

  • 实现 ApplicationListener 接口
    • 根据接口泛型确定事件类型

10.2 @EventListener

  • 根据监听器方法参数确定事件类型
  • 解析时机:在 SmartInitializingSingleton(所有单例初始化完成后),解析每个单例 bean

10.3 事件发布器

  • addApplicationListenerBean 负责收集容器中的监听器
    • 监听器会统一转换为 GenericApplicationListener 对象,以支持判断事件类型
  • multicastEvent 遍历监听器集合,发布事件
    • 发布前先通过 GenericApplicationListener.supportsEventType 判断支持该事件类型才发事件
    • 可以利用线程池进行异步发事件优化
  • 如果发送的事件对象不是 ApplicationEvent 类型,Spring 会把它包装为 PayloadApplicationEvent 并用泛型技术解析事件对象的原始类型
    • 视频中未讲解