若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识。无他用。
参考文章:【Spring 的三级缓存解决循环依赖】
推荐阅读文章:【Spring & SpringBoot 常用注解总结】
前言
- 学习资料链接:Java 面试专题课资料(提取码:1234)
博主的相关学习笔记
1.ApplicationContext refresh 的流程
要求:掌握 refresh 的 12 个步骤
org/springframework/context/support/AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException
refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 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
文件的键值)
- Environment 对象的作用之一是为后续
1.2.obtainFreshBeanFactory
要点:
- 这一步 获取 / 创建 了 BeanFactory
- 理解 BeanFactory 的作用
- 理解 BeanDefinition 的作用
- BeanDefinition 从何而来
- 这一步获取(或创建) BeanFactory(它是作为 ApplicationContext 的一个成员变量)
- BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义
- BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
- BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加
- 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合
1.3.prepareBeanFactory
要点:
- 完善 BeanFactory
- 了解是谁来解析 SpEL(Spring 表达式语言) 的
- 了解是谁执行的类型转换
- 了解特殊 bean 的注入
- 两个内置的 BeanPostProcessors 的作用
- 这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
- beanExpressionResolver 用来解析 SpEL,常见实现为 StandardBeanExpressionResolver
- propertyEditorRegistrars 会注册类型转换器
- 它在这里使用了 ResourceEditorRegistrar 实现类
- 并应用 ApplicationContext 提供的 Environment 完成
${ }
解析
- registerResolvableDependency 来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入
- beanPostProcessors 是 bean 后处理器集合,会工作在 bean 的生命周期各个阶段,此处会添加两个:
- ApplicationContextAwareProcessor 用来解析 Aware 接口
- ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean
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
- ConfigurationClassPostProcessor:解析
1.6.registerBeanPostProcessors
要点:
- 理解 bean 后处理器的作用
- 掌握常见的 bean 后处理器
- 这一步是继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中
- bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段,常见的有:
- AutowiredAnnotationBeanPostProcessor 功能有:解析
@Autowired
,@Value
注解 - CommonAnnotationBeanPostProcessor 功能有:解析
@Resource
,@PostConstruct
,@PreDestroy
- AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理
- AutowiredAnnotationBeanPostProcessor 功能有:解析
1.7.initMessageSource
要点:
- 理解 MessageSource 的作用
- MessageSource 从何而来
- 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能
- 去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现
1.8.initApplicationEventMulticaster
要点:
- 理解事件广播器的作用
- 事件广播器从何而来
- 如何发布事件
- 这一步为 ApplicationContext 添加事件广播器成员,即 applicationContextEventMulticaster
- 它的作用是发布事件给监听器
- 去 beanFactory 找名为 applicationEventMulticaster 的 bean 作为事件广播器,若没有,会创建默认的事件广播器
- 之后就可以调用
ApplicationContext.publishEvent(事件对象)
来发布事件
1.9.onRefresh
要点:这一步是空实现,留给子类扩展
- 这一步是空实现,留给子类扩展
- 例:SpringBoot 中的子类在这里准备了 WebServer,即内嵌 Web 容器
- 体现的是模板方法设计模式
1.10.registerListeners
要点:
- 理解事件监听器的作用
- 监听器从何而来
- 如何接受事件
- 这一步会从多种途径找到事件监听器,并添加至 applicationEventMulticaster
- 事件监听器顾名思义,用来接收事件广播器发布的事件,有如下来源
- 事先编程添加的
- 来自容器中的 bean
- 来自于
@EventListener
的解析
- 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中
onApplicationEvent(E e)
方法即可
1.11.finishBeanFactoryInitialization
要点:
- 了解 conversionService
- 了解内嵌值解析器
- 单例池 - singletonObjects
- 这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
- conversionService 是一套转换机制,作为对 PropertyEditor 的补充
- embeddedValueResolvers 即内嵌值解析器,用来解析
@Value
中的${ }
,借用的是 Environment 的功能- 其实 embeddedValueResolvers 就是对 Environment 做了一个封装
- singletonObjects 即单例池,缓存所有单例对象
- 对象的创建都分三个阶段(创建、依赖注入、初始化),每一阶段都有不同的 bean 后处理器参与进来,扩展功能
1.12.finishRefresh
要点:
- 了解 lifecycleProcessor
- lifecycleProcessor 从何而来
- 如何控制 lifecycleProcessor
- 发布 ContextRefreshed 事件
- 这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,用来控制容器内需要生命周期管理的 bean
- 如果容器中有名称为 lifecycleProcessor 的 bean 就用它,否则创建默认的生命周期管理器
- 准备好生命周期管理器,就可以实现
- 调用 context 的 start,即可触发所有实现 LifeCycle 接口 bean 的 start
- 调用 context 的 stop,即可触发所有实现 LifeCycle 接口 bean 的 stop
- 发布 ContextRefreshed 事件,整个 refresh 执行完成
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 的生命周期是从调用 beanFactory 的 getBean 开始的。
bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:
- 处理名称,检查缓存
- 处理父子容器(检查父工厂)
- 处理 dependsOn
- 选择 scope 策略
- 创建 singleton
- 创建 prototype
- 创建其他 scope
- 创建 bean
- 创建 bean 实例:
@Autowired
、唯一带参构造、默认构造 - 依赖注入:
@Autowired
@Value
、@Resource
、ByName ByType、精确指定 - 初始化:Aware 接口处理、
@PostConstruct
、InitializingBean、initMethod、创建代理对象 - 等级可销毁的 bean
- 创建 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,这一步会提前创建这些 dependsOn 的 bean
- 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,需要手动控制它们的创建先后顺序
要点 | 总结 |
---|---|
了解有 dependsOn 时的 bean 初始化顺序 | dependsOn 用在非显示依赖的 bean 的创建顺序控制 |
了解 @Conditional 的解析时机 | @Conditional 由 ConditionEvaluator 解析看是否满足装配条件 |
了解 beanName 的解析时机 | beanName 的解析要分情况 组件扫描:AnnotationBeanNameGenerator @Import :FullyQualifiedAnnotationBeanNameGenerator@Bean :ConfigurationClassBeanDefinitionReader |
掌握 @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 被创建,到调用 BeanFactory 的 destoryBean 时会被销毁。
(没有谁会记录该 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 注解(${ } 、#{ } 、类型转换);⑤ 集合类型(Collection、Map、数组等); ⑥ 泛型和 @Qualifier (用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义) |
AUTOWIRE_BY_NAME(根据名字匹配) | 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员 |
AUTOWIRE_BY_TYPE(根据类型匹配) | 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues |
applyPropertyValues(精确指定) | 根据 mbd 的 propertyValues 进行依赖注入(即 xml 中的 <propety/> 标签中的精确指定) |
问:如果针对同一个成员,采用了多种方式给它配置了依赖注入,哪种方式的优先级最高呢?
优先级顺序(从高到低排序):[精确指定注入 bean 的名称来匹配] > [AUTOWIRE_BY_TYPE 匹配] > [注解方式匹配]
2.5.3.初始化
要点 | 总结 |
---|---|
内置 Aware 接口的装配 | 包括 BeanNameAware,BeanFactoryAware 等 |
扩展 Aware 接口的装配 | 由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization |
@PostConstruct | 由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization |
实现 InitializingBean 接口 | 通过接口回调执行初始化 |
initMethod | 根据 BeanDefinition 得到的初始化方法执行初始化,即 <bean init-method> 或 @Bean(initMethod) |
创建 aop 代理 | 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization |
2.5.4.注册可销毁 bean
要点:判断并登记可销毁 bean
- 判断依据
- 如果实现了 DisposableBean 或 AutoCloseable 接口,则为可销毁 bean
- 如果自定义了 destroyMethod,则为可销毁 bean
- 如果采用
@Bean
没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close、shutdown) - 如果有
@PreDestroy
标注的方法,也会被认为是一个可以销毁的 bean
- 存储位置
- singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中
- 自定义 scope 的可销毁 bean 会存储于对应的域对象当中
- prototype scope 不会存储,需要自己找到此对象销毁
- 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配
2.6.类型转换处理
- 如果 getBean 的 requiredType 参数与实际得到的对象类型不同,会尝试进行类型转换
2.7.销毁 bean
要点:
- singleton bean 的销毁时机
- 自定义 scope bean 的销毁时机
- prototype bean 的销毁时机
- 同一 bean 中的不同形式销毁方法的调用次序
- 销毁时机
- singleton bean 的销毁在
ApplicationContext.close
时,此时会找到所有 DisposableBean 的名字,逐一销毁 - 自定义 scope bean 的销毁在作用域对象生命周期结束时
- prototype bean 的销毁可以通过自己手动调用
AutowireCapableBeanFactory.destroyBean
方法执行销毁
- singleton bean 的销毁在
- 同一 bean 中不同形式销毁方法的调用次序
- 优先调用的是后处理器销毁,即
@PreDestroy
- 其次的是 DisposableBean 接口销毁
- 最后的是 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)
- 优先调用的是后处理器销毁,即
3.Spring 循环依赖
要求
- 掌握创建代理的过程
- 掌握单例 set 方式循环依赖的原理
- 掌握其它循环依赖的解决方法
3.1.创建代理
- 要完全理解循环依赖,需要理解代理对象的创建时机
- 掌握 ProxyFactory 创建代理的过程,理解 Advisor,Advice,Pointcut 与 Aspect
- 掌握 AnnotaionAwareAspectJAutoProxyCreator 筛选 Advisor 合格者,创建代理的过程
3.1.1.AOP 的相关概念
- AOP(Aspect Oriented Programming,面向切面编程)是对面向对象编程 OOP 的升华。
- OOP 是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。
- AOP 是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面。
用这种思维去设计编程的方式叫做面向切面编程。
- 在运行期间,对目标对象的方法进行增强
- 在代理对象同名方法内,可以在其执行原有逻辑的同时,嵌入执行 其他增强逻辑 或 其他对象的方法。
概念 | 单词 | 解释 |
---|---|---|
目标对象 | Target | 被增强的方法所在的对象 |
代理对象 | Proxy | 对目标对象进行增强后的对象,客户端实际调用的对象 |
连接点 | Joinpoint | 目标对象中可以被增强的方法 |
切入点 | Pointcut | 目标对象中实际被增强的方法 |
通知 / 增强 | Advice | 增强部分的代码逻辑 |
切面 | Aspect | 增强和切入点的组合 |
织入 | Weaving | 通知和切入点动态组合的过程(生成了代理对象) |
通知名称 | 执行时机 |
---|---|
前置通知 | 目标方法执行之前执行 |
后置通知 | 目标方法执行之后执行,目标方法异常时,不再执行 |
环绕通知 | 目标方法执行前后执行,目标方法异常时,环绕后方法不再执行 |
异常通知 | 目标方法抛出异常时执行 |
最终通知 | 不管目标方法是否有异常,最终都会执行 |
- 基本概念
- aspect = 通知(advice)+ 切点(pointcut)(一个切面类中可能有多个通知、多个切点)
- 在 Sping 的底层,它可能并不会以切面为单位进行编程,而是以一个更细粒度的切面为单位:advisor。
- advisor:是更细粒度的切面,只包含一个通知和其对应的一个固定切点。
- 使用场景:
- 在通知类型多、允许随意搭配的情况下,可以使用 aspect 进行配置;
- 在通知类型单一、且通知类中通知方法一次性都会使用到的情况下,可以使用 advisor 进行配置;
- 在通知类型已经固定,不用人为指定通知类型时,可以使用 advisor 进行配置,例如 Sping 事务控制的配置。
- 在实际开发中,自定义 AOP 功能的配置大多使用 aspect 的配置方式
代理技术 | 使用条件 |
---|---|
JDK 动态代理技术 | 目标类有接口(也就是说目标类是某个接口的实现类),是基于接口动态生成实现类的代理对象 |
Cglib 动态代理技术 | 目标类无接口,且不能使用 final 修饰,是基于被代理对象动态生成的子对象为代理对象 |
动态代理的实现的选择:
- 在代理工厂对象(proxyFactory)调用 getProxy() 方法时,可选用的 AopProxy 接口有两个实现类
- CglibAopProxy、JdkDynamicAopProxy
- 这两种都是动态生成代理对象的方式,一种就是基于 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 的情况下,它才会增强指定的方法。
3.1.4.动态代理的两种方式
动态代理的实现的选择:
- 在代理工厂对象(proxyFactory)调用 getProxy() 方法时,可选用的 AopProxy 接口有两个实现类
- CglibAopProxy、JdkDynamicAopProxy
- 这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib 的
下面对 3.1.2 小节里的 App1.java 进行 Debug
- Cglib 动态代理方式
proxyFactory.setTarget(new Target1()); // 设置目标对象
// 添加通知/功能增强 ... ...
Target1 proxy = (Target1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)
// 既然这里可以用 Target1 强转,说明在 Cglib 动态代理下,目标对象和代理对象是父子关系
- JDK 动态代理方式
只是改动了几处代码,其余代码不变(这里我图方便,直接重新创建了一个类,内容与之前相比,并没有差别)
proxyFactory.setTarget(new Target1());
proxyFactory.addInterface(I1.class);
// 添加通知/功能增强 ... ...
I1 proxy = (I1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)
// 这里是不可以用 Target1 强转的,说明在 JDK 动态代理下,目标对象和代理对象是平级的关系
吐槽:个人感觉它这个例子并不算太好。接口实现类混在一起的,其实并不好区分两种动态代理方式的区别。
就 Cglib 动态代理方式示例而言,不用应该用到接口,不让目标对象继承接口。这样不容易混淆。
Cglib 动态代理方式,就是可以直接动态生成目标对象的子类代理对象的。
JDK 动态代理方式,它的目标类必须要有接口(也就是说它是接口的实现类),是基于接口动态生成接口实现类的代理对象。
加上下面一行的代码,直接让程序统一采用 Cglib 动态代理方式来生成代理对象。
proxyFactory.setProxyTargetClass(true);
3.1.5.代理对象与 advisor 的关系
代理对象调用方法的时候,需要在内部先找到切面,之后通过切面去检查切点是不是跟方法匹配。
如果方法与切点是匹配的,代理对象才去调通知。
这里就抛出一个问题:这个切面的信息都被保存在哪里去了?将来我们需要在哪里找到切面对象呢?
下面对 3.1.3 小节里的 App2.java 进行 Debug
proxy.foo(); // 打断点
显然,代理对象(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();
}
}
这种注解的方式最终还是会被转换成 advisor 与 MethodInterceptor (Spring 内置的环绕通知方式)的方式。
它们之间的转换是以通知方法为单位进行转换的。
@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 的调用时机:创建阶段、依赖注入阶段、初始化阶段
3.1.8.小结
- 最基本的切面类是 advisor,一个 Aspect 切面根据内置方法不同对应一到多个 Advisor。
- 最基本的通知是 MethodInterceptor,其他 Advice(比如前置,后置,环绕)最终都会适配为 MethodInterceptor
- 代理创建的方式
- 实现了用户自定义的接口,采用 JDK 的动态代理。
- 没有实现用户自定义接口,采用 Cglib 代理。
- 如果设置了
setProxyTargetClass(ture)
,统一采用 Cglib 代理。
- 切面,切点,通知等是不会被代理的
- annotationAwareAspectJAutoProxyCreator 的调用时机:创建阶段、依赖注入阶段、初始化阶段
3.2.循环依赖的产生和 set 循环依赖
首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
set 方法(包括成员变量)的循环依赖如图所示
- 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
- 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
- a 的顺序,及 b 的顺序都能得到保障
示例代码
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();
}
}
控制台打印信息
3.3.构造循环依赖的解决
构造方法的循环依赖如图所示,显然无法用前面的方法解决(三级缓存的思路是在对象实例创建完成后,把对象的实例 a 封装成一个工厂对象,放入 singletonFactories 缓存中的,这样 b 就可以从缓存中直接拿出 a 来完成自己内部的注入(即 setA()),进而完成整个依赖注入流程。但这里连半成品的实例对象都没有,所以无法使用上面的方法来解决这里的问题)
3.2.1.解决思路
- 思路①
- a 注入 b 的代理对象,这样可以优先能够保证 a 的流程走通(其实也是推迟了实例对象 b 的创建)
- 后续 a 需要用到 b 的真实对象时,可以通过代理间接访问
- 思路②
- a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
- 后续 a 需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问
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();
}
}
控制台打印信息
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("==========================================");
}
}
控制台打印信息
ObjectProvider 是 ObjectFactory 的子类型,用 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();
}
}
控制台打印信息
3.2.5.用 Provider 接口解决(示例四)
用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</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("-------------------------------------------");
}
}
控制台打印信息
3.3.解决 set 循环依赖的原理
!注意事项:教程中给的几张图是根据解决循环依赖的演进过程而画的,并不是和源码一一对应的,在叫法上也有区别。
3.3.1.一级缓存
一级缓存:限制 bean 在 beanFactory 中只存一份,即实现 singleton scope
作用:保证单例对象仅被创建一次
- 第一次走
getBean("a")
流程时,会创建实例对象 a,最后将成品 a 放入 singletonObjects 一级缓存 - 之后再走
getBean("a")
流程的时后,会先从一级缓存中找 a。因为此时已有成品 a,故无需再次创建。
3.3.2.一级缓存与循环依赖
A 中有 B,B 中有 A
一级缓存无法解决循环依赖问题,分析如下
- 无论是获取 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()
会将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存 - 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程
这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。
对于上面的图
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 的依赖注入和初始化也可以顺利完成了,最终 a、b 的成品对象都会放入到 singletonObjects 中
3.3.4.二级缓存与创建代理(Spring 中的三级缓存)
这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。
二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下
- Spring 默认要求,在
a.init
完成之后才能创建代理pa = proxy(a)
- 由于 a 的代理创建时机靠后,在执行
factories.put(a)
向 singletonFactories 中放入的还是原始对象 - 接下来【箭头 3】【箭头 4】【箭头 5】 这几步 b 对象拿到和注入的都是原始对象
3.3.4.三级缓存(Spring 中的二级缓存)
这里只是教程中称其为三级缓存,其实它是 Spring 中的二级缓存的。
工厂和缓存相互配合可以解决循环依赖中代理对象创建过晚的问题。
简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 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】
- 最终返回的就是刚刚创建的代理 pa。
b.setA()
得以成功注入代理对象,保证了正确性【红色箭头 7】 - 最后它还需要把 pa 存入新加的 earlySingletonObjects 缓存【红色箭头 6】
a.init
完成后,就无需二次创建代理了。那么从哪儿找到 pa 呢?
就在 earlySingletonObjects 中(pa 已经存在此处)【蓝色箭头 9】
当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处了,清除即可
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 拿到代理对象,来调用
- 解法③:通过 CTW,LTW 实现功能增强
@Transactional
没有保证原子行为- 原因:事务的原子性仅涵盖
insert
、update
、delete
、select … for update
语句,select
方法并不阻塞
- 原因:事务的原子性仅涵盖
@Transactional
方法导致的 synchronized 失效- synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有
commit
等操作,它们并未处于 sync 块内 - 解法①:synchronized 范围应扩大至代理方法调用
- 解法②:使用
select … for update
替换select
(这里更推荐解法②,在数据库层面解决原子性和加锁问题会更好)
- synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有
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);
- 在 catch 块添加
- 解法②:手动设置
TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
- 在 catch 块添加
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,可能会出现扣减为负数的情况
- 原因:事务的原子性仅涵盖
insert
、update
、delete
、select … 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 足够余额来转账
- 解法①:synchronized 范围应扩大至代理方法调用
- 解法②:使用
select … for update
替换select
- 意向排他锁:意向排他锁与表读锁、写锁都是互斥的
这里涉及到一些数据库的基础知识,如果各位有兴趣了解的话,不妨看一下我之前写的关于 MySQL 的文章:【MySQL 学习笔记②】
5.SpringMVC 执行流程
要求
- 掌握 Spring MVC 的执行流程
- 了解 Spring MVC 的重要组件的作用
本文在这里把整个流程分成了三个阶段:准备阶段、匹配阶段、执行阶段
5.1.准备阶段
- 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
- init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
- refresh 过程中会创建并初始化 SpringMVC 中的重要组件
例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等 - 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用
5.2.匹配阶段
- 用户发送的请求统一到达前端控制器 DispatcherServlet
- DispatcherServlet 遍历所有 HandlerMapping,找到与路径匹配的处理器
- HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。
其中能识别@RequestMapping
的优先级最高 - 对应
@RequestMapping
的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息 - 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好
- HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。
- 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
- 遍历 HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用
5.3.执行调用阶段
- 执行拦截器 preHandle
- 由 HandlerAdapter 调用 HandlerMethod
- 调用前处理不同类型的参数
- 调用后处理不同类型的返回值
- 第 2 步没有异常
- 返回 ModelAndView
- 执行拦截器 postHandle 方法
- 解析视图,得到 View 对象,进行视图渲染
- 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程
- 最后都会执行拦截器的 afterCompletion 方法
- 如果控制器方法标注了
@ResponseBody
注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染
6.Spring 注解
这里笔者推荐一篇蛮不错的的文章:Spring & SpringBoot 常用注解总结
在官方提供的资料里,有一个文件:面试题-spring-注解.xmid
- 注解的详细列表请参考:面试题-spring-注解.xmid
- 下面列出了视频中重点提及的注解,考虑到诸位对大部分注解已经比较熟悉了,故这里仅对对个别的注解作简要说明
6.1.事务注解
@EnableTransactionManagement
:启用声明式的事务控制。- 会额外加载 4 个 bean
- BeanFactoryTransactionAttributeSourceAdvisor:事务切面类
- TransactionAttributeSource:该类用来解析事务属性(可以用来解析
@Transactional
) - TransactionInterceptor:事务拦截器(这里面是有通知功能的)
- TransactionalEventListenerFactory:事务监听器工厂
- 会额外加载 4 个 bean
名称 | @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 框架。
@Aspect
(org/aspectj/lang/annotation/Aspect.java
)
名称 | @Aspect |
---|---|
类型 | 类注解 |
位置 | 切面类定义上方 |
作用 | 设置当前类为 AOP 切面类 |
@Pointcut
(org/aspectj/lang/annotation/Pointcut.java
)
名称 | @Pointcut |
---|---|
类型 | 方法注解 |
位置 | 切入点方法定义上方 |
作用 | 设置切入点方法 |
属性 | value(默认):切入点表达式 |
@Before
(org/aspectj/lang/annotation/Pointcut.java
)
名称 | @Before |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
6.4.组件扫描与配置类
@Component
:一个通用的注解,可以标注任意类为 Spring 组件@Controller
:对应 SpringMVC 中的控制层@Service
:对应表现层@Repository
:对应数据层
名称 | @Component /@Controller /@Service /@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 Spring 管理的 bean |
属性 | value(默认):定义 bean 的 id |
@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(默认):定义 bean 的 id |
@Bean
名称 | @Bean |
---|---|
类型 | 方法注解 |
位置 | 方法定义上方 |
作用 | 设置该方法的返回值作为 Spring 管理的 bean |
属性 | value(默认):定义 bean 的 id |
@Import
:指定一个类型,根据类型或类型本身找到一些其他的类,之后再把它们交给 Spring 容器管理。- 四种用法
- 引入单个 bean
- 引入一个配置类
- 通过 Selector 引入多个类
- 通过 beanDefinition 注册器
- 解析规则
- 同一配置类中,
@Import
先解析@Bean
后解析 - 同名定义,默认后面解析的会覆盖前面解析的
- 在不允许覆盖的情况下,如何能够让 MyConfig(主配置类) 的配置优先?(虽然覆盖方式能解决)
- 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析
@Bean
,再 Import
- 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析
- 同一配置类中,
- 四种用法
名称 | @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 方法上方 或 方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true / 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 |
---|---|
类型 | 方法注解 |
位置 | 基于 SpringMVC 的 Restful 开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @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 |
---|---|
类型 | 类注解 |
位置 | 基于 SpringMVC 的 RESTful 开发控制器类定义上方 |
作用 | 设置当前控制器类为 RESTful 风格,等同于 @Controller 与 @ResponseBody 两个注解组合功能 |
@RestController
注解标注在类上,表明这是一个控制器 bean,直接将函数的返回值填入 HTTP 响应体中。
@ResponseStatus
:可以控制响应的状态码
6.6.3.mvc 统一处理
@ControllerAdvice
,结合@ResponseBody
可为@RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | REST 风格开发的控制器增强类定义上方 |
作用 | 为 REST 风格开发的控制器类做增强 |
说明:此注解自带 @ResponseBody
注解与 @Component
注解,具备对应的功能
@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
只有在条件成立的时候,才会执行后续的操作
@ConditionalOnClass
,classpath 下存在某个 class 时,条件才成立@ConditionalOnMissingBean
,beanFactory 内不存在某个 bean 时,条件才成立@ConditionalOnProperty
,配置文件中存在某个我们期望的 property(键、值)时,条件才成立
6.7.3.boot properties
@ConfigurationProperties
,会将当前 bean 的属性与配置文件中的键值进行绑定,可以简化 bean 的初始化@EnableConfigurationProperties
,会添加两个较为重要的 bean- ConfigurationPropertiesBindingPostProcessor:bean 后处理器,在 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 {}
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
- 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析
- 同一配置类中,
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
DeferredImportSelector 是 ImportSelector 的子接口
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 pattern 与 Spring 中的 singleton bean
- 使用单例模式的目的:Ensure a class only has one instance, and provide a global point of access to it
- 显然 Spring 中的 singleton bean 并非实现了单例模式,singleton bean 只能保证每个容器内,相同 id 的 bean 是单实例
当然 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 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法
在 getBean 方法中,它实际上是隐藏了 bean (产品)的创建过程和具体实现
Spring 中的其它工厂:
org.springframework.beans.factory.FactoryBean
@Bean
标注的静态方法及实例方法- ObjectFactory 及 ObjectProvider
前两种工厂主要封装第三方的 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 命名的类,如 JdbcTemplate,TransactionTemplate
- 很多以 Abstract 命名的类,如 AbstractApplicationContext
其它
这里推荐某位视频博主的 Spring 相关视频
这个 UP 主做的小动画也是蛮不错的,如果感兴趣的话,诸位也可以看看。(PS:重点是他做了小动画,蛮形象的,没有那么干)