Java 面试宝典【学习笔记】Spring 篇


若文章内容或图片失效请留言反馈部分素材来自网络若不小心影响到您的利益请联系博主删除


写这篇博客旨在制作笔记方便个人在线阅览巩固知识无他用


参考文章:【Spring 的三级缓存解决循环依赖


推荐阅读文章:【Spring & SpringBoot 常用注解总结


前言


  • 学习视频链接
    • Java 面试宝典 | Spring 篇【黑马程序员】
    • SSM 框架教程【黑马程序员】
    • 一套搞定 Spring 全套视频教程黑马程序员

  • 学习资料链接Java 面试专题课资料提取码1234

Java 面试宝典【学习笔记】Spring 篇_第1张图片


博主的相关学习笔记

  • Java 面试宝典 | 基础篇
  • Java 面试宝典 | Spring 篇

1.ApplicationContext refresh 的流程


要求掌握 refresh 的 12 个步骤


org/springframework/context/support/AbstractApplicationContext.java

@Override
public void refresh() throws BeansException, IllegalStateException 

refreshAbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。

它的内部主要会调用 12 个方法,我们把它们称为 refresh 的 12 个步骤。


  • 准备工作
    • prepareRefresh:做好准备工作
  • 准备并创建 BeanFactory 容器对象的各项功能
    • obtainFreshBeanFactory:获取或创建 BeanFactory
    • prepareBeanFactory:准备 BeanFactory 的各个成员变量
    • postProcessBeanFactory:交给子类去扩展 BeanFactory
    • invokeBeanFactoryPostProcessors:通过 BeanFactory 的后处理器去扩展工厂功能
    • registerBeanPostProcessors:准备 Bean 的后处理器
  • ApplicationContext 的一些特有功能做准备
    • initMessageSource:为 ApplicationContext 提供国际化功能
    • initApplicationEventMulticaster:为 ApplicationContext 提供事件发布器
    • onRefresh:留给子列扩展
    • registerListeners:为 ApplicationContext 准备 监听器
    • finishBeanFactoryInitialization:初始化单例 Bean,执行之前准备的 Bean 后处理扩展的功能【重要】
    • finishRefresh:准备 Bean 的生命周期管理器,发布 ContextRefreshed 事件

1.1.prepareRefresh


要点:

  • 这一步创建和准备了 Environment 对象
  • 理解 Environment 对象的作用

  • 这一步创建和准备了 Environment 对象(它是作为 ApplicationContext 的一个成员变量)
    • Environment 对象的作用之一是为后续 @Value,值注入时提供键值
    • Environment 分成三个主要部分
      • systemProperties:保存 Java 环境键值
      • systemEnvironment:保存系统环境键值
      • 自定义 PropertySource:保存自定义键值。(例如来自于 *.properties 文件的键值)

Java 面试宝典【学习笔记】Spring 篇_第2张图片


1.2.obtainFreshBeanFactory


要点:

  • 这一步 获取 / 创建 了 BeanFactory
  • 理解 BeanFactory 的作用
  • 理解 BeanDefinition 的作用
  • BeanDefinition 从何而来

  • 这一步获取(或创建) BeanFactory(它是作为 ApplicationContext 的一个成员变量)
  • BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义
    • BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
    • BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加
  • 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合

Java 面试宝典【学习笔记】Spring 篇_第3张图片


1.3.prepareBeanFactory


要点:

  • 完善 BeanFactory
  • 了解是谁来解析 SpELSpring 表达式语言) 的
  • 了解是谁执行的类型转换
  • 了解特殊 bean 的注入
  • 两个内置的 BeanPostProcessors 的作用

  • 这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
  • beanExpressionResolver 用来解析 SpEL,常见实现为 StandardBeanExpressionResolver
  • propertyEditorRegistrars 会注册类型转换器
    • 它在这里使用了 ResourceEditorRegistrar 实现类
    • 并应用 ApplicationContext 提供的 Environment 完成 ${ } 解析
  • registerResolvableDependency 来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入
  • beanPostProcessorsbean 后处理器集合,会工作在 bean 的生命周期各个阶段,此处会添加两个:
    • ApplicationContextAwareProcessor 用来解析 Aware 接口
    • ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean

Java 面试宝典【学习笔记】Spring 篇_第4张图片


1.4.postProcessBeanFactory


要点:这一步是空实现,留给子类扩展。


  • 这一步是空实现,留给子类扩展。
    • 例:一般 Web 环境的 ApplicationContext 都要利用它注册新的 Scope,完善 Web 下的 BeanFactory
  • 这里体现的是模板方法设计模式。
    • Template Method 设计模式:在父类中定义处理流程的框架,在子类中实现具体的处理。

1.5.invokeBeanFactoryPostProcessors


要点:

  • 理解 beanFactory 后处理器的作用
  • 掌握常见的 beanFactory 后处理器

  • 这一步会调用 beanFactory 后处理器
  • beanFactory 后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition
  • 常见的 beanFactory 后处理器有
    • ConfigurationClassPostProcessor:解析 @Configuration@Bean@Import@PropertySource
    • PropertySourcesPlaceHolderConfigurer:替换 BeanDefinition 中的 ${ }
    • MapperScannerConfigurer:补充 Mapper 接口对应的 BeanDefinition

Java 面试宝典【学习笔记】Spring 篇_第5张图片


1.6.registerBeanPostProcessors


要点:

  • 理解 bean 后处理器的作用
  • 掌握常见的 bean 后处理器

  • 这一步是继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中
  • bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段,常见的有:
    • AutowiredAnnotationBeanPostProcessor 功能有:解析 @Autowired@Value 注解
    • CommonAnnotationBeanPostProcessor 功能有:解析 @Resource@PostConstruct@PreDestroy
    • AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理

Java 面试宝典【学习笔记】Spring 篇_第6张图片


1.7.initMessageSource


要点:

  • 理解 MessageSource 的作用
  • MessageSource 从何而来

  • 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能
  • beanFactory 内找名为 messageSourcebean,如果没有,则提供空的 MessageSource 实现

Java 面试宝典【学习笔记】Spring 篇_第7张图片


1.8.initApplicationEventMulticaster


要点:

  • 理解事件广播器的作用
  • 事件广播器从何而来
  • 如何发布事件

  • 这一步为 ApplicationContext 添加事件广播器成员,即 applicationContextEventMulticaster
  • 它的作用是发布事件给监听器
  • beanFactory 找名为 applicationEventMulticasterbean 作为事件广播器,若没有,会创建默认的事件广播器
  • 之后就可以调用 ApplicationContext.publishEvent(事件对象) 来发布事件

Java 面试宝典【学习笔记】Spring 篇_第8张图片


1.9.onRefresh


要点:这一步是空实现,留给子类扩展


  • 这一步是空实现,留给子类扩展
    • 例:SpringBoot 中的子类在这里准备了 WebServer,即内嵌 Web 容器
  • 体现的是模板方法设计模式

1.10.registerListeners


要点:

  • 理解事件监听器的作用
  • 监听器从何而来
  • 如何接受事件

  • 这一步会从多种途径找到事件监听器,并添加至 applicationEventMulticaster
  • 事件监听器顾名思义,用来接收事件广播器发布的事件,有如下来源
    • 事先编程添加的
    • 来自容器中的 bean
    • 来自于 @EventListener 的解析
  • 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中 onApplicationEvent(E e) 方法即可

Java 面试宝典【学习笔记】Spring 篇_第9张图片


1.11.finishBeanFactoryInitialization


要点:

  • 了解 conversionService
  • 了解内嵌值解析器
  • 单例池 - singletonObjects

  • 这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
  • conversionService 是一套转换机制,作为对 PropertyEditor 的补充
  • embeddedValueResolvers 即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能
    • 其实 embeddedValueResolvers 就是对 Environment 做了一个封装
  • singletonObjects 即单例池,缓存所有单例对象
    • 对象的创建都分三个阶段(创建、依赖注入、初始化),每一阶段都有不同的 bean 后处理器参与进来,扩展功能

Java 面试宝典【学习笔记】Spring 篇_第10张图片


1.12.finishRefresh


要点:

  • 了解 lifecycleProcessor
  • lifecycleProcessor 从何而来
  • 如何控制 lifecycleProcessor
  • 发布 ContextRefreshed 事件

  • 这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,用来控制容器内需要生命周期管理的 bean
  • 如果容器中有名称为 lifecycleProcessorbean 就用它,否则创建默认的生命周期管理器
  • 准备好生命周期管理器,就可以实现
    • 调用 contextstart,即可触发所有实现 LifeCycle 接口 beanstart
    • 调用 contextstop,即可触发所有实现 LifeCycle 接口 beanstop
  • 发布 ContextRefreshed 事件,整个 refresh 执行完成

Java 面试宝典【学习笔记】Spring 篇_第11张图片


2.SpringBean 的生命周期


要求掌握 Spring bean 的生命周期


org/springframework/beans/factory/support/AbstractBeanFactory.java

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	String beanName = transformedBeanName(name);
	Object beanInstance;
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) { ... ... } 
	else {
		... ...
		BeanFactory parentBeanFactory = getParentBeanFactory();
		... ...
	}

	return adaptBeanInstance(name, beanInstance, requiredType);
}

Spring 里面的 Bean 是懒惰式初始化的,一开始并不会创建 Bean 的实例,只有在调用的时候才会创建 Bean 的实例,然后再去进行依赖注入、初始化等操作 … … 所以说 bean 的生命周期是从调用 beanFactorygetBean 开始的。


bean 的生命周期从调用 beanFactorygetBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:

  • 处理名称,检查缓存
  • 处理父子容器(检查父工厂)
  • 处理 dependsOn
  • 选择 scope 策略
    • 创建 singleton
    • 创建 prototype
    • 创建其他 scope
  • 创建 bean
    • 创建 bean 实例:@Autowired、唯一带参构造、默认构造
    • 依赖注入:@Autowired @Value@ResourceByName ByType、精确指定
    • 初始化:Aware 接口处理、@PostConstructInitializingBeaninitMethod、创建代理对象
    • 等级可销毁的 bean
  • 类型转换处理
  • 销毁 bean

注意:划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情


2.1.处理名称 + 检查缓存


要点:

  • 掌握别名处理
  • 了解 FactoryBean 的名字规范
  • 掌握三级缓存的概念

  • 这一步会处理别名,将别名解析为实际名称,之后才会进行后续的处理操作
  • FactoryBean 也会做特殊处理:如果是以 & 开头,则表示要获取 FactoryBean 本身;否则表示要获取其产品
  • 针对单例对象,这里会检查一级、二级、三级缓存
    • singletonObjects 一级缓存,存放单例成品对象
    • earlySingletonObjects 二级缓存,存放单例工厂的产品对象
      • 如果发生了循环依赖,产品是代理;无循环依赖,产品是原始对象
    • singletonFactories 三级缓存,存放单例工厂对象

参考文章:【Spring 的三级缓存解决循环依赖

  • 第一级缓存:也叫单例池,存放已经经历了完整生命周期的 Bean 对象。
  • 第二级缓存:存放早期暴露出来的 Bean 对象,实例化以后,就把对象放到这个 Map 中。
    • 这里的 Bean 可能只是经历过了实例化,其属性还尚未填充。
  • 第三级缓存:存放早期暴露的 Bean 的工厂。


注意细节

  • 只有单例的 bean 会通过三级缓存的提前暴露来解决循环依赖的问题;
    而非单例的 bean,每次从容器中获取的对象都是一个新的对象,都会重新创建;
    所以非单例的 bean 是没有缓存的,不会将其放到三级缓存中。
  • 为了解决第二级缓存中 AOP 生成新对象的问题,Spring 就提前 AOP
    • 例:在加载 b 的流程中,发生了循环依赖,b 依赖了 a,就要对 a 执行 AOP,提前获取增强以后的 a 对象。
      这样 b 对象依赖的 a 对象就是增强以后的 a 了。
  • 二级缓存和三级缓存就是要解决循环依赖的,且之所以设置这两个级别的缓存,主要的原因就是:
    • 它们可以实现循环依赖对象需要提前被 AOP 代理的操作;
    • 如果没有循环依赖,早期的 bean 也不会真正暴露,不用提前执行代理过程,也不用重复执行代理过程。

2.2.处理父子容器


要点:了解有父容器时的查找规则


  • 优先查找子容器的 bean,找到了则直接返回。找不到则继续去父容器找;
    如果父容器里存在这个 bean 的话,则执行父容器的 getBean 流程;
    如果父容器里还是没有,则会继续执行下面的流程。
  • 父子容器的 bean 名称可以重复

2.3.处理 dependsOn


  • 如果当前 bean有通过 dependsOn 指定了 非显式依赖的 bean,这一步会提前创建这些 dependsOnbean
  • 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,需要手动控制它们的创建先后顺序

要点 总结
了解有 dependsOn 时的 bean 初始化顺序 dependsOn 用在非显示依赖的 bean 的创建顺序控制
了解 @Conditional 的解析时机 @ConditionalConditionEvaluator 解析看是否满足装配条件
了解 beanName 的解析时机 beanName 的解析要分情况
    组件扫描AnnotationBeanNameGenerator
    @ImportFullyQualifiedAnnotationBeanNameGenerator
    @BeanConfigurationClassBeanDefinitionReader
掌握 @Bean 的解析 @Bean 相当于工厂方法,其所在类相当于工厂
了解 @DependsOn@Lazy@Primary 的解析时机 这些注解由 AnnotationConfigUtils 补充为 BeanDefinition
了解 @Scope 代理的解析 Scope 标注的 bean 会为之生成 ScopeProxyFactoryBean BeanDefinition 取代原有的
原有的 BeanDefinition 则会成为内嵌定义

2.4.选择 scope 策略


要点理解三种 scope


scope 可以理解为从 XXX 范围内找这个 bean 更加贴切

  • 对于 singleton scope,首先会到单例池范围内去获取 bean,如果有则直接返回,没有则创建并放入单例池
  • 对于 prototype scope,其从不缓存 bean,每次都会创建新的
  • 对于自定义 scope
    • 这里 request 为例:首先到 request 域去获取 bean,如果有则直接返回,没有则创建并放入 request 域中。

单例 bean首次 refresh 被创建,到 close 被销毁(其中 BeanFactory 会记录哪些 bean 需要调用销毁方法)。

多例 bean首次 getBean 被创建,到调用 BeanFactorydestoryBean 时会被销毁。
(没有谁会记录该 bean 需要调用销毁方法,需要我们自行调用来做清理工作)


2.5.创建 bean


2.5.1.创建 bean 实例


要点 总结
有自定义 TargetSource 的情况 AnnotationAwareAspectJAutoProxyCreator 创建代理返回
Supplier 方式创建 bean 实例 Spring 5.0 新增功能,方便编程方式创建 bean 实例
FactoryMethod 方式 创建 bean 实例 ① 分成静态工厂与实例工厂;
② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency
③ 如果有多个工厂方法候选者,还要进一步按权重筛选
AutowiredAnnotationBeanPostProcessor 选择构造 ① 优先选择带 @Autowired 注解的构造;
② 若有唯一的带参构造,也会入选
mbd.getPreferredConstructors 选择所有公共构造,这些构造之间按权重筛选
采用默认构造 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的

2.5.2.依赖注入


要点 总结
AutowiredAnnotationBeanPostProcessor(注解匹配) 识别 @Autowired@Value 标注的成员,封装为 InjectionMetadata 进行依赖注入
CommonAnnotationBeanPostProcessor(注解匹配) 识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入
resolveDependency 用来查找要装配的值,可以识别
     Optional
    ObjectFactory ObjectProvider
    @Lazy 注解;
    ④ @Value 注解
${ }
#{ }类型转换
    ⑤ 集合类型(CollectionMap、数组等);
    ⑥ 泛型和 @Qualifier用来区分类型歧义
     primary 及名字匹配(用来区分类型歧义)
AUTOWIRE_BY_NAME(根据名字匹配) 根据成员名字找 bean 对象,修改 mbd propertyValues,不会考虑简单类型的成员
AUTOWIRE_BY_TYPE(根据类型匹配) 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd propertyValues
applyPropertyValues(精确指定) 根据 mbd propertyValues 进行依赖注入 xml 中的 标签中的精确指定

问:如果针对同一个成员,采用了多种方式给它配置了依赖注入,哪种方式的优先级最高呢?

优先级顺序(从高到低排序):[精确指定注入 bean 的名称来匹配] > [AUTOWIRE_BY_TYPE 匹配] > [注解方式匹配]


2.5.3.初始化


要点 总结
内置 Aware 接口的装配 包括 BeanNameAwareBeanFactoryAware
扩展 Aware 接口的装配 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization
@PostConstruct CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization
实现 InitializingBean 接口 通过接口回调执行初始化
initMethod 根据 BeanDefinition 得到的初始化方法执行初始化,即 @Bean(initMethod)
创建 aop 代理 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization

2.5.4.注册可销毁 bean


要点:判断并登记可销毁 bean


  • 判断依据
    • 如果实现了 DisposableBeanAutoCloseable 接口,则为可销毁 bean
    • 如果自定义了 destroyMethod,则为可销毁 bean
    • 如果采用 @Bean 没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(closeshutdown
    • 如果有 @PreDestroy 标注的方法,也会被认为是一个可以销毁的 bean
  • 存储位置
    • singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中
    • 自定义 scope 的可销毁 bean 会存储于对应的域对象当中
    • prototype scope 不会存储,需要自己找到此对象销毁
  • 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配

2.6.类型转换处理


  • 如果 getBeanrequiredType 参数与实际得到的对象类型不同,会尝试进行类型转换

2.7.销毁 bean


要点:

  • singleton bean 的销毁时机
  • 自定义 scope bean 的销毁时机
  • prototype bean 的销毁时机
  • 同一 bean 中的不同形式销毁方法的调用次序

  • 销毁时机
    • singleton bean 的销毁在 ApplicationContext.close 时,此时会找到所有 DisposableBean 的名字,逐一销毁
    • 自定义 scope bean 的销毁在作用域对象生命周期结束时
    • prototype bean 的销毁可以通过自己手动调用 AutowireCapableBeanFactory.destroyBean 方法执行销毁
  • 同一 bean 中不同形式销毁方法的调用次序
    • 优先调用的是后处理器销毁,即 @PreDestroy
    • 其次的是 DisposableBean 接口销毁
    • 最后的是 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)

3.Spring 循环依赖


要求

  • 掌握创建代理的过程
  • 掌握单例 set 方式循环依赖的原理
  • 掌握其它循环依赖的解决方法

3.1.创建代理


  • 要完全理解循环依赖,需要理解代理对象的创建时机
  • 掌握 ProxyFactory 创建代理的过程,理解 AdvisorAdvicePointcutAspect
  • 掌握 AnnotaionAwareAspectJAutoProxyCreator 筛选 Advisor 合格者,创建代理的过程

3.1.1.AOP 的相关概念


  • AOP 的概念
  • AOP 思想的实现方案(动态代理技术)
  • AOP 切面编程涉及到的一些专业术语
  • AspectJ 的通知有以下五种类型
  • aspect 和 advisor
  • 动态代理的两种方式
  • 切点表达式

AOP 的概念

  • AOPAspect Oriented Programming面向切面编程)是对面向对象编程 OOP 的升华。
  • OOP 是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。
  • AOP 是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面。
    用这种思维去设计编程的方式叫做面向切面编程。

Java 面试宝典【学习笔记】Spring 篇_第12张图片


AOP 思想的实现方案(动态代理技术)

  • 在运行期间,对目标对象的方法进行增强
  • 在代理对象同名方法内,可以在其执行原有逻辑的同时,嵌入执行 其他增强逻辑 或 其他对象的方法。

Java 面试宝典【学习笔记】Spring 篇_第13张图片


AOP 切面编程涉及到的一些专业术语

概念 单词 解释
目标对象 Target 被增强的方法所在的对象
代理对象 Proxy 对目标对象进行增强后的对象,客户端实际调用的对象
连接点 Joinpoint 目标对象中可以被增强的方法
切入点 Pointcut 目标对象中实际被增强的方法
通知 / 增强 Advice 增强部分的代码逻辑
切面 Aspect 增强和切入点的组合
织入 Weaving 通知和切入点动态组合的过程(生成了代理对象)

Java 面试宝典【学习笔记】Spring 篇_第14张图片


AspectJ 的通知有以下五种类型

通知名称 执行时机
前置通知 目标方法执行之前执行
后置通知 目标方法执行之后执行,目标方法异常时,不再执行
环绕通知 目标方法执行前后执行,目标方法异常时,环绕后方法不再执行
异常通知 目标方法抛出异常时执行
最终通知 不管目标方法是否有异常,最终都会执行

aspect advisor

  • 基本概念
    • aspect = 通知(advice)+ 切点(pointcut一个切面类中可能有多个通知、多个切点
    • Sping 的底层,它可能并不会以切面为单位进行编程,而是以一个更细粒度的切面为单位:advisor
    • advisor是更细粒度的切面,只包含一个通知和其对应的一个固定切点。
  • 使用场景:
    • 在通知类型多、允许随意搭配的情况下,可以使用 aspect 进行配置;
    • 在通知类型单一、且通知类中通知方法一次性都会使用到的情况下,可以使用 advisor 进行配置;
    • 在通知类型已经固定,不用人为指定通知类型时,可以使用 advisor 进行配置,例如 Sping 事务控制的配置。
    • 在实际开发中,自定义 AOP 功能的配置大多使用 aspect 的配置方式

动态代理的两种方式

代理技术 使用条件
JDK 动态代理技术 目标类有接口(也就是说目标类是某个接口的实现类),是基于接口动态生成实现类的代理对象
Cglib 动态代理技术 目标类无接口,且不能使用 final 修饰,是基于被代理对象动态生成的子对象为代理对象

Java 面试宝典【学习笔记】Spring 篇_第15张图片
动态代理的实现的选择:

  • 在代理工厂对象(proxyFactory)调用 getProxy() 方法时,可选用的 AopProxy 接口有两个实现类
    • CglibAopProxyJdkDynamicAopProxy
    • 这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib

切点表达式

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强

execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略不写;
  • 返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
  • 包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;
  • 参数列表可以使用两个点 .. 表示任意参数

3.1.2.一个简单的功能增强


示例代码circularDependency/App1.java

public class App1 {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new Target1()); // 设置目标对象
        
        // 添加通知(也可以说是功能增强)
        proxyFactory.addAdvice(new MethodInterceptor() { // MethodInvocation 是环绕通知
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                try {
                    System.out.println("目标方法调用前_增强处理");
                    return invocation.proceed();
                } finally { System.out.println("目标方法调用后_增强处理"); }
            }
        });

        Target1 proxy = (Target1) proxyFactory.getProxy(); // 创建代理对象(Cglib 动态代理)

        System.out.println("------------[proxy.foo]------------");
        proxy.foo();
        System.out.println("------------[proxy.bar]------------");
        proxy.bar();
        System.out.println("-----------------------------------");
    }

    interface I1 { 
   		void foo();
        void bar();
    }

    static class Target1 implements I1 {
        @Override
        public void foo() { System.out.println("target1 foo"); }
        @Override
        public void bar() { System.out.println("target1 bar"); }
    }
}

输出结果

------------[proxy.foo]------------
目标方法调用前_增强
target1 foo
目标方法调用后_增强
------------[proxy.bar]------------
目标方法调用前_增强
target1 bar
目标方法调用后_增强
-----------------------------------

3.1.3.advisor = 一个切点 + 一个固定通知


示例代码circularDependency/App2.java

public class App2 {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new Target1());
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); // 创建切点
        pointcut.setExpression("execution(* foo())"); // 配置切点表达式(此处只增强 foo() 方法)
        
        // 切点 + 通知 = advisor
        proxyFactory.addAdvisor(
                new DefaultPointcutAdvisor(pointcut,
                        // 这里我直接用 Lambda表达式 的形式代替之前的 匿名内部类 形式了
                        (MethodInterceptor) invocation -> {
                            try {
                                System.out.println("before_1_增强处理");
                                return invocation.proceed();
                            } finally { System.out.println("after_1_增强处理"); }}));

        proxyFactory.addAdvisor(
                new DefaultPointcutAdvisor(pointcut,
                        (MethodInterceptor) invocation -> {
                            try {
                                System.out.println("before_2_增强处理");
                                return invocation.proceed();
                            } finally { System.out.println("after_2_增强处理"); }}));
		
		Target1 proxy = (Target1) proxyFactory.getProxy();
		System.out.println(proxy.getClass() + "\n");  // 查看打印结果,看看这段代码的动态代理方式
        proxy.foo();
        proxy.bar(); // 这里没有对 bar() 方法做增强处理
    }

    interface I1 {
        void foo();
        void bar();
    }

    static class Target1 implements I1 {
        @Override
        public void foo() { System.out.println("target1 foo"); }
        @Override
        public void bar() { System.out.println("target1 bar"); }
    }
}

打印结果

class circularDependency.App2$Target1$$EnhancerBySpringCGLIB$$c461669a

before_1_增强处理
before_2_增强处理
target1 foo
after_2_增强处理
after_1_增强处理
target1 bar

其实 3.1.2 小节里使用的 addAdvice() 内部调用的依旧是 addAdvisor()

在没有传入 pointcut 的情况下,它会默认增强所有的方法。

在传入了 pointcut 的情况下,它才会增强指定的方法。

Java 面试宝典【学习笔记】Spring 篇_第16张图片


3.1.4.动态代理的两种方式


动态代理的实现的选择:

  • 在代理工厂对象(proxyFactory)调用 getProxy() 方法时,可选用的 AopProxy 接口有两个实现类
    • CglibAopProxyJdkDynamicAopProxy
    • 这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib

下面对 3.1.2 小节里的 App1.java 进行 Debug


  • Cglib 动态代理方式
proxyFactory.setTarget(new Target1()); // 设置目标对象
// 添加通知/功能增强 ... ...
Target1 proxy = (Target1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)

// 既然这里可以用 Target1 强转,说明在 Cglib 动态代理下,目标对象和代理对象是父子关系

Java 面试宝典【学习笔记】Spring 篇_第17张图片


  • JDK 动态代理方式

只是改动了几处代码,其余代码不变(这里我图方便,直接重新创建了一个类,内容与之前相比,并没有差别)

proxyFactory.setTarget(new Target1());
proxyFactory.addInterface(I1.class);
// 添加通知/功能增强 ... ...
I1 proxy = (I1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)

// 这里是不可以用 Target1 强转的,说明在 JDK 动态代理下,目标对象和代理对象是平级的关系

Java 面试宝典【学习笔记】Spring 篇_第18张图片


吐槽:个人感觉它这个例子并不算太好。接口实现类混在一起的,其实并不好区分两种动态代理方式的区别。
Cglib 动态代理方式示例而言,不用应该用到接口,不让目标对象继承接口。这样不容易混淆。

Cglib 动态代理方式,就是可以直接动态生成目标对象的子类代理对象的。

JDK 动态代理方式,它的目标类必须要有接口(也就是说它是接口的实现类),是基于接口动态生成接口实现类的代理对象。


加上下面一行的代码,直接让程序统一采用 Cglib 动态代理方式来生成代理对象。

proxyFactory.setProxyTargetClass(true);

3.1.5.代理对象与 advisor 的关系


代理对象调用方法的时候,需要在内部先找到切面,之后通过切面去检查切点是不是跟方法匹配。

如果方法与切点是匹配的,代理对象才去调通知。

这里就抛出一个问题:这个切面的信息都被保存在哪里去了?将来我们需要在哪里找到切面对象呢?


下面对 3.1.3 小节里的 App2.java 进行 Debug


proxy.foo(); // 打断点

Java 面试宝典【学习笔记】Spring 篇_第19张图片

显然,代理对象(proxy)的内部(advisors 集合)会记录 advisor 切面的信息.

这里只举了 Cglib 动态代理的例子,其实 JDK 动态代理也会这样做 (懒得贴图了)


3.1.6.采用注解的方式来进行 AOP 编程


AOP 编程三要素:切面(advisor),切点(pointcut),通知(advice


在实际开发中,我们往往会用注解的方式来进行 AOP 编程。

  • 标注了 @Aspect 的类是切面类;
  • @Around@Before@After 等通知类型的注解里的是切点表达式;
    • 切点表达式是要配置信息的:要对哪些连接点(哪些类的哪些方法)进行通知的增强
  • 通知类型的注解下的方法是通知方法。
@Aspect
static class Aspect1 {
    @Around("execution(* foo())") // 一个 advisor 切面(切点 + 通知)
    // 环绕通知依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("aspect1 around");
        return pjp.proceed();
    }
}

这种注解的方式最终还是会被转换成 advisorMethodInterceptorSpring 内置的环绕通知方式)的方式。

它们之间的转换是以通知方法为单位进行转换的。

@Aspect
static class Aspect2 {
    @Before("execution(* foo())")  // 一个 advisor 切面
    public void before() { System.out.println("aspect2 before"); }

    @After("execution(* foo())") // 一个 advisor 切面
    public void after() { System.out.println("aspect2 after"); }
}

示例代码(circularDependency/App3.java

public class App3 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("aspect1", Aspect1.class);
        context.registerBean("aspect2", Aspect2.class);
        context.registerBean("aspect3", Aspect3.class);

        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class); // 自动代理后处理器
        
        context.registerBean("target1", Target1.class);
        context.registerBean("target2", Target2.class);

        context.refresh();

        Target1 target1 = context.getBean(Target1.class);
        Target2 target2 = context.getBean(Target2.class);

        target1.foo();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
        target2.bar();
    }

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

    static class Target2 {
        public void bar() { System.out.println("Target2 bar"); }
    }
    
    @Aspect
    static class Aspect1 {
        @Around("execution(* foo())") // 一个 advisor 切面(切点 + 通知)
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("aspect1 around");
            return pjp.proceed();
        }
    }

    @Aspect
    static class Aspect2 {
        @Before("execution(* foo())")  // 一个 advisor 切面
        public void before() { System.out.println("aspect2 before"); }
        
        @After("execution(* foo())") // 一个 advisor 切面
        public void after() { System.out.println("aspect2 after"); }
    }

    @Aspect
    static class Aspect3 {
        @Before("execution(* bar())") // 一个 advisor 切面
        public void before() { System.out.println("aspect3 before"); }
    }

}

控制台打印信息

aspect1 around
aspect2 before
Target1 foo
aspect2 after
>>>>>>>>>>>>>>>>>>>>>>>>>
aspect3 before
Target2 bar

3.1.7.自动代理的后处理器


org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java

public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator

在这里插入图片描述


annotationAwareAspectJAutoProxyCreator 会先根据切点表达式查看是否有与切点表达式匹配的目标。
如果存在该目标的话,它就会自动创建相应的代理对象,创建的代理对象会放到单例池中。
最终我们通过 getBean() 拿到的对象都是代理对象。

当我们在调用代理对象内部的已经被增强过了的方法时,它就会找到每一个代理对象关联的 advisor 切面。
之后再通过 advisor 切面去匹配切点表达式。
匹配成功的话,它就会调用相应的通知。


annotationAwareAspectJAutoProxyCreator 类中重要的方法:

  • wrapIfNecessary:检查创建代理对象的条件,条件满足的时候,才会创建代理对象。
    比如容器里的切面、切点、通知这些 AOP 里的基础设施类型,是不需要进行创建代理的,否则就无限循环了。
    如果目标方法根本没有匹配的切点表达式,也是不需要创建代理的。
    • 检查是否为 AOP 基础设施类型
    • 根据当前 bean 的类型找到真正的切面(avisor
    • 通过代理工厂创建代理对象

annotationAwareAspectJAutoProxyCreator 的调用时机:创建阶段、依赖注入阶段、初始化阶段

Java 面试宝典【学习笔记】Spring 篇_第20张图片


3.1.8.小结


  • 最基本的切面类是 advisor,一个 Aspect 切面根据内置方法不同对应一到多个 Advisor
  • 最基本的通知是 MethodInterceptor,其他 Advice(比如前置,后置,环绕)最终都会适配为 MethodInterceptor
  • 代理创建的方式
    • 实现了用户自定义的接口,采用 JDK 的动态代理。
    • 没有实现用户自定义接口,采用 Cglib 代理。
    • 如果设置了 setProxyTargetClass(ture) ,统一采用 Cglib 代理。
  • 切面,切点,通知等是不会被代理的
  • annotationAwareAspectJAutoProxyCreator 的调用时机:创建阶段、依赖注入阶段、初始化阶段

3.2.循环依赖的产生和 set 循环依赖


首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱

Java 面试宝典【学习笔记】Spring 篇_第21张图片


set 方法(包括成员变量)的循环依赖如图所示

  • 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
  • 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
  • a 的顺序,及 b 的顺序都能得到保障

Java 面试宝典【学习笔记】Spring 篇_第22张图片


示例代码

public class App60_1 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;

        public A() { log.info("A()"); }

        @Autowired
        public void setB(B b) {
            log.info("setB({})", b.getClass());
            this.b = b;
        }

        @PostConstruct
        public void init() { log.info("init()"); }

        public void foo() { System.out.println("foo()"); }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B() { log.info("B()"); }

        @Autowired
        public void setA(A a) {
            log.info("setA({})", a.getClass());
            this.a = a;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    @Aspect
    static class MyAspect {
        @Before("execution(* foo())")
        public void before() { System.out.println("before ..."); }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        context.registerBean(MyAspect.class);
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();

        context.getBean(A.class).foo();
    }
}

控制台打印信息

Java 面试宝典【学习笔记】Spring 篇_第23张图片


3.3.构造循环依赖的解决


构造方法的循环依赖如图所示,显然无法用前面的方法解决(三级缓存的思路是在对象实例创建完成后,把对象的实例 a 封装成一个工厂对象,放入 singletonFactories 缓存中的,这样 b 就可以从缓存中直接拿出 a 来完成自己内部的注入(即 setA()),进而完成整个依赖注入流程。但这里连半成品的实例对象都没有,所以无法使用上面的方法来解决这里的问题)

Java 面试宝典【学习笔记】Spring 篇_第24张图片


3.2.1.解决思路


  • 思路①
    • a 注入 b 的代理对象,这样可以优先能够保证 a 的流程走通(其实也是推迟了实例对象 b 的创建)
    • 后续 a 需要用到 b 的真实对象时,可以通过代理间接访问

Java 面试宝典【学习笔记】Spring 篇_第25张图片


  • 思路②
    • a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
    • 后续 a 需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问

Java 面试宝典【学习笔记】Spring 篇_第26张图片


3.2.2.用 @Lazy 为构造方法参数生成代理(示例一)


示例代码

public class App61 {
    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;

        public A(@Lazy B b) {
            log.info("A(B b) {}", b.getClass());
            this.b = b;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.info("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() {
            log.info("init()");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println();
    }
}

控制台打印信息

Java 面试宝典【学习笔记】Spring 篇_第27张图片


3.2.3.用 ObjectProvider 延迟依赖对象的创建(示例二)


public class App62 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private ObjectProvider<B> b;

        public A(ObjectProvider<B> b) {
            log.info("A({})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.info("B({})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println("==========================================");
        System.out.println(context.getBean(A.class).b.getObject());
        System.out.println(context.getBean(B.class));
        System.out.println("==========================================");
    }
}

控制台打印信息

Java 面试宝典【学习笔记】Spring 篇_第28张图片


ObjectProviderObjectFactory 的子类型,用 ObjectFactory 替换掉上面代码中的 ObjectProvider 也是没有问题的。


3.2.4.用 @Scope 产生代理(示例三)


示例代码

  • circularDependency/test2/others/A.java
@Component
public class A {
    private static final Logger log = LoggerFactory.getLogger("A");
    private B b;

    public A(B b) {
        log.info("A(B b) {}", b.getClass());
        this.b = b;
    }

    @PostConstruct
    public void init() {
        log.info("init()");
    }
}
  • circularDependency/test2/others/B.java
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class B {
    private static final Logger log = LoggerFactory.getLogger("B");
    private A a;

    public B(A a) {
        log.info("B({})", a.getClass());
        this.a = a;
    }

    @PostConstruct
    public void init() {
        log.info("init()");
    }
}
  • circularDependency/test2/App63.java
public class App63 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
        scanner.scan("circularDependency.test2.others");
        context.refresh();
        System.out.println();
    }
}

控制台打印信息

Java 面试宝典【学习笔记】Spring 篇_第29张图片


3.2.5.用 Provider 接口解决(示例四)


Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖

<dependency>
    <groupId>javax.injectgroupId>
    <artifactId>javax.injectartifactId>
    <version>1version>
dependency>

示例代码

public class App64 {

    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private Provider<B> b;

        public A(Provider<B> b) {
            log.info("A({}})", b);
            this.b = b;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;

        public B(A a) {
            log.info("B({}})", a);
            this.a = a;
        }

        @PostConstruct
        public void init() { log.info("init()"); }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println("-------------------------------------------");
        System.out.println(context.getBean(A.class).b.get());
        System.out.println(context.getBean(B.class));
        System.out.println("-------------------------------------------");
    }
}

控制台打印信息

Java 面试宝典【学习笔记】Spring 篇_第30张图片


3.3.解决 set 循环依赖的原理


!注意事项教程中给的几张图是根据解决循环依赖的演进过程而画的,并不是和源码一一对应的,在叫法上也有区别。


3.3.1.一级缓存


一级缓存:限制 beanbeanFactory 中只存一份,即实现 singleton scope

Java 面试宝典【学习笔记】Spring 篇_第31张图片


作用保证单例对象仅被创建一次

  • 第一次走 getBean("a") 流程时,会创建实例对象 a,最后将成品 a 放入 singletonObjects 一级缓存
  • 之后再走 getBean("a") 流程的时后,会先从一级缓存中找 a。因为此时已有成品 a,故无需再次创建。

3.3.2.一级缓存与循环依赖


A 中有 BB 中有 A

Java 面试宝典【学习笔记】Spring 篇_第32张图片


一级缓存无法解决循环依赖问题,分析如下

  • 无论是获取 bean a 还是获取 bean b。走的方法都是同一个 getBean 方法,假设先走 getBean("a")
  • a 的实例对象创建,接下来执行 a.setB() 时,需要走 getBean("b") 流程【红色箭头 1】
  • b 的实例对象创建,接下来执行 b.setA() 时,又回到了 getBean("a") 的流程【红色箭头 2】
  • 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环

3.3.3.二级缓存(Spring 中的三级缓存)


针对 “一级缓存无法解决循环依赖问题” 的解决思路如下:

  • 再增加一个 singletonFactories 缓存
  • 在依赖注入前,即 a.setB() 以及 b.setA() 会将 ab 的半成品对象(未完成依赖注入和初始化)放入此缓存
  • 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程

这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。

Java 面试宝典【学习笔记】Spring 篇_第33张图片


对于上面的图

  • a = new A() 执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)
  • 接下来执行 a.setB(),走入 getBean("b") 流程【红色箭头 3】
  • 这回再执行到 b.setA() 时,需要一个 a 对象,有没有呢?有!
  • factories.get()singletonFactories 缓存中就可以找到【红色箭头 4 】【红色箭头5】
  • b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程【红色箭头 6】
  • 之后 a 的依赖注入和初始化也可以顺利完成了,最终 ab 的成品对象都会放入到 singletonObjects

3.3.4.二级缓存与创建代理(Spring 中的三级缓存)


这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。

Java 面试宝典【学习笔记】Spring 篇_第34张图片


二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下

  • Spring 默认要求,在 a.init 完成之后才能创建代理 pa = proxy(a)
  • 由于 a 的代理创建时机靠后,在执行 factories.put(a)singletonFactories 中放入的还是原始对象
  • 接下来【箭头 3】【箭头 4】【箭头 5】 这几步 b 对象拿到和注入的都是原始对象

3.3.4.三级缓存(Spring 中的二级缓存)


这里只是教程中称其为三级缓存,其实它是 Spring 中的二级缓存的。

工厂和缓存相互配合可以解决循环依赖中代理对象创建过晚的问题。

Java 面试宝典【学习笔记】Spring 篇_第35张图片


简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 Spring 仍然希望代理的创建时机在 init 之后。

只有在发生了循环依赖的时候,才会将代理的创建时机提前。


思路如下:

  • 图中 factories.put(fa) 放入 singletonFactories 的既不是原始对象,也不是代理对象,而是工厂对象 fa
  • 以后程序走到 singletonFactories 时,fa -> pa || a 会检查 bean 对象是否需要 AOP
    • 如果需要,fa 生产出来的产品就是代理 pa
    • 如果不需要,fa 生产出来的产品就是原始对象 a
  • 之后走到了 factories.put(fb) 的时候,它也会放入缓存 singletonFactories 中放入工厂对象 fb
  • 之后执行 b.setA() 的时候,它会调用 factories.get()(去 singletonFactories 去找对象),拿到了 fa
    拿到了 fa 之后,其还会判断该 bean 对象是否需要 AOP:如果需要则立刻执行 AOP (即创建代理)并返回。
  • 这里假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,并创建了代理 pa【红色箭头 5】
  • 最终返回的就是刚刚创建的代理 pab.setA() 得以成功注入代理对象,保证了正确性【红色箭头 7】
  • 最后它还需要把 pa 存入新加的 earlySingletonObjects 缓存【红色箭头 6】
  • a.init 完成后,就无需二次创建代理了。那么从哪儿找到 pa 呢?
    就在 earlySingletonObjects 中(pa 已经存在此处)【蓝色箭头 9】

当成品对象产生,放入 singletonObject 后,singletonFactoriesearlySingletonObjects 就中的对象就没有用处了,清除即可


3.4.总结


单例 set 方法(包括成员变量)循环依赖,Spring 会利用三级缓存来解决,无需额外的配置

  • Spring 的三级缓存
    • 一级缓存存放成品对象
    • Spring 中的二级缓存中存放了发生循环依赖的产品对象(可能是原始的 bean,也可能是代理 bean
    • Spring 中的三级缓存中存放的是工厂对象,发生循环依赖的时候,会调用工厂获取产品
  • Spring 期望在初始化时创建代理。
    • 但是如果发生了循环依赖,会由工厂提前创建代理,后续初始化时就不必重复创建代理
  • 二级缓存的意义:如果提前创建了代理对象,在最后的阶段就需要从二级缓存中获取此代理对象,作为最终的结果

构造方法及多例循环依赖解决的办法

  • @Lazy 注解(使用代理方式来解决问题)
  • @scope 注解(使用代理方式来解决问题)
  • ObjectFactory & ObjectProvider(使用工厂方式来解决问题)
  • JSR330 的接口 Provider 接口(使用工厂方式来解决问题)

4.Spring 事务失效


要求掌握 Spring 事务失效的八种场景及原因


  • 抛出检查异常导致事务不能正常回滚
    • 原因:Spring 默认只会回滚非检查异常
    • 解决办法:在注解上配置 rollbackFor 属性(如 @Transactional(rollbackFor = Exception.class)
  • 业务方法内自己 try-catch 导致事务不能正确回滚
    • 原因:事务通知只有捕捉到了目标抛出的异常,才可以进行后续的回滚处理。如果目标自己处理掉异常,事务通知就无法获悉了。
    • 解决办法①:在 catch 块添加 throw new RuntimeException(e);
    • 解决办法②:在 catch 块手动设置 TransactionStatus.setRollbackOnly()
  • AOP 切面顺序导致事务不能正确回滚
    • 原因:事务切面的优先级是最低的。但如果自定义切面的优先级和他一样,在还是自定义切面在更内层。
      如果此时自定义切面没有正确抛出异常 … …
    • 解决方法:同情况 2(手动调整切面的执行顺序的解决办法 不是很推荐)
  • public 方法导致事务失效
    • 原因: Spring 为方法创建代理、添加事务通知、前提条件的方法都必须是 public 修饰符
    • 解决办法:更改方法为 public 方法(手动添加配置,使得事务对非公共方法生效的方法,不推荐)
  • 父子容器导致的事务失效
    • 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
    • 解决办法①:各扫描各的包,不要图简便直接扫描父级包
    • 解决办法②:不用父子容器,直接把所有 bean 放在同一容器
    • 应用场景:开发传统的 MVC 程序,在整合 SpringMVC 时会遇到这个问题;
      SpringBoot 项目是不会遇到这个问题的,因为其把所有的 bean 都放在一个容器里了。
  • 调用本类方法导致传播行为失效
    • 原因:本类方法调用不经过代理,因此无法增强(常见的 AOP 失效场景)
    • 解法①:依赖注入自己(代理)来调用
    • 解法②:通过 AopContext 拿到代理对象,来调用
    • 解法③:通过 CTWLTW 实现功能增强
  • @Transactional 没有保证原子行为
    • 原因:事务的原子性仅涵盖 insertupdatedeleteselect … for update 语句,select 方法并不阻塞
  • @Transactional 方法导致的 synchronized 失效
    • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
    • 解法①:synchronized 范围应扩大至代理方法调用
    • 解法②:使用 select … for update 替换 select(这里更推荐解法②,在数据库层面解决原子性和加锁问题会更好)

4.1.抛出检查异常导致事务不能正确回滚


@Service
public class Service1 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
    
}

  • 原因:Spring 默认事务只会在遇到 RuntimeException 的时候回滚
  • 解法:配置 rollbackFor 属性
    • @Transactional(rollbackFor = Exception.class),如此可以让事务在遇到非运行时异常时也执行回滚操作。

4.2.业务方法内自己 try-catch 异常导致事务不能正确回滚


@Service
public class Service2 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount)  {
        try {
            int fromBalance = accountMapper.findBalanceBy(from);
            if (fromBalance - amount >= 0) {
                accountMapper.update(from, -1 * amount);
                new FileInputStream("aaa");
                accountMapper.update(to, amount);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
  • 解法①:异常原样抛出
    • catch 块添加 throw new RuntimeException(e);
  • 解法②:手动设置 TransactionStatus.setRollbackOnly()
    • catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

4.3.AOP 切面顺序导致事务不能正确回滚


@Service
public class Service3 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
@Aspect
public class MyAspect {
    @Around("execution(* transfer(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        LoggerUtils.get().debug("log:{}", pjp.getTarget());
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

  • 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在更内层。
    这时若自定义切面没有正确抛出异常,事务切面就无法捕捉到异常,虽然程序会报错,但是数据仍然被提交了。
  • 解法①、解法②:同情况 2 中的解法①、解法②(两种思路:要么告诉外层有异常,要么告诉外层需要进行回滚操作)
  • 解法③:调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1) (不推荐)

4.4.非 public 方法导致的事务失效


@Service
public class Service4 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}

  • 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public
  • 解法①:改为 public 方法
  • 解法②:添加 bean 配置如下(不推荐)
    @Bean
    public TransactionAttributeSource transactionAttributeSource() {
    	// 使得 Transaction 对非公共方法也生效
        return new AnnotationTransactionAttributeSource(false);
    }
    

4.5.父子容器导致的事务失效


  • 业务类
package day04.tx.app.service;

public class Service5 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}
  • 控制器类
package day04.tx.app.controller;

@Controller
public class AccountController {

    @Autowired
    public Service5 service;

    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        service.transfer(from, to, amount);
    }
}
  • App 配置类
@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
    // ... 有事务相关配置
}
  • Web 配置类
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
    // ... 无事务配置
}

现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效


  • 原因:在上述操作中,子容器的扫描范围过大,把未加事务配置的 service 扫描进来
  • 解法①:各扫描各的,不要图简便
  • 解法②:不要用父子容器,所有 bean 放在同一容器

4.6.调用本类方法导致传播行为失效


@Service
public class Service6 {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

  • 原因:本类方法调用不经过代理,因此无法增强
  • 解法①:依赖注入自己(代理)来调用
  • 解法②:通过 AopContext 拿到代理对象,来调用
  • 解法③:通过 CTW(编译时织入),LTW(加载时织入) 来实现功能增强

  • 解法①:依赖注入自己(代理)来调用
@Service
public class Service6 {

	@Autowired
	private Service6 proxy; // 本质上是一种循环依赖

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
		System.out.println(proxy.getClass());
		proxy.bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

  • 解法②:还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class Service6 {
    
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        ((Service6) AopContext.currentProxy()).bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

4.7.@Transactional 没有保证原子行为


@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况

Java 面试宝典【学习笔记】Spring 篇_第36张图片


  • 原因:事务的原子性仅涵盖 insertupdatedeleteselect … for update 语句,select 方法并不阻塞
  • 如上图所示,红色线程和蓝色线程的查询都发生在扣减之前,都以为自己有足够的余额做扣减

4.8.@Transactional 方法导致的 synchronized 失效


针对上面的问题,能否在方法上加 synchronized 锁来解决呢?

@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public synchronized void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

答案是不行,原因如下:

  • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
  • 可以参考下图发现,蓝色线程的查询只要在红色线程提交之前执行,那么依然会查询到有 1000 足够余额来转账

Java 面试宝典【学习笔记】Spring 篇_第37张图片

  • 解法①:synchronized 范围应扩大至代理方法调用
  • 解法②:使用 select … for update 替换 select
    • 意向排他锁:意向排他锁与表读锁、写锁都是互斥的

这里涉及到一些数据库的基础知识,如果各位有兴趣了解的话,不妨看一下我之前写的关于 MySQL 的文章:【MySQL 学习笔记②


5.SpringMVC 执行流程


要求

  • 掌握 Spring MVC 的执行流程
  • 了解 Spring MVC 的重要组件的作用

本文在这里把整个流程分成了三个阶段:准备阶段、匹配阶段、执行阶段


5.1.准备阶段


  1. Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
  2. init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
  3. refresh 过程中会创建并初始化 SpringMVC 中的重要组件
    例如 MultipartResolverHandlerMappingHandlerAdapterHandlerExceptionResolverViewResolver
  4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用

Java 面试宝典【学习笔记】Spring 篇_第38张图片


5.2.匹配阶段


  1. 用户发送的请求统一到达前端控制器 DispatcherServlet
  2. DispatcherServlet 遍历所有 HandlerMapping,找到与路径匹配的处理器
    1. HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。
      其中能识别 @RequestMapping 的优先级最高
    2. 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息
    3. 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好

Java 面试宝典【学习笔记】Spring 篇_第39张图片

  1. HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回

Java 面试宝典【学习笔记】Spring 篇_第40张图片

  1. 遍历 HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用

Java 面试宝典【学习笔记】Spring 篇_第41张图片


5.3.执行调用阶段


  1. 执行拦截器 preHandle

Java 面试宝典【学习笔记】Spring 篇_第42张图片

  1. HandlerAdapter 调用 HandlerMethod
    1. 调用前处理不同类型的参数
    2. 调用后处理不同类型的返回值

Java 面试宝典【学习笔记】Spring 篇_第43张图片

  1. 第 2 步没有异常
    1. 返回 ModelAndView
    2. 执行拦截器 postHandle 方法
    3. 解析视图,得到 View 对象,进行视图渲染

Java 面试宝典【学习笔记】Spring 篇_第44张图片

  1. 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程

Java 面试宝典【学习笔记】Spring 篇_第45张图片

  1. 最后都会执行拦截器的 afterCompletion 方法
  2. 如果控制器方法标注了 @ResponseBody 注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染

6.Spring 注解


这里笔者推荐一篇蛮不错的的文章:Spring & SpringBoot 常用注解总结


在官方提供的资料里,有一个文件:面试题-spring-注解.xmid

Java 面试宝典【学习笔记】Spring 篇_第46张图片


  • 注解的详细列表请参考:面试题-spring-注解.xmid
  • 下面列出了视频中重点提及的注解,考虑到诸位对大部分注解已经比较熟悉了,故这里仅对对个别的注解作简要说明

Java 面试宝典【学习笔记】Spring 篇_第47张图片


6.1.事务注解


  • @EnableTransactionManagement:启用声明式的事务控制。
    • 会额外加载 4 个 bean
      • BeanFactoryTransactionAttributeSourceAdvisor:事务切面类
      • TransactionAttributeSource:该类用来解析事务属性(可以用来解析 @Transactional
      • TransactionInterceptor:事务拦截器(这里面是有通知功能的)
      • TransactionalEventListenerFactory:事务监听器工厂
名称 @EnableTransactionManagement
类型 配置类注解
位置 配置类定义上方
作用 设置当前 Spring 环境中开启注解式事务支持

  • @Transactional:启用声明式的事务控制。
名称 @Transactional
类型 接口注解 / 类注解 / 方法注解
位置 业务层接口上方 / 业务层实现类上方 / 业务方法上方
作用 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)

6.2.核心


  • @Order:控制 bean 的优先级(执行顺序)。
    • 注解内的数越小,其优先级越高。最低优先级为取最大整型时。

6.3.切面


  • @EnableAspectJAutoProxy
    • 会加载 AnnotationAwareAspectJAutoProxyCreator,它是一个 bean 后处理器,用来创建代理
    • 如果没有配置 @EnableAspectJAutoProxy,又需要用到代理(如事务),
      则会使用 InfrastructureAdvisorAutoProxyCreator 这个 bean 后处理器
名称 @EnableAspectJAutoProxy
类型 配置类注解
位置 配置类定义上方
作用 开启注解格式 AOP 功能

下方的三个注解虽然在 AOP 中常常出现,但它们并不是 Spring 的注解。

@Aspect@Pointcut@Before 三个注解都属于 AspectJ 框架。

AspectJ 是一个面向切面的框架,是目前最好用的 AOP 框架。

  • @Aspectorg/aspectj/lang/annotation/Aspect.java
名称 @Aspect
类型 类注解
位置 切面类定义上方
作用 设置当前类为 AOP 切面类
  • @Pointcutorg/aspectj/lang/annotation/Pointcut.java
名称 @Pointcut
类型 方法注解
位置 切入点方法定义上方
作用 设置切入点方法
属性 value(默认):切入点表达式
  • @Beforeorg/aspectj/lang/annotation/Pointcut.java
名称 @Before
类型 方法注解
位置 通知方法定义上方
作用 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

6.4.组件扫描与配置类


  • @Component:一个通用的注解,可以标注任意类为 Spring 组件
    • @Controller:对应 SpringMVC 中的控制层
    • @Service:对应表现层
    • @Repository:对应数据层
名称 @Component/@Controller/@Service/@Repository
类型 类注解
位置 类定义上方
作用 设置该类为 Spring 管理的 bean
属性 value(默认):定义 beanid

  • @ComponentScan:扫描包,把包里面所有标注了特定注解的类交给 Spring 容器管理
名称 @ComponentScan
类型 类注解
位置 类定义上方
作用 设置 Spring 配置类扫描路径,用于加载使用注解格式定义的 bean
相关属性 value(默认):扫描路径,此路径可以逐层向下扫描

excludeFilters:排除扫描路径中加载的 bean,需要指定类别(type)和具体项(classes

includeFilters:加载指定的 bean,需要指定类别(type)和具体项(classes

  • @Conditional
    • 在组件扫描时,做一些条件装配的动作。只有符合条件的,才会加入到 Spring 的容器中。
    • 该注解也可以和 @Bean 配合使用。在配置类解析 @Bean 注解时,会使用到该注解,发挥上述的功能。

  • @Configuration:可以用来声明配置类。可以使用 @Component 注解替代该注解,但 @Configuration 注解更加语义化。
    • 配置类其实相当于一个工厂,标注 @Bean 注解的方法相当于工厂方法
    • @Bean 不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法(方法参数越多,注入优先级越高)
    • @Configuration 默认会为标注的类生成代理,其目的是保证 @Bean 方法相互调用时,仍然能保证其单例特性
      • 可以使用 proxyBeanMethods = false 来关闭这个代理
    • @Configuration 中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,使其依赖注入失败。
      • 解决方法:改用静态工厂方法;对 @Bean 的方法进行参数依赖注入的操作;针对 Mapper 扫描,用注解方式。
名称 @Configuration
类型 类注解
位置 类定义上方
作用 设置该类为 Spring 配置类
属性 value(默认):定义 beanid
  • @Bean
名称 @Bean
类型 方法注解
位置 方法定义上方
作用 设置该方法的返回值作为 Spring 管理的 bean
属性 value(默认):定义 beanid

  • @Import:指定一个类型,根据类型或类型本身找到一些其他的类,之后再把它们交给 Spring 容器管理。
    • 四种用法
      • 引入单个 bean
      • 引入一个配置类
      • 通过 Selector 引入多个类
      • 通过 beanDefinition 注册器
    • 解析规则
      • 同一配置类中,@Import 先解析 @Bean 后解析
      • 同名定义,默认后面解析的会覆盖前面解析的
      • 在不允许覆盖的情况下,如何能够让 MyConfig(主配置类) 的配置优先?(虽然覆盖方式能解决)
        • 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析 @Bean,再 Import
名称 @Import
类型 类注解
位置 类定义上方
作用 导入配置类
属性 value(默认):定义导入的配置类类名
    当配置类有多个时使用数组格式一次性导入多个配置类

  • @Lazy
    • 加在类上,表示此类延迟实例化、初始化
    • 加在方法参数上,此参数会以代理方式注入

  • @PropertySource
名称 @PropertySource
类型 类注解
位置 类定义上方
作用 加载 properties 文件中的属性值
属性 value(默认):设置加载的 properties 文件对应的文件名或文件名组成的数组

  • Scope
名称 @Scope
类型 类注解
位置 类定义上方
作用 设置该类创建对象的作用范围
可用于设置创建出的 bean 是否为单例对象
属性 value(默认):定义 bean 作用范围
默认值 singleton(单例),可选值 prototype(非单例)

6.5.依赖注入


  • @Autowired@Autowired 是按照类型注入的。
    • 当根据类型在容器中找到多个 bean,注入参数的属性名又和容器中 bean 的名称不一致,这个时候该如何解决呢?
    • 此时就需要用到 @Qualifier 了,可以靠该注解来指定注入哪个名称的 bean 对象。
  • @Qualifier:该注解后的值就是需要注入的 bean 的名称。
    • 注意@Qualifier 不能独立使用,必须和 @Autowired 一起使用
名称 @Autowired
类型 属性注解 或 方法注解 或 方法形参注解
位置 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 或 方法形参前面
作用 为引用类型属性设置值
属性 requiredtrue / false,定义该属性是否允许为 null
名称 @Qualifier
类型 属性注解 或 方法注解(了解)
位置 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方
作用 为引用类型属性指定注入的 beanId
属性 value(默认):设置注入的 beanId

  • @Value:一般会被用在从 properties 配置文件中读取内容进行使用
名称 @Value
类型 属性注解 或 方法注解(了解)
位置 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方
作用 为 基本数据类型 或 字符串类型 属性设置值
属性 value(默认):要注入的属性值

6.6.mvc


关于 SpringMVC 方面的注解,其实我之前也写过一篇博文:SpringMVC 相关注解【学习笔记】。诸位若有兴趣的话,不妨阅览一二。


6.6.1.mvc mapping


  • @RequestMapping:可以派生多个注解如 @GetMapping
名称 @RequestMapping
类型 类注解 或 方法注解
位置 SpringMVC 控制器类或方法定义上方
作用 设置当前控制器方法请求访问路径
相关属性 value(默认),请求访问路径

Rest 风格的代码里,每个方法的 @RequestMapping 注解中都要使用 method 属性定义请求方式,这样一来,重复性太高了。

可以使用 @GetMapping@PostMapping@PutMapping@DeleteMapping 代替

名称 @GetMapping + @PostMapping + @PutMapping + @DeleteMapping
类型 方法注解
位置 基于 SpringMVCRestful 开发控制器方法定义上方
作用 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @GetMapping 对应 GET 请求
相关属性 value(默认):请求访问路径

6.6.2.mvc rest


  • @RequestBody:使用该注解可以将请求的 body 中的 json 字符串转换为 Java 对象
名称 @RequestBody
类型 形参注解
位置 SpringMVC 控制器方法形参定义前面
作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

  • @ResponseBody:把控制器方法返回的 Java 对象解析为 json 数据,然后写入响应体(不走视图解析层)
名称 @ResponseBody
类型 方法注解 或 类注解
位置 SpringMVC 控制器方法定义上方和控制类上
作用 设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能
相关属性 pattern:指定日期时间格式字符串

  • @RestController

使用 @RestController 注解替换 @Controller@ResponseBody 注解,简化书写

名称 @RestController
类型 类注解
位置 基于 SpringMVCRESTful 开发控制器类定义上方
作用 设置当前控制器类为 RESTful 风格,等同于 @Controller@ResponseBody 两个注解组合功能

@RestController 注解标注在类上,表明这是一个控制器 bean,直接将函数的返回值填入 HTTP 响应体中。


  • @ResponseStatus:可以控制响应的状态码

6.6.3.mvc 统一处理


  • @ControllerAdvice,结合 @ResponseBody 可为 @RestControllerAdvice
名称 @RestControllerAdvice
类型 类注解
位置 REST 风格开发的控制器增强类定义上方
作用 REST 风格开发的控制器类做增强

说明:此注解自带 @ResponseBody 注解与 @Component 注解,具备对应的功能

Java 面试宝典【学习笔记】Spring 篇_第48张图片


  • @ExceptionHandler
名称 @ExceptionHandler
类型 方法注解
位置 专用于异常处理的控制器方法上方
作用 设置指定异常的处理方案,功能等同于控制器方法,
出现异常后终止原始控制器执行,并转入当前方法执行

说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常。

注意

  • 如果将该注解标注的方法放在一个单独的控制器上,就相当于是一个局部的异常处理器
  • 如果将该注解标注的方法放在标注了 @ControllerAdvice 标注的类中,那就相当于一个全局的异常处理器

一般我们可以用 @RestControllerAdvice + @ExceptionHandler 做统一异常处理


6.6.4.mvc 参数


  • @PathVariable
名称 @PathVariable
类型 形参注解
位置 SpringMVC 控制器方法形参定义前面
作用 绑定路径参数与处理器方法形参间的关系,要求 路径参数名形参名 一一对应
@RequestMapping(value="/users/{id}", method = RequestMethod.DELETE)
@ReponseBody
public String delete(@PathVariable Integer id){ }

  • @RequestParam
名称 @RequestParam
类型 形参注解
位置 SpringMVC 控制器方法形参定义前面
作用 绑定请求参数与处理器方法形参间的关系
相关参数 required:是否为必传参数

defaultValue:参数默认值

@RequestParam 可以用来获取查询参数(比如 http://localhost/user?type=student)。

此外,它还可以传递集合参数(http://localhost/listParam?likes=game&likes=music&likes=travel

同名请求参数可以使用 @RequestParam 注解映射到对应名称的集合对象中作为数据

@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
    System.out.println("集合参数传递 likes ==> "+ likes);
    return "{'module':'list param'}";
}

6.6.5.mvc ajax


  • @CrossOrigin:可以解决 Ajax 的跨域问题
    • 原理是往响应头上加一些特殊的响应头,允许 Ajax 进行跨域请求
    • B/S 上没有这种跨域问题,在 JavaScript 中使用 Ajax 进行请求的时候会出现跨域问题。

6.7.boot


6.7.1.boot auto


  • @SpringBootApplication
    • 有三个核心注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
  • @EnableAutoConfiguration:主要作用是去找到自动配置类,然后把自动配置类里相关联的 bean 都注册到容器
  • @SpringBootConfiguration:仅说明这个是一个 Spring Boot 的配置类,几乎与原生的 @Configuration 的功能等价。

6.7.2.boot condition


只有在条件成立的时候,才会执行后续的操作

  • @ConditionalOnClassclasspath 下存在某个 class 时,条件才成立
  • @ConditionalOnMissingBeanbeanFactory 内不存在某个 bean 时,条件才成立
  • @ConditionalOnProperty,配置文件中存在某个我们期望的 property(键、值)时,条件才成立

6.7.3.boot properties


  • @ConfigurationProperties,会将当前 bean 的属性与配置文件中的键值进行绑定,可以简化 bean 的初始化
  • @EnableConfigurationProperties,会添加两个较为重要的 bean
    • ConfigurationPropertiesBindingPostProcessorbean 后处理器,在 bean 初始化前调用下面的 binder
    • ConfigurationPropertiesBinder:真正执行绑定操作

7.SpringBoot 自动配置原理


要求:掌握 SpringBoot 自动配置原理


7.1.@Configuration的底层原理(前置知识)


7.1.1.基本概述


org/springframework/context/annotation/Configuration.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
	@AliasFor(annotation = Component.class)
	String value() default "";
	
	boolean proxyBeanMethods() default true;
}

@Configuration:可以用来声明配置类。

可以用 @Component 注解替代该注解,但 @Configuration 注解更加语义化。


  • 配置类其实相当于一个工厂,标注 @Bean 注解的方法相当于工厂方法
  • @Bean 不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法(方法参数越多,注入优先级越高)
  • @Configuration 默认会为标注的类生成代理,其目的是保证 @Bean 方法相互调用时,仍然能保证其单例特性
    • 可以使用 proxyBeanMethods = false 来关闭这个代理
  • @Configuration 中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,造成其依赖注入失败。
    • 解决方法:改用静态工厂方法;直接对 @Bean 的方法进行参数的依赖注入;针对 Mapper 扫描可以改用注解方式。

7.1.2.配置类相当于一个工厂


注意点(一)

  • 配置类相当于一个工厂,标注了 Bean 注解的方法相当于工厂方法

这个是测试代码的主方法,我先贴在这里了(之后不再赘述)。

public static void main(String[] args) {
    GenericApplicationContext context = new GenericApplicationContext();

    // BeanFactory 的后处理器
    AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

    context.registerBean("myConfig", MyConfig.class);
    context.refresh();
    System.out.println();
    // System.out.println(context.getBean(MyConfig.class).getClass());
}

@Configuration
static class MyConfig {
    @Bean
    public Bean1 bean1() {
        System.out.println("bean1()");
        return new Bean1();
    }
}

static class Bean1 {}

Java 面试宝典【学习笔记】Spring 篇_第49张图片


7.1.3.@Bean 不支持方法重载


注意点(二)

  • @Bean 不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法
  • 方法的参数越多,注入的优先级越高

@Configuration
static class MyConfig {
    @Bean
    public Bean1 bean1() {
        System.out.println("bean1()");
        return new Bean1();
    }

    @Bean
    public Bean1 bean1(@Value("${java.class.version") String a) {
        System.out.println("bean1(" + a + ")");
        return new Bean1();
    }

    @Bean
    public Bean1 bean1(@Value("${java.class.version}") String a, @Value("${JAVA_HOME}") String b) {
        System.out.println("bean1(" + a + "," + b + ")");
        return new Bean1();
    }
}

static class Bean1 { }

最终输出的是参数最多的方法

bean1(52.0,C:\Program Files\Java\jdk1.8.0_144)

7.1.5.该注解默认为标注的类生成代理


注意点(三)

  • @Configuration 默认会为标注的类生成代理其目的是保证 @Bean 方法相互调用时仍然能保证其单例特性
  • 可以使用 proxyBeanMethods = false 来关闭这个代理

// @Configuration
static class MyConfig {
    @Bean
    public Bean1 bean1(Bean2 bean2) {
        System.out.println("bean1()");
        System.out.println(bean2);
        System.out.println(bean2);
        System.out.println(bean2);
        return new Bean1();
    }

    @Bean
    public Bean2 bean2() {
        System.out.println("bean2()");
        return new Bean2();
    }
}

static class Bean1 { }
static class Bean2 { }

上方代码类似于手动调用 set 方法建立依赖关系,最终打印的 bean2 都是同一个对象

bean2()
bean1()
annotation.ConfigurationTest$Bean2@176d53b2
annotation.ConfigurationTest$Bean2@176d53b2
annotation.ConfigurationTest$Bean2@176d53b2

当然,我们也可以不注入参数直接调用 bean2() 方法

@Configuration
static class MyConfig {
    @Bean
    public Bean1 bean1() {
        System.out.println("bean1()");
        System.out.println(bean2());
        System.out.println(bean2());
        System.out.println(bean2());
        return new Bean1();
    }
    // ... ...
}

最终输出的仍然是同一个对象

bean1()
bean2()
annotation.ConfigurationTest$Bean2@4b14c583
annotation.ConfigurationTest$Bean2@4b14c583
annotation.ConfigurationTest$Bean2@4b14c583

但是如果没有在配置类上加 @Configuration 注解的话,那么程序每次都会重新创建对象

bean1()
bean2()
annotation.ConfigurationTest$Bean2@345965f2
bean2()
annotation.ConfigurationTest$Bean2@429bd883
bean2()
annotation.ConfigurationTest$Bean2@4d49af10
bean2()

@Configuration 默认会为标注的类生成代理对象,之后就不会再创建了。

这样可以保证各个(@Bean)方法相互调用时的的单例特性

class annotation.ConfigurationTest$MyConfig$$EnhancerBySpringCGLIB$$46049279

可以使用 proxyBeanMethods = false 来关闭这个代理。

@Configuration(proxyBeanMethods = false)

7.1.6.实例工厂方法会导致配置类依赖注入失败


注意点(四)

  • @Configuration 中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,造成其依赖注入失败。
  • 解决方法:
    • 改用静态工厂方法;
    • 直接在 @Bean 的方法上进行参数的依赖注入;
    • 针对 Mapper 扫描可以改用注解方式。

@Configuration
static class MyConfig {
    @Value("${java.class.version}")
    private String version;

    @Bean
    public MapperScannerConfigurer configurer() {
        MapperScannerConfigurer scanner = new MapperScannerConfigurer();
        scanner.setBasePackage("aaa");
        return scanner;
    }

    @Bean
    public Bean3 bean3() {
        System.out.println("bean3()" + version);
        return new Bean3();
    }
}

static class Bean3 { }

由下面的输出信息可以得知依赖注入失败了,原因就是配置类被提前创建了。

[INFO] 11:49:13.289 [main] - 
	Cannot enhance @Configuration bean definition 'myConfig' since its singleton instance has been created too early. 
	The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: 
		Consider declaring such methods as 'static'. 
bean3()null

后处理器是在 refresh() 的第五步阶段创建的

// Invoke factory processors registered as beans in the context.

invokeBeanFactoryPostProcessors(beanFactory);

在上面的代码中,后处理器工厂方法作为配置类的成员方法,要创建后处理器,就必须先创建配置类的对象。

但是配置类应该在 refresh() 的第十一步阶段创建的

// Instantiate all remaining (non-lazy-init) singletons.

finishBeanFactoryInitialization(beanFactory);

  • 解决办法①:使用静态的工厂方法(通过类名即可调用该方法,无需创建实例对象,再通过实例对象来调用该方法)
@Bean
public static MapperScannerConfigurer configurer(){ ... }

  • 解决办法②:直接在 @Bean 的方法上使用参数的依赖注入(不管之前如何创建,bean3() 方法是不受影响的)
@Bean
public Bean3 bean3(@Value("${java.class.version}") String version) { ... }

  • 解决办法③:针对 Mapper 扫描,可以改用注解方式。
@Configuration
@MapperScan("aaa")
static class MyConfig { ... }

7.2.@Import 的底层原理(前置知识)


7.2.1.基本概述


org/springframework/context/annotation/Import.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	Class<?>[] value();
}

@Import:指定一个类型,根据类型或类型本身找到一些其他的类,之后再把它们交给 Spring 容器管理。


  • 四种用法
    • 引入单个 bean
    • 引入一个配置类
    • 通过 Selector 引入多个类
    • 通过 beanDefinition 注册器
  • 解析规则
    • 同一配置类中,@Import 先解析 @Bean 后解析
    • 同名定义,默认后面解析的会覆盖前面解析的
    • 在不允许覆盖的情况下,如何能够让 MyConfig(主配置类) 的配置优先?(虽然覆盖方式能解决)
      • 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析 @Bean,再 Import

7.2.2.四种用法


下方是测试代码中的主方法,我先贴在这里了。

public class ImportTest {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(MyConfig.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

第一种用法:引入单个 bean


@Configuration
@Import(Bean1.class) // 引入单个 bean
static class MyConfig { }

static class Bean1 { }

控制台输出(部分)

annotation.ImportTest$MyConfig
annotation.ImportTest$Bean1

第二种用法:引入一个配置类


@Configuration
@Import(OtherConfig.class) // 引入配置类
static class MyConfig { }

@Configuration
static class OtherConfig {
    @Bean
    public Bean2 bean2() { return new Bean2(); }
}

static class Bean1 { }
static class Bean2 { }

控制台输出(部分)

annotation.ImportTest$MyConfig
annotation.ImportTest$OtherConfig
bean2

第三种用法先实现 ImportSelector 接口,然后重写 selectImports 方法最后再通过这个 Selector 引入多个类


@Configuration
@Import(MySelector.class) // 通过 Selector 引入多个类
static class MyConfig { }

static class MySelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Bean3.class.getName(), Bean4.class.getName()};
    }
}

static class Bean3 { }
static class Bean4 { }

输出结果(部分)

annotation.ImportTest$MyConfig
annotation.ImportTest$Bean3
annotation.ImportTest$Bean4

由上面的输出结果可以得知 ImportSelector 接口的实现类并不会变成一个 Bean,它只是一个选择器。


第四种用法:通过 beanDefinition 注册器


@Configuration
@Import(MyRegistrar.class) // 通过 beanDefinition 注册器来注册 bean
static class MyConfig {

}

static class MyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("bean5", BeanDefinitionBuilder.genericBeanDefinition(Bean5.class).getBeanDefinition());
    }
}

控制台输出(部分)

annotation.ImportTest$MyConfig
bean5

同上面介绍的选择器一样,注册器自身是不会变成 Bean


7.2.3.ImportsSelector 的解析规则


解析规则

  • 同一配置类中,@Import 先解析 @Bean 后解析
  • 同名定义,默认后面解析的会覆盖前面解析的

public class ImportSelectorTest {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // beanFactory.setAllowBeanDefinitionOverriding(false); // 不允许同名定义覆盖
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
        context.registerBean(MyConfig.class);
        context.refresh();
        System.out.println(context.getBean(MyBean.class));
    }

    @Configuration
    @Import(OtherConfig.class)
    static class MyConfig { // 主配置(程序员编写)
        @Bean
        public MyBean myBean() { return new Bean1(); }
    }

    @Configuration
    static class OtherConfig { // 从属配置(自动配置)
        @Bean
        public MyBean myBean() { return new Bean2(); }
    }

    interface MyBean { }
    static class Bean1 implements MyBean { }
    static class Bean2 implements MyBean { }
}

输出结果

annotation.ImportSelectorTest$Bean1@49438269

显然,运行上方的代码没有报错,可以正常运行。

在有同名定义的 bean 的时候,默认后面解析的会覆盖前面解析的。

同一配置类中,@Import 先解析 @Bean 后解析。


我们可以在拿到 beanFactory 后,通过设置它的属性,让其不能使用同名定义覆盖的规则

beanFactory.setAllowBeanDefinitionOverriding(false); // 默认值是 true,这里设置为 false(不允许同名定义覆盖)

此时再次运行上方的代码后,控制台输出报错信息(这里只贴了部分关键信息)

Exception in thread "main" 
	org.springframework.beans.factory.support.BeanDefinitionOverrideException: 
		Invalid bean definition with name 'myBean' defined in annotation.
		ImportSelectorTest$MyConfig: Cannot register bean definition [ ... ] for bean 'myBean': 
			There is already [ ... 
				factoryBeanName=annotation.ImportSelectorTest$OtherConfig; 
				factoryMethodName=myBean; 
				defined in annotation.ImportSelectorTest$OtherConfig] 
			bound.

那么在不允许覆盖的情况下,如何能够让 MyConfig(主配置类) 的配置优先呢?

使用 @ConditionalOnMissingBean 注解?程序依然会报错

  • 该注解的意思是:如果当前容器中不存在同名 Bean,就不装配。
  • 但在上面的代码里,我们是先注入的从配置类,是 @Import 注解先解析的,这时候容器里还没有 MyConfig
  • 所以最后的结果是从属配置类就注册成功了。然后程序依旧会报错。

正确解法是使用 DeferredImportSelector,延迟 Import 操作,让它最后工作。

可以简单认为先解析主配置中的 @Bean,最后才解析 @Import(XXX.class) 中出现的类。


7.2.4.DeferredImportsSelector


DeferredImportSelectorImportSelector 的子接口

public interface DeferredImportSelector extends ImportSelector 

使用 DeferredImportSelector,可以延迟 Import 操作,让它最后工作。

可以简单认为先解析主配置中的 @Bean,最后才解析 @Import(XXX.class) 中出现的类。


SpringBoot 的自动装配里会用到它:

  • 优先处理完主配置类中的所有配置,最后才会处理 从属配置 / 默认配置 类中的配置(这时候用条件检查就没有问题了)。

DeferredImportSelector + @ConditionalOnMissingBean(条件检查)


public class DeferredImportSelectorTest {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        beanFactory.setAllowBeanDefinitionOverriding(false); 
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
        context.registerBean(MyConfig.class);
        context.refresh();
        System.out.println(context.getBean(MyBean.class));
    }

    @Configuration
    @Import(MySelector.class)
    static class MyConfig { // 主配置
        @Bean
        public MyBean myBean() { return new Bean1(); }
    }

    static class MySelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{OtherConfig.class.getName()};
        }
    }

    @Configuration
    static class OtherConfig { // 从属配置
        @Bean
        @ConditionalOnMissingBean
        public MyBean myBean() { return new Bean2(); }
    }

    interface MyBean { }
    static class Bean1 implements MyBean { }
    static class Bean2 implements MyBean { }
}

运行上面的程序后,控制台没有报错,成功打印了 Bean1

annotation.DeferredImportSelectorTest$Bean1@672872e1

7.3.自动配置原理(@SpringBootApplication


@SpringBootConfiguration 是一个组合注解:由 @ComponentScan@EnableAutoConfiguration@SpringBootConfiguration 组成


  • @SpringBootConfiguration 与普通 @Configuration 相比
    • 两者唯一区别是前者要求 @SpringBootConfiguration 所注释的配置类在整个 app 中只可以出现一次
  • @ComponentScan:组件扫描
    • excludeFilters:用在组件扫描时进行排除的操作,也会排除 SpringBoot 自身带的自动配置类
  • @EnableAutoConfiguration 也是一个组合注解,由下面注解组成
    • @AutoConfigurationPackage:用来记住扫描的起始包
    • @Import(AutoConfigurationImportSelector.class):用来加载 META-INF/spring.factories 中的自动配置类
      • 默认自动配置类的存放的路径:META-INF/spring.factories

org/springframework/boot/autoconfigure/SpringBootApplication.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

7.4.为什么不使用 @Import 直接引入自动配置类


有两个原因:

  • 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
  • 直接用 @Import(自动配置类.class),引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置

因此,采用了 @Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 去读取 META-INF/spring.factories 中的自动配置类,实现了弱耦合。
  • 另外 AutoConfigurationImportSelector.class 实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析

org/springframework/boot/autoconfigure/EnableAutoConfiguration.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java

public class AutoConfigurationImportSelector implements DeferredImportSelector,
        BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, 
        EnvironmentAware, Ordered { ... }

8.Spring 中的设计模式


要求:掌握 Spring 中常见的设计模式


8.1.Spring 中的 Singleton


请大家区分 singleton patternSpring 中的 singleton bean

  • 使用单例模式的目的:Ensure a class only has one instance, and provide a global point of access to it
  • 显然 Spring 中的 singleton bean 并非实现了单例模式,singleton bean 只能保证每个容器内,相同 idbean 是单实例

当然 Spring 中也用到了单例模式,例如

  • org.springframework.transaction.TransactionDefinition#withDefaults
  • org.springframework.aop.TruePointcut#INSTANCE
  • org.springframework.aop.interceptor.ExposeInvocationInterceptor#ADVISOR
  • org.springframework.core.annotation.AnnotationAwareOrderComparator#INSTANCE
  • org.springframework.core.OrderComparator#INSTANCE

8.2.Spring 中的 Builder


定义:Separate the construction of a complex object from its representation so that the same construction process can create different representations


它的主要亮点有三处:

  • 较为灵活的构建产品对象
  • 在不执行最后 build 方法前,产品对象都不可用
  • 构建过程采用链式调用,看起来比较爽

Spring 中体现 Builder 模式的地方:

  • org.springframework.beans.factory.support.BeanDefinitionBuilder
  • org.springframework.web.util.UriComponentsBuilder
  • org.springframework.http.ResponseEntity.HeadersBuilder
  • org.springframework.http.ResponseEntity.BodyBuilder

8.3.Spring 中的 Factory Method


定义:Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses


根据上面的定义,可以得知 Spring 中的 ApplicationContextBeanFactory 中的 getBean 都可以视为工厂方法

getBean 方法中,它实际上是隐藏了 bean (产品)的创建过程和具体实现


Spring 中的其它工厂:

  • org.springframework.beans.factory.FactoryBean
  • @Bean 标注的静态方法及实例方法
  • ObjectFactoryObjectProvider

前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题


8.4.Spring 中的 Adapter


定义:Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces


Spring 中,有两处的实现是比较典型的:

  • org.springframework.web.servlet.HandlerAdapter
    • 控制器的实现方式是多种多样的,比如有
      • 大家熟悉的 @RequestMapping 标注的控制器实现
      • 传统的基于 Controller 接口(不是 @Controller 注解)的实现
      • 较新的基于 RouterFunction 接口的实现
    • 它们的处理方法都不一样,为了统一调用,必须适配为 HandlerAdapter 接口
  • org.springframework.beans.factory.support.DisposableBeanAdapter
    • 因为销毁方法多种多样,因此都要适配为 DisposableBean 来统一调用销毁方法

8.5.Spring 中的 Composite


定义:Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly


典型实现有:

  • org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
  • org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
  • org.springframework.web.servlet.view.ViewResolverComposite

composite 对象的作用是,将分散的调用集中起来,提供了一个统一的调用入口。

它的特征是与具体干活的实现实现同一个接口。

当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成。


8.6.Spring 中的 Decorator


定义:Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality


典型实现:

  • org.springframework.web.util.ContentCachingRequestWrapper

8.7.Spring 中的 Proxy


定义:Provide a surrogate or placeholder for another object to control access to it


  • 装饰器模式注重的是功能增强,避免子类继承方式进行功能扩展。
  • 代理模式更注重控制目标的访问。

典型实现:

  • org.springframework.aop.framework.JdkDynamicAopProxy
  • org.springframework.aop.framework.ObjenesisCglibAopProxy

8.8.Spring 中的 Chain of Responsibility


定义:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it


典型实现:

  • org.springframework.web.servlet.HandlerInterceptor

8.9.Spring 中的 Observer


定义:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically


典型实现:

  • org.springframework.context.ApplicationListener
  • org.springframework.context.event.ApplicationEventMulticaster
  • org.springframework.context.ApplicationEvent

8.10.Spring 中的 Strategy


定义:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it


典型实现:

  • org.springframework.beans.factory.support.InstantiationStrategy
  • org.springframework.core.annotation.MergedAnnotations.SearchStrategy
  • org.springframework.boot.autoconfigure.condition.SearchStrategy

8.11.Spring 中的 Template Method


定义:Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure


典型实现:

  • 大部分以 Template 命名的类,如 JdbcTemplateTransactionTemplate
  • 很多以 Abstract 命名的类,如 AbstractApplicationContext

其它


这里推荐某位视频博主的 Spring 相关视频

B 站 UP 主【猿人林克】的个人空间
Java 面试宝典【学习笔记】Spring 篇_第50张图片

这个 UP 主做的小动画也是蛮不错的,如果感兴趣的话,诸位也可以看看。(PS:重点是他做了小动画,蛮形象的,没有那么干)


你可能感兴趣的:(SSM,学习,java,spring,面试)