1 Spring
1.1 概述
一句话概括:Spring是一个轻量级、非侵入式的控制反转(IOC)和面向切面(AOP)的框架

目前企业开发的标配基本就是Spring5+SpringBoot2+JDK8
1.2 特性

1.IOC和DI的支持
Spring的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系
Spring工厂用于生成Bean,并且管理Bean的声明周期,实现高内聚低耦合的设计理念
2.AOP编程的支持
Spring提供了面向切面编程,可以实现对程序进行权限拦截、运行监控等切面功能
3.声明式事务的支持
支持通过配置来完成对事务的管理,而不需要通过硬编码的方式
(像以前重复的一些事务提交、回滚的JDBC代码,都可以不用自己来编写)
4.快捷测试的支持
Spring对Junit提供支持,可以通过注解快捷的测试Spring程序
5.快速集成功能
方便继承各种优秀框架,Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis等)的直接支持
6.复杂API模板封装
Spring对JavaEE开发中难用的一些API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装API的提供使得应用难度大大降低
1.3 模块
Spring框架是分模块存在的,除了最核心的Spring Core Container是必要模块之外,其他模块都是可选,大约有20多个模块
其中最主要的七大模块:
Spring Core:Spring核心,框架最基础的部分,提供IOC和依赖注入DI特性
Spring Context:Spring上下文容器,它是BeanFactory功能加强的一个子接口
Spring Web:它提供Web应用开发的支持
Spring MVC:它针对Web应用中MVC思想的实现
Spring DAO:提供对JDBC抽象层,简化了JDBC编码,同时编码更具有健壮性
Spring ORM:它支持用于流行的ORM框架的整合
Spring AOP:即面向切面编程,它提供了与AOP兼容的编程实现
1.4 常用注解
Spring提供了大量的注解来简化Java应用的开发和配置,主要用于Web开发、往容器注入Bean、AOP、事务控制等

1.4.1 Web
@Controller:用于标注控制层组件
@RestController:是@Controller和@ResponseBody的结合体,返回JSON数据时使用
@RequstMapping:用于映射请求URL到具体的方法上,还可以细分为:
@GetMapping:只能用于处理GET请求
@PostMapping:只能用于处理POST请求
@DeleteMapping:只能用于处理DELETE请求
@ResponseBody:直接将返回的数据放入HTTP响应正文中,一般用于返回JSON数据
@RequestBody:表示一个方法参数应该绑定到Web请求体
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”),这里的name就是路径参数
@RequestParam:用于接收请求参数,比如@RequestParam(name=“key”) String key,这里的key就是请求参数
1.4.2 容器
@Component:标识一个类为Spring组件,使其能够被Spring容器自动扫描和管理
@Service:标识一个业务逻辑组件(服务层)
@Repository:标识一个数据访问组件(持久层)
@Autowired:按类型自动注入依赖
@Autowired和@Resource
联系:
两者都是作为bean对象注入的时候使用的
两者都可以声明在字段和setter方法上
区别:
@Autowired注解是Spring提供的,而@Resource注解是J2EE本身提供的
@Autowired注解默认通过byType方式注入,而@Resource注解默认通过byName方式注入
注意事项:
byName就是变量名去匹配bean的id属性,而byType则是变量类型去匹配bean的class属性
@Autowired注解注入的对象需要在IOC容器中存在,否则需要加上属性required=false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
@Resource根据byName进行注入时,也会进行类型检查,不一致会报BeanCreationException异常
@Qualifier:配合@Autowired使用,显式指定name值
@Configuration:用于定义配置类
@Value:用于将配置文件中所配置的属性值赋值给变量
@Scope:定义Bean的作用域
1.4.3 AOP
@Aspect:用于定义切面,标注在切面类上
@PointCut:定义切点,标注在方法上,用于指定连接点
@Before:在方法执行之前执行通知
@After:在方法执行之后执行通知
@Around:方法前后都执行通知
@AfterReturning:在方法执行后返回结果后执行通知
@AfterThrowing:在方法抛出异常后执行通知
@Advice:通用的通知类型,可以替代@Before、@After等
1.4.4 事务
@Transactional:用于声明一个方法需要事务支持
2 Spring IOC/DI
IOC(Inversion of Control),即控制反转的意思,它是一种创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式。
传统开发过程中,我们需要通过new关键字来创建对象。使用IOC思想开发方式的话,我们可以不通过new关键字创建对象,而是通过IOC容器来帮我们实例化对象。通过IOC的方式,可以大大降低对象之间的耦合度。


所谓控制就是对象的创建、初始化、销毁
创建对象:原来是new一个,现在是由Spring容器创建
初始化对象:原来是对象自己同各国构造器或者setter方法给依赖的对象赋值,现在是由Spring容器自动注入
销毁对象:原来是直接给对象赋值null或做一些销毁操作,现在是Spring容器管理生命周期负责销毁对象。
2.1 IOC实现机制
反射:Spring IOC容器利用Java的反射机制动态的加载类、创建对象实例以及调用对象方法,反射允许再运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理
依赖注入:IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解
设计模式-工程模式:Spring IOC容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化Bean并管理它们的生命周期,将Bean的实例化过程交给容器来管理
容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。
2.2 依赖注入(DI)实现方式
在传统编程中,当一个类需要使用另一个类的对象时,通常会在该类内部通过new关键字来创建依赖对象,这使得类与类之间的耦合度较高。
而依赖注入则是将对象的创建和依赖关系的管理交给Spring容器来完成,类只需要声明自己所依赖的对象,容器,容器会在运行时将这些依赖对象注入到类中,从而降低了类与类之间的耦合度,提高了代码的可维护性和可测试性。
常见的依赖注入的实现方式:
**构造器注入:**通过构造函数传递依赖对象,保证对象初始化时依赖已就绪
@Service
public class UserService{
private final UserRepository userRepository;
//构造器注入(Spring4.3+ 自动识别单构造器,无需显式@Autowired)
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
}
**Setter方法注入:**通过Setter方法设置依赖,灵活性高,但依赖可能未完全初始化(提前暴露)
public class PaymentService{
private PaymentGateway gateway;
@Autowired
public void setGateway(PaymentGateway gateway){
this.gateway = gateway;
}
}
**字段注入:**直接通过@Autowired注解字段,代码简洁但隐藏依赖关系,不推荐生产代码
@Service
public class OrderService{
@Autowired
priavte OrderRepository orderRepository;
}
2.3 IOC的工作流程

第一各阶段,就是IOC容器的初始化
这个阶段主要是根据程序中定义的xml或者注解等Bean的声明方式通过解析和加载后生成BeanDefinition,然后BeanDefinition注册到IOC容器

通过注解或者xml声明的bean都会解析得到一个BeanDefinition实体,实体中包含这个bean中定义的基本属性,最后把这个BeanDefinition保存到一个Map集合里面从而实现IOC的初始化
IOC容器的作用就是对这些注册的Bean的定义信息进行处理和维护,也是IOC容器控制反转的核心
第二个阶段,完成Bean初始化以及依赖注入
这个阶段会做两件事
a.通过反射针对没有设置lazy-init属性的单例bean进行初始化
b.完成bean的依赖注入

第三个阶段,Bean的使用
通常我们会通过@Autowired或者BeanFactory.getBean()从IOC容器中获取指定的bean实例
另外,针对设置lazy-init属性以及非单例bean的实例化,是每次获取bean对象的时候,调用bean的初始化方法来完成实例化的,并且Spring IOC容器不会去管理这些Bean

2.3.1 ApplicationContext & BeanFactory
1.相同
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当作Spring的容器
2.区别
BeanFactory是Spring里面最底层的接口,是IOC的核心,定义了IOC的基本功能,包含了各种Bean的定义、加载、实例化、依赖注入和声明周期管理。
ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
a.可以载入多个有继承关系的Context,使得每个Context都专注于一个特定的层次
b.支持事件监听机制
c.通过MessageSource提供国际化的支持
BeanFactory创建的bean采用延迟加载形式,只有在使用到某个Bean时,才对该Bean进行加载实例化(这样就不能提前发现一些存在的Spring的配置问题)
ApplicatioContext是在容器启动时,一次性预载入所有的单例实例(非懒加载Bean)(这样在容器启动时,就可以发现Spring中存在的配置错误,这样有利于检查所有依赖属性是否注入)
两者都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
[!NOTE]
BeanFatcory与FactoryBean的辨析
FactoryBean:如果某个Bean的配置比较复杂,或者想要使用编码的形式去构建它,可以提供一个构建该Bean实例的工厂,这个工厂就是FactoryBean接口实现类,FactoryBean接口实现类也是需要Spring管理
二者联系:String FACTORY_BEAN_PREFIX = "&"是BeanFactory中的一个成员属性,用于区分是 FactoryBean 还是创建的 Bean,当我们用getBean()获取FactoryBean实例本身需在bean名称前添加前缀 &,不添加的话获取的是Bean
2.3.2 处理器
通过创建类继承相应的处理器的接口,重写后置处理的方法,来实现**拦截Bean的生命周期**,来实现自己自定义的逻辑
1.BeanPostProcessor
bean后置处理器,bean创建对象初始化前后进行拦截工作
2.BeanFactoryPostProcessor
加载时机:在BeanFactory初始化之后调用,此时所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建,用于修改Bean定义
执行流程:IOC容器创建相关组件
refresh() →invokeBeanFactoryPostProcessors(beanFactory):在 BeanFactory 中找到所有类型是 BeanFactoryPostProcessor 的组件,并执行它们的方法
3.BeanDefinitionRegistryPostProcessor
加载时机:在所有 bean 定义信息将要被加载,但是 bean 实例还未创建,优先于BeanFactoryPostProcessor 执行;可以用于动态注册或删除 Bean 定义
执行流程:ioc 容器创建相关组件
refresh() → invokeBeanFactoryPostProcessors(beanFactory):从容器中获取到所有的 BeanDefinitionRegistryPostProcessor 组件,依次触发所有的 postProcessBeanDefinitionRegistry() 方法,再来触发 postProcessBeanFactory() 方法
2.3.3 监听器
ApplicationListener:监听容器中发布的事件,完成事件驱动模型开发
应用监听器步骤
写一个监听器(ApplicationListener实现类)来监听某个事件(ApplicationEvent及其子类),并使用@Component把监听器加入容器,这种方法比较适合用来监听单一事件;
或者使用@EventListener注解,其原理是使用 EventListenerMethodProcessor 处理器来解析方法上的 @EventListener,Spring 扫描使用注解的方法,并为之创建一个监听对象,其更灵活,支持条件过滤、异步和多个事件监听。
只要容器中有相关类型事件的发布,就能监听到这个事件,监听器中的方法会触发
2.3.4 容器刷新(refresh())
AbstractApplicationContext.refresh():这是一个模板方法模式,规定了IOC容器的启动流程,有些方法可以在子类中实现
1.prepareRefresh()
刷新前的预处理
2.obtainFreshBeanFactory()
获取一个全新的BeanFactory接口实例,如果容器中存在工厂实例,就直接销毁工厂和其中的Bean实例,通过loadBeanDefinition(beanFactory)加载BeanDefinition信息,注册BeanDefinition到新BeanFactory中
3.prepareBeanFactory()
BeanFactory 的预准备工作,向容器中添加一些组件,比如说:
类加载器:setBeanClassLoader(getClassLoader());
监听探测器:addBeanPostProcessor(new ApplicationListenerDetector(this));
注册可解析的依赖:registerResolvableDependency(),当其他 Bean 需要依赖 BeanFactory、ApplicationEventPublisher等类型时,直接返回容器自身的实例
4.postProcessBeanFactory()
为容器的某些子类指定特殊的BeanPost事件处理器
5.invokeBeanFactoryPostProcessors()
先获取并执行BeanDefinitionRegistryPostProcessor,再获取并执行BeanFactoryPostProcessor,前者可以用于动态注册/删除Bean定义,后者可以用于修改Bean的定义
6.registerBeanPostProcessor()
注册Bean的后置处理器
7.initMessageSource()
初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析
8.initApplicationEventMulticaster()
初始化事件多播器
9.onRefresh()
留给用户去实现,可以硬编码提供一些组件,比如在DispatcherServlet中就使用onRefresh()初始化了九大组件
10.registerListeners()
注册通过配置提供的Listener,这些监听器最终注册到ApplicationEventMulticaster内
11.finishBeanFactoryInitialization()
实例化非懒加载状态的单实例
12.finishRefresh()
启动生命周期组件(如Web服务器、定时任务)
发布ContextRefreshedEvent事件,通知应用已就绪
将当前应用上下文注册到Spring的实时Bean视图中,以便监控和管理容器中的Bean状态
2.4 Spring Bean
1.Spring Bean声明式配置内容
关于Spring Bean的配置内容非常多,我主要列举九个关键的配置属性,比如:class、scope、lazy-init、depends-on、name、constructor-arg、properties、init-method、destroy-method等。
这些属性都是要在Spring配置文件中声明的内容。在Spring容器启动后,这些配置内容都会映射到一个叫做BeanDefinition的对象中。然后,所有的BeanDefinition对象都会保存到一个叫做beanDefinitionMap的容器中,这个容器是Map类型,以Bean的唯一标识作为key,以BeanDefiniton对象实例作为值。这样Spring容器创建Bean时,就不需要再次读取和解析配置文件,只需要根据Bean的唯一标识,去beanDefinitionMap中取到对应的BeanDefinition对象即可。
2.BeanDefinition与配置文件的关系
BeanDefinition的基础实现类AbstractBeanDefinition类,这个类下面的所有属性都能够和声明配置文件中的内容一一对应上:
public AbstractBeanDefinition implements BeanDefinition {
...
@Nullable
private volatile Object beanClass;
@Nullable
private String scope = SCOPE_DEFAULT;
private boolean lazyInit = false;
@Nullable
private String[] dependsOn;
@Nullable
private String factoryBeanName;
@Nullable
private ConstructorArgumentValues constructorArgumentValues;
@Nullable
private MutablePropertyValues propertyValues;
@Nullable
private String initMethodName;
@Nullable
private String destroyMethodName;
...
}
BeanDefinition中定义的属性和声明式的配置内容从命名上看比较类似。

3.Spring如何解析配置文件
Spring容器启动之后,会调用BeanDefinitionReader工具类的loadBeanDefinitions()方法,启动对配置文件的加载和解析。 BeanDefinitionReader 的主要作用是读取 Spring 配置文件中的内容,将其转换为BeanDefinition对象。而BeanDefinitionReader又有非常多的实现类,每种类型的配置具体解析的过程又不一样,比如
XmlBeanDefinitionReader,用于读取 XML 文件并解析为BeanDefinition对象。
PropertiesBeanDefinitionReader,用于读取属性文件,将Resource,Property 等解析为BeanDefinition对象。
GroovyBeanDefinitionReader,用于读取 Groovy 语言定义的 Bean,将它们解析为BeanDefinition对象。
2.4.1 Bean的作用域
1.Spring为什么要定义作用域
定义Bean的作用域,相当于用户可以通过配置的方式限制Spring Bean的使用范围,以起到保护Bean安全的作用。在日常开发中,根据业务需要,选择不同的作用域,以保护Bean的使用安全。
2.作用域的定义
在Spring配置中,我们可以通过scope属性来定义Spring Bean的作用域,可以接受5个内建的值,分别代表5种作用域类型:
singleton,用来定义一个Bean为单例,也就是说在Spring loC容器中仅有唯一的一个实例对象,Spring中的Bean默认都是单例的。它的作用域范围是ApplicationContext容器
prototype,用来定义一个Bean为多例,也就是说在每次请求获取Bean的时都会重新创建实例,因此每次获取到的实例对象都是不同的。它的作用域范围是调用getBean方法直至获取对象。
request,用来定义一个作用范围仅在request中的Bean,也就是说在每次HTTP请求时会创建一个实例,该实例仅在当前Request中有效。它的作用域范围是每次发起HTTP请求直至拿到响应结果。
session,用来定义一个作用范围仅在session中的Bean,也就是说在每次HTTP请求时会创建—个实例,该实例仅在当前HTTP Session中有效。它的作用域范围是浏览器首次访问至浏览器关闭。
globalSession,用来定义一个作用范围仅在中的Bean。这种方式仅用于应用环境,也就是说该实例仅存在于WebApplicationContext环境中。它的作用域范围是整个WebApplicationContext容器。
第一个singleton和第二个prototye比较常用,其他三种仅适用于Web应用环境中。


2.4.2 Bean的生命周期
1.创建前准备阶段

步骤1:Bean容器在配置文件中找到Spring Bean的定义以及相关的配置,如init-method和destroy-method指定的方法
步骤2:实例化回调相关的后置处理器如BeanFactoryPostProcessor、BeanPostProcessor、InstantiationAwareBeanPostProcessor等
2.实例化阶段

步骤3:Spring容器使用Java反射创建Bean的实例
步骤4:扫描Bean声明的属性并解析
3.依赖注入阶段

步骤5:开始依赖注入,解析所有需要赋值的属性并赋值
[!NOTE]
静态注入问题
Spring不支持静态变量的依赖注入,原因:依赖注入发生在实例化之后,而静态变量的初始化发生在类加载过程中,由JVM控制,Spring无法干涉
解决方案:
a.在实例Bean的初始化方法(如@PostConstruct)中,将依赖注入到静态变量
b.使用@Autowired+Setter方法注入静态字段
步骤6:如果实现Aware接口
如果Bean实现了BeanNameAware接口,Spring将Bean的Id传递给setBeanName()方法
如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
如果Bean实现了ApplicationContextAware接口,Spring将调用Bean的setApplicationConetxt()方法,将bean所在应用上下文引用传入进来
4.初始化阶段

步骤7:如果Bean实现了BeanPostProcessor接口,Spring就将调用postProcessorBeforeInitialization()方法
步骤8:如果Bean实现了InitializingBean接口,Spring将调用afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
步骤9:如果Bean实现了BeanPostProcessor接口,Spring就将调用postProcessorAfterInitialization()方法
5.运行阶段
此时,Bean已经准备就绪,可以被应用程序使用。它们将一直驻留在应用上下文中,直到应用上下文被销毁
6.销毁阶段

步骤10:如果bean实现了DisposableBean接口,Spring将调用它的destroy()方法。同样,如果bean使用了destroy-method声明销毁方法,该方法也会被调用。
2.4.3 Bean的线程安全
Spring中的Bean是否线程安全与Spring容器本身无关。Spring框架中没有提供线程安全的策略,因此Spring容器中的Bean本身也不具备线程安全的特性。
在Spring容器中,什么样的Bean会存在线程安全问题呢?回答这个问题之前,先回顾一下Bean的作用域。在Spring定义的作用域中,有多例prototype和单例singleton,多例Bean在每次getBean时都会创建一个新的对象,单例Bean在Spring中只会存在一个全局共享的实例。
多例Bean:
多例Bean每次都会创建新实例,也就是说线程之间不存在Bean共享的问题。因此,多例Bean是不存在线程安全问题的。
单例Bean:
单例Bean是所有线程共享一个实例,因此可能会存在线程安全问题。
但是单例Bean又分为有状态Bean和无状态Bean。
在多线程操作中只会对Bean的成员变量进行查询操作,不会修改成员变量的值,这样的Bean叫做无状态Bean。所以无状态的单例Bean是不存在线程安全问题的。
在多线程操作中,如果需要对Bean中的成员变量进行数据更新,那么这样的Bean叫做有状态Bean。所以有状态的单例Bean可能存在线程安全问题。
[!NOTE]
解决有状态的单例Bean的线程安全问题
1.单例改多例
2.在Bean对象中避免定义可变的成员变量
3.在类中定义ThreadLocal的成员变量,并将需要的可变成员变量保存在ThreadLocal中,ThreadLocal本身就具备线程隔离的特性,这就相当于为每个线程提供了一个独立的变量副本,每个线程只需要操作自己的线程变量副本,即可解决线程安全问题
2.5 循环依赖问题
循环依赖指的是两个类中的属性相互依赖对方:假如A类中有B属性,B类中有A属性,从而形成了依赖闭环,如下图:

循环依赖问题在Spring中主要有三种情况:
第一种:通过构造方法进行依赖注入时产生的循环依赖问题;
第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题;
第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题
只有第三种的循环依赖被Spring解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。
2.5.1 三级缓存

Spring在DefaultSingletonBeanRegistry类中维护三个重要的缓存(Map),称为“三级缓存”:
singletonObjects(一级缓存):存放的是完全初始化好的、可用的Bean实例,getBean()方法最终返回的就是这里面的Bean。此时Bean已经实例化、属性已填充,初始化方法已执行、AOP代理(如果需要)也已生成。
earlySingletonObjects(二级缓存):存放的是提前暴露的Bean的原始对象引用或早期代理对象引用,专门用来处理循环依赖。当一个Bean还在创建过程中(尚未完成属性填充和初始化),但它的引用需要被注入到另一个Bean时,就暂时放在这里。此时Bean已实例化,但属性尚未填充,初始化方法尚未执行,它可能是一个原始对象,也可能是一个为了解决AOP代理问题而提前生成的代理对象。
singletonFactories(三级缓存):存放的是Bean的ObjectFactory工厂对象。这是解决循环依赖和AOP代理协同工作的关键。当Bean被实例化后,Spring会创建一个ObjectFactory并将其放入三级缓存。这个工厂的getObject()方法负责返回该Bean的早期引用(可能是原始对象,也可能是提前生成的代理对象),当检测到循环依赖需要注入一个尚未完全初始化的Bean时,就会调用这个工厂来获取早期引用。
[!NOTE]
AOP代理与循环依赖的兼容:循环依赖解决需要三级缓存的 ObjectFactory提前引用动态代理对象,AOP 动态代理是在 Bean 初始化后的后置处理中进行,这时的 bean 已经是成品对象。此时就需要三级缓存的 ObjectFactory 提前产生需要代理的对象,把提前引用放入二级缓存,此时后置处理wrapIfNecessary()不创建代理对象,真正创建代理对象的过程是getBean()中得到的
Spring通过三级缓存和提前暴露未完全初始化的对象引用的机制来解决单例作用域Bean的setter注入方式的循环依赖问题。
2.5.2 解决过程

假设存在两个相互依赖的单例Bean:BeanA依赖BeanB,同时BeanB也依赖BeanA。当Spring容器启动时,他会按照以下流程处理:
第一步:创建BeanA的实例并提前暴露工厂
Spring首先调用BeanA的构造函数进行实例化,此时得到一个原始对象(尚未填充属性)。
紧接着,Spring会将一个特殊的ObjectFactory工厂对象存入三级缓存(singletonFactories)。这个工厂的使命是:当其他Bean需要引用BeanA时,它能动态返回当前这个半成品BeanA(可能时原始对象,也可能是未应对AOP而提前生成的代理对象)。
此时BeanA的状态是“已实例化但未初始化”,像一座刚搭好钢筋骨架的大楼。
第二步:填充BeanA的属性时触发BeanB的创建
Spring开始为BeanA注入属性,发现它依赖BeanB。于是容器转向开始创建BeanB,同样先调用其构造函数实例化,并将BeanB对应的ObjectFactory工厂存入三级缓存。至此,三级缓存中同时存在BeanA和BeanB的工厂,它们都代表未完成初始化的半成品。
第三步:BeanB属性注入时发现循环依赖
当Spring试图填充BeanB的属性时,检测到它需要注入BeanA。此时容器启动依赖查找:
在一级缓存中(存放完整Bean)中未找到BeanA;
在二级缓存(存放已暴露的早期引用)中同样未命中;
在三级缓存中定位到BeanA的工厂
Spring立即调用该工厂的getObject()方法。
这个方法会执行关键决策:若BeanA需要AOP代理,则动态生成代理对象(即使BeanA还未初始化);若无需代理,则直接返回原始对象。得到的这个早期引用被放入二级缓存,同时从三级缓存中清理工厂条目。
最后,Spring将这个早期引用注入到BeanB的属性中。
至此,BeanB成功持有BeanA的引用——尽管BeanA此时仍是个半成品。
第四步:完成BeanB的生命周期
BeanB获得所有依赖后,Spring执行其初始化方法(如@PostConstruct),将其转换为完整可用的BeanB。随后BeanB被提升至一级缓存(singletonObjects),二级和三级缓存中关于BeanB的临时条目均被清除。此时BeanB已准备就绪,可被其他对象使用。
第五步:回溯完成BeanA的构建
随着BeanB创建完毕,流程回溯到最初中断的BeanA属性注入环节。Spring将已完备的BeanB实例注入BeanA,接着执行BeanA的初始化方法。这里有个细节:若之前为BeanA生成过早期代理,Spring会直接复用二级缓存中的代理对象作为最终Bean,而非重复创建。最终,完成初始化的BeanA(可能时原始对象或代理对象)放入一级缓存,其早期引用从二级缓存移除。
至此循环闭环完成,两个Bean皆可用。
[!NOTE]
三级缓存的设计精髓:
三级缓存工厂(singletonFactories)负责在实例化后立刻暴露对象生成能力,兼顾AOP代理的提前生成;
二级缓存(earlySingletonObjects)临时存储已确定的早期引用,避免重复生成代理;
一级缓存(singletonObjects)最终交付完整Bean
整个机制通过中断初始化流程、逆向注入半成品、延迟代理生成(真正需要实例化的时候,通过singletonFactory.getObject()来获取Bean或者Bean的代理)三大策略,将循环依赖的死结转换为有序的接力协作。
值得注意的是,此方案仅适用于Setter/Field注入的单例Bean;
构造器注入因为必须在实例化前获得依赖,仍会导致无解的死锁(BeanCreationException);
多例下的set注入由于多例的存在,多个Bean对象,不是唯一的一个Bean,无法确定具体是哪个,Bean无法提前曝光(又或者说每次获取都会创建一个新Bean)(BeanCreationException)
特殊情况:
假设一个Bean的作用域是多例,一个Bean的作用域是单例,当前情况下的循环依赖也无法解决!
(会报出BeanCurrentlyInCreationException异常)
因为多例Bean需要的依赖Bean一定是位于一级缓存当中的
2.5.3 三级 or 二级 缓存?
Spring必须用三级缓存解决循环依赖,核心是为了**正确处理需要AOP代理的Bean**。如果只用二级缓存,会导致注入的对象形态错误,甚至破坏单例原则。
假设BeanA依赖B,B又依赖A,且A需要被动态代理。如果只有二级缓存,当B创建时去注入A,拿到的是A的原始对象。但A在后续初始化完成后才会生成代理对象,结果就是:B拿着原始对象A,而Spring容器里面存的是代理对象A——同一个Bean出现了两个不同实例,这直接违反了单例的核心约束。
三级缓存中的ObjectFactory就是解决这个问题的关键。它不是直接缓存对象,而是存了一个能生产对
象的工厂。当发生循环依赖时,利用这个工厂的getObject()方法,这时 Spring会智能判断:如果这个
Bean最终需要代理,就在之前生成代理对象并进入二级缓存;如果不需要代理,就返回原始对象。这样一
来,B注入的A是最终状态(可能是代理对象),后来A初始化完成后也不会再创建新代理,保证对
象全局唯一。
简单说,三级缓存的本质是“按需延迟生成正确引用”。它维持了Bean生命周期的完整性(正常流程在初始化后生成代理),又在循环依赖时特殊处理,避免逻辑矛盾。而二级缓存缺乏这种动态决策能力,因此无法替代三级缓存。
3 Spring AOP
Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。
Java是一门面向对象编程的语言,在OOP中最小的单元就是“Class对象”,但是在AOP中,最小的单元是“切面”。一个“切面”可以包含很多种类型和对象,对它们进行模块化管理。
在面向切面编程的思想里面,把功能分为两种:
核心业务:登录、注册、增、删、改、查…
周边业务:日志、事务管理…
在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面和核心业务功能“编制”在一起,这就叫AOP。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块简单耦合度,并有利于未来的可拓展性和可维护性。
3.1 重要概念
AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是Join point,Advice和Pointcut的一个统称。
Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在Spring AOP中,仅支持方法级别的连接点。
Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”、“before”、“after”三种类型。在很多AOP实现框架中,Advice通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着Jointpoint进行处理。
Pointcut:切点,用于匹配连接点,一个AspectJ中包含哪些Joint point需要由Pointcut进行筛选。
Introduction:引介,让一个切面可以声明被通知的对象,实现任何它们没有真正实现的额外接口。例如可以让一个代理对象代理两个目标类。
Weaving:织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
AOP proxy:AOP代理,指在AOP实现框架中实现切面协议的对象。在Spring AOP中有两种代理,分别是JDK动态代理和CGLIB动态代理。
Target object:目标对象,就是被代理的对象
3.2 AOP实现原理
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和兴,从而实现在不修改源码的情况下,增强方法的功能。
Spring AOP支持两种动态代理:
基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
3.2.1 JDK 动态代理
基于接口的代理。
这种类型的代理要求目标对象必须至少实现一个接口,Java动态代理会创建一个实现了相同接口的代理类。
这种代理的实现核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。每一个动态代理类都必须实现InvocationHandler接口,并且每个代理类的实例都关联到一个handler。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler接口的invoke()方法来调用。
3.2.2 CGLIB动态代理

基于类的代理。
CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。
CGLIB动态代理无需原始被代理对象,但需要被代理对象的字节码并通过其动态创建出新的代理对象。
如果目标类的方法依赖实例状态,则需要在MethodInterceptor中传入手动创建的目标对象。
3.2.3 动态代理 & 静态代理
代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。
区别:
静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类,静态代理通常只代理一个类;
动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。
[!NOTE]
那能使用静态代理实现AOP吗?
技术上可行,但硬伤太多:
第一是代码爆炸:比如你有100个Service类需要加事务,就得写100个对应的静态代理类,里面全是重复代码,实现复杂,维护复杂;
第二是僵化:一旦业务接口改了个方法名,所有相关的代理类都得跟着改,而动态代理通过反射调用目标方法,根本不怕这种改动;
第三是无法动态筛选:比如你想只给带
@Transactional注解的方法加事务,静态代理只能写死逻辑,无法动态精准匹配。
3.3 @EnableAspectJAutoProxy注解原理
该注解会给容器中导入 AspectJAutoProxyRegistrar,AspectJAutoProxyRegistrar 在用来向容器中注册 AnnotationAwareAspectJAutoProxyCreator,其以 BeanDefiantion 形式存在,在容器初始化时加载。AnnotationAwareAspectJAutoProxyCreator 间接实现了 InstantiationAwareBeanPostProcessor与Order 接口,所以该类会在 Bean 的实例化和初始化的前后起作用
工作流程:创建 IOC 容器,调用 refresh() 刷新容器,registerBeanPostProcessors(beanFactory)阶段,通过 getBean() 创建 AnnotationAwareAspectJAutoProxyCreator 对象,在生命周期的初始化阶段会执行回调 initBeanFactory() 方法(其实现了BeanFactoryAware接口)初始化注册三个工具类:BeanFactoryAdvisorRetrievalHelperAdapter、ReflectiveAspectJAdvisorFactory、BeanFactoryAspectJAdvisorsBuilderAdapter,这三个工具类的作用是确保所有切面逻辑(Advisor)能被正确识别、解析、注册
3.4 AOP工作流程
深入理解 SpringAOP(二):AOP的执行流程 - Createsequence - 博客园
3.4.1 代理的创建时机
- 实例化前后:依靠
InstantiationAwareBeanPostProcessor完成,不太常见; - 初始化后:依靠
BeanPostProcessor完成,如果没有因为循环依赖导致 bean 被提前实例化,那么正常情况 bean 会在这时被代理; - 获取早期引用时:依靠
SmartInstantiationAwareBeanPostProcessor完成,当需要被代理的 bean 由于循环依赖导致初始化前就要被其他 bean 获取时,会在这时被提前完成代理;
其中,后两者最终都会通过 AbstractAutoProxyCreator.wrapIfNecessary() 完成代理对象的创建。
3.4.2 通知器的获取
当进入 wrapIfNecessary() 方法后,在真正的创建代理对象前,需要通过AbstractAutoProxyCreator.getAdvicesAndAdvisorsForBean() 方法获取可应用于代理对象的通知器。
该方法的具体实现位于 AbstractAdvisorAutoProxyCreator 中:
- 调用
findEligibleAdvisors()用于查出是否有可用的通知器; - 在
findEligibleAdvisors()方法中,通过findCandidateAdvisors()加载 spring 容器中的所有Advisor,在AnnotationAwareAspectJAutoProxyCreator中通过重写该方法,额外加载了一些基于AspectJ的通知器; - 再调用
findAdvisorsThatCanApply()从所有的Advisor中筛选出可应用于当前 bean 的通知器:- 如果通知器是
IntroductionAdvisor类型,则通过通知器的ClassFilter进行类型匹配; - 如果通知器是
PointcutAdvisor类型,则通过通知器的Pointcut切点中的ClassFilter与MethodMatcher分别对类型和类中的方法进行匹配。其中,若MethodMatcher类型为IntroductionAwareMethodMatcher,则支持同时根据方法与其类型进行匹配; - 如果通知器不是上述两者,则认为其必定可应用与当前 bean;
- 如果通知器是
经过上述流程后若当前 bean 没有任何可用的通知器,则说明其无需代理,否则需要通过通知器进行代理增强。
3.4.3 代理前的准备
获得可用的通知器后,将会调用 createProxy() 方法真正的进入创建代理对象的逻辑中。
- 创建一个
ProxyFactory用于后续创建代理对象; - 尝试推断代理对象的类型:
- 若
proxyFactory设置了proxyTargetClass标志为true,则说明要使用目标类(target class)作为代理的基础。在这种情况下,如果目标类是 JDK 动态代理的代理类(比如Annotation)或者是 Lambda 表达式生成的匿名内部类,则需要遍历目标类的接口,将这些接口添加到代理工厂中,以保证代理对象具备这些接口的方法; - 如果
proxyFactory的proxyTargetClass标志没有设置为true,则执行下面的逻辑。这表示没有显式要求使用目标类作为代理基础,则:
a.应该使用目标类作为代理基础,则将proxyTargetClass标志设置为true,即需要进行 CGLib 代理;
b.不满足使用目标类作为代理基础的条件,则调用evaluateProxyInterfaces方法,根据目标类的接口信息,将适合的接口添加到代理工厂中;
- 若
- 适配通知器:在这一步,大部分的Advisor都已经被搜集到了,不过仍有一些通知器需要在buildAdvisors方法中进行适配或者特殊的处理:
- 收集通用通知器:处了在容器中注册的通知器外,有另一部分通用通知器直接通过直接在
AbstractAutoProxyCreator中登记beanName的方法设置,此时需要根据beanName将其从容器中取出; - 适配通知器:此时,已收集的通知器中混有多种类型的对象,因此需要通过
AdvisorAdapterRegistry.wrap方法将其中非Advisor 对象进行适配:- 如果已经是
Advisor了,就直接返回; - 如果不是
Advisor,那必须是Advice,否则直接抛异常; - 如果它是
MethodInterceptor,那就将其适配为DefaultPointcutAdvisor; - 如果是其他类型的
Advice,就调用适配器链,找到一个支持处理这个拦截器的AdvisorAdapter去对它做适配;
- 如果已经是
- 收集通用通知器:处了在容器中注册的通知器外,有另一部分通用通知器直接通过直接在
3.4.4 创建代理对象
- 当在
ProxyFactory中配置好了代理对象的类型、需要实现的接口类型、需要应用的通知器及其他配置后; ProxyFactory将会在createAopProxy()方法中获取一个AopProxyFactory(默认为DefaultAopProxyFactory);- 接着调用
DefaultAopProxyFactory的createAopProxy()方法,根据情况去创建一个AopProxy对象:- 如果需要基于目标类代理,那么就使用 CGLib 代理,返回一个
ObjenesisCglibAopProxy; - 如果需要基于接口代理,那么使用 JDK 的动态代理,返回一个
JdkDynamicAopProxy;
- 如果需要基于目标类代理,那么就使用 CGLib 代理,返回一个
- 接着我们调用
AopProxy的getProxy()方法,该方法将会真正的创建一个代理对象。
3.4.5 方法代理(责任链)
对于 JDK 动态代理来说,用于生成代理的 InvocationHandler 即为 JdkDynamicAopProxy 本身,当我们调用代理对象的方法时,将会统一经过 invoke 方法。
其中,根据方法类型又分为三种类型:
equals和hashCode方法:若两方法没有在接口中重新定义,则直接按JdkDynamicAopProxy去进行比较/获取哈希值;- 如果方法是
DecoratingProxy或Advised接口中声明的方法,则直接通过当前代理对象内部持有的相应实例进行调用; - 如果是普通方法,则在调用前后一次调用通知器中的
Advice增强逻辑;
其中,对于普通方法,在首次调用 invoke 的时候,将会:
- 通过内部持有的
AdvisedSupport对象(即Advised的默认实现)的getInterceptorsAndDynamicInterceptionAdvice()方法获取相应的方法拦截器; - 然后
AdvisedSupport将会通过内部的AdvisorChainFactory(默认为DefaultAdvisorChainFactory)的getInterceptorsAndDynamicInterceptionAdvice()方法创建方法拦截器链; - 在
DefaultAdvisorChainFactory中,将会:- 先取出
AdvisedSupport中存放的所有可应用于当前 bean 的通知器,然后遍历它们:- 如果当前的
Advisor是一个PointcutAdvisor,即基于切点的通知器,那么根据其持有的切点Pointcut中的ClassFilter与MethodMatcher进行匹配; - 如果当前的
Advisor是一个IntroductionAdvisor,即引介增强器,那么根据类过滤器的匹配规则判断是否需要将它添加到拦截器链中; - 如果当前的
Advisor不是上述两种类型,直接将它的拦截器数组添加到拦截器链中;
- 如果当前的
- 在获得的可用的通知器后,通过
AdvisorAdapterRegistry.getInterceptors()(此处的适配器注册表在上文也用于适配Advisor)方法将其全部适配为MethodInterceptor;
- 先取出
- 通过
DefaultAdvisorChainFactory获取方法拦截器链后,将会将拦截器链与要增强的代理方法封装为一个方法调用对象MethodInvocation(默认为ReflectiveMethodInvocation); - 然后调用
MethodInvocation.processed方法,此时将会调用链首的拦截器,接着拦截器再继续调用processed()方法,递归此步骤,直到整个调用链完成或者中断为止;
3.4.5 总结
Spring AOP的执行过程可以划分为4个阶段:创建代理对象阶段、拦截目标对象阶段、调用代理对象阶段、调用目标对象阶段

1.第一阶段:创建代理对象阶段
在Spring中,创建Bean实例都是从getBean()方法开始的。如下图所示,在实例创建之后,Spring容器将根据AOP的配置匹配目标类的类名,看目标类的类名是否满足切面规则。如果满足切面规则,就会调用ProxyFactory创建代理Bean并缓存到IOC容器中,再根目标对象自动选择不同的代理策略。

2.拦截目标对象阶段
当用户调用目标对象的某个方法时,将会被一个叫做AopProxy的对象拦截,Spring将所有的调用策略封装到这个接口的invoke()方法中,会触发MethodInvocation的proceed()方法,如下图所示。在这个方法中会按顺序执行符合所有AOP拦截规则的拦截器链。

3.调用代理对象阶段
Spring AOP拦截器链中的每个元素被命名为MethodInterceptor,其实就是切面配置中的Advice通知。这个回调通知可以简单的理解为新生成的代理Bean中的方法,也就是我们常说的被织入的代码片段,这些被织入的代码片段会在这个阶段执行。

4.调用目标对象阶段
MethodInteceptor接口也有一个invoke()方法,该方法会触发对目标对象方法的调用,也就是说会**反射**调用目标对象的方法。


4 Spring MVC
4.1 MVC分层
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显式分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面以及用户交互的同时,不需要重新编写业务逻辑。
**视图(view):**为用户提供使用界面,与用户直接进行交互
模型(model):代表一个存取数据的对象或Java pojo(Plain Old Java Object,简单java对象)。它也可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。模型分为两类,一类称为数据承载Bean,一类称为业务处理Bean。所谓数据承载Bean是指实体类(如User类),专门为用户承载业务数据的;而业务处理Bean则是指Service或Dao对象,专门用于处理用户提交请求的。
**控制器(controller):**用于将用户请求转发给相应的Model进行处理,并根据Model的计算结果向用户提供相应响应。它使视图与模型分离。

流程步骤:
1.用户通过View页面向服务端提出请求,可以是表单请求、超链接请求、AJAX请求等;
2.服务端Controller控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行Model处理;
3.将处理结果再交给Controller(控制器其实只是起到了承上启下的作用);
4.根据处理结果找到要作为向客户端发回的响应View页面,页面经渲染后发送给客户端。
4.2 核心组件

**1.DispatcherServlet:**核心控制器,是SpringMVC的核心,整体流程控制的中心,所有的请求第一步都会先到达这里,由其调用其他组件处理用户的请求,它就是在web.xml配置的核心Servlet,有效的降低了组件间的耦合性
**2.HandlerMapping:**处理器映射器,负责根据请求找到对应具体的Handler处理器,SpringMVC中针对配置文件方式、注解方式等提供了不同的映射器来处理
**3.Handler:**处理器,其实就是Controller,业务处理的核心类,通常由开发者编写,并且必须遵守Controller开发的规则,这样适配器才能正确的执行。
**4.HandlerAdapter:**处理器适配器,根据映射器中找到的Handler,通过HandlerAdapter去执行Handler,这是适配器模式的应用
**5.ViewResolver:**视图解析器,将Handler中返回的逻辑视图(ModelAndView)解析为一个具体的视图(View)对象
4.3 MVC工作流程

1.用户发送请求至前端控制器DispatcherServlet
2.DispatcherServlet收到请求后,调用处理器映射器HandlerMapping
3.处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet
4.DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
5.执行处理器Handler(Controller,也叫页面控制器)
6.Handler执行完成返回ModelAndView
7.HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9.ViewResolver解析后返回具体View
10.DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)
11.DispatcherServlet响应用户
4.4 Restful风格
Restful 一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
Web应用程序最重要的REST原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 协议,比如 GET、PUT、POST和 DELETE。
对于我们Web开发人员来说。就是使用一个url地址表示一个唯一的资源。然后把原来的请求参数加入到请求资源地址中。然后原来请求的增,删,改,查操作。改为使用HTTP协议中请求方式GET、POST、PUT、DELETE表示。
1.把请求参数加入到请求的资源地址中
2.原来的增,删,改,查。使用HTTP请求方式,POST、DELETE、PUT、GET分别一一对应。
eg.
传统的方式是:
比如:http://ip:port/工程名/资源名?请求参数
举例:http://127.0.0.1:8080/springmvc/book?action=delete&id=1
restful风格是:
比如:http://ip:port/工程名/资源名/请求参数/请求参数
举例:http://127.0.0.1:8080/springmvc/book/1
请求的动作删除由请求方式DELETE决定
5 Spring事务
5.1 事务机制
5.1.1 传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题,也就是方法嵌套。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
1.支持当前事务
REQUIRED: 如果当前存在事务则加入该事务;如果当前没有事务则创建一个新的事务
SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
2.不支持当前事务
REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起
NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起
NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常
3.其他情况
NESTED: 开启新事务,提交事务依赖于外层事务,如果外层事务回滚,则里面事务也回滚;如果里层事务回滚,外层不回滚(类似于SVAE POINT,回滚点)
5.1.2 隔离级别
DEFAULT(-1):数据库默认的隔离级别
READ_UNCOMMITTED(1):读未提交 ru,会导致脏读
READ_COMMITTED(2):读已提交 rc 避免脏读,允许不可重复读和幻读
REPEATABLE_READ(4):可重复读 rr 避免脏读,不可重复读,允许幻读,innodb存储引擎解决了幻读
SERIALIZABLE:串行化
从上到下,隔离级别越来越高,并发性能就越来越差,Spring事务的本质还是数据库的事务,如果数据库不支持事务,Spring的事务也就没有了意义,Spring 只提供统一事务管理接口
5.1.3 超时属性
事务超时,指一个事务所允许执行的最长时间,如果超过该时间限制事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为 -1
5.1.4 只读属性
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务;只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中
读操作为什么需要启用事务支持:执行多条查询语句,如果方法加上了 @Transactional注解,这个方法执行的所有 SQL 会被放在一个事务中,而且声明了只读事务的话数据库就会去优化它的执行。如果不加 @Transactional,每条 SQL 会开启一个单独的事务,中间被其它事务修改了数据,比如在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则这次整体的统计查询将会出现读数据不一致的状态
5.2 声明式事务
5.2.1 @Transactional
1.@Transactional注解只有作用到 public 方法上事务才生效
2.不推荐在接口上使用 @Transactional 注解
原因:在接口上使用注解,只有在使用基于接口的代理(JDK)时才会生效,因为注解是不能被继承的,这就意味着如果正在使用基于继承了原始类的代理(CGLIB)时,那么事务的设置将不能被基于类的代理所识别
3.默认情况下,事务只有遇到运行期异常 和 Error 会导致事务回滚,但是在遇到检查型(Checked)异常时不会回滚
RuntimeException或error的派生类是非受检异常,比如空指针和索引越界,而其余的继承自 Exception 的则是受检异常,比如 IOException、ClassNotFoundException,RuntimeException 本身继承 Exception
非检查型类异常可以不用捕获,而检查型异常则必须用 try 语句块把异常交给上级方法,这样事务才能有效
5.2.2 事务失效
1.方法没有被public修饰
如果@Transactional注解标注在非public方法上,事务就会失效
2.类没有被Spring托管
如果事务方法所在的类没有加载到Spring IOC容器中,也就是说,事务方法所在的类没有被Spring管理,那么Spring将无法实现代理,Spring事务也会失效
3.跨方法调用事务问题
如果一个事务方法内部调用另一个方法,而这个被调用的方法没有@Transactional注解,这种情况下外层事务会失效
4.事务传播属性设置不当
如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用@Transactional注解的方法时要注意
5.在业务层捕获异常后,未向上抛出,事务不生效
在业务层捕获异常并通过try…catch处理,Spring就不知道这里有错,也不会主动去回滚数据
(推荐做法是在业务层统一抛出异常,然后在控制层统一处理)
6.rollbackFor参数设置错误
如果在@Transactional注解中rollbackFor参数标注了错误的异常类型,那么Spring事务的回滚就无法识别,导致事务回滚失效
7.多数据源的事务管理

8.数据库本身不支持事务
Spring事务生效的前提是所连接的数据库要支持事务,如果底层的数据库不支持事务,那么Spring的事务肯定会失效。例如数据库为MySQL,存储引擎为MYISAM(MYISAM不支持事务)
6 SpringBoot
SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率
SpringBoot 功能:
1.自动配置,自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素选择使用哪个配置,该过程是SpringBoot 自动完成的
2.起步依赖,起步依赖本质上是一个 Maven 项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能
3.辅助功能,提供了一些大型项目中常见的非功能性特性,如内嵌 web 服务器、安全、指标,健康检测、外部配置等
6.1 约定优于配置
约定大于配置是Spring Boot的核心设计理念,它通过预设合理的默认行为和项目规范,大幅减少开发者需要手动配置的步骤,从而提升开发效率和项目标准化程度。
理解Spring Boot中的“约定大于配置”原则,可以从以下几个方面来解释:
自动化配置:Spring Boot提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为.开发者无需显式的配置每个细节,大部分常用的配置都已经预设好了。例如,引入spring-boot-starter-web后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需手动编写XML。
默认配置:Spring Boot为诸多方面提供大量默认配置,如连接数据库、设置Web服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。例如,默认的日志配置可让应用程序快速输出日志信息,无需开发者额外繁琐配置日志级别、输出格式与位置等。
约定的项目结构:Spring Boot提倡特定项目结构,通常主应用程序类(含main方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包。此约定使团队成员更易理解项目结构与组织,新成员加入项目时能快速定位各功能代码位置,提升协作效率。
6.2 自动装配
SpringBoot 的自动装配原理是基于Spring Framework的条件化配置和@EnableAutoConfiguration注解实现的。这种机制允许开发者在项目中引入相关的依赖,SpringBoot将根据这些依赖自动配置应用程序的上下文和功能。
SpringBoot定义了一套接口规范,这套规范规定:SpringBoot在启动时会扫描外部引用jar包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到Spring容器,并执行类中定义的各种操作。对于外部jar来说,只需要按照SpringBoot定义的标准,就能将自己的功能装置进SpringBoot。
通俗来讲,自动装配就是通过注解或一些简单的配置就可以在SpringBoot的帮助下开启和配置各种功能,比如数据库访问、Web开发。
6.2.1 @SpringBootApplication注解原理
其中需要关注的是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解
@SpringBootConfiguration
就是表示当前类是个配置类,作用同@Configuration一样
@EnableAutoConfiguration
这个注解启动了自动配置机制,其向容器中注册了两个关键组件AutoConfigurationImportSelector和AutoConfigurationPackage.Registrar
前者的会在refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory)方法被调用,会从META-INF/spring.factories中加载资源,把需要的自动配置类的候选项都获取,再进行去重和条件过滤
后者的作用则是向容器中注册一个名为AutoConfigurationPackages的BeanDefiniton,其作用是为三方Starter提供统一的启动类所在的包路径,便于它们扫描包中三方注解
@ComponentScan
默认扫描当前类所在包及其子级包下的所有文件
6.2.2 装配流程
Spring Boot 通过 @EnableAutoConfiguration 开启自动装配,通过 SpringFactoriesLoader 加载 META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional 注解按需加载的配置类,想要其生效必须引入 spring-boot-starter-xxx包实现起步依赖
SpringBoot 先加载所有的自动配置类 xxxxxAutoConfiguration
每个自动配置类进行条件装配,默认都会绑定配置文件指定的值(xxxProperties类和配置文件通过@ConfigurationProperties(prefix = “”)进行了绑定)
SpringBoot 默认会在底层配好所有的组件,如果用户自己配置了以用户的优先
定制化配置:
用户可以使用 @Bean 新建自己的组件来替换底层的组件
用户可以去看这个组件是获取的配置文件前缀值,在配置文件中修改
6.3 Starter
Starter是Spring Boot的四大核心功能特性之一,除此之外,Spring Boot还有自动装配、Actuator监控等特性。
Spring Boot里面的这些特性,都是为了让开发者在开发基于Spring生态下的企业级应用时,只需要关心业务逻辑,减少对配置和外部环境的依赖。
其中,Starter是启动依赖,它的主要作用有几个:
- Starter组件以功能为纬度,来维护对应的jar包的版本依赖,使得开发者可以不需要去关心这些版本冲突这种容易出错的细节。
- Starter组件会把对应功能的所有jar包依赖全部导入进来,避免了开发者自己去引入依赖带来的麻烦。
- Starter内部集成了自动装配的机制,也就说在程序中依赖对应的starter组件以后,这个组件自动会集成到Spring生态下,并且对于相关Bean的管理,也是基于自动装配机制来完成。
- 依赖Starter组件后,这个组件对应的功能所需要维护的外部化配置,会自动集成到Spring Boot里面,
我们只需要在application.properties文件里面进行维护就行了,比如Redis这个starter,只需要在application.properties文件里面添加redis的连接信息就可以直接使用了。

另外,Spring Boot官方提供了很多的Starter组件,比如Redis、JPA、MongoDB等等。
但是官方并不一定维护了所有中间件的Starter,所以对于不存在的Starter,第三方组件一般会自己去维护一个。官方的starter和第三方的starter组件,最大的区别在于命名上。官方维护的starter的以spring-boot-starter开头的前缀;第三方维护的starter是以spring-boot-starter结尾的后缀。这也是一种约定优于配置的体现。

6.4 过滤器 & 拦截器
在SpringBoot中,过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的两种不同机制。

过滤器是Java Servlet规范中的一部分,它可以对进入Servlet容器的请求和响应进行预处理和后处理。过滤器通过实现javax.servlet.Filter接口,并重写其中的init、doFilter和destroy方法来完成相应的逻辑。当请求进入Servlet容器时,会按照配置的顺序一次经过各个过滤器,然后再到达目标Sevrlet或控制器;响应返回时,也会按照相反的顺序再次经过这些过滤器。
拦截器是Spring框架提供的一种机制,它可以对控制器方法的执行进行拦截。拦截器通过实现org.springframework.web.servlet.HandlerInterceptor接口,并重写其中的preHandle、postHandle和afterCompletion方法来完成相应的逻辑。当请求到达控制器时,会先经过拦截器的preHandle方法,如果该方法返回true,则继续执行后续的控制器方法和其他拦截器;在控制器方法执行完成后,会调用拦截器的postHandle方法;最后,在请求处理完成后,会调用拦截器的afterCompletion方法。
过滤器和拦截器的区别如下:
所属规范:过滤器是Java Servlet规范的一部分,而拦截器是Spring框架提供的机制
执行顺序:过滤器在请求进入Servlet容器后,在到达目标Servlet或控制器之前执行;拦截器在请求到达控制器之后,在控制器方法执行前后执行
使用范围:过滤器可以对所有类型的请求进行过滤,包括静态资源请求;拦截器只能对Spring MVC控制器的请求进行拦截
功能特性:过滤器主要用于对请求和响应进行预处理和后处理,如字符编码处理、请求日志记录等;拦截器可以更细粒度的控制控制器方法的执行,如权限验证、性能监控等。
7 Spring设计模式
7.1 工厂模式
Spring框架的核心设计思想是控制反转(IoC),而工厂模式正是实现IoC的核心手段。Spring通过工厂模式解耦对象的创建与使用,使开发者无需关心对象的实例化细节。
核心思想:
工厂模式的核心是将对象的创建逻辑封装在工厂类中,客户端通过工厂接口获取对象而非直接实例化,从而:解耦,隔离对象创建于业务逻辑;扩展性,新增对象类型无需修改客户端代码;统一管理,集中控制对象的生命周期。
Spring中工厂模式主要分为三类:
**简单工厂:**通过静态方法创建对象(非标准设计模式)
**工厂方法:**由子类决定实例化哪个类
**抽象工厂:**创建一组相关或依赖对象的接口
核心工厂接口:
BeanFactory:基础工厂接口,提供给getBean()方法,按名称或类型获取Bean实例
ApplicationContext:增强型工厂,BeanFactory的子接口,添加企业级功能(事件发布、国际化支持、资源加载)
FactoryBean:定制化工厂,创建复杂对象(如数据库连接池、代理对象)
应用场景:
动态Bean创建:通过FactoryBean实现条件化Bean创建(如根据环境生成不同数据源)
多数据源管理:使用AbstractRoutingDataSource(抽象工厂)动态切换数据源
整合第三方框架:MyBatis的SqlSessionFactoryBean通过FactoryBean创建SqlSessionFactory
延迟依赖注入:利用ObjectProvider实现按需获取Bean(避免过早初始化)
7.2 单例模式
Spring框架中的单例模式是其IoC(控制反转)容器的核心设计之一,用于高效管理Bean的生命周期和资源复用。
核心定义:
本质:Spring容器为每个Bean定义仅创建一个实例,并在整个应用上下文中共享该实例
默认行为:Spring中所有Bean默认均为单例(无需显式配置)
实现机制:

总结:
Spring的单例模式通过容器级实例复用和三级缓存机制,实现了高性能的对象管理。其核心价值在于减少资源消耗与简化对象共享,但需警惕线程安全与依赖注入的陷阱。在实际开发中,结合业务场景选择作用域,并遵循无状态设计原则,能最大化发挥单例模式的优势。
7.3 策略模式
策略模式(Strategy Pattern)在 Spring 框架中的应用是一种将算法封装为独立对象、实现动态替换的核心设计思想,能显著提升代码的灵活性和可维护性。
核心概念:
策略模式将一组可互换的算法封装为独立类,通过统一接口调用,使算法变化独立于客户端。主要角色包括:
策略接口,定义算法规范
具体策略,实现接口的具体算法
上下文,持有策略引用,负责调用算法
策略模式主要消除复杂业务逻辑中的if-else和switch-case分支,支持动态扩展新策略而不修改原有代码(符合开闭原则)
应用场景:
支付集成,不同支付渠道(支付宝、微信)实现 PaymentStrategy 接口,通过 Map 注入动态切换;
折扣计算,多种优惠策略(满减、折扣券、VIP 特权)封装为独立策略类,避免分支判断;
国际化处理,Spring MVC 的 LocaleResolver 通过策略模式支持 Session/Cookie 等解析方式;
事务管理,Spring 的 PlatformTransactionManager 是策略模式的经典应用,JDBC/JPA 事务通过不同策略实现
7.4 观察者模式
Spring框架中的观察者模式通过事件驱动模型(Event-Driven Model) 实现,核心目标是解耦组件,让发布者(事件源)与观察者(监听器)无需直接依赖,而是通过事件通信。
核心组件:
ApplicationEvent(事件),被观察的目标状态变化载体,继承自Java的EventObject。
ApplicationListener(监听器),观察者,监听特定事件并响应
ApplicationEventPublisher(发布器),事件发布者,通常由ApplicationContext实现
ApplicationEventMulticaster(事件广播器),管理监听器并广播事件,默认实现为SimpleApplicationEventMulticaster
实现原理:

应用场景:
业务解耦,用户注册后发送邮件、发送优惠卷、更新推荐系统互不干扰
状态变更响应,订单支付后扣减库存、更新积分、通知物流
系统审计,关键操作(如删除数据)自动记录审计日志(异步执行)
缓存同步,数据更新时刷新多级缓存
7.5 装饰器模式
装饰器模式(Decorator Pattern)在Spring框架中是一种通过动态包装对象来扩展功能的结构型设计模式,遵循“组合优于继承”原则,避免因继承导致的类爆炸问题。
核心原理与组件:
装饰器模式的核心是透明地增强对象功能,不改变原有对象结构。其核心角色包括:
组件接口,定义核心功能的接口(如 MessageService、OrderService),是原始对象与装饰器的共同契约;
具体组件,实现组件接口的基础对象(如 SimpleMessageService、SimpleOrderService),提供核心业务逻辑;
装饰器基类,抽象类或接口,持有组件接口的引用,并实现相同的接口(如 MessageDecorator、OrderServiceDecorator)。其作用是承上启下,默认将请求委托给被包装对象;
具体装饰器,继承装饰器基类,添加新功能(如日志、加密、缓存)。
透明性:装饰后的对象与原始对象接口一致,调用方无需感知装饰层存在。
应用场景:
业务逻辑增强 eg:订单服务扩展:基础订单服务(计算总价) + 折扣装饰器(满减/百分比折扣) + 税费装饰器;
流操作 eg:缓存流:CachingInputStream 装饰原始输入流,支持重复读取
7.6 代理模式
Spring框架中的代理模式是实现AOP(面向切面编程)的核心机制,它通过动态生成代理对象,在不修改原始代码的前提下为业务逻辑添加横切关注点(如事务、日志、安全控制)。
核心角色:
Subject(抽象主题):定义业务接口(如UserService),是代理对象和目标对象的共同契约;
RealSubject(真实主题):实现业务逻辑的具体类(如UserServiceImpl),是被代理的对象;
Proxy(代理):持有真实主题的引用,控制访问并添加增强逻辑(如日志记录);
Client(客户端):通过代理对象间接访问真实主题,无需感知增强逻辑
**实现方式:**静态代理、动态代理(JDK/CGLIB)
应用场景:
声明式事务管理、统一日志记录、安全权限控制、缓存管理
7.7 建造者模式
在Spring框架中,建造者模式(Builder Pattern)是一种核心的创建型设计模式,通过分步骤构建复杂对象,将对象的构建过程与其表示分离,显著提升代码的灵活性和可维护性。
核心原理:
1.角色定义
产品(Product):需构建的复杂对象(如UriComponents、BeanDefinition)
建造者(Builder):定义构建步骤的接口(如UriComponentsBuilder)
具体建造者(Concrete Builder):实现构建逻辑(如DefaultUriComponentsBuilder)
指挥者(Director):控制构建流程(可省略,由建造者自包含逻辑)
2.解决的问题
参数爆炸:避免构造函数包含过多参数(如超过4个),导致代码臃肿
可选参数灵活处理:支持部分参数缺省或动态配置
构建过程安全可控:确保对象在完整构建后才可被使用,避免半成品状态
| 维度 | 传统构造方法 | 建造者模式 |
|---|---|---|
| 参数过多处理 | 需重载多个构造函数,可读性差 | 链式调用逐步配置,逻辑清晰 |
| 可选参数支持 | 需传递null或默认值,易出错 | 显式指定参数,避免遗漏 |
| 线程安全性 | 对象状态可能被部分修改 | 可设计为不可变对象(final字段) |
| 扩展性 | 新增参数需修改构造函数 | 新增构建方法不影响现有调用 |
应用场景:
URI构建:UriComponentsBuilder,动态生成含路径参数、查询参数的URL;
Bean定义构建:BeanDefinitionBuilder,动态注册Bean到Spring容器(如插件化系统);
测试环境构建:MockMvcBuilders,分层配置Web测试环境(控制器、安全过滤器等);
Spring Security配置链,构建HTTP安全规则(认证、授权、CSRF等)。
7.8 适配器模式
Spring中的适配器模式(Adapter Pattern)是一种核心的结构型设计模式,用于解决接口不兼容问题,使原本无法协同工作的类能够协同工作。其核心思想是通过一个中间层(适配器)将已有接口转换为目标接口,实现无缝集成。
核心原理:
1.角色
目标接口(Target):客户端期望的接口(如Spring MVC的HandlerAdapter、支付系统中的PaymentGateway)
适配者(Adaptee):已存在但接口不兼容的类(如旧版支付接口AlipayService)
适配器(Adapter):实现目标接口并持有适配者引用,将目标方法调用转发给适配者(如AlipayAdapter)
2.实现方式
| 类型 | 实现方式 | 适用场景 |
|---|---|---|
| 类适配器 | 继承适配者类 + 实现目标接口 | Java单继承限制下较少使用 |
| 对象适配器 | 组合适配者对象(Spring主流) | 更灵活,支持多适配者 |
| 接口适配器 | 抽象类空实现接口方法(默认适配器) | 接口方法过多时选择性实现(如WebMvcConfigurerAdapter) |
应用场景:
Spring MVC:
HandlerAdapter问题:
DispatcherServlet需处理多种控制器(如@Controller、HttpRequestHandler),但控制器接口不统一解决方案:
RequestMappingHandlerAdapter适配注解控制器,将HttpServletRequest转换为方法参数,调用@RequestMapping方法; 其他适配器如
SimpleControllerHandlerAdapter处理基于接口的控制器
Spring AOP:通知适配器(
AdviceAdapter)问题:多种通知类型(如
@Before、@After)需统一为AOP框架的MethodInterceptor接口解决方案:
MethodBeforeAdviceAdapter将MethodBeforeAdvice转换为MethodInterceptor;
AfterReturningAdviceAdapter适配返回通知
7.9 桥接模式
桥接模式(Bridge Pattern)在 Spring 框架中是一种关键的结构型设计模式,通过将抽象与实现解耦,使两者能独立变化,有效避免了继承导致的类爆炸问题。
核心原理:
桥接模式的核心是将一个多维度的系统拆分为抽象层和实现层,通过组合而非继承连接两者:
抽象部分(Abstraction):定义高层业务逻辑接口,持有实现层引用(如 JdbcTemplate);
扩展抽象(Refined Abstraction):对抽象层的扩展(如特定消息类型 EmailMessage);
实现接口(Implementor):定义底层操作的接口(如 DataSource)
具体实现(Concrete Implementor):实现接口的具体类(如 HikariDataSource)
应用场景:
数据访问层:
JdbcTemplate与DataSource 抽象层:
JdbcTemplate封装了数据库操作的统一接口(如query()、update()) 实现层:
DataSource接口定义连接管理,具体实现如HikariDataSource(连接池)、DriverManagerDataSource(直连) 桥接关系:
JdbcTemplate构造函数注入DataSource,运行时动态选择数据库实现
7.10 命令模式
Spring 框架中的命令模式(Command Pattern)是一种行为型设计模式,通过将请求封装为独立对象,实现操作发起者与执行者的解耦,支持操作的排队、撤销、重做等高级功能。
角色定义:
命令接口(Command):声明执行操作的方法(如 execute() 或 undo())
具体命令(Concrete Command):实现命令接口,绑定接收者并调用其方法(如 CreateOrderCommand)
接收者(Receiver):实际执行业务逻辑的对象(如 OrderService)
调用者(Invoker):触发命令执行(如 OrderCommandManager)
客户端(Client):创建命令对象并组装依赖
核心价值:
解耦:调用者(如控制器)无需知晓接收者的具体实现,只需调用命令接口
扩展性:新增命令无需修改现有代码(开闭原则)
高阶功能支持:通过记录命令历史,轻松实现撤销(Undo)、重做(Redo)或日志记录
应用场景:
Spring MVC:
HandlerMethod 场景:HTTP 请求的分发与处理
实现:
HandlerMethod封装控制器方法(如@GetMapping方法),作为具体命令
RequestMappingHandlerAdapter作为调用者,解析请求后调用handlerMethod.invoke() 控制器类(如
UserController)是接收者,执行实际业务逻辑
7.11 状态模式
Spring中的状态模式(State Pattern)是一种行为型设计模式,通过将对象的行为委托给代表其当前状态的对象,实现状态驱动的行为变化。该模式在Spring框架中广泛应用于订单管理、工作流、审批流程等场景,尤其在Spring StateMachine框架中得到深度集成。
核心角色:
上下文(Context):持有当前状态对象,定义客户端接口(如 StateMachine 接口)
抽象状态(State):定义状态行为的接口(如 State<S, E> 接口)
具体状态(Concrete State):实现特定状态的行为逻辑(如订单的 PaidState、ShippedState)
核心价值:
消除条件分支:将 if-else/switch-case 逻辑拆解到独立状态类中,提升代码可读性
行为与状态绑定:对象在不同状态下自动切换行为(如订单在“待支付”状态下仅允许支付操作)
开闭原则:新增状态只需添加新类,无需修改现有代码
应用场景:
订单状态管理
问题:订单涉及多状态(待支付、已发货、已完成),传统
if-else导致代码臃肿 解决方案:每个状态封装独立行为(如
PaidState处理发货逻辑) 优势:状态转换通过事件显式触发(如
PAY事件触发支付到发货的转换)
7.12 模板方法模式
Spring框架中的模板方法模式(Template Method Pattern)是一种行为型设计模式,其核心思想是定义算法骨架,将可变步骤延迟到子类实现,从而实现代码复用和扩展性。
核心原理:
模板方法(Template Method),在抽象类中定义算法骨架(固定流程),包含一系列步骤的调用顺
基本方法:
抽象方法:子类必须实现的步骤(如Spring IoC中的refreshBeanFactory())
具体方法:父类已实现的通用逻辑(如prepareRefresh())
钩子方法(Hook Method):父类提供默认空实现,子类可选择性覆盖(如postProcessBeanFactory())
核心价值:
代码复用:将通用流程(如数据库连接/释放、事务管理)封装在父类,避免重复代码
扩展性:子类仅需关注个性化步骤(如SQL执行、HTTP请求处理),不改变算法结构
符合开闭原则:新增功能只需扩展子类,无需修改父类骨架
应用场景:
IoC容器初始化:
AbstractApplicationContext.refresh() 模板方法:
refresh()定义了容器初始化的12个步骤(如准备上下文、初始化Bean工厂、发布事件) 关键钩子:
refreshBeanFactory():抽象方法,子类实现以创建Bean工厂(如ClassPathXmlApplicationContext)
postProcessBeanFactory():钩子方法,子类可添加自定义Bean工厂后处理逻辑
数据访问:
JdbcTemplate 模板方法:
execute()封装JDBC操作流程(获取连接、执行SQL、释放资源) 可变部分:通过回调接口(如
ConnectionCallback)注入自定义SQL逻辑
7.13 责任链模式
责任链模式(Chain of Responsibility Pattern)在Spring框架中是一种行为型设计模式,通过将请求的发送者与多个处理者解耦,使多个对象都有机会处理请求,形成一条处理链。其核心价值在于动态管理处理流程,支持灵活扩展和职责分离。
模式结构:
抽象处理者(Handler):定义处理请求的接口(如HandlerInterceptor),通常包含指向下一个处理者的引用;
具体处理者(Concrete Handler):实现处理逻辑(如AuthInterceptor),若无法处理则传递给下一个处理者;
客户端(Client):创建责任链并触发请求(如DispatcherServlet)
核心价值:
解耦:请求发送者无需知道具体处理者,只需将请求传递给链首;
动态扩展:通过增减处理者节点调整流程,符合开闭原则;
责任分离:每个处理者专注单一职责(如日志、权限校验)
应用场景:
Spring MVC:
HandlerExecutionChain 处理链:封装
Handler(Controller方法)和多个HandlerInterceptor(拦截器) 调用流程:
DispatcherServlet根据URL匹配Handler并创建责任链; 按顺序执行拦截器的
preHandle()(如权限校验); 若全部通过则执行
Handler,否则中断链条; 倒序执行拦截器的
postHandle()和afterCompletion()(如日志记录)
7.14 迭代器模式
迭代器模式(Iterator Pattern)在Spring框架中是一种行为型设计模式,其核心思想是提供一种统一的方式来顺序访问聚合对象(如集合)中的元素,而不暴露其内部实现细节。该模式在Spring中广泛应用于数据遍历、事件分发和资源管理,是解耦集合结构与遍历逻辑的关键设计。
角色定义:
抽象迭代器(Iterator):定义访问元素的接口(如hasNext(), next()),Java标准库中的java.util.Iterator是典型代表
具体迭代器(Concrete Iterator):实现迭代逻辑(如Spring的BeanDefinitionNamesIterator)
抽象聚合(Aggregate):定义创建迭代器的方法(如iterator())
具体聚合(Concrete Aggregate):实现聚合对象并返回迭代器实例(如ListableBeanFactory)
核心价值:
解耦遍历与存储:客户端无需知道集合底层是数组、链表还是数据库结果集
统一访问接口:不同集合类型(List、Set、Map)均可通过同一迭代器接口访问
支持多种遍历方式:如正向、反向或条件过滤遍历,无需修改聚合类
应用场景:
Bean定义遍历
场景:Spring容器启动时遍历所有
BeanDefinition进行初始化 实现:
ConfigurableListableBeanFactory.getBeanDefinitionNames()返回Bean名称数组,通过迭代器或for-each循环遍历
7.15 访问者模式
访问者模式(Visitor Pattern)在 Spring 框架中是一种行为型设计模式,其核心思想是将数据结构与数据操作分离,允许在不修改现有对象结构的前提下定义新的操作。该模式通过引入独立的访问者对象来处理不同类型的元素,特别适用于对象结构稳定但操作频繁扩展的场景。
角色定义:
抽象访问者(Visitor):定义对每个元素的操作接口(如 visit(ElementA)、visit(ElementB))
具体访问者(ConcreteVisitor):实现访问者接口,定义针对不同元素的具体操作(如 PriceCalculator 计算订单价格)
抽象元素(Element):声明接受访问者的方法(如 accept(Visitor))
具体元素(ConcreteElement):实现 accept 方法,调用访问者的对应操作(如 ProductOrderItem)
对象结构(Object Structure):聚合元素的容器(如 Order 类管理订单项)
核心价值:
解耦数据结构与操作:元素类仅关注自身属性,操作逻辑由访问者实现
开闭原则:新增操作只需添加访问者类,无需修改元素类(如新增报表生成不影响订单结构)
双重分派机制:通过 element.accept(visitor) 触发 visitor.visit(element),实现操作动态绑定
应用场景:
Bean 定义处理(BeanDefinitionVisitor)
场景:解析
BeanDefinition中的占位符(如${db.url}) 实现:
元素:
BeanDefinition对象 访问者:
BeanDefinitionVisitor类,通过visitBeanDefinition()方法替换属性值public void visitBeanDefinition(BeanDefinition beanDefinition) { visitParentName(beanDefinition); // 处理父类名 visitBeanClassName(beanDefinition); // 处理Bean类名 visitPropertyValues(beanDefinition.getPropertyValues()); // 处理属性值 }
&spm=1001.2101.3001.5002&articleId=149351154&d=1&t=3&u=9a5bb3b43b2142cebfaeb724a0561a56)
1549

被折叠的 条评论
为什么被折叠?



