Java面试八股文-框架篇
1. Spring框架
1.1 什么是Spring框架?它的核心功能是什么?
Spring框架是一个轻量级的Java企业级应用开发框架,它为Java应用提供了全面的基础设施和编程模型,使开发者能够更加专注于业务逻辑的实现。
核心功能:
| 功能 | 描述 | 作用 |
|---|---|---|
| 控制反转(IoC) | 将对象的创建和依赖关系的管理交给Spring容器,而不是由对象自己创建和管理依赖 | 实现对象之间的解耦,提高代码的可维护性和可测试性 |
| 依赖注入(DI) | 容器将依赖的对象注入到需要的对象中 | 简化对象的创建和管理,减少代码耦合 |
| 面向切面编程(AOP) | 将横切关注点(如日志、事务、安全等)与业务逻辑分离 | 提高代码的可维护性和可重用性 |
| 事务管理 | 提供声明式事务管理,简化事务处理代码 | 确保数据的一致性和完整性 |
| 数据访问 | 整合各种数据访问技术(如JDBC、ORM框架等) | 简化数据访问代码,提高开发效率 |
| Web MVC | 提供Web应用开发框架,支持RESTful风格的Web服务 | 简化Web应用的开发 |
| 测试支持 | 提供强大的测试框架,支持单元测试和集成测试 | 提高代码的质量和可靠性 |
| 企业级集成 | 与各种企业级技术(如JMS、JCA等)集成 | 增强应用的功能和扩展性 |
Spring的优势:
- 轻量级:核心容器很小,不需要依赖其他外部库
- 非侵入性:应用代码不需要实现Spring特定的接口
- 模块化:可以根据需要选择使用Spring的不同模块
- 可测试性:依赖注入使得测试更加容易
- 灵活:可以与其他框架无缝集成
- 生态丰富:拥有庞大的生态系统和社区支持
示例:
1 | // 1. 定义用户实体类 |
1.2 什么是IoC和DI?它们的区别是什么?
IoC(Inversion of Control,控制反转):
- 是一种设计思想,将对象的创建和依赖关系的管理交给容器,而不是由对象自己创建和管理依赖
- 传统的程序设计中,对象的创建和依赖关系的管理由对象自己控制,而IoC将这种控制权转移给了容器
- 目的是实现对象之间的解耦,提高代码的可维护性和可测试性
DI(Dependency Injection,依赖注入):
- 是IoC的具体实现方式,容器将依赖的对象注入到需要的对象中
- 依赖注入的方式包括:构造函数注入、setter方法注入、字段注入
区别:
| 特性 | IoC | DI |
|---|---|---|
| 性质 | 设计思想 | 实现方式 |
| 关注点 | 控制权的转移 | 依赖的注入方式 |
| 目的 | 解耦 | 实现IoC |
| 应用 | 架构设计 | 具体实现 |
依赖注入的方式:
| 注入方式 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 构造函数注入 | 通过构造函数参数注入依赖 | 强制性依赖,确保对象创建时依赖已注入 | 构造函数参数可能过多 |
| Setter方法注入 | 通过setter方法注入依赖 | 灵活性高,可选择性注入依赖 | 依赖可能在使用时未注入 |
| 字段注入 | 通过字段直接注入依赖 | 代码简洁,使用方便 | 不利于测试,耦合度高 |
示例:
1 | // 传统方式:对象自己创建和管理依赖 |
1.3 Spring中的Bean作用域有哪些?
Spring中的Bean作用域定义了Bean实例的生命周期和可见范围,主要包括以下几种:
| 作用域 | 描述 | 生命周期 | 适用场景 |
|---|---|---|---|
| singleton | 单例,默认作用域,整个应用中只有一个实例 | 容器启动时创建,容器关闭时销毁 | 无状态的Bean,如服务层、DAO层 |
| prototype | 原型,每次获取Bean时都会创建一个新的实例 | 获取时创建,不再管理其生命周期 | 有状态的Bean,如命令对象、表单对象 |
| request | 请求,每个HTTP请求创建一个实例 | 请求开始时创建,请求结束时销毁 | Web应用中与请求相关的Bean |
| session | 会话,每个HTTP会话创建一个实例 | 会话开始时创建,会话结束时销毁 | Web应用中与会话相关的Bean,如用户信息 |
| application | 应用,每个ServletContext创建一个实例 | 应用启动时创建,应用关闭时销毁 | Web应用中全局共享的Bean |
| websocket | WebSocket,每个WebSocket会话创建一个实例 | WebSocket会话开始时创建,会话结束时销毁 | WebSocket应用中与会话相关的Bean |
示例:
1 | // 1. 单例作用域(默认) |
Bean作用域的配置方式:
使用@Scope注解:适用于注解配置方式
1
2
3
4
5
public class UserForm {
// 实现
}使用XML配置:适用于XML配置方式
1
<bean id="userForm" class="com.example.UserForm" scope="prototype"/>
注意事项:
- singleton作用域的Bean在容器启动时创建,prototype作用域的Bean在每次获取时创建
- request、session、application作用域只在Web应用中有效
- 使用这些作用域时,需要在Web应用中配置
RequestContextListener或RequestContextFilter - 对于singleton Bean依赖prototype Bean的情况,需要使用
@Lookup注解或ObjectFactory来确保每次获取到新的prototype Bean
1.4 什么是AOP?AOP的核心概念有哪些?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它将横切关注点(如日志、事务、安全等)与业务逻辑分离,提高代码的可维护性和可重用性。
AOP的核心概念:
| 概念 | 描述 | 作用 |
|---|---|---|
| 切面(Aspect) | 横切关注点的模块化,包含通知和切点 | 定义需要增强的横切逻辑 |
| 连接点(Join Point) | 程序执行过程中的点,如方法调用、异常抛出等 | 表示可以被增强的位置 |
| 通知(Advice) | 在连接点执行的代码 | 定义增强的具体逻辑 |
| 切点(Pointcut) | 匹配连接点的表达式 | 确定哪些连接点会被增强 |
| 目标对象(Target) | 被切面增强的对象 | 原始业务逻辑对象 |
| 代理(Proxy) | AOP框架创建的代理对象 | 执行增强后的代码 |
| 织入(Weaving) | 将切面应用到目标对象并创建代理对象的过程 | 实现AOP的核心过程 |
通知类型:
| 通知类型 | 描述 | 执行时机 | 特点 |
|---|---|---|---|
| 前置通知(Before) | 在连接点执行前执行 | 方法调用前 | 不能阻止方法执行 |
| 后置通知(After Returning) | 在连接点正常执行后执行 | 方法返回后 | 可以获取返回值 |
| 异常通知(After Throwing) | 在连接点抛出异常后执行 | 方法抛出异常后 | 可以获取异常信息 |
| 最终通知(After) | 在连接点执行后执行,无论是否异常 | 方法执行后 | 类似于finally块 |
| 环绕通知(Around) | 包围连接点,在连接点前后执行 | 方法调用前后 | 可以控制方法执行 |
AOP的实现方式:
基于代理:
- JDK动态代理:基于接口的代理
- CGLIB代理:基于类的代理
基于字节码增强:
- AspectJ:编译时织入
示例:
1 | // 1. 定义用户实体类 |
AOP的应用场景:
- 日志记录:记录方法的调用、参数和返回值
- 事务管理:控制事务的开始、提交和回滚
- 安全检查:检查用户是否有权限执行操作
- 性能监控:统计方法的执行时间
- 异常处理:统一处理方法抛出的异常
- 缓存:缓存方法的返回结果
- 权限控制:控制方法的访问权限
AOP的优缺点:
优点:
- 代码解耦:将横切关注点与业务逻辑分离
- 代码复用:横切关注点可以在多个地方重用
- 代码维护性:横切关注点的变更只需要修改一处
- 业务逻辑清晰:业务代码只关注核心业务逻辑
缺点:
- 增加系统复杂度:AOP的引入增加了系统的复杂性
- 调试难度:增强后的代码可能难以调试
- 性能开销:代理和织入过程会带来一定的性能开销
1.5 Spring中的事务传播行为有哪些?
事务传播行为是指当一个方法调用另一个方法时,事务如何在这些方法之间传播。Spring中定义了7种事务传播行为:
| 传播行为 | 描述 | 适用场景 | 特点 |
|---|---|---|---|
| REQUIRED | 如果当前没有事务,创建一个新事务;如果当前有事务,加入当前事务 | 大多数场景,如业务逻辑方法 | 默认行为 |
| SUPPORTS | 如果当前有事务,加入当前事务;如果当前没有事务,以非事务方式执行 | 可选事务的场景,如查询操作 | 灵活选择 |
| MANDATORY | 如果当前有事务,加入当前事务;如果当前没有事务,抛出异常 | 必须在事务中执行的操作 | 强制事务 |
| REQUIRES_NEW | 创建一个新事务,暂停当前事务 | 需要独立事务的场景,如日志记录 | 独立事务 |
| NOT_SUPPORTED | 以非事务方式执行,暂停当前事务 | 不需要事务的场景,如批量操作 | 非事务执行 |
| NEVER | 以非事务方式执行,如果当前有事务,抛出异常 | 绝对不能在事务中执行的操作 | 禁止事务 |
| NESTED | 如果当前有事务,创建一个嵌套事务;如果当前没有事务,创建一个新事务 | 需要部分回滚的场景 | 嵌套事务 |
详细解释:
1. REQUIRED:
- 这是默认的事务传播行为
- 如果当前存在事务,就加入到当前事务中
- 如果当前不存在事务,就创建一个新的事务
- 适用场景:大多数业务逻辑方法,如用户注册、订单创建等
2. SUPPORTS:
- 如果当前存在事务,就加入到当前事务中
- 如果当前不存在事务,就以非事务方式执行
- 适用场景:可选事务的操作,如查询操作,这些操作可以在事务中执行,也可以不在事务中执行
3. MANDATORY:
- 如果当前存在事务,就加入到当前事务中
- 如果当前不存在事务,就抛出异常
- 适用场景:必须在事务中执行的操作,如某些关键的业务操作
4. REQUIRES_NEW:
- 无论当前是否存在事务,都创建一个新的事务
- 如果当前存在事务,就暂停当前事务,直到新事务完成
- 适用场景:需要独立事务的操作,如日志记录、审计操作等,这些操作应该与主业务逻辑事务隔离
5. NOT_SUPPORTED:
- 以非事务方式执行
- 如果当前存在事务,就暂停当前事务,直到非事务操作完成
- 适用场景:不需要事务的操作,如批量数据导入、报表生成等
6. NEVER:
- 以非事务方式执行
- 如果当前存在事务,就抛出异常
- 适用场景:绝对不能在事务中执行的操作,如某些特殊的系统操作
7. NESTED:
- 如果当前存在事务,就创建一个嵌套事务,嵌套事务是外部事务的一部分
- 如果当前不存在事务,就创建一个新的事务
- 嵌套事务可以独立回滚,而不会影响外部事务
- 适用场景:需要部分回滚的操作,如批量操作中部分失败的情况
示例:
1 | // 1. 定义UserService |
执行结果分析:
当调用
userService.addUser("admin", "123456")时:- UserService.addUser在事务中执行
- LogService.logOperation创建新事务执行
- 两个事务都成功提交
当调用
userService.addUser("error", "123456")时:- UserService.addUser在事务中执行
- LogService.logOperation创建新事务执行并成功提交
- UserService.addUser抛出异常,事务回滚
- 结果:用户数据回滚,但日志数据已提交
事务传播行为的使用场景:
| 传播行为 | 典型使用场景 |
|---|---|
| REQUIRED | 核心业务逻辑,如用户注册、订单创建 |
| SUPPORTS | 查询操作,如获取用户信息 |
| MANDATORY | 必须在事务中执行的操作,如资金转账 |
| REQUIRES_NEW | 日志记录、审计操作 |
| NOT_SUPPORTED | 批量数据导入、报表生成 |
| NEVER | 特殊系统操作,如系统初始化 |
| NESTED | 批量操作,如批量导入数据,部分失败不影响整体 |
注意事项:
- 事务传播行为只在方法调用时生效,同一类内部的方法调用不会触发事务传播行为(需要使用AOP代理)
- 不同的事务传播行为适用于不同的场景,需要根据具体情况选择合适的传播行为
- 嵌套事务(NESTED)需要数据库支持保存点(Savepoint)功能
- 使用REQUIRES_NEW时,新事务与原事务完全独立,原事务的回滚不会影响新事务
- 使用NESTED时,嵌套事务是原事务的一部分,原事务的回滚会影响嵌套事务
2. Spring Boot
2.1 什么是Spring Boot?它的核心功能是什么?
Spring Boot是一个基于Spring框架的快速开发框架,它简化了Spring应用的初始化和开发过程,使开发者能够快速构建生产级别的应用。Spring Boot的设计理念是”约定优于配置”,通过提供合理的默认值和自动配置,减少了开发者的配置工作。
核心功能:
| 功能 | 描述 | 优势 |
|---|---|---|
| 自动配置(Auto-configuration) | 根据项目的依赖自动配置应用 | 减少XML配置,提高开发效率 |
| 起步依赖(Starter Dependencies) | 提供预配置的依赖集合 | 简化依赖管理,避免版本冲突 |
| 内嵌容器(Embedded Containers) | 内置Tomcat、Jetty、Undertow等Web容器 | 无需外部容器,直接以JAR包运行 |
| 监控(Actuator) | 提供应用监控和管理功能 | 实时查看应用状态,便于运维 |
| 无代码生成 | 基于注解和约定优于配置 | 减少样板代码,提高可读性 |
| 外部化配置 | 支持多种配置方式 | 环境隔离,配置灵活 |
| 生产就绪 | 提供健康检查、安全等生产功能 | 确保应用在生产环境稳定运行 |
| 测试支持 | 提供强大的测试框架 | 提高代码质量,确保功能正确 |
详细解释:
1. 自动配置(Auto-configuration):
- 根据项目的依赖自动配置应用
- 减少了传统Spring应用中大量的XML配置
- 可以通过
application.properties或application.yml文件自定义配置 - 示例:如果项目依赖了
spring-boot-starter-web,Spring Boot会自动配置Tomcat、DispatcherServlet等组件
2. 起步依赖(Starter Dependencies):
- 提供了一系列预配置的依赖集合,简化了依赖管理
- 每个起步依赖都针对特定的应用场景,如Web开发、数据访问等
- 自动处理依赖的版本冲突,确保依赖的兼容性
- 示例:
spring-boot-starter-web包含了Web开发所需的所有依赖
3. 内嵌容器(Embedded Containers):
- 内置了Tomcat、Jetty、Undertow等Web容器
- 不需要外部部署容器,应用可以直接以JAR包的形式运行
- 简化了应用的部署和运行过程
- 可以通过配置文件自定义容器的参数
4. 监控(Actuator):
- 提供了应用监控和管理功能
- 可以查看应用的健康状态、性能指标、环境信息等
- 支持通过HTTP、JMX等方式访问监控端点
- 可以自定义监控指标和端点
5. 无代码生成(No Code Generation):
- 不需要生成代码或XML配置
- 基于注解和约定优于配置的原则
- 减少了样板代码,提高了开发效率
6. 外部化配置(Externalized Configuration):
- 支持多种配置方式,如属性文件、环境变量、命令行参数等
- 可以根据不同的环境(开发、测试、生产)使用不同的配置
- 支持配置的热更新
7. 生产就绪(Production Ready):
- 提供了健康检查、安全、监控等生产环境所需的功能
- 支持优雅停机、应用指标收集等
- 可以与云平台无缝集成
8. 测试支持(Testing Support):
- 提供了强大的测试框架,支持单元测试和集成测试
- 内置了多种测试注解和工具
- 支持测试环境的自动配置
Spring Boot的优势:
- 快速开发:简化了应用的初始化和配置过程
- 开箱即用:提供了大量的预配置组件
- 减少样板代码:基于注解和约定优于配置的原则
- 微服务友好:适合构建微服务架构
- 社区活跃:拥有庞大的社区支持和丰富的文档
- 易于部署:可以打包为可执行JAR包,简化部署流程
- 监控完善:内置Actuator,提供全面的监控功能
示例:
1 | // 1. Spring Boot应用的主类 |
Spring Boot的应用场景:
- Web应用:构建RESTful API、传统Web应用等
- 微服务:作为微服务架构的基础框架
- 批处理:处理大量数据的批处理任务
- 云原生应用:与云平台无缝集成
- IoT应用:构建物联网应用
- 命令行应用:构建命令行工具
- 企业应用:快速开发企业级应用
- 移动后端:为移动应用提供后端服务
2.2 Spring Boot的启动流程?
Spring Boot的启动流程是一个复杂的过程,涉及多个步骤和组件的初始化。以下是详细的启动流程:
1. 执行main方法:
- 调用
SpringApplication.run()方法,这是Spring Boot应用的入口点 - 创建
SpringApplication实例,初始化必要的组件
2. 初始化SpringApplication:
- 配置资源加载器(
ResourceLoader) - 确定应用类型(Web应用或非Web应用)
- 加载初始化器(
ApplicationContextInitializer) - 加载监听器(
ApplicationListener) - 确定主类(通过堆栈跟踪找到main方法所在的类)
3. 调用run方法:
- 启动计时器,记录启动时间
- 创建并配置
Environment(环境) - 打印Banner(可以通过
spring.banner.location自定义) - 创建
ApplicationContext(应用上下文) - 刷新上下文
- 执行CommandLineRunner和ApplicationRunner
- 启动完成,打印启动时间
4. 准备环境(Environment):
- 加载系统属性(System Properties)
- 加载环境变量(Environment Variables)
- 加载配置文件(application.properties/yml,支持多环境配置)
- 加载命令行参数
- 解析Profiles(如dev、test、prod)
5. 创建ApplicationContext:
- 根据应用类型创建相应的ApplicationContext
- Web应用(Servlet):
AnnotationConfigServletWebServerApplicationContext - Web应用(Reactive):
AnnotationConfigReactiveWebServerApplicationContext - 非Web应用:
AnnotationConfigApplicationContext
- Web应用(Servlet):
- 应用初始化器(
ApplicationContextInitializer) - 注册监听器(
ApplicationListener)
6. 刷新上下文(refresh):
- 准备刷新上下文
- 加载Bean定义(通过@ComponentScan扫描)
- 处理BeanFactoryPostProcessor(如@Configuration类的处理)
- 注册BeanPostProcessor(用于Bean的增强)
- 初始化事件发布器
- 注册监听器
- 实例化单例Bean(非延迟加载的)
- 完成刷新
7. 启动内嵌容器:
- 如果是Web应用,创建并启动内嵌容器(Tomcat、Jetty、Undertow等)
- 注册Servlet、Filter、Listener等
- 启动容器
8. 执行CommandLineRunner和ApplicationRunner:
- 按照顺序执行所有实现了
CommandLineRunner和ApplicationRunner接口的Bean - 这些Runner可以在应用启动后执行一些初始化任务
- CommandLineRunner接收原始字符串参数,ApplicationRunner接收ApplicationArguments对象
9. 应用启动完成:
- 打印启动成功信息
- 应用开始接收请求
启动流程的关键组件:
| 组件 | 职责 | 重要性 |
|---|---|---|
| SpringApplication | 协调应用启动过程 | 核心 |
| Environment | 管理配置信息 | 重要 |
| ApplicationContext | 管理Bean生命周期 | 核心 |
| EmbeddedWebServer | 处理HTTP请求 | Web应用必需 |
| CommandLineRunner/ApplicationRunner | 执行初始化任务 | 扩展点 |
示例:
1 | // 1. Spring Boot应用主类 |
启动流程的扩展点:
| 扩展点 | 执行时机 | 用途 |
|---|---|---|
| ApplicationContextInitializer | ApplicationContext创建前 | 初始化上下文 |
| ApplicationListener | 监听应用事件 | 响应应用事件 |
| CommandLineRunner | 应用启动后 | 执行初始化任务 |
| ApplicationRunner | 应用启动后 | 执行初始化任务(更丰富的参数) |
| @PostConstruct | Bean初始化后 | 初始化Bean |
| SmartInitializingSingleton | 所有单例Bean初始化完成后 | 执行全局初始化 |
| BeanPostProcessor | Bean创建前后 | 增强Bean |
| BeanFactoryPostProcessor | Bean定义加载后 | 修改Bean定义 |
启动流程的事件:
- ApplicationStartingEvent:应用启动开始时触发
- ApplicationEnvironmentPreparedEvent:环境准备完成时触发
- ApplicationContextInitializedEvent:ApplicationContext初始化完成时触发
- ApplicationPreparedEvent:ApplicationContext准备完成时触发
- ApplicationStartedEvent:应用启动完成时触发
- ApplicationReadyEvent:应用就绪时触发
- ApplicationFailedEvent:应用启动失败时触发
注意事项:
- 启动流程中的任何步骤失败都会导致应用启动失败
- 可以通过
spring.main.*配置项自定义启动行为 - 可以通过
@SpringBootApplication的属性自定义组件扫描和自动配置行为 - 可以通过
SpringApplicationBuilder自定义SpringApplication的配置 - 启动时间过长可能是因为Bean初始化耗时或自动配置过多,可以通过分析启动日志和使用Spring Boot Actuator进行优化
2.3 Spring Boot的自动配置原理?
Spring Boot的自动配置是其核心特性之一,它根据项目的依赖和配置,自动配置应用所需的组件,减少了开发者的配置工作。Spring Boot的自动配置基于”约定优于配置”的原则,通过提供合理的默认值和条件配置,使得开发者可以专注于业务逻辑的实现。
自动配置的原理:
1. 基于条件注解(@Conditional):
- Spring Boot使用条件注解来决定是否应用某个配置
- 条件注解包括:
@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnProperty等 - 这些注解可以根据类的存在、Bean的存在、属性的设置等条件来决定是否应用配置
2. 扫描META-INF/spring.factories文件:
- Spring Boot在启动时会扫描classpath下所有的
META-INF/spring.factories文件 - 这些文件中定义了自动配置类的全限定名
- Spring Boot会加载这些自动配置类,并根据条件注解决定是否应用
3. 自动配置的加载过程:
- 启动Spring Boot应用时,
@SpringBootApplication注解会开启自动配置(通过@EnableAutoConfiguration) @EnableAutoConfiguration会导入AutoConfigurationImportSelectorAutoConfigurationImportSelector会扫描META-INF/spring.factories文件,加载自动配置类- 对每个自动配置类,检查其条件注解是否满足
- 如果条件满足,应用该自动配置类
4. 自动配置的优先级:
- 自动配置类的优先级低于用户自定义的配置
- 用户可以通过
@Primary注解或配置文件覆盖自动配置 - 可以通过
@EnableAutoConfiguration(exclude = {...})排除某些自动配置 - 可以通过
spring.autoconfigure.exclude属性在配置文件中排除自动配置
5. 条件注解的使用:
| 条件注解 | 作用 | 示例 | 应用场景 |
|---|---|---|---|
@ConditionalOnClass |
当类存在时生效 | @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") |
当依赖了某个库时应用配置 |
@ConditionalOnMissingClass |
当类不存在时生效 | @ConditionalOnMissingClass(name = "org.springframework.web.servlet.DispatcherServlet") |
当未依赖某个库时应用配置 |
@ConditionalOnBean |
当Bean存在时生效 | @ConditionalOnBean(name = "dataSource") |
当某个Bean已存在时应用配置 |
@ConditionalOnMissingBean |
当Bean不存在时生效 | @ConditionalOnMissingBean(name = "dataSource") |
当某个Bean不存在时应用配置 |
@ConditionalOnProperty |
当属性满足条件时生效 | @ConditionalOnProperty(name = "spring.datasource.enabled", havingValue = "true") |
根据配置属性决定是否应用配置 |
@ConditionalOnWebApplication |
当是Web应用时生效 | @ConditionalOnWebApplication |
只在Web应用中应用配置 |
@ConditionalOnNotWebApplication |
当不是Web应用时生效 | @ConditionalOnNotWebApplication |
只在非Web应用中应用配置 |
@ConditionalOnExpression |
当SpEL表达式为true时生效 | @ConditionalOnExpression("${spring.datasource.enabled:false}") |
根据复杂的表达式决定是否应用配置 |
@ConditionalOnResource |
当资源存在时生效 | @ConditionalOnResource(resources = "classpath:application.properties") |
当某个资源存在时应用配置 |
@ConditionalOnJndi |
当JNDI存在时生效 | @ConditionalOnJndi("java:comp/env/jdbc/dataSource") |
当JNDI资源存在时应用配置 |
6. 自动配置的示例:
1 | // 1. 自动配置类示例 |
7. 自定义自动配置:
步骤:
- 创建一个配置类,使用
@Configuration注解 - 使用条件注解来控制配置的应用条件
- 创建配置属性类,使用
@ConfigurationProperties注解 - 在
META-INF/spring.factories文件中注册自动配置类 - 打包成jar包,供其他项目依赖
示例:
1 | // 1. 自定义服务接口 |
8. 自动配置的最佳实践:
- 只在必要时使用自动配置:避免过度使用自动配置,只在确实能简化配置的场景下使用
- 为自动配置提供合理的默认值:确保默认配置能够满足大多数场景的需求
- 允许用户通过配置文件覆盖默认配置:通过
@ConfigurationProperties提供配置选项 - 使用条件注解来确保自动配置只在合适的条件下应用:避免在不适合的场景下应用配置
- 提供清晰的文档:说明自动配置的行为和配置选项
- 遵循Spring Boot的命名约定:自动配置类通常以
AutoConfiguration结尾 - 使用
@Order注解控制自动配置的顺序:确保自动配置按正确的顺序应用
9. 查看自动配置的状态:
- 启动日志:通过启动时的日志查看自动配置的应用情况
- Actuator端点:使用
spring-boot-actuator的/actuator/conditions端点查看自动配置的条件评估结果 - Debug模式:使用
--debug参数启动应用,查看详细的自动配置日志 - spring-boot-starter-test:使用
@SpringBootTest和AutoConfigurationImportedEvent测试自动配置
10. 自动配置的原理流程图:
- 启动应用 → 2. @SpringBootApplication → 3. @EnableAutoConfiguration → 4. AutoConfigurationImportSelector → 5. 扫描META-INF/spring.factories → 6. 加载自动配置类 → 7. 检查条件注解 → 8. 应用满足条件的配置 → 9. 完成自动配置
注意事项:
- 自动配置是基于约定优于配置的原则
- 自动配置的优先级低于用户自定义的配置
- 可以通过
@EnableAutoConfiguration(exclude = {...})排除不需要的自动配置 - 可以通过
spring.autoconfigure.exclude属性在配置文件中排除自动配置 - 自动配置类通常位于
org.springframework.boot.autoconfigure包下 - 自动配置的顺序很重要,有些自动配置依赖于其他自动配置的结果
- 可以通过
@AutoConfigureBefore、@AutoConfigureAfter和@AutoConfigureOrder注解控制自动配置的顺序
2.4 Spring Boot的配置方式有哪些?
Spring Boot提供了多种配置方式,以满足不同场景的需求。以下是Spring Boot的主要配置方式:
1. 配置文件:
(1) application.properties文件:
- 基于键值对的配置文件格式
- 位于
src/main/resources目录下 - 支持
@占位符引用其他配置 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13# 服务器配置
server.port=8080
server.servlet.context-path=/app
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
# 引用其他配置
my.app.name=My Application
my.app.version=1.0.0
my.app.full-name=${my.app.name} v${my.app.version}
(2) application.yml文件:
- 基于YAML格式的配置文件,更加简洁易读
- 位于
src/main/resources目录下 - 支持缩进和嵌套结构
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20server:
port: 8080
servlet:
context-path: /app
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true
my:
app:
name: My Application
version: 1.0.0
full-name: ${my.app.name} v${my.app.version}
(3) 多环境配置文件:
- 可以为不同环境创建不同的配置文件
- 命名格式:
application-{profile}.properties/yml - 示例:
application-dev.yml:开发环境配置application-test.yml:测试环境配置application-prod.yml:生产环境配置
- 通过
spring.profiles.active属性激活对应环境的配置 - 可以同时激活多个环境,如
spring.profiles.active=dev,local
(4) 外部配置文件:
- 可以将配置文件放在应用外部,如
config目录或指定路径 - 优先级:外部配置文件 > 应用内配置文件
- 可以通过
spring.config.location指定配置文件位置
2. 命令行参数:
- 在启动应用时通过命令行参数指定配置
- 格式:
--key=value - 示例:
1
java -jar myapp.jar --server.port=8080 --spring.profiles.active=prod
- 命令行参数的优先级最高,可以覆盖其他配置
3. 环境变量:
- 通过操作系统的环境变量进行配置
- Spring Boot会自动将环境变量转换为配置属性
- 格式:环境变量名使用大写,下划线替换点,前缀为
SPRING_ - 示例:
- 环境变量
SERVER_PORT对应配置属性server.port - 环境变量
SPRING_DATASOURCE_URL对应配置属性spring.datasource.url
- 环境变量
- 在Docker容器中特别有用
4. 系统属性:
- 通过JVM系统属性进行配置
- 格式:
-Dkey=value - 示例:
1
java -Dserver.port=8080 -Dspring.profiles.active=prod -jar myapp.jar
- 系统属性的优先级高于环境变量
5. 注解配置:
(1) @Value注解:
- 用于注入单个配置属性
- 支持SpEL表达式
- 支持默认值
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyComponent {
private int port;
private String datasourceUrl;
private String appName;
private double randomNumber;
}
(2) @ConfigurationProperties注解:
- 用于将一组相关的配置属性绑定到一个Java类
- 支持属性验证、嵌套属性和松散绑定
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
// getter和setter
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getDriverClassName() { return driverClassName; }
public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
}
6. 配置中心:
- 使用外部配置中心,如Spring Cloud Config、Apollo、Nacos等
- 集中管理配置,支持动态更新
- 适用于微服务架构
- 支持配置的版本管理和环境隔离
7. 编程方式配置:
- 通过Java代码进行配置
- 实现
EnvironmentPostProcessor接口 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> properties = new HashMap<>();
properties.put("my.custom.property", "value");
properties.put("server.port", 8081);
environment.getPropertySources().addLast(new MapPropertySource("myProperties", properties));
}
}
// 需要在META-INF/spring.factories中注册
// org.springframework.boot.env.EnvironmentPostProcessor=
// com.example.MyEnvironmentPostProcessor
8. 配置优先级:
Spring Boot的配置优先级从高到低依次为:
- 命令行参数
- 系统属性 (
-D参数) - 环境变量
- 应用内的
application-{profile}.properties/yml文件 - 应用内的
application.properties/yml文件 - 外部的
application-{profile}.properties/yml文件 - 外部的
application.properties/yml文件 - 默认配置(Spring Boot内置的默认值)
9. 配置的使用:
(1) 注入配置:
- 使用
@Value注解注入单个配置 - 使用
@ConfigurationProperties注解绑定配置类 - 使用
Environment接口获取配置
(2) 配置的热更新:
- 开发环境下,使用
spring-boot-devtools可以实现配置的热更新 - 生产环境下,可以使用Spring Cloud Config或其他配置中心实现配置的动态更新
- 对于
@ConfigurationProperties绑定的配置类,可以添加@RefreshScope注解实现热更新
(3) 配置的验证:
- 使用
@Validated和JSR-303注解进行配置验证 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyAppProperties {
private String name;
private int maxUsers;
private String apiKey;
// getter和setter
}
10. 配置的最佳实践:
- 使用YAML格式:YAML格式更加简洁易读,支持嵌套结构
- 使用多环境配置:为不同环境创建不同的配置文件
- 使用配置属性类:通过
@ConfigurationProperties将配置绑定到Java类,提高类型安全性 - 使用配置验证:添加验证规则,确保配置的正确性
- 避免硬编码:将所有配置项外部化,便于维护
- 使用配置中心:在微服务架构中使用配置中心集中管理配置
- 配置加密:对于敏感配置(如密码、API密钥),使用加密工具进行加密
- 配置文档:为配置项添加注释,说明其用途和默认值
11. 配置示例:
完整的application.yml配置示例:
1 | # 服务器配置 |
使用@ConfigurationProperties的示例:
1 | // 1. 定义配置属性类 |
2.5 Spring Boot的核心注解有哪些?
Spring Boot提供了一系列核心注解,用于简化应用的开发和配置。以下是Spring Boot的主要核心注解:
1. @SpringBootApplication:
- 作用:Spring Boot应用的核心注解,是一个组合注解
- 包含的注解:
@Configuration:标记类为配置类@EnableAutoConfiguration:开启自动配置@ComponentScan:扫描组件
- 属性:
exclude:排除指定的自动配置类excludeName:排除指定名称的自动配置类scanBasePackages:指定组件扫描的包路径scanBasePackageClasses:指定组件扫描的类所在的包
- 使用场景:应用的主类上,标识这是一个Spring Boot应用
- 示例:
1
2
3
4
5
6
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. @Configuration:
- 作用:标记类为配置类,替代XML配置文件
- 使用场景:定义Bean配置的类上
- 示例:
1
2
3
4
5
6
7
public class AppConfig {
public UserService userService() {
return new UserServiceImpl();
}
}
3. @EnableAutoConfiguration:
- 作用:开启Spring Boot的自动配置功能
- 使用场景:通常与
@Configuration一起使用,或通过@SpringBootApplication间接使用 - 属性:
exclude:排除指定的自动配置类excludeName:排除指定名称的自动配置类
- 示例:
1
2
3
4
5
public class AppConfig {
// 配置内容
}
4. @ComponentScan:
- 作用:扫描指定包下的组件,包括
@Component、@Service、@Repository、@Controller等 - 属性:
basePackages:指定扫描的包路径basePackageClasses:指定扫描的类所在的包excludeFilters:排除指定的组件includeFilters:包含指定的组件
- 使用场景:需要自定义组件扫描范围时
- 示例:
1
2
3
4
5
6
7
8
public class AppConfig {
// 配置内容
}
5. @RestController:
- 作用:标记类为REST控制器,返回JSON格式的响应
- 包含的注解:
@Controller和@ResponseBody - 使用场景:RESTful API的控制器类上
- 示例:
1
2
3
4
5
6
7
8
9
public class UserController {
public List<User> getUsers() {
// 业务逻辑
return Arrays.asList(new User(1, "admin"), new User(2, "user"));
}
}
6. @RequestMapping:
- 作用:映射HTTP请求路径到控制器方法
- 属性:
value:请求路径method:HTTP方法(GET、POST、PUT、DELETE等)params:请求参数headers:请求头consumes:请求媒体类型produces:响应媒体类型
- 使用场景:控制器类或方法上,用于定义请求路径
- 简化注解:
@GetMapping:处理GET请求@PostMapping:处理POST请求@PutMapping:处理PUT请求@DeleteMapping:处理DELETE请求@PatchMapping:处理PATCH请求
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserController {
public List<User> getUsers() {
// 业务逻辑
return Arrays.asList(new User(1, "admin"), new User(2, "user"));
}
public User createUser( User user) {
// 业务逻辑
return user;
}
}
7. @Autowired:
- 作用:自动注入依赖
- 使用场景:字段、构造函数、setter方法上
- 属性:
required:是否必须注入,默认为true
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
public class UserService {
private final UserDao userDao;
// 构造函数注入(推荐)
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 业务逻辑
}
8. @Bean:
- 作用:标记方法返回的对象为Spring Bean
- 属性:
name:Bean的名称initMethod:初始化方法destroyMethod:销毁方法autowire:自动装配模式
- 使用场景:配置类中的方法上,用于定义Bean
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
public class AppConfig {
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
}
9. @Conditional:
- 作用:根据条件决定是否应用配置
- 使用场景:配置类或Bean方法上,用于条件配置
- 派生注解:
@ConditionalOnClass:当类存在时生效@ConditionalOnMissingClass:当类不存在时生效@ConditionalOnBean:当Bean存在时生效@ConditionalOnMissingBean:当Bean不存在时生效@ConditionalOnProperty:当属性满足条件时生效@ConditionalOnWebApplication:当是Web应用时生效@ConditionalOnNotWebApplication:当不是Web应用时生效
- 示例:
1
2
3
4
5
6
7
8
9
public class DatabaseConfig {
public DataSource dataSource() {
// 配置数据源
return new DriverManagerDataSource();
}
}
10. @ConfigurationProperties:
- 作用:将配置文件中的属性绑定到Java类
- 属性:
prefix:配置属性的前缀value:同prefixignoreInvalidFields:是否忽略无效字段ignoreUnknownFields:是否忽略未知字段
- 使用场景:配置类上,用于绑定配置属性
- 示例:
1
2
3
4
5
6
7
8
public class DataSourceProperties {
private String url;
private String username;
private String password;
// getter和setter
}
11. @EnableConfigurationProperties:
- 作用:启用配置属性类
- 使用场景:配置类上,用于启用
@ConfigurationProperties注解的类 - 示例:
1
2
3
4
5
6
7
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
12. @Value:
- 作用:注入单个配置属性
- 使用场景:字段、方法参数上,用于注入配置属性
- 示例:
1
2
3
4
5
6
7
8
public class MyComponent {
private int port;
private String appName;
}
13. @Profile:
- 作用:根据环境配置激活不同的Bean
- 使用场景:类或方法上,用于环境特定的配置
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DatabaseConfig {
public DataSource devDataSource() {
// 开发环境数据源
return new DriverManagerDataSource();
}
public DataSource prodDataSource() {
// 生产环境数据源
return new HikariDataSource();
}
}
14. @Transactional:
- 作用:标记方法或类为事务性的
- 属性:
value:事务管理器的名称propagation:事务传播行为isolation:事务隔离级别timeout:事务超时时间readOnly:是否为只读事务rollbackFor:触发回滚的异常类noRollbackFor:不触发回滚的异常类
- 使用场景:服务层方法上,用于事务管理
- 示例:
1
2
3
4
5
6
7
public class UserService {
public void createUser(User user) {
// 业务逻辑
}
}
15. @ControllerAdvice:
- 作用:全局异常处理和全局数据绑定
- 使用场景:异常处理类上,用于全局异常处理
- 示例:
1
2
3
4
5
6
7
public class GlobalExceptionHandler {
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
16. @CrossOrigin:
- 作用:允许跨域请求
- 属性:
origins:允许的源methods:允许的HTTP方法allowedHeaders:允许的请求头exposedHeaders:暴露的响应头allowCredentials:是否允许凭证maxAge:预检请求的缓存时间
- 使用场景:控制器类或方法上,用于跨域请求处理
- 示例:
1
2
3
4
5
6
public class UserController {
// 控制器方法
}
17. @EnableAsync:
- 作用:启用异步方法支持
- 使用场景:配置类上,用于启用异步方法
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class AsyncConfig {
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(30);
executor.initialize();
return executor;
}
}
18. @Async:
- 作用:标记方法为异步方法
- 使用场景:方法上,用于异步执行
- 示例:
1
2
3
4
5
6
7
8
public class AsyncService {
public CompletableFuture<String> asyncMethod() {
// 异步执行的逻辑
return CompletableFuture.completedFuture("Async result");
}
}
19. @EnableScheduling:
- 作用:启用定时任务支持
- 使用场景:配置类上,用于启用定时任务
- 示例:
1
2
3
4
5
public class SchedulingConfig {
// 配置内容
}
20. @Scheduled:
- 作用:标记方法为定时任务
- 属性:
cron:Cron表达式fixedRate:固定速率执行fixedDelay:固定延迟执行initialDelay:初始延迟
- 使用场景:方法上,用于定时任务
- 示例:
1
2
3
4
5
6
7
8
public class ScheduledService {
public void scheduledTask() {
// 定时执行的逻辑
System.out.println("Scheduled task executed at: " + new Date());
}
}
}}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
**17. @Scheduled**:
- **作用**:标记方法为定时任务
- **属性**:
- `cron`:Cron表达式
- `fixedRate`:固定速率执行
- `fixedDelay`:固定延迟执行
- **使用场景**:定时任务方法上
- **示例**:
```java
@Component
public class ScheduledTasks {
@Scheduled(fixedRate = 10000) // 每10秒执行一次
public void reportCurrentTime() {
System.out.println("Current time: " + new Date());
}
}
18. @EnableAsync:
- 作用:开启Spring的异步方法支持
- 使用场景:需要使用异步方法时
- 示例:
1
2
3
4
5
6
7
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
19. @Async:
- 作用:标记方法为异步方法
- 使用场景:需要异步执行的方法上
- 示例:
1
2
3
4
5
6
7
8
public class AsyncService {
public CompletableFuture<String> doSomething() {
// 异步执行的任务
return CompletableFuture.completedFuture("Done");
}
}
20. @CrossOrigin:
- 作用:允许跨域请求
- 属性:
origins:允许的源methods:允许的HTTP方法allowedHeaders:允许的请求头
- 使用场景:需要处理跨域请求的控制器类或方法上
- 示例:
1
2
3
4
5
6
public class UserController {
// 控制器方法
}
注意事项:
- 注解的使用应遵循Spring的最佳实践
- 有些注解需要配合其他注解使用,如
@EnableAsync和@Async - 注解的属性应根据实际需求进行配置
- 应理解每个注解的作用和适用场景,避免滥用
3. Spring Cloud
3.1 什么是Spring Cloud?它的核心组件有哪些?
Spring Cloud是Spring官方提供的微服务框架,它基于Spring Boot,提供了一套完整的微服务解决方案,包括服务注册与发现、负载均衡、熔断器、API网关、配置中心等组件。Spring Cloud的设计理念是”开箱即用”,通过提供一系列的starter依赖,简化了微服务的开发和部署。
Spring Cloud的核心组件:
| 组件 | 作用 | 功能 | 适用场景 |
|---|---|---|---|
| Eureka | 服务注册与发现 | 服务注册、服务发现、健康检查 | 微服务架构中的服务管理 |
| Ribbon | 客户端负载均衡 | 负载均衡、服务调用 | 服务间调用的负载均衡 |
| Hystrix | 熔断器 | 服务降级、服务熔断、服务限流 | 防止服务雪崩 |
| Feign | 声明式REST客户端 | 简化服务调用、集成Ribbon和Hystrix | 服务间调用的简化 |
| Zuul/Gateway | API网关 | 路由转发、过滤器、负载均衡 | 统一入口、权限控制 |
| Config | 配置中心 | 集中管理配置、动态刷新配置 | 多环境配置管理 |
| Sleuth | 分布式追踪 | 跟踪请求链路、性能监控 | 分布式系统的监控 |
| Bus | 消息总线 | 配置刷新、事件传播 | 配置中心的动态刷新 |
详细介绍:
1. Eureka:
- 作用:服务注册与发现
- 功能:
- 服务提供者将自己注册到Eureka Server
- 服务消费者从Eureka Server获取服务列表
- 支持服务的健康检查和自动剔除
- 支持集群部署,提高可用性
- 架构:
- Eureka Server:服务注册中心
- Eureka Client:服务提供者和消费者
- 示例:配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// Eureka Server配置
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
// 服务提供者配置
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
// 服务消费者配置
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# Eureka Server配置
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 服务提供者配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: service-provider
2. Ribbon:
- 作用:客户端负载均衡
- 功能:
- 基于客户端的负载均衡
- 支持多种负载均衡策略(轮询、随机、权重等)
- 与Eureka集成,自动获取服务列表
- 支持自定义负载均衡策略
- 负载均衡策略:
- RoundRobinRule:轮询(默认)
- RandomRule:随机
- WeightedResponseTimeRule:基于响应时间加权
- BestAvailableRule:选择并发量最小的
- AvailabilityFilteringRule:过滤掉故障和并发量高的服务
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ConsumerController {
private RestTemplate restTemplate;
public String hello() {
// 使用Ribbon进行负载均衡
return restTemplate.getForObject("http://SERVICE-PROVIDER/hello", String.class);
}
}
public class RestTemplateConfig {
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 自定义负载均衡策略
public IRule ribbonRule() {
return new RandomRule(); // 使用随机策略
}
}
3. Hystrix:
- 作用:熔断器,防止服务雪崩
- 功能:
- 服务降级:当服务不可用时,返回默认值
- 服务熔断:当服务失败率达到阈值时,自动熔断
- 服务限流:限制服务的并发请求数
- 监控面板:Hystrix Dashboard
- 分布式系统的故障隔离
- 工作原理:
- 当服务调用失败率超过阈值(默认50%)时,熔断器打开
- 熔断器打开后,所有请求直接走降级逻辑
- 经过一段时间(默认5秒)后,熔断器进入半开状态
- 半开状态下,允许部分请求尝试调用服务
- 如果调用成功,熔断器关闭;否则,继续保持打开状态
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class UserService {
private RestTemplate restTemplate;
public String getUserInfo(String id) {
return restTemplate.getForObject("http://USER-SERVICE/user/{id}", String.class, id);
}
public String fallback(String id) {
return "Service unavailable, please try again later.";
}
}
// 启用Hystrix Dashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
4. Feign:
- 作用:声明式REST客户端
- 功能:
- 基于接口的注解方式定义服务调用
- 自动集成Ribbon和Hystrix
- 简化服务间的调用
- 支持请求参数绑定、请求头设置等
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface UserServiceClient {
String getUserInfo( String id);
User createUser( User user);
List<User> getUsers( int page, int size);
}
public class UserServiceFallback implements UserServiceClient {
public String getUserInfo(String id) {
return "Service unavailable, please try again later.";
}
public User createUser(User user) {
return null;
}
public List<User> getUsers(int page, int size) {
return Collections.emptyList();
}
}
// 启用Feign
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
5. Zuul/Gateway:
- 作用:API网关
- 功能:
- 路由转发:将请求转发到对应的服务
- 过滤器:实现认证、授权、限流等功能
- 负载均衡:集成Ribbon
- 监控和日志记录
- Zuul vs Gateway:
- Zuul 1.x:基于Servlet,同步阻塞
- Gateway:基于WebFlux,异步非阻塞,性能更好
- 示例(Gateway):配置文件:
1
2
3
4
5
6
7
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user/**
filters:
- AddRequestHeader=X-Request-Id, 12345
- id: order-service
uri: lb://ORDER-SERVICE
predicates:
- Path=/order/**
6. Config:
- 作用:配置中心
- 功能:
- 集中管理配置
- 支持多种配置源(Git、SVN、本地文件等)
- 动态刷新配置
- 环境隔离
- 示例:配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// Config Server配置
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
// 客户端配置
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# Config Server配置
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/example/config-repo
search-paths: config
# 客户端配置
spring:
cloud:
config:
uri: http://localhost:8888
name: application
profile: dev
7. Sleuth:
- 作用:分布式追踪
- 功能:
- 跟踪请求链路
- 性能监控
- 与Zipkin集成,可视化追踪数据
- 示例:配置文件:
1
2
3
4
5
6
public class SleuthApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthApplication.class, args);
}
}1
2
3
4
5
6spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1.0 # 采样率,1.0表示全部采样
8. Bus:
- 作用:消息总线
- 功能:
- 配置刷新
- 事件传播
- 与RabbitMQ或Kafka集成
- 示例:
1
2
3
4
5
6
7
8
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Spring Cloud的优势:
- 开箱即用:提供了一系列starter依赖,简化配置
- 微服务生态:完整的微服务解决方案
- 与Spring Boot集成:基于Spring Boot,开发体验一致
- 社区活跃:拥有庞大的社区支持和丰富的文档
- 可扩展性:支持自定义和扩展
Spring Cloud的应用场景:
- 微服务架构:构建分布式微服务系统
- 云原生应用:与云平台无缝集成
- 大型企业应用:复杂的企业级应用
- 微服务迁移:从单体应用迁移到微服务架构
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
**8. Sleuth**:
- **作用**:分布式追踪
- **功能**:
- 跟踪请求的调用链
- 收集服务调用的耗时信息
- 与Zipkin集成,可视化调用链
- **示例**:
```java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
9. Stream:
- 作用:消息驱动
- 功能:
- 简化消息的发送和接收
- 支持多种消息中间件(Kafka、RabbitMQ等)
- 提供统一的消息编程模型
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MessageProducer {
private Source source;
public void sendMessage(String message) {
source.output().send(MessageBuilder.withPayload(message).build());
}
}
public class MessageConsumer {
public void receiveMessage(String message) {
System.out.println("Received message: " + message);
}
}
10. Bus:
- 作用:消息总线
- 功能:
- 用于在微服务之间传递消息
- 支持配置的动态刷新
- 示例:
1
2
3
4
5# 配置文件
spring:
cloud:
bus:
enabled: true
Spring Cloud的版本:
- Spring Cloud使用英文单词作为版本号,按字母顺序排列
- 常见版本:
- Hoxton
- Greenwich
- Finchley
- Edgware
- Dalston
Spring Cloud与Spring Boot的版本对应关系:
| Spring Cloud版本 | Spring Boot版本 |
|---|---|
| Hoxton | 2.2.x, 2.3.x |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.x |
注意事项:
- Spring Cloud的组件在不断演进,一些组件可能会被替换或废弃
- 在使用Spring Cloud时,应注意版本的兼容性
- 不同的Spring Cloud版本可能有不同的组件和配置方式
3.2 什么是服务注册与发现?Spring Cloud中如何实现?
服务注册与发现是微服务架构中的重要概念,它解决了服务之间的动态发现和通信问题。
服务注册:服务提供者在启动时,将自己的服务信息(如服务名称、IP地址、端口等)注册到注册中心。
服务发现:服务消费者在需要调用其他服务时,从注册中心获取服务提供者的信息,然后进行调用。
Spring Cloud中通过Eureka实现服务注册与发现:
1. Eureka的架构:
- Eureka Server:注册中心,负责管理服务提供者的信息
- Eureka Client:服务提供者和服务消费者,负责注册服务和发现服务
2. Eureka Server的配置:
步骤:
- 添加依赖
- 配置application.yml
- 启用Eureka Server
示例:
1 | <!-- 添加依赖 --> |
1 | # application.yml配置 |
1 | // 启用Eureka Server |
3. Eureka Client的配置:
步骤:
- 添加依赖
- 配置application.yml
- 启用Eureka Client
示例:
1 | <!-- 添加依赖 --> |
1 | # application.yml配置 |
1 | // 启用Eureka Client |
4. 服务调用:
方式一:使用RestTemplate
1 |
|
方式二:使用Feign
1 | // 定义Feign客户端 |
5. Eureka的特性:
- 服务注册:服务提供者将自己的信息注册到Eureka Server
- 服务续约:服务提供者定期向Eureka Server发送心跳,证明自己还活着
- 服务下线:服务关闭时,主动向Eureka Server发送下线请求
- 服务剔除:Eureka Server定期清理没有续约的服务
- 服务发现:服务消费者从Eureka Server获取服务列表
- 自我保护机制:当网络分区发生时,Eureka Server会保留所有服务,避免误删
6. Eureka的高可用:
- 集群部署:部署多个Eureka Server,它们之间互相注册
- 配置示例:
1 | # 第一个Eureka Server |
7. 其他服务注册与发现组件:
- Consul:HashiCorp提供的服务注册与发现工具
- Zookeeper:Apache提供的分布式协调服务,也可用于服务注册与发现
- Nacos:阿里巴巴提供的服务注册与发现、配置中心
注意事项:
- Eureka Server需要部署多个实例,以保证高可用
- 服务提供者需要设置合理的续约时间和过期时间
- 服务消费者需要处理服务不可用的情况
- 在生产环境中,应使用域名或负载均衡器访问Eureka Server
3.3 什么是负载均衡?Spring Cloud中如何实现?
负载均衡是一种分布式系统设计模式,它将请求分发到多个服务实例上,以提高系统的可用性、可靠性和性能。
负载均衡的作用:
- 提高系统可用性:当某个服务实例故障时,请求可以被分发到其他健康的实例上
- 提高系统性能:通过并行处理请求,提高系统的处理能力
- 实现水平扩展:通过增加服务实例的数量,线性提高系统的处理能力
Spring Cloud中通过Ribbon实现负载均衡:
1. Ribbon的特点:
- 客户端负载均衡:在客户端进行负载均衡,不需要额外的负载均衡服务器
- 与Eureka集成:自动从Eureka Server获取服务列表
- 支持多种负载均衡策略:轮询、随机、权重、响应时间等
- 可自定义负载均衡策略:可以根据业务需求自定义负载均衡策略
2. Ribbon的配置:
步骤:
- 添加依赖(Spring Cloud Netflix Ribbon)
- 配置RestTemplate或Feign
- 配置负载均衡策略
示例:
1 | <!-- 添加依赖 --> |
1 | // 配置RestTemplate并启用负载均衡 |
3. 负载均衡策略:
| 策略名称 | 描述 | 适用场景 |
|---|---|---|
| RoundRobinRule | 轮询策略,依次分发请求 | 所有实例性能相近 |
| RandomRule | 随机策略,随机分发请求 | 所有实例性能相近 |
| WeightedResponseTimeRule | 权重响应时间策略,根据响应时间分配权重 | 实例性能差异较大 |
| BestAvailableRule | 最佳可用策略,选择并发请求数最少的实例 | 实例负载差异较大 |
| AvailabilityFilteringRule | 可用性过滤策略,过滤故障实例和并发请求数超过阈值的实例 | 有实例故障的场景 |
| ZoneAvoidanceRule | 区域避免策略,优先选择同一区域的实例 | 多区域部署 |
4. 配置负载均衡策略:
方式一:全局配置
1 |
|
方式二:针对特定服务配置
1 | # application.yml配置 |
5. 使用RestTemplate进行负载均衡:
1 |
|
6. 使用Feign进行负载均衡:
Feign默认集成了Ribbon,所以不需要额外配置:
1 | // 定义Feign客户端 |
7. Ribbon的核心组件:
- ILoadBalancer:负载均衡器的核心接口,定义了负载均衡的基本操作
- IRule:负载均衡策略接口,定义了如何选择服务实例
- IPing:服务实例健康检查接口,用于检查服务实例是否可用
- ServerList:服务列表接口,用于获取服务实例列表
- ServerListFilter:服务列表过滤器,用于过滤服务实例
8. 自定义负载均衡策略:
1 | public class CustomRule extends AbstractLoadBalancerRule { |
9. 负载均衡的最佳实践:
- 选择合适的负载均衡策略:根据服务实例的特点选择合适的负载均衡策略
- 配置合理的健康检查:确保只有健康的实例接收请求
- 监控负载均衡状态:监控服务实例的负载情况,及时调整策略
- 实现服务降级:当所有服务实例都不可用时,提供降级方案
10. 其他负载均衡方案:
- Nginx:服务器端负载均衡,功能强大,支持多种负载均衡策略
- HAProxy:服务器端负载均衡,性能优异,支持TCP和HTTP协议
- Kubernetes Service:基于Kubernetes的负载均衡,适用于容器化环境
注意事项:
- Ribbon是客户端负载均衡,需要在每个客户端配置
- 负载均衡策略的选择应根据实际业务场景进行调整
- 应定期检查服务实例的健康状态,确保负载均衡的有效性
- 在高并发场景下,应合理配置连接池和超时时间
3.4 什么是熔断器?Spring Cloud中如何实现?
熔断器是一种容错机制,用于防止分布式系统中的级联失败。当服务调用失败率达到阈值时,熔断器会熔断服务调用,避免对故障服务的持续调用,从而保护系统的整体稳定性。
熔断器的作用:
- 防止级联失败:当某个服务故障时,避免其他服务对其持续调用,导致整个系统崩溃
- 提供服务降级:当服务不可用时,返回默认值或备选方案,保证系统的可用性
- 自我修复:当服务恢复正常时,熔断器会自动关闭,恢复正常的服务调用
Spring Cloud中通过Hystrix实现熔断器:
1. Hystrix的特点:
- 服务降级:当服务不可用时,返回默认值
- 服务熔断:当服务失败率达到阈值时,自动熔断
- 服务限流:限制服务的并发请求数
- 监控面板:Hystrix Dashboard,可视化监控服务状态
- 请求缓存:缓存服务调用的结果
- 请求合并:合并多个请求,减少网络开销
2. Hystrix的配置:
步骤:
- 添加依赖
- 启用Hystrix
- 配置熔断器
示例:
1 | <!-- 添加依赖 --> |
1 | // 启用Hystrix |
3. 使用Hystrix:
方式一:使用@HystrixCommand注解
1 |
|
方式二:使用Feign集成Hystrix
1 | # application.yml配置 |
1 | // 定义Feign客户端 |
4. Hystrix的工作原理:
- 关闭状态:熔断器关闭,正常处理请求
- 开启状态:当失败率达到阈值时,熔断器开启,直接执行fallback方法
- 半开状态:经过一段时间后,熔断器进入半开状态,允许部分请求通过,检查服务是否恢复
- 恢复关闭:如果半开状态下的请求成功,熔断器关闭;否则,重新进入开启状态
5. Hystrix的核心配置:
| 配置项 | 描述 | 默认值 |
|---|---|---|
| circuitBreaker.requestVolumeThreshold | 熔断器触发的最小请求数 | 20 |
| circuitBreaker.errorThresholdPercentage | 熔断器触发的错误率阈值 | 50% |
| circuitBreaker.sleepWindowInMilliseconds | 熔断器从开启到半开的时间窗口 | 5000ms |
| execution.isolation.thread.timeoutInMilliseconds | 服务调用的超时时间 | 1000ms |
| execution.isolation.strategy | 隔离策略(THREAD/SEMAPHORE) | THREAD |
| coreSize | 线程池大小 | 10 |
6. Hystrix Dashboard:
步骤:
- 添加依赖
- 启用Hystrix Dashboard
- 配置监控端点
示例:
1 | <!-- 添加依赖 --> |
1 | // 启用Hystrix Dashboard |
1 | # application.yml配置 |
7. Turbine:
- 作用:聚合多个Hystrix流,在一个面板中监控多个服务
- 配置示例:
1 | <!-- 添加依赖 --> |
1 | // 启用Turbine |
1 | # application.yml配置 |
8. Hystrix的最佳实践:
- 合理设置熔断阈值:根据服务的特性设置合适的请求数和错误率阈值
- 提供有意义的fallback:fallback方法应返回有意义的默认值,而不是简单的错误信息
- 监控熔断器状态:通过Hystrix Dashboard监控熔断器的状态,及时发现问题
- 使用舱壁模式:为不同的服务调用创建不同的线程池,避免服务之间的影响
- 合理设置超时时间:根据服务的响应时间设置合适的超时时间
9. 替代方案:
- Resilience4j:轻量级的熔断器实现,性能更好,API更简洁
- Sentinel:阿里巴巴开源的流量控制、熔断降级框架,功能丰富
10. 服务降级的策略:
- 静态返回值:返回固定的默认值
- 缓存返回值:返回缓存的旧值
- 备选服务:调用备选服务
- 模拟数据:生成模拟数据返回
示例:
1 |
|
注意事项:
- Hystrix已经进入维护模式,推荐使用Resilience4j或Sentinel
- 熔断器的配置应根据实际业务场景进行调整
- 应避免在fallback方法中调用可能失败的服务
- 应合理设置超时时间,避免服务调用等待时间过长
- 应监控熔断器的状态,及时发现和解决问题
3.5 什么是API网关?Spring Cloud中如何实现?
API网关是微服务架构中的一个核心组件,它作为所有外部请求的统一入口,负责请求的路由、过滤、限流、认证等功能。
API网关的作用:
- 统一入口:所有外部请求都通过API网关进入系统
- 路由转发:根据请求路径将请求转发到对应的微服务
- 请求过滤:对请求进行验证、转换、日志记录等处理
- 限流熔断:限制请求频率,防止系统过载
- 认证授权:统一处理用户认证和授权
- 监控统计:监控请求的响应时间、成功率等指标
Spring Cloud中实现API网关的方式:
1. Zuul(第一代API网关):
特点:
- 基于Servlet实现,同步阻塞式架构
- 功能丰富,支持多种过滤器
- 与Spring Cloud生态集成良好
配置示例:
1 | <!-- 添加依赖 --> |
1 | // 启用Zuul |
1 | # application.yml配置 |
2. Gateway(新一代API网关):
特点:
- 基于WebFlux实现,响应式非阻塞架构
- 性能更好,支持高并发
- 提供更丰富的路由规则
- 支持过滤器链
- 与Spring Cloud生态集成良好
配置示例:
1 | <!-- 添加依赖 --> |
1 | // 启用Gateway(不需要额外注解) |
3. Gateway的配置方式:
方式一:YAML配置
1 | # application.yml配置 |
方式二:Java代码配置
1 |
|
4. 过滤器:
Zuul过滤器:
1 |
|
Gateway过滤器:
1 |
|
5. 限流配置:
Gateway限流:
1 | # application.yml配置 |
1 |
|
6. 集成Spring Security:
1 | <!-- 添加依赖 --> |
1 |
|
7. API网关的最佳实践:
- 统一路由管理:集中管理所有服务的路由规则
- 合理使用过滤器:只添加必要的过滤器,避免性能损耗
- 实现限流熔断:保护后端服务不被过载
- 统一认证授权:在网关层处理认证和授权,简化服务端逻辑
- 监控和日志:记录请求的详细信息,便于问题排查
- 高可用部署:部署多个网关实例,使用负载均衡
8. API网关的部署架构:
- 单实例部署:适用于开发和测试环境
- 集群部署:适用于生产环境,通过负载均衡器分发请求
- 混合部署:将API网关部署在边缘节点,靠近用户
9. 替代方案:
- Kong:基于Nginx的API网关,功能丰富,支持插件扩展
- Apigee:Google云提供的API管理平台
- AWS API Gateway:AWS提供的托管API网关服务
- Azure API Management:Azure提供的API管理服务
10. 注意事项:
- API网关是系统的入口,应确保其高可用性和性能
- 应合理配置路由规则,避免路由冲突
- 应实现有效的限流策略,防止系统过载
- 应保护API网关免受攻击,如DDoS攻击
- 应定期监控API网关的性能和健康状态
示例:
1 | # 完整的Gateway配置示例 |
总结:
- API网关是微服务架构中的重要组件,负责统一管理外部请求
- Spring Cloud提供了Zuul和Gateway两种实现方式
- Gateway是新一代API网关,基于响应式架构,性能更好
- API网关应实现路由、过滤、限流、认证等功能
- 应根据实际业务场景选择合适的API网关实现
4. MyBatis
4.1 什么是MyBatis?它的核心功能是什么?
MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,是Java生态中最流行的持久层框架之一。
MyBatis的核心功能:
| 功能 | 描述 | 特点 |
|---|---|---|
| SQL映射 | 将SQL语句映射到Java方法 | 支持XML和注解两种配置方式,支持参数映射和结果映射 |
| 动态SQL | 根据条件动态生成SQL语句 | 支持if、choose、foreach等标签,提高SQL的灵活性 |
| 高级映射 | 复杂对象的映射关系 | 支持一对一、一对多、多对多映射,支持延迟加载 |
| 缓存机制 | 提高查询性能 | 一级缓存(SqlSession级别)、二级缓存(SqlSessionFactory级别) |
| 批量操作 | 批量处理数据 | 支持批量插入、更新、删除,提高操作效率 |
| 存储过程支持 | 调用数据库存储过程 | 支持获取存储过程的输出参数 |
| 类型处理器 | Java类型与数据库类型的转换 | 支持自定义类型处理器 |
| 插件机制 | 扩展MyBatis功能 | 可以拦截Executor、StatementHandler等组件 |
详细介绍:
1. SQL映射:
- XML配置:通过XML文件定义SQL语句和映射关系
- 注解配置:通过注解直接在接口方法上定义SQL语句
- 参数映射:支持基本类型、对象、Map等参数类型
- 结果映射:支持将查询结果映射到对象、List、Map等
2. 动态SQL:
- if:条件判断,根据条件生成SQL片段
- choose/when/otherwise:类似switch-case语句
- foreach:循环处理集合参数
- sql/include:定义可重用的SQL片段
- trim/where/set:智能处理SQL语句的拼接
3. 高级映射:
- association:一对一映射,处理对象关联
- collection:一对多映射,处理集合关联
- 多对多映射:通过中间表实现
- 延迟加载:按需加载关联对象,提高性能
4. 缓存机制:
- 一级缓存:SqlSession级别的缓存,默认开启
- 二级缓存:SqlSessionFactory级别的缓存,需要手动开启
- 缓存配置:可以配置缓存的大小、过期时间等
- 自定义缓存:支持集成第三方缓存框架(如Redis)
5. 批量操作:
- 批量插入:一次执行多条插入语句
- 批量更新:一次执行多条更新语句
- 批量删除:一次执行多条删除语句
- ExecutorType.BATCH:使用批处理执行器提高性能
6. 存储过程支持:
- 调用存储过程:通过
call语句调用存储过程 - 输出参数:支持获取存储过程的输出参数
- 游标参数:支持处理存储过程返回的游标
7. 类型处理器:
- 内置类型处理器:处理常见的Java类型与数据库类型的转换
- 自定义类型处理器:处理特殊类型的转换
- 类型别名:简化类型名称的配置
8. 插件机制:
- 拦截点:Executor、StatementHandler、ParameterHandler、ResultSetHandler
- 插件开发:实现Interceptor接口,重写intercept方法
- 插件配置:在MyBatis配置文件中注册插件
MyBatis的优势:
- 灵活性高:可以编写复杂的SQL语句,适合各种复杂场景
- 性能优异:直接操作SQL,避免了ORM框架的性能损耗
- 易于学习:API简单,配置直观,学习曲线平缓
- 与Spring集成:可以与Spring框架无缝集成,支持Spring Boot
- 支持动态SQL:可以根据条件动态生成SQL,提高代码的灵活性
- 开源社区活跃:拥有庞大的社区支持和丰富的文档
MyBatis的适用场景:
- 复杂SQL场景:需要编写复杂SQL语句的项目,如报表系统
- 性能要求高:对性能要求较高的项目,如电商系统
- 遗留系统:需要与遗留系统集成的项目
- 多数据库:需要支持多种数据库的项目
- 需要精细控制SQL:需要对SQL语句进行精细控制的项目
示例:
1 | // 定义Mapper接口 |
1 | <!-- UserMapper.xml --> |
注解方式示例:
1 |
|
- 注解方式:使用@Select、@Insert、@Update、@Delete等注解
- XML方式:使用XML配置文件定义SQL
- 示例:
1
2
3
4
5
6
7
8
9// 注解方式
public interface UserMapper {
User selectById(Long id);
int insert(User user);
}
4. Mapper XML:
- 作用:配置SQL语句和结果集映射
- 主要元素:
<mapper>:根元素,指定命名空间<select>:查询语句<insert>:插入语句<update>:更新语句<delete>:删除语句<resultMap>:结果集映射<parameterMap>:参数映射(已废弃)<sql>:SQL片段<include>:引用SQL片段
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
</resultMap>
<select id="selectById" resultMap="UserMap">
SELECT id, name, age, email
FROM user
WHERE id = #{id}
</select>
</mapper>
5. Executor:
- 作用:执行SQL的执行器,是MyBatis的核心组件之一
- 类型:
- SimpleExecutor:简单执行器,每次执行SQL都创建新的Statement
- ReuseExecutor:重用执行器,重用Statement
- BatchExecutor:批量执行器,批量处理SQL
- 示例:
1
2// MyBatis内部使用,通常不需要手动创建
Executor executor = configuration.newExecutor(tx);
6. StatementHandler:
- 作用:处理SQL语句的执行
- 类型:
- SimpleStatementHandler:处理简单SQL语句
- PreparedStatementHandler:处理预处理SQL语句
- CallableStatementHandler:处理存储过程
- 主要方法:
prepare():预处理SQL语句parameterize():设置参数execute():执行SQL语句query():执行查询
7. ResultSetHandler:
- 作用:处理结果集的映射
- 主要方法:
handleResultSets():处理结果集handleOutputParameters():处理存储过程的输出参数
- 示例:
1
2// MyBatis内部使用,通常不需要手动创建
ResultSetHandler resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
8. ParameterHandler:
- 作用:处理SQL语句的参数
- 主要方法:
setParameters():设置参数值
- 示例:
1
2// MyBatis内部使用,通常不需要手动创建
ParameterHandler parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
9. TypeHandler:
- 作用:处理Java类型与数据库类型的转换
- 内置类型处理器:处理常见的Java类型和数据库类型的转换
- 自定义类型处理器:处理自定义类型的转换
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 自定义类型处理器
public class DateTypeHandler extends BaseTypeHandler<Date> {
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, new Timestamp(parameter.getTime()));
}
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
Timestamp timestamp = rs.getTimestamp(columnName);
return timestamp != null ? new Date(timestamp.getTime()) : null;
}
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Timestamp timestamp = rs.getTimestamp(columnIndex);
return timestamp != null ? new Date(timestamp.getTime()) : null;
}
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Timestamp timestamp = cs.getTimestamp(columnIndex);
return timestamp != null ? new Date(timestamp.getTime()) : null;
}
}
10. Plugin:
- 作用:插件机制,用于拦截MyBatis的核心组件
- 可以拦截的组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 自定义插件
public class LoggingPlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
// 执行前处理
System.out.println("Before executing update");
// 执行原始方法
Object result = invocation.proceed();
// 执行后处理
System.out.println("After executing update");
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
// 设置插件属性
}
}
MyBatis的执行流程:
- 加载配置:加载MyBatis的配置文件和映射文件
- 创建SqlSessionFactory:通过SqlSessionFactoryBuilder创建SqlSessionFactory
- 创建SqlSession:通过SqlSessionFactory创建SqlSession
- 获取Mapper:通过SqlSession获取Mapper接口的代理实现
- 执行SQL:调用Mapper接口的方法执行SQL
- 处理结果:将SQL执行结果转换为Java对象
- 提交事务:如果是写操作,提交事务
- 关闭SqlSession:关闭SqlSession,释放资源
注意事项:
- SqlSessionFactory应该是单例的,在应用启动时创建
- SqlSession应该在每次操作后关闭,避免资源泄漏
- Mapper接口应该保持简洁,只定义SQL操作方法
- 应该合理使用插件,避免过度使用影响性能
- 应该合理配置类型处理器,确保类型转换正确
4.3 MyBatis的映射方式有哪些?
MyBatis的映射方式是指如何将SQL语句与Java方法进行关联,以及如何将查询结果映射到Java对象。MyBatis提供了多种映射方式,以满足不同场景的需求。
1. XML映射:
- 特点:通过XML文件配置SQL语句和结果集映射,是MyBatis最传统、最灵活的映射方式
- 优势:
- 支持复杂的SQL语句
- 支持动态SQL
- 支持高级结果集映射
- 便于维护和管理
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="UserMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 一对一映射 -->
<association property="address" column="address_id" javaType="com.example.entity.Address">
<id property="id" column="address_id"/>
<result property="street" column="street"/>
<result property="city" column="city"/>
</association>
<!-- 一对多映射 -->
<collection property="orders" column="id" ofType="com.example.entity.Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>
<!-- 查询所有用户 -->
<select id="selectAll" resultMap="UserMap">
SELECT u.id, u.name, u.age, u.email,
a.id AS address_id, a.street, a.city,
o.id AS order_id, o.order_no, o.amount
FROM user u
LEFT JOIN address a ON u.address_id = a.id
LEFT JOIN order o ON u.id = o.user_id
</select>
<!-- 动态SQL -->
<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultMap="UserMap">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
</mapper>
2. 注解映射:
- 特点:通过Java注解配置SQL语句,简洁直观
- 优势:
- 代码量少,配置简单
- 不需要额外的XML文件
- 适合简单的SQL语句
- 劣势:
- 不支持复杂的SQL语句
- 不支持动态SQL(需要使用XML或混合映射)
- 不支持高级结果集映射
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public interface UserMapper {
// 简单查询
User selectById(Long id);
// 插入操作
int insert(User user);
// 更新操作
int update(User user);
// 删除操作
int delete(Long id);
// 复杂查询(使用Provider)
List<User> selectByCondition(UserCondition condition);
}
// SQL Provider
public class UserSqlProvider {
public String selectByCondition(UserCondition condition) {
StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
if (condition.getName() != null && !condition.getName().isEmpty()) {
sql.append(" AND name LIKE CONCAT('%', #{name}, '%')");
}
if (condition.getAge() != null) {
sql.append(" AND age = #{age}");
}
return sql.toString();
}
}
3. 混合映射:
- 特点:同时使用XML和注解进行映射
- 优势:
- 结合了XML和注解的优点
- 简单的SQL使用注解,复杂的SQL使用XML
- 灵活度高
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13public interface UserMapper {
// 简单查询使用注解
User selectById(Long id);
// 复杂查询使用XML
List<User> selectByCondition(UserCondition condition);
// 插入操作使用注解
int insert(User user);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</where>
<order by>
<if test="orderBy != null and orderBy != ''">
#{orderBy}
</if>
<otherwise>
id DESC
</otherwise>
</order>
</select>
</mapper>
4. 结果集映射:
(1) 简单结果映射:
- 使用
resultType指定返回类型 - MyBatis会自动将查询结果映射到指定类型
- 适用于简单的查询
(2) 高级结果映射:
- 使用
resultMap定义复杂的结果映射 - 支持一对一、一对多、多对多映射
- 支持延迟加载
- 适用于复杂的查询
5. 参数映射:
(1) 基本类型参数:
- 直接使用
#{parameter}引用参数 - 支持设置参数类型和jdbcType
(2) 对象参数:
- 使用
#{property}引用对象的属性 - 支持嵌套属性,如
#{user.name}
(3) 集合参数:
- 使用
foreach标签遍历集合 - 支持List、Set、Map等集合类型
(4) 多参数:
- 使用
@Param注解指定参数名称 - 使用Map传递多个参数
- 使用自定义对象传递多个参数
6. 动态SQL映射:
(1) if:
- 条件判断,根据条件生成SQL
**(2) choose (when, otherwise)**:
- 选择条件,类似于Java的switch语句
**(3) trim (where, set)**:
- 处理SQL语句的前缀和后缀
where:自动处理WHERE子句set:自动处理SET子句
(4) foreach:
- 遍历集合,生成IN语句或批量操作
(5) bind:
- 绑定变量,用于复杂的表达式
7. 最佳实践:
- 简单SQL:使用注解映射
- 复杂SQL:使用XML映射
- 混合使用:简单SQL使用注解,复杂SQL使用XML
- 结果集映射:简单结果使用resultType,复杂结果使用resultMap
- 参数映射:使用@Param注解或自定义对象传递参数
- 动态SQL:使用XML的动态SQL标签
8. 注意事项:
- XML映射文件的命名空间必须与Mapper接口的全限定名一致
- XML映射文件中的id必须与Mapper接口中的方法名一致
- 注解映射和XML映射可以共存,但XML映射的优先级更高
- 应该合理选择映射方式,根据SQL的复杂度和维护性
- 应该避免在注解中编写复杂的SQL语句,影响代码可读性
4.4 MyBatis的缓存机制?
MyBatis的缓存机制是MyBatis提高性能的重要手段,它通过缓存查询结果,减少数据库访问次数,从而提高系统性能。
1. 一级缓存:
- 作用范围:SqlSession级别的缓存,每个SqlSession有自己的缓存
- 默认状态:默认开启,不需要额外配置
- 工作原理:
- 当执行查询操作时,MyBatis会先在一级缓存中查找
- 如果找到,直接返回缓存的结果
- 如果没有找到,执行SQL查询,将结果存入一级缓存
- 当执行增删改操作时,一级缓存会被清空
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询,从数据库获取
User user1 = userMapper.selectById(1L);
System.out.println(user1);
// 第二次查询,从一级缓存获取
User user2 = userMapper.selectById(1L);
System.out.println(user2);
// user1和user2是同一个对象
System.out.println(user1 == user2); // true
// 执行更新操作,清空一级缓存
userMapper.update(user1);
sqlSession.commit();
// 第三次查询,从数据库获取
User user3 = userMapper.selectById(1L);
System.out.println(user3);
// user1和user3是不同的对象
System.out.println(user1 == user3); // false
} finally {
sqlSession.close();
}
2. 二级缓存:
- 作用范围:Mapper级别的缓存,多个SqlSession共享
- 默认状态:默认关闭,需要手动开启
- 开启方式:
- 在MyBatis配置文件中开启全局二级缓存
- 在Mapper XML文件中开启二级缓存
- 实体类需要实现Serializable接口
- 工作原理:
- 当执行查询操作时,MyBatis会先在一级缓存中查找
- 如果一级缓存中没有,再在二级缓存中查找
- 如果二级缓存中也没有,执行SQL查询,将结果存入一级缓存和二级缓存
- 当执行增删改操作时,二级缓存会被清空
- 示例:
1
2
3
4
5
6
7<!-- mybatis-config.xml -->
<configuration>
<!-- 开启全局二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>1
2
3
4
5
6
7
8
9
10
11<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="false"/>
<!-- 其他映射配置 -->
</mapper>1
2
3
4
5
6
7
8// 实体类需要实现Serializable接口
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
private String email;
// getter和setter
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37// 测试二级缓存
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
// 第一次查询,从数据库获取
User user1 = mapper1.selectById(1L);
System.out.println(user1);
// 提交事务,将结果存入二级缓存
sqlSession1.commit();
// 第二次查询,从二级缓存获取
User user2 = mapper2.selectById(1L);
System.out.println(user2);
// user1和user2是不同的对象,但内容相同
System.out.println(user1 == user2); // false
System.out.println(user1.equals(user2)); // true
// 执行更新操作,清空二级缓存
mapper1.update(user1);
sqlSession1.commit();
// 第三次查询,从数据库获取
User user3 = mapper2.selectById(1L);
System.out.println(user3);
// user1和user3是不同的对象,内容也不同
System.out.println(user1.equals(user3)); // false
} finally {
sqlSession1.close();
sqlSession2.close();
}
3. 缓存实现:
(1) 内置缓存实现:
- PerpetualCache:默认缓存实现,使用HashMap存储缓存数据
- LruCache:基于LRU(最近最少使用)算法的缓存实现
- FifoCache:基于FIFO(先进先出)算法的缓存实现
- SoftCache:基于软引用的缓存实现,当内存不足时会被垃圾回收
- WeakCache:基于弱引用的缓存实现,随时可能被垃圾回收
(2) 自定义缓存实现:
- 实现
Cache接口 - 在Mapper XML文件中配置自定义缓存
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44public class MyCache implements Cache {
private String id;
private Map<Object, Object> cache;
public MyCache(String id) {
this.id = id;
this.cache = new ConcurrentHashMap<>();
}
public String getId() {
return id;
}
public int getSize() {
return cache.size();
}
public void putObject(Object key, Object value) {
cache.put(key, value);
}
public Object getObject(Object key) {
return cache.get(key);
}
public Object removeObject(Object key) {
return cache.remove(key);
}
public void clear() {
cache.clear();
}
public ReadWriteLock getReadWriteLock() {
return null; // 不需要锁
}
}1
2
3
4
5
6
7<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 使用自定义缓存 -->
<cache type="com.example.cache.MyCache"/>
<!-- 其他映射配置 -->
</mapper>
4. 缓存配置:
(1) 全局缓存配置:
1 | <settings> |
(2) Mapper缓存配置:
1 | <cache |
(3) 缓存淘汰策略:
- LRU:最近最少使用,移除最长时间不被使用的对象
- FIFO:先进先出,按对象进入缓存的顺序移除
- SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
- WEAK:弱引用,基于垃圾回收器状态和弱引用规则移除对象
5. 缓存失效:
- 增删改操作:当执行insert、update、delete操作时,缓存会被清空
- 手动清空:调用
SqlSession.clearCache()清空一级缓存 - 缓存过期:当缓存达到刷新间隔或缓存大小限制时,缓存会被清空
- 序列化问题:当实体类未实现Serializable接口时,二级缓存会失效
6. 缓存的使用场景:
适合使用缓存的场景:
- 查询频率高,数据变化频率低的场景
- 数据一致性要求不高的场景
- 系统性能要求高的场景
不适合使用缓存的场景:
- 数据变化频率高的场景
- 数据一致性要求高的场景
- 内存资源有限的场景
7. 缓存的最佳实践:
- 合理配置缓存:根据实际业务场景配置缓存的大小、刷新间隔和淘汰策略
- 使用二级缓存:对于频繁查询的数据,使用二级缓存提高性能
- 注意序列化:使用二级缓存时,实体类必须实现Serializable接口
- 避免缓存穿透:对于不存在的数据,也应该缓存,避免每次都查询数据库
- 避免缓存雪崩:设置不同的缓存过期时间,避免缓存同时过期
- 监控缓存:监控缓存的命中率和内存使用情况,及时调整缓存策略
8. 缓存的优缺点:
优点:
- 减少数据库访问次数,提高系统性能
- 减轻数据库压力
- 提高用户体验
缺点:
- 占用内存资源
- 可能导致数据不一致
- 增加系统复杂度
9. 注意事项:
- 一级缓存是SqlSession级别的,不能跨SqlSession共享
- 二级缓存是Mapper级别的,可以跨SqlSession共享
- 使用二级缓存时,实体类必须实现Serializable接口
- 当执行增删改操作时,缓存会被清空
- 应该根据实际业务场景选择合适的缓存策略
- 应该避免缓存大量数据,导致内存溢出
- 应该监控缓存的使用情况,及时调整缓存配置
4.5 MyBatis的动态SQL有哪些?
MyBatis的动态SQL是MyBatis的核心特性之一,它允许根据条件动态生成SQL语句,提高了SQL语句的灵活性和可维护性。
1. if:
- 作用:条件判断,根据条件生成SQL片段
- 语法:
1
2
3<if test="条件">
SQL片段
</if> - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
SELECT * FROM user
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</select>
**2. choose (when, otherwise)**:
- 作用:选择条件,类似于Java的switch语句
- 语法:
1
2
3
4
5
6
7
8
9
10
11<choose>
<when test="条件1">
SQL片段1
</when>
<when test="条件2">
SQL片段2
</when>
<otherwise>
SQL片段3
</otherwise>
</choose> - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
SELECT * FROM user
WHERE 1=1
<choose>
<when test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND status = 'active'
</otherwise>
</choose>
</select>
**3. trim (where, set)**:
- 作用:处理SQL语句的前缀和后缀
- 语法:
1
2
3<trim prefix="前缀" suffix="后缀" prefixOverrides="前缀覆盖" suffixOverrides="后缀覆盖">
SQL片段
</trim> - where:自动处理WHERE子句,移除多余的AND或OR
- set:自动处理SET子句,移除多余的逗号
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<!-- where示例 -->
<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<!-- set示例 -->
<update id="update" parameterType="com.example.entity.User">
UPDATE user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
</set>
WHERE id = #{id}
</update>
4. foreach:
- 作用:遍历集合,生成IN语句或批量操作
- 语法:
1
2
3<foreach collection="集合" item="元素" index="索引" open="开始" close="结束" separator="分隔符">
#{元素}
</foreach> - 属性:
collection:集合的名称item:集合元素的别名index:集合元素的索引open:SQL片段的开始close:SQL片段的结束separator:元素之间的分隔符
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- IN语句示例 -->
<select id="selectByIds" parameterType="java.util.List" resultType="com.example.entity.User">
SELECT * FROM user
WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 批量插入示例 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (name, age, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
5. bind:
- 作用:绑定变量,用于复杂的表达式
- 语法:
1
<bind name="变量名" value="表达式"/>
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13<select id="selectByCondition" parameterType="com.example.condition.UserCondition" resultType="com.example.entity.User">
<bind name="nameLike" value="'%' + name + '%'"/>
<bind name="emailLike" value="'%' + email + '%'"/>
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE #{nameLike}
</if>
<if test="email != null and email != ''">
AND email LIKE #{emailLike}
</if>
</where>
</select>
6. sql和include:
- 作用:定义SQL片段,提高代码复用性
- 语法:
1
2
3
4
5
6
7<!-- 定义SQL片段 -->
<sql id="sql片段ID">
SQL片段
</sql>
<!-- 引用SQL片段 -->
<include refid="sql片段ID"/> - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 定义SQL片段 -->
<sql id="userColumns">
id, name, age, email
</sql>
<!-- 引用SQL片段 -->
<select id="selectById" parameterType="java.lang.Long" resultType="com.example.entity.User">
SELECT <include refid="userColumns"/>
FROM user
WHERE id = #{id}
</select>
<select id="selectAll" resultType="com.example.entity.User">
SELECT <include refid="userColumns"/>
FROM user
</select>
7. 动态SQL的最佳实践:
- 使用where标签:自动处理WHERE子句,避免多余的AND或OR
- 使用set标签:自动处理SET子句,避免多余的逗号
- 使用foreach标签:处理集合参数,生成IN语句或批量操作
- 使用bind标签:处理复杂的表达式,提高代码可读性
- 使用sql和include标签:定义可复用的SQL片段,提高代码复用性
- 合理使用choose标签:处理多条件选择场景
- 避免嵌套过深:动态SQL嵌套过深会影响代码可读性和性能
8. 动态SQL的优缺点:
优点:
- 提高SQL语句的灵活性
- 减少代码重复
- 提高代码可维护性
- 支持复杂的条件查询
缺点:
- 可能导致SQL语句过于复杂
- 可能影响SQL语句的性能
- 调试难度较大
9. 注意事项:
- 动态SQL的条件判断应使用OGNL表达式
- 应避免在动态SQL中使用复杂的业务逻辑
- 应注意SQL注入问题,使用#{ }占位符
- 应合理使用动态SQL,避免过度使用
- 应测试动态SQL生成的SQL语句,确保其正确性
10. 示例:
1 | <!-- 综合示例 --> |
总结:
- MyBatis的动态SQL是其核心特性之一,允许根据条件动态生成SQL语句
- 常用的动态SQL标签包括:if、choose、when、otherwise、trim、where、set、foreach、bind、sql、include
- 动态SQL可以提高SQL语句的灵活性和可维护性
- 应合理使用动态SQL,避免过度使用导致SQL语句过于复杂
- 应注意动态SQL的性能和安全性
5. 其他框架相关问题
5.1 Spring MVC的工作原理?
Spring MVC的工作原理:
- 客户端发送请求到DispatcherServlet
- DispatcherServlet根据请求路径查找HandlerMapping
- HandlerMapping返回HandlerExecutionChain
- DispatcherServlet调用HandlerAdapter执行Handler
- Handler执行完毕返回ModelAndView
- DispatcherServlet根据ViewName查找ViewResolver
- ViewResolver返回View
- DispatcherServlet渲染View并返回响应
5.2 什么是ORM?常见的ORM框架有哪些?
ORM(Object-Relational Mapping)是一种将对象与关系数据库映射的技术。常见的ORM框架包括:
- Hibernate:全自动化ORM框架
- MyBatis:半自动化ORM框架
- JPA:Java持久化API
- Spring Data JPA:基于JPA的框架
5.3 什么是依赖注入?依赖注入的方式有哪些?
依赖注入是指将依赖的对象注入到需要的对象中。依赖注入的方式包括:
- 构造函数注入:通过构造函数注入依赖
- setter方法注入:通过setter方法注入依赖
- 字段注入:通过字段注入依赖
- 接口注入:通过接口注入依赖
5.4 什么是控制反转?控制反转的实现方式有哪些?
控制反转是指将对象的创建和管理交给容器,而不是由对象自己创建和管理依赖。控制反转的实现方式包括:
- 依赖注入:通过构造函数、setter方法或字段注入依赖
- 服务定位器:通过服务定位器获取依赖
- 工厂模式:通过工厂创建对象
- 模板方法:通过模板方法定义算法骨架
