Spring

Spring 框架核心特性包括:
- IoC 容器:Spring 通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好 Bean 及其依赖关系,Spring 容器负责创建和组装这些对象。
- AOP:面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过 AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。
- 事务管理:Spring 提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务 API。
- MVC 框架:Spring MVC 是一个基于 Servlet API 构建的 Web 框架,采用了模型 - 视图 - 控制器(MVC)架构。它支持灵活的 URL 到页面控制器的映射,以及多种视图技术
核心思想
| 核心思想 | 解决的问题 | 实现手段 | 典型应用场景 |
|---|---|---|---|
| IOC(反转控制) | 对象创建与依赖管理的高耦合 | 容器管理 Bean 生命周期 | 动态替换数据库实现、服务组装 |
| DI(依赖注入) | 依赖关系的硬编码问题 | Setter/构造器/注解注入 | 注入数据源、服务层依赖 DAO 层 |
| AOP(面向切面) | 横切逻辑分散在业务代码中 | 动态代理与切面配置 | 日志、事务、权限校验统一处理 |
- IoC:即控制反转的意思,它是一种创建和获取对象的技术思想,依赖注入 (DI) 是实现这种技术的一种方式。传统开发过程中,我们需要通过 new 关键字来创建对象。使用 IoC 思想开发方式的话,我们不通过 new 关键字创建对象,而是通过 IoC 容器来帮我们实例化对象。 通过 IoC 的方式,可以大大降低对象之间的耦合度。
- AOP:是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。 在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如:
- 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。
- 使用 IOC 容器管理 Service 层和 DAO 层的依赖关系,然后通过 AOP 在 Service 层实现事务管理、日志记录等横切功能,使得业务逻辑更加清晰和可维护。
IOC 实现机制
- 反射:Spring IOC 容器利用 Java 的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。
- 依赖注入:IOC 的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring 通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。
- 设计模式 - 工厂模式:Spring IOC 容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化 Bean 并管理它们的生命周期,将 Bean 的实例化过程交给容器来管理。
- 容器实现:Spring IOC 容器是实现 IOC 的核心,通常使用 BeanFactory 或 ApplicationContext 来管理 Bean。BeanFactory 是 IOC 容器的基本形式,提供基本的 IOC 功能;ApplicationContext 是 BeanFactory 的扩展,并提供更多企业级功能。
AOP 实现机制
Spring AOP 的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。
Spring AOP 支持两种动态代理:
- 基于 JDK 的动态代理:使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。这种方式需要代理的类实现一个或多个接口。
- 基于 CGLIB 的动态代理:当被代理的类没有实现接口时,Spring 会使用 CGLIB 库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
依赖倒置,依赖注入,控制反转分别是什么?
- 控制反转:“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
- 依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
- 依赖倒置:这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
依赖注入实现方式
依赖注入则是将对象的创建和依赖关系的管理交给 Spring 容器来完成,类只需要声明自己所依赖的对象,容器会在运行时将这些依赖对象注入到类中,从而降低了类与类之间的耦合度,提高了代码的可维护性和可测试性。
- **构造器注入:**通过构造函数传递依赖对象,保证对象初始化时依赖已就绪。
@Service
public class UserService {
private final UserRepository userRepository;
// 构造器注入(Spring 4.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
private OrderRepository orderRepository;
}
动态代理
一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用进行拦截和增强。
Java 动态代理主要分为两种类型:
- 基于接口的代理(JDK 动态代理): 这种类型的代理要求目标对象必须实现至少一个接口。Java 动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。每一个动态代理类都必须实现InvocationHandler接口,并且每个代理类的实例都关联到一个handler。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler接口的invoke()方法来进行调用。 - 基于类的代理(CGLIB 动态代理): CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB 代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用 CGLIB 来创建动态代理。
AOP 常用注解
- @Aspect:用于定义切面,标注在切面类上。
- @Pointcut:定义切点,标注在方法上,用于指定连接点。
- @Before:在方法执行之前执行通知。
- @After:在方法执行之后执行通知。
- @Around:在方法执行前后都执行通知。
- @AfterReturning:在方法执行后返回结果后执行通知。
- @AfterThrowing:在方法抛出异常后执行通知。
- @Advice:通用的通知类型,可以替代@Before、@After 等。
循环依赖问题
- 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
- 第二种:通过 setter 方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 第三种:通过 setter 方法进行依赖注入且是在单例模式下产生的循环依赖问题。
只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring 都会产生异常。
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 时,就会调用这个工厂来获取早期引用。 Spring 通过 三级缓存 和 提前暴露未完全初始化的对象引用 的机制来解决单例作用域 Bean 的 sette 注入方式的循环依赖问题。 假设存在两个相互依赖的单例 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还未初始化);若无需代理,则直接返回原始对象。得到的这个早期引用(可能是代理)被放入二级缓存(earlySingletonObjects),同时从三级缓存清理工厂条目。最后,Spring 将这个早期引用注入到BeanB的属性中。至此,BeanB成功持有BeanA的引用——尽管BeanA此时仍是个半成品。
- 第四步:完成
BeanB的生命周期。BeanB获得所有依赖后,Spring 执行其初始化方法(如@PostConstruct),将其转化为完整可用的 Bean。随后,BeanB被提升至一级缓存(singletonObjects),二级和三级缓存中关于BeanB的临时条目均被清除。此时BeanB已准备就绪,可被其他对象使用。 - 第五步:回溯完成
BeanA的构建。 随着BeanB创建完毕,流程回溯到最初中断的BeanA属性注入环节。Spring 将已完备的BeanB实例注入BeanA,接着执行BeanA的初始化方法。这里有个精妙细节:若之前为BeanA生成过早期代理,Spring 会直接复用二级缓存中的代理对象作为最终 Bean,而非重复创建。最终,完全初始化的BeanA(可能是原始对象或代理)入驻一级缓存,其早期引用从二级缓存移除。至此循环闭环完成,两个 Bean 皆可用。 三级缓存的设计的精髓: - 三级缓存工厂(
singletonFactories)负责在实例化后立刻暴露对象生成能力,兼顾 AOP 代理的提前生成; - 二级缓存(
earlySingletonObjects)临时存储已确定的早期引用,避免重复生成代理; - 一级缓存(
singletonObjects)最终交付完整 Bean。 整个机制通过中断初始化流程、逆向注入半成品、延迟代理生成三大策略,将循环依赖的死结转化为有序的接力协作。 值得注意的是,此方案仅适用于 Setter/Field 注入的单例 Bean;构造器注入因必须在实例化前获得依赖,仍会导致无解的死锁。
Spring 为什么用 3 级缓存解决循环依赖问题?用 2 级缓存不行吗?
Spring 必须用三级缓存解决循环依赖,核心是为了正确处理需要 AOP 代理的 Bean。如果只用二级缓存,会导致注入的对象形态错误,甚至破坏单例原则。
举个例子:假设 Bean A 依赖 B,B 又依赖 A,且 A 需要被动态代理(比如加了 @Transactional)。如果只有二级缓存,当 B 创建时去注入 A,拿到的是 A 的原始对象。但 A 在后续初始化完成后才会生成代理对象,结果就是:B 拿着原始对象 A,而 Spring 容器里存的是代理对象 A —— 同一个 Bean 出现了两个不同实例,这直接违反了单例的核心约束。
三级缓存中的 ObjectFactory 就是解决这个问题的关键。它不是直接缓存对象,而是存了一个能生产对象的工厂。当发生循环依赖时,调用这个工厂的 getObject() 方法,这时 Spring 会智能判断:如果这个 Bean 最终需要代理,就提前生成代理对象并放入二级缓存;如果不需要代理,就返回原始对象。这样一来,B 注入的 A 就是最终形态(可能是代理对象),后续 A 初始化完成后也不会再创建新代理,保证了对象全局唯一。
简单说,三级缓存的本质是 “按需延迟生成正确引用” 。它既维持了 Bean 生命周期的完整性(正常流程在初始化后生成代理),又在循环依赖时特殊处理,避免逻辑矛盾。而二级缓存缺乏这种动态决策能力,因此无法替代三级缓存。
Spring 框架使用的设计模式
- 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知 (Advice) 使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller。
事务什么情况下会失效?
Spring Boot 通过 Spring 框架的事务管理模块来支持事务操作。事务管理在 Spring Boot 中通常是通过 @Transactional 注解来实现的。事务可能会失效的一些常见情况包括:
- 未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
- 非受检异常: 默认情况下,Spring 对非受检异常(RuntimeException 或其子类)进行回滚处理,这意味着当事务方法中抛出这些异常时,事务会回滚。
- 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用有 @Transactional 注解的方法时要特别注意。
- 多数据源的事务管理: 如果在使用多数据源时,事务管理没有正确配置或者存在多个 @Transactional 注解时,可能会导致事务失效。
- 跨方法调用事务问题: 如果一个事务方法内部调用另一个方法,而这个被调用的方法没有 @Transactional 注解,这种情况下外层事务可能会失效。
- 事务在非公开方法中失效: 如果 @Transactional 注解标注在私有方法上或者非 public 方法上,事务也会失效。
Bean 生命周期

- Spring 启动,查找并加载需要被 Spring 管理的 bean,进行 Bean 的实例化
- Bean 实例化后对将 Bean 的引入和值注入到 Bean 的属性中
- 如果 Bean 实现了 BeanNameAware 接口的话,Spring 将 Bean 的 Id 传递给 setBeanName() 方法
- 如果 Bean 实现了 BeanFactoryAware 接口的话,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入
- 如果 Bean 实现了 ApplicationContextAware 接口的话,Spring 将调用 Bean 的 setApplicationContext() 方法,将 bean 所在应用上下文引用传入进来。
- 如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的 postProcessBeforeInitialization() 方法。
- 如果 Bean 实现了 InitializingBean 接口,Spring 将调用他们的 afterPropertiesSet() 方法。类似的,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用
- 如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的 postProcessAfterInitialization() 方法。
- 此时,Bean 已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destory() 接口方法,同样,如果 bean 使用了 destory-method 声明销毁方法,该方法也会被调用
Bean 单例和非单例,生命周期是否一样
不一样的,Spring Bean 的生命周期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 Bean,Spring 在创建好交给使用者之后,则不会再管理后续的生命周期。
| 阶段 | 单例(Singleton) | 非单例(如 Prototype) |
|---|---|---|
| 创建时机 | 容器启动时创建(或首次请求时,取决于配置)。 | 每次请求时创建新实例。 |
| 初始化流程 | 完整执行生命周期流程(属性注入、Aware 接口、初始化方法等)。 | 每次创建新实例时都会完整执行生命周期流程(仅到初始化完成)。 |
| 销毁时机 | 容器关闭时销毁,触发 DisposableBean 或 destroy-method。 | 容器不管理销毁,需由调用者自行释放资源(Spring 不跟踪实例)。 |
| 内存占用 | 单实例常驻内存,高效但需注意线程安全。 | 每次请求生成新实例,内存开销较大,需手动管理资源释放。 |
| 适用场景 | 无状态服务(如 Service、DAO 层)。 | 有状态对象(如用户会话、临时计算对象)。 |
然 Spring 的默认行为是将 Bean 设置为单例模式,但在一些情况下,使用多例模式是更为合适的,例如在创建状态不可变的 Bean 或有状态 Bean 时。此外,需要注意的是,如果 Bean 单例是有状态的,那么在使用时需要考虑线程安全性问题。
Bean 的作用域有哪些?
Spring 框架中的 Bean 作用域(Scope)定义了 Bean 的生命周期和可见性。不同的作用域影响着 Spring 容器如何管理这些 Bean 的实例,包括它们如何被创建、如何被销毁以及它们是否可以被多个用户共享。
Spring 支持几种不同的作用域,以满足不同的应用场景需求。以下是一些主要的 Bean 作用域:
- Singleton(单例):在整个应用程序中只存在一个 Bean 实例。默认作用域,Spring 容器中只会创建一个 Bean 实例,并在容器的整个生命周期中共享该实例。
- Prototype(原型):每次请求时都会创建一个新的 Bean 实例。次从容器中获取该 Bean 时都会创建一个新实例,适用于状态非常瞬时的 Bean。
- Request(请求):每个 HTTP 请求都会创建一个新的 Bean 实例。仅在 Spring Web 应用程序中有效,每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用中需求局部性的 Bean。
- Session(会话):Session 范围内只会创建一个 Bean 实例。该 Bean 实例在用户会话范围内共享,仅在 Spring Web 应用程序中有效,适用于与用户会话相关的 Bean。
- Application:当前 ServletContext 中只存在一个 Bean 实例。仅在 Spring Web 应用程序中有效,该 Bean 实例在整个 ServletContext 范围内共享,适用于应用程序范围内共享的 Bean。
- WebSocket(Web 套接字):在 WebSocket 范围内只存在一个 Bean 实例。仅在支持 WebSocket 的应用程序中有效,该 Bean 实例在 WebSocket 会话范围内共享,适用于 WebSocket 会话范围内共享的 Bean。
- Custom scopes(自定义作用域):Spring 允许开发者定义自定义的作用域,通过实现 Scope 接口来创建新的 Bean 作用域。
在 Spring 配置文件中,可以通过
<bean>标签的 scope 属性来指定 Bean 的作用域。例如:
<bean id="myBean" class="com.example.MyBeanClass" scope="singleton"/>
在 Spring Boot 或基于 Java 的配置中,可以通过@Scope 注解来指定 Bean 的作用域。例如:
@Bean
@Scope("prototype")
public MyBeanClass myBean() {
return new MyBeanClass();
}
在 bean 加载/销毁前后,如果想实现某些逻辑,可以怎么做
在 Spring 框架中,如果你希望在 Bean 加载(即实例化、属性赋值、初始化等过程完成后)或销毁前后执行某些逻辑,你可以使用 Spring 的生命周期回调接口或注解。这些接口和注解允许你定义在 Bean 生命周期的关键点执行的代码。
- 使用 init-method 和 destroy-method 在 XML 配置中,你可以通过 init-method 和 destroy-method 属性来指定 Bean 初始化后和销毁前需要调用的方法。
<bean id="myBean" class="com.example.MyBeanClass"
init-method="init" destroy-method="destroy"/>
然后,在你的 Bean 类中实现这些方法:
public class MyBeanClass {
public void init() {
// 初始化逻辑
}
public void destroy() {
// 销毁逻辑
}
}
- 实现 InitializingBean 和 DisposableBean 接口 你的 Bean 类可以实现 org.springframework.beans.factory.InitializingBean 和 org.springframework.beans.factory.DisposableBean 接口,并分别实现 afterPropertiesSet 和 destroy 方法。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class MyBeanClass implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}
- 使用@PostConstruct 和@PreDestroy 注解
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class MyBeanClass {
@PostConstruct
public void init() {
// 初始化逻辑
}
@PreDestroy
public void destroy() {
// 销毁逻辑
}
}
- 使用@Bean 注解的 initMethod 和 destroyMethod 属性 在基于 Java 的配置中,你还可以在@Bean 注解中指定 initMethod 和 destroyMethod 属性。
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public MyBeanClass myBean() {
return new MyBeanClass();
}
}
Spring 给我们提供了很多扩展点
Spring 框架提供了许多扩展点,使得开发者可以根据需求定制和扩展 Spring 的功能。以下是一些常用的扩展点:
- BeanFactoryPostProcessor:允许在 Spring 容器实例化 bean 之前修改 bean 的定义。常用于修改 bean 属性或改变 bean 的作用域。
- BeanPostProcessor:可以在 bean 实例化、配置以及初始化之后对其进行额外处理。常用于代理 bean、修改 bean 属性等。
- PropertySource:用于定义不同的属性源,如文件、数据库等,以便在 Spring 应用中使用。
- ImportSelector 和 ImportBeanDefinitionRegistrar:用于根据条件动态注册 bean 定义,实现配置类的模块化。
- Spring MVC 中的 HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
- Spring MVC 中的 ControllerAdvice:用于全局处理控制器的异常、数据绑定和数据校验。
- Spring Boot 的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
- 自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等。
SpringMVC
MVC 分层
- 视图 (view): 为用户提供使用界面,与用户直接进行交互。
- 模型 (model): 代表一个存取数据的对象或 JAVA POJO(Plain Old Java Object,简单 java 对象)。它也可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。模型分为两类,一类称为数据承载 Bean,一类称为业务处理 Bean。所谓数据承载 Bean 是指实体类(如:User 类),专门为用户承载业务数据的;而业务处理 Bean 则是指 Service 或 Dao 对象, 专门用于处理用户提交请求的。
- 控制器 (controller): 用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。它使视图与模型分离。
处理流程:

SpringBoot
- 简化开发
- 快速启动
- 自动化配置
SpringBoot 比 Spring 好在哪里
- Spring Boot 提供了自动化配置,大大简化了项目的配置过程。通过约定优于配置的原则,很多常用的配置可以自动完成,开发者可以专注于业务逻辑的实现。
- Spring Boot 提供了快速的项目启动器,通过引入不同的 Starter,可以快速集成常用的框架和库(如数据库、消息队列、Web 开发等),极大地提高了开发效率。
- Spring Boot 默认集成了多种内嵌服务器(如 Tomcat、Jetty、Undertow),无需额外配置,即可将应用打包成可执行的 JAR 文件,方便部署和运行
SpringBoot 设计模式?
- 代理模式:Spring 的 AOP 通过动态代理实现方法级别的切面增强,有静态和动态两种代理方式,采用动态代理方式。
- 策略模式:Spring AOP 支持 JDK 和 Cglib 两种动态代理实现方式,通过策略接口和不同策略类,运行时动态选择,其创建一般通过工厂方法实现。
- 装饰器模式:Spring 用 TransactionAwareCacheDecorator 解决缓存与数据库事务问题增加对事务的支持。
- 单例模式:Spring Bean 默认是单例模式,通过单例注册表(如 HashMap)实现。
- 简单工厂模式:Spring 中的 BeanFactory 是简单工厂模式的体现,通过工厂类方法获取 Bean 实例。
- 工厂方法模式:Spring 中的 FactoryBean 体现工厂方法模式,为不同产品提供不同工厂。
- 观察者模式:Spring 观察者模式包含 Event 事件、Listener 监听者、Publisher 发送者,通过定义事件、监听器和发送者实现,观察者注册在 ApplicationContext 中,消息发送由 ApplicationEventMulticaster 完成。
- 模板模式:Spring Bean 的创建过程涉及模板模式,体现扩展性,类似 Callback 回调实现方式。
- 适配器模式:Spring MVC 中针对不同方式定义的 Controller,利用适配器模式统一函数定义,定义了统一接口 HandlerAdapter 及对应适配器类。
SpringBoot 自动装配原理
什么是自动装配
SpringBoot 的自动装配原理是基于 Spring Framework 的条件化配置和@EnableAutoConfiguration 注解实现的。这种机制允许开发者在项目中引入相关的依赖,SpringBoot 将根据这些依赖自动配置应用程序的上下文和功能。
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
通俗来讲,自动装配就是通过注解或一些简单的配置就可以在 SpringBoot 的帮助下开启和配置各种功能,比如数据库访问、Web 开发。
自动装配原理
@SpringBootApplication 注解的内部

接下来将逐个解释这些注解的作用:
@Target({ElementType.TYPE}): 该注解指定了这个注解可以用来标记在类上。在这个特定的例子中,这表示该注解用于标记配置类。@Retention(RetentionPolicy.RUNTIME): 这个注解指定了注解的生命周期,即在运行时保留。这是因为 Spring Boot 在运行时扫描类路径上的注解来实现自动配置,所以这里使用了 RUNTIME 保留策略。@Documented: 该注解表示这个注解应该被包含在 Java 文档中。它是用于生成文档的标记,使开发者能够看到这个注解的相关信息。@Inherited: 这个注解指示一个被标注的类型是被继承的。在这个例子中,它表明这个注解可以被继承,如果一个类继承了带有这个注解的类,它也会继承这个注解。@SpringBootConfiguration: 这个注解表明这是一个 Spring Boot 配置类。如果点进这个注解内部会发现与标准的 @Configuration 没啥区别,只是为了表明这是一个专门用于 SpringBoot 的配置。@EnableAutoConfiguration: 这个注解是 Spring Boot 自动装配的核心。它告诉 Spring oot 启用自动配置机制,根据项目的依赖和配置自动配置应用程序的上下文。通过这个注解,SpringBoot 将尝试根据类路径上的依赖自动配置应用程序。@ComponentScan: 这个注解用于配置组件扫描的规则。在这里,它告诉 SpringBoot 在指定的包及其子包中查找组件,这些组件包括被注解的类、@Component 注解的类等。其中的 excludeFilters 参数用于指定排除哪些组件,这里使用了两个自定义的过滤器,分别是 TypeExcludeFilter 和 AutoConfigurationExcludeFilter。@EnableAutoConfiguration这个注解是实现自动装配的核心注解
- @AutoConfigurationPackage,将项目 src 中 main 包下的所有组件注册到容器中,例如标注了 Component 注解的类等
- @Import({AutoConfigurationImportSelector.class}),是自动装配的核心,接下来分析一下这个注解 AutoConfigurationImportSelector 是 Spring Boot 中一个重要的类,它实现了 ImportSelector 接口,用于实现自动配置的选择和导入。具体来说,它通过分析项目的类路径和条件来决定应该导入哪些自动配置类。 代码太多,选取部分主要功能的代码:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// ... (其他方法和属性)
// 获取所有符合条件的类的全限定类名,例如RedisTemplate的全限定类名(org.springframework.data.redis.core.RedisTemplate;),这些类需要被加载到 IoC 容器中。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 扫描类路径上的 META-INF/spring.factories 文件,获取所有实现了 AutoConfiguration 接口的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 过滤掉不满足条件的自动配置类,比如一些自动装配类
configurations = filter(configurations, annotationMetadata, attributes);
// 排序自动配置类,根据 @AutoConfigureOrder 和 @AutoConfigureAfter/@AutoConfigureBefore 注解指定的顺序
sort(configurations, annotationMetadata, attributes);
// 将满足条件的自动配置类的类名数组返回,这些类将被导入到应用程序上下文中
return StringUtils.toStringArray(configurations);
}
// ... (其他方法)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取自动配置类的候选列表,从 META-INF/spring.factories 文件中读取
// 通过类加载器加载所有候选类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
// 过滤出实现了 AutoConfiguration 接口的自动配置类
configurations = configurations.stream()
.filter(this::isEnabled)
.collect(Collectors.toList());
// 对于 Spring Boot 1.x 版本,还需要添加 spring-boot-autoconfigure 包中的自动配置类
// configurations.addAll(getAutoConfigEntry(getAutoConfigurationEntry(metadata)));
return configurations;
}
// ... (其他方法)
protected List<String> filter(List<String> configurations, AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 使用条件判断机制,过滤掉不满足条件的自动配置类
configurations = configurations.stream()
.filter(configuration -> isConfigurationCandidate(configuration, metadata, attributes))
.collect(Collectors.toList());
return configurations;
}
// ... (其他方法)
protected void sort(List<String> configurations, AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 根据 @AutoConfigureOrder 和 @AutoConfigureAfter/@AutoConfigureBefore 注解指定的顺序对自动配置类进行排序
configurations.sort((o1, o2) -> {
int i1 = getAutoConfigurationOrder(o1, metadata, attributes);
int i2 = getAutoConfigurationOrder(o2, metadata, attributes);
return Integer.compare(i1, i2);
});
}
// ... (其他方法)
}
梳理一下,以下是 AutoConfigurationImportSelector 的主要工作:
- 扫描类路径: 在应用程序启动时,
AutoConfigurationImportSelector会扫描类路径上的META-INF/spring.factories文件,这个文件中包含了各种 Spring 配置和扩展的定义。在这里,它会查找所有实现了AutoConfiguration接口的类,具体的实现为getCandidateConfigurations方法。 - 条件判断: 对于每一个发现的自动配置类,
AutoConfigurationImportSelector会使用条件判断机制(通常是通过@ConditionalOnXxx注解)来确定是否满足导入条件。这些条件可以是配置属性、类是否存在、Bean 是否存在等等。 - 根据条件导入自动配置类: 满足条件的自动配置类将被导入到应用程序的上下文中。这意味着它们会被实例化并应用于应用程序的配置
启动类 starter
starter 种类
- spring-boot-starter-web:这是最常用的起步依赖之一,它包含了 Spring MVC 和 Tomcat 嵌入式服务器,用于快速构建 Web 应用程序。
- spring-boot-starter-security:提供了 Spring Security 的基本配置,帮助开发者快速实现应用的安全性,包括认证和授权功能。
- mybatis-spring-boot-starter:这个 Starter 是由 MyBatis 团队提供的,用于简化在 Spring Boot 应用中集成 MyBatis 的过程。它自动配置了 MyBatis 的相关组件,包括 SqlSessionFactory、MapperScannerConfigurer 等,使得开发者能够快速地开始使用 MyBatis 进行数据库操作。
- spring-boot-starter-data-jpa 或 spring-boot-starter-jdbc:如果使用的是 Java Persistence API (JPA) 进行数据库操作,那么应该使用 spring-boot-starter-data-jpa。这个 Starter 包含了 Hibernate 等 JPA 实现以及数据库连接池等必要的库,可以让你轻松地与 MySQL 数据库进行交互。你需要在 application.properties 或 application.yml 中配置 MySQL 的连接信息。如果倾向于直接使用 JDBC 而不通过 JPA,那么可以使用 spring-boot-starter-jdbc,它提供了基本的 JDBC 支持。
- spring-boot-starter-data-redis:用于集成 Redis 缓存和数据存储服务。这个 Starter 包含了与 Redis 交互所需的客户端(默认是 Jedis 客户端,也可以配置为 Lettuce 客户端),以及 Spring Data Redis 的支持,使得在 Spring Boot 应用中使用 Redis 变得非常便捷。同样地,需要在配置文件中设置 Redis 服务器的连接详情。
- spring-boot-starter-test:包含了单元测试和集成测试所需的库,如 JUnit, Spring Test, AssertJ 等,便于进行测试驱动开发 (TDD)
写一个 SpringBoot start
1.创建 Maven 项目
首先,需要创建一个新的 Maven 项目。在 pom.xml 中添加 Spring Boot 的 starter parent 和一些必要的依赖。例如:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.添加自动配置
在 src/main/resources/META-INF/spring.factories 中添加自动配置的元数据。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.starter.MyAutoConfiguration
然后,创建 MyAutoConfiguration 类,该类需要@Configuration 和@EnableConfigurationProperties 注解。@EnableConfigurationProperties 用于启用你定义的配置属性类。
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Autowired
private MyProperties properties;
@Bean
public MyService myService() {
return new MyServiceImpl(properties);
}
}
3.创建配置属性类
创建一个配置属性类,使用@ConfigurationProperties 注解来绑定配置文件中的属性。
@ConfigurationProperties(prefix = "my")
public class MyProperties {
private String name;
// getters and setters
}
4.创建服务和控制器
创建一个服务类和服务实现类,以及一个控制器来展示你的 starter 的功能。
@Service
public interface MyService {
String getName();
}
@Service
public class MyServiceImpl implements MyService {
private final MyProperties properties;
public MyServiceImpl(MyProperties properties) {
this.properties = properties;
}
@Override
public String getName() {
return properties.getName();
}
}
@RestController
public class MyController {
private final MyService myService;
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/name")
public String getName() {
return myService.getName();
}
}
5.发布 Starter
将你的 starter 发布到 Maven 仓库,无论是私有的还是公共的,如 Nexus 或 Maven Central。
6.使用 Starter
在你的主应用的 pom.xml 中添加你的 starter 依赖,然后在 application.yml 或 application.properties 中配置你的属性。
my:
name: Hello World
SpringBoot 常用注解
- @SpringBootApplication:用于标注主应用程序类,标识一个 Spring Boot 应用程序的入口点,同时启用自动配置和组件扫描。
- @Controller:标识控制器类,处理 HTTP 请求。
- @RestController:结合@Controller 和@ResponseBody,返回 RESTful 风格的数据。
- @Service:标识服务类,通常用于标记业务逻辑层。
- @Repository:标识数据访问组件,通常用于标记数据访问层。
- @Component:通用的 Spring 组件注解,表示一个受 Spring 管理的组件。
- @Autowired:用于自动装配 Spring Bean。
- @Value:用于注入配置属性值。
- @RequestMapping:用于映射 HTTP 请求路径到 Controller 的处理方法。
- @GetMapping、@PostMapping、@PutMapping、@DeleteMapping:简化@RequestMapping 的 GET、POST、PUT 和 DELETE 请求。 与配置相关的重要注解:
- @Configuration:用于指定一个类为配置类,其中定义的 bean 会被 Spring 容器管理。通常与@Bean 配合使用,@Bean 用于声明一个 Bean 实例,由 Spring 容器进行管理。
SpringBoot 开启事务
只需在服务层的方法上添加 @Transactional 注解即可:
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}
Springboot 怎么做到导入就可以直接使用的?
主要依赖于自动配置、起步依赖和条件注解等特性。
起步依赖
起步依赖是一种特殊的 Maven 或 Gradle 依赖,它将项目所需的一系列依赖打包在一起。例如,spring-boot-starter-web 这个起步依赖就包含了 Spring Web MVC、Tomcat 等构建 Web 应用所需的核心依赖。开发者只需在项目中添加一个起步依赖,Maven 或 Gradle 就会自动下载并管理与之关联的所有依赖,避免了手动添加大量依赖的繁琐过程。
比如,在 pom.xml 中添加 spring-boot-starter-web 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
自动配置
Spring Boot 的自动配置机制会根据类路径下的依赖和开发者的配置,自动创建和配置应用所需的 Bean。它通过 @EnableAutoConfiguration 注解启用,该注解会触发 Spring Boot 去查找 META - INF/spring.factories 文件。
spring.factories 文件中定义了一系列自动配置类,Spring Boot 会根据当前项目的依赖情况,选择合适的自动配置类进行加载。例如,如果项目中包含 spring-boot-starter-web 依赖,Spring Boot 会加载 WebMvcAutoConfiguration 类,该类会自动配置 Spring MVC 的相关组件,如 DispatcherServlet、视图解析器等。
开发者可以通过自定义配置来覆盖自动配置的默认行为。
如果开发者在 application.properties 或 application.yml 中定义了特定的配置,或者在代码中定义了同名的 Bean,Spring Boot 会优先使用开发者的配置。
条件注解
条件注解用于控制 Bean 的创建和加载,只有在满足特定条件时,才会创建相应的 Bean。Spring Boot 的自动配置类中广泛使用了条件注解,如 @ConditionalOnClass、@ConditionalOnMissingBean 等。
比如,@ConditionalOnClass 表示只有当类路径中存在指定的类时,才会创建该 Bean。例如,在 WebMvcAutoConfiguration 类中,可能会有如下代码:
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
public class WebMvcAutoConfiguration {
// 配置相关的 Bean
}
这段代码表示只有当类路径中存在 Servlet、DispatcherServlet 和 WebMvcConfigurer 类时,才会加载 WebMvcAutoConfiguration 类中的配置。
SpringBoot 过滤器和拦截器
在 Spring Boot 中,过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的两种不同机制。
| 特性 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 规范/框架 | Servlet 规范(javax.servlet.Filter) | Spring MVC 框架(org.springframework.web.servlet.HandlerInterceptor) |
| 作用范围 | 全局(所有请求、静态资源) | Controller 层(仅拦截 Spring 管理的请求) |
| 执行顺序 | 在 Servlet 之前执行 | 在 DispatcherServlet 之后、Controller 方法前后执行 |
| 依赖注入支持 | 无法直接注入 Spring Bean(需间接获取) | 支持自动注入 Spring Bean |
| 触发时机 | doFilter() 在请求前/响应后被调用 | preHandle、postHandle、afterCompletion 分阶段触发 |
| 适用场景 | 全局请求处理(编码、日志、安全) | 业务逻辑相关的处理(权限、参数校验) |
过滤器是 Java Servlet 规范中的一部分,它可以对进入 Servlet 容器的请求和响应进行预处理和后处理。过滤器通过实现 javax.servlet.Filter 接口,并重写其中的 init、doFilter 和 destroy 方法来完成相应的逻辑。当请求进入 Servlet 容器时,会按照配置的顺序依次经过各个过滤器,然后再到达目标 Servlet 或控制器;响应返回时,也会按照相反的顺序再次经过这些过滤器。
拦截器是 Spring 框架提供的一种机制,它可以对控制器方法的执行进行拦截。拦截器通过实现 org.springframework.web.servlet.HandlerInterceptor 接口,并重写其中的 preHandle、postHandle 和 afterCompletion 方法来完成相应的逻辑。当请求到达控制器时,会先经过拦截器的 preHandle 方法,如果该方法返回 true,则继续执行后续的控制器方法和其他拦截器;在控制器方法执行完成后,会调用拦截器的 postHandle 方法;最后,在请求处理完成后,会调用拦截器的 afterCompletion 方法。
过滤器和拦截器的区别如下:
- 所属规范:过滤器是 Java Servlet 规范的一部分,而拦截器是 Spring 框架提供的机制。
- 执行顺序:过滤器在请求进入 Servlet 容器后,在到达目标 Servlet 或控制器之前执行;拦截器在请求到达控制器之后,在控制器方法执行前后执行。
- 使用范围:过滤器可以对所有类型的请求进行过滤,包括静态资源请求;拦截器只能对 Spring MVC 控制器的请求进行拦截。
- 功能特性:过滤器主要用于对请求和响应进行预处理和后处理,如字符编码处理、请求日志记录等;拦截器可以更细粒度地控制控制器方法的执行,如权限验证、性能监控等。
MyBatis
与传统的 JDBC 相比,MyBatis 的优点
- 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
- 与 JDBC 相比,减少了 50% 以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;
- 很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持。
- 能够与 Spring 很好的集成,开发效率高
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护
MyBatis 特点
- SQL 与代码解耦,灵活可控:MyBatis 允许开发者直接编写和优化 SQL,相比全自动 ORM(如 Hibernate),MyBatis 让开发者明确知道每条 SQL 的执行逻辑,便于性能调优。
<!-- 示例:XML 中定义 SQL -->
<select id="findUserWithRole" resultMap="userRoleMap">
SELECT u.*, r.role_name
FROM user u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{userId}
</select>
- 动态 SQL 的强大支持:比如可以动态拼接 SQL,通过
<if>,<choose>,<foreach>等标签动态生成 SQL,避免 Java 代码中繁琐的字符串拼接。
<select id="searchUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name LIKE #{name}</if>
<if test="status != null">AND status = #{status}</if>
</where>
</select>
- 自动映射与自定义映射结合:自动将查询结果字段名与对象属性名匹配(如驼峰转换)。
<resultMap id="userRoleMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="roles" ofType="Role">
<result property="roleName" column="role_name"/>
</collection>
</resultMap>
- 插件扩展机制:可编写插件拦截 SQL 执行过程,实现分页、性能监控、SQL 改写等通用逻辑。
@Intercepts({
@Signature(type=Executor.class, method="query", args={...})
})
public class PaginationPlugin implements Interceptor {
// 实现分页逻辑
}
- 与 Spring 生态无缝集成:通过
@MapperScan快速扫描 Mapper 接口,结合 Spring 事务管理,配置简洁高效。
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
// 数据源和 SqlSessionFactory 配置
}
JDBC 连接数据库步骤
- 加载数据库驱动程序:在使用 JDBC 连接数据库之前,需要加载相应的数据库驱动程序。可以通过 Class.forName(“com.mysql.jdbc.Driver”) 来加载 MySQL 数据库的驱动程序。不同数据库的驱动类名会有所不同。
- 建立数据库连接:使用 DriverManager 类的 getConnection(url, username, password) 方法来连接数据库,其中 url 是数据库的连接字符串(包括数据库类型、主机、端口等)、username 是数据库用户名,password 是密码。
- 创建 Statement 对象:通过 Connection 对象的 createStatement() 方法创建一个 Statement 对象,用于执行 SQL 查询或更新操作。
- 执行 SQL 查询或更新操作:使用 Statement 对象的 executeQuery(sql) 方法来执行 SELECT 查询操作,或者使用 executeUpdate(sql) 方法来执行 INSERT、UPDATE 或 DELETE 操作。
- 处理查询结果:如果是 SELECT 查询操作,通过 ResultSet 对象来处理查询结果。可以使用 ResultSet 的 next() 方法遍历查询结果集,然后通过 getXXX() 方法获取各个字段的值。
- 关闭连接:在完成数据库操作后,需要逐级关闭数据库连接相关对象,即先关闭 ResultSet,再关闭 Statement,最后关闭 Connection。
import java.sql.*;
public class Main {
public static void main(String[] args) {
try {
// 加载数据库驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
// 创建 Statement 对象
Statement statement = connection.createStatement();
// 执行 SQL 查询
ResultSet resultSet = statement.executeQuery("SELECT * FROM mytable");
// 处理查询结果
while (resultSet.next()) {
// 处理每一行数据
}
// 关闭资源
resultSet.close();
statement.close();
connection.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
请注意,在实际应用中,需要进行异常处理以确保资源的正确释放,以及使用 try-with-resources 来简化代码和确保资源的及时关闭。
Mybatis 里的 # 和 $ 的区别?
- Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,调用 PreparedStatement 的 set 方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止 SQL 注入,提供更高的安全性,适合传递参数值。
- Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。
MybatisPlus 和 Mybatis 的区别?
MybatisPlus 是一个基于 MyBatis 的增强工具库,旨在简化开发并提高效率。以下是 MybatisPlus 和 MyBatis 之间的一些主要区别:
- CRUD 操作:MybatisPlus 通过继承 BaseMapper 接口,提供了一系列内置的快捷方法,使得 CRUD 操作更加简单,无需编写重复的 SQL 语句。
- 代码生成器:MybatisPlus 提供了代码生成器功能,可以根据数据库表结构自动生成实体类、Mapper 接口以及 XML 映射文件,减少了手动编写的工作量。
- 通用方法封装:MybatisPlus 封装了许多常用的方法,如条件构造器、排序、分页查询等,简化了开发过程,提高了开发效率。
- 分页插件:MybatisPlus 内置了分页插件,支持各种数据库的分页查询,开发者可以轻松实现分页功能,而在传统的 MyBatis 中,需要开发者自己手动实现分页逻辑。
- 多租户支持:MybatisPlus 提供了多租户的支持,可以轻松实现多租户数据隔离的功能。
- 注解支持:MybatisPlus 引入了更多的注解支持,使得开发者可以通过注解来配置实体与数据库表之间的映射关系,减少了 XML 配置文件的编写。
MyBatis 设计模式
- 建造者模式(Builder),如:SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder 等;
- 工厂模式,如:SqlSessionFactory、ObjectFactory、MapperProxyFactory;
- 单例模式,例如 ErrorContext 和 LogFactory;
- 代理模式,Mybatis 实现的核心,比如 MapperProxy、ConnectionLogger,用的 jdk 的动态代理;还有 executor.loader 包使用了 cglib 或者 javassist 达到延迟加载的效果;
- 组合模式,例如 SqlNode 和各个子类 ChooseSqlNode 等;
- 模板方法模式,例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler;
- 适配器模式,例如 Log 的 Mybatis 接口和它对 jdbc、log4j 等各种日志框架的适配实现;
- 装饰者模式,例如 Cache 包中的 cache.decorators 子包中等各个装饰者的实现;
- 迭代器模式,例如迭代器模式 PropertyTokenizer;
SpringCloud
Spring Boot 是用于构建单个 Spring 应用的框架,而 Spring Cloud 则是用于构建分布式系统中的微服务架构的工具,Spring Cloud 提供了服务注册与发现、负载均衡、断路器、网关等功能。
两者可以结合使用,通过 Spring Boot 构建微服务应用,然后用 Spring Cloud 来实现微服务架构中的各种功能。
微服务组件

微服务常用的组件:
- 注册中心:注册中心是微服务架构最核心的组件。它起到的作用是对新节点的注册与状态维护,解决了「如何发现新节点以及检查各节点的运行状态的问题」。微服务节点在启动时会将自己的服务名称、IP、端口等信息在注册中心登记,注册中心会定时检查该节点的运行状态。注册中心通常会采用心跳机制最大程度保证已登记过的服务节点都是可用的。
- 负载均衡:负载均衡解决了「如何发现服务及负载均衡如何实现的问题」,通常微服务在互相调用时,并不是直接通过 IP、端口进行访问调用。而是先通过服务名在注册中心查询该服务拥有哪些节点,注册中心将该服务可用节点列表返回给服务调用者,这个过程叫服务发现,因服务高可用的要求,服务调用者会接收到多个节点,必须要从中进行选择。因此服务调用者一端必须内置负载均衡器,通过负载均衡策略选择合适的节点发起实质性的通信请求。
- 服务通信:服务通信组件解决了「服务间如何进行消息通信的问题」,服务间通信采用轻量级协议,通常是 HTTP RESTful 风格。但因为 RESTful 风格过于灵活,必须加以约束,通常应用时对其封装。例如在 SpringCloud 中就提供了 Feign 和 RestTemplate 两种技术屏蔽底层的实现细节,所有开发者都是基于封装后统一的 SDK 进行开发,有利于团队间的相互合作。
- 配置中心:配置中心主要解决了「如何集中管理各节点配置文件的问题」,在微服务架构下,所有的微服务节点都包含自己的各种配置文件,如 jdbc 配置、自定义配置、环境配置、运行参数配置等。要知道有的微服务可能可能有几十个节点,如果将这些配置文件分散存储在节点上,发生配置更改就需要逐个节点调整,将给运维人员带来巨大的压力。配置中心便由此而生,通过部署配置中心服务器,将各节点配置文件从服务中剥离,集中转存到配置中心。一般配置中心都有 UI 界面,方便实现大规模集群配置调整。
- 集中式日志管理:集中式日志主要是解决了「如何收集各节点日志并统一管理的问题」。微服务架构默认将应用日志分别保存在部署节点上,当需要对日志数据和操作数据进行数据分析和数据统计时,必须收集所有节点的日志数据。那么怎么高效收集所有节点的日志数据呢?业内常见的方案有 ELK、EFK。通过搭建独立的日志收集系统,定时抓取各节点增量日志形成有效的统计报表,为统计和分析提供数据支撑。
- 分布式链路追踪:分布式链路追踪解决了「如何直观的了解各节点间的调用链路的问题」。系统中一个复杂的业务流程,可能会出现连续调用多个微服务,我们需要了解完整的业务逻辑涉及的每个微服务的运行状态,通过可视化链路图展现,可以帮助开发人员快速分析系统瓶颈及出错的服务。
- 服务保护:服务保护主要是解决了「如何对系统进行链路保护,避免服务雪崩的问题」。在业务运行时,微服务间互相调用支撑,如果某个微服务出现高延迟导致线程池满载,或是业务处理失败。这里就需要引入服务保护组件来实现高延迟服务的快速降级,避免系统崩溃。
SpringCloud Alibaba 实现的微服务架构:

- SpringCloud Alibaba 中使用Alibaba Nacos组件实现注册中心,Nacos 提供了一组简单易用的特性集,可快速实现动态服务发现、服务配置、服务元数据及流量管理。
- SpringCloud Alibaba 使用Nacos 服务端均衡实现负载均衡,与 Ribbon 在调用端负载不同,Nacos 是在服务发现的同时利用负载均衡返回服务节点数据。
- SpringCloud Alibaba 使用Netflix Feign和Alibaba Dubbo组件来实现服务通行,前者与 SpringCloud 采用了相同的方案,后者则是对自家的RPC 框架 Dubbo也给予支持,为服务间通信提供另一种选择。
- SpringCloud Alibaba 在API 服务网关组件中,使用与 SpringCloud 相同的组件,即:SpringCloud Gateway。
- SpringCloud Alibaba 在配置中心组件中使用Nacos 内置配置中心,Nacos 内置的配置中心,可将配置信息存储保存在指定数据库中
- SpringCloud Alibaba 在原有的ELK 方案外,还可以使用阿里云日志服务(LOG)实现日志集中式管理。
- SpringCloud Alibaba 在分布式链路组件中采用与 SpringCloud 相同的方案,即:Sleuth/Zipkin Server。
- SpringCloud Alibaba 使用Alibaba Sentinel实现系统保护,Sentinel 不仅功能更强大,实现系统保护比 Hystrix 更优雅,而且还拥有更好的 UI 界面。
如何实现一直均衡给一个用户?
可以通过「一致性哈希算法」来实现,根据请求的客户端 ip、或请求参数通过哈希算法得到一个数值,利用该数值取模映射出对应的后端服务器,这样能保证同一个客户端或相同参数的请求每次都使用同一台服务器。
服务熔断
服务熔断是应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝。
比如说,微服务之间的数据交互是通过远程调用来完成的。服务 A 调用服务,服务 B 调用服务 c,某一时间链路上对服务 C 的调用响应时间过长或者服务 C 不可用,随着时间的增长,对服务 C 的调用也越来越多,然后服务 C 崩溃了,但是链路调用还在,对服务 B 的调用也在持续增多,然后服务 B 崩溃,随之 A 也崩溃,导致雪崩效应。
服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
所以,服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
在 Spring Cloud 框架里,熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内 20 次调用失败,就会启动熔断机制。
服务降级
服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行。
服务器的资源是有限的,而请求是无限的。在用户使用即并发高峰期,会影响整体服务的性能,严重的话会导致宕机,以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性,就需要对某些服务降级处理。可以理解为舍小保大
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。