框架背诵

Java框架核心知识详解

一、Spring框架深入解析

1.1 Spring核心概念与原理

Spring框架是一个分层的企业级应用开发框架,其核心是控制反转(IoC)和面向切面编程(AOP)。

IoC容器原理深入分析

IoC(控制反转)的本质: 传统开发中,对象的创建和依赖关系由程序代码直接控制,而IoC将这个控制权交给了外部容器。Spring通过依赖注入(DI)来实现IoC。

IoC容器的实现机制:

  1. BeanFactory: 基础容器,提供基本的IoC功能
  2. ApplicationContext: 高级容器,继承BeanFactory,提供更多企业级功能

Bean的生命周期详解:

1
实例化 → 属性赋值 → 初始化前处理 → 初始化 → 初始化后处理 → 使用 → 销毁前处理 → 销毁

详细生命周期步骤:

  1. Bean元数据解析(XML、注解、Java配置)
  2. 调用Bean构造函数实例化
  3. 依赖注入(setter方法、构造函数、字段注入)
  4. 如果实现了BeanNameAware,调用setBeanName()
  5. 如果实现了BeanFactoryAware,调用setBeanFactory()
  6. 如果实现了ApplicationContextAware,调用setApplicationContext()
  7. 如果有BeanPostProcessor,调用postProcessBeforeInitialization()
  8. 如果实现了InitializingBean,调用afterPropertiesSet()
  9. 如果配置了init-method,调用自定义初始化方法
  10. 如果有BeanPostProcessor,调用postProcessAfterInitialization()
  11. Bean可以被使用
  12. 容器关闭时,如果实现了DisposableBean,调用destroy()
  13. 如果配置了destroy-method,调用自定义销毁方法

DI的三种注入方式对比

  • 构造器注入(Constructor Injection)

    • 优点:强制依赖、依赖清晰、不可变性
    • 缺点:当依赖项过多时构造函数臃肿
    • 最佳实践:官方推荐,特别是当依赖是必需的、不可选的时候
  • Setter注入(Setter Injection)

    • 优点:可选依赖,灵活性高
    • 缺点:对象可能处于不完整状态,无法使用final修饰
    • 最佳实践:用于注入可选的依赖项
  • 字段注入(Field Injection)

    • 优点:代码简洁
    • 缺点:不推荐使用,隐藏依赖、测试困难、无法使用final修饰

AOP原理深入分析

AOP实现机制: Spring AOP基于代理模式实现,支持两种代理方式:

  1. JDK动态代理: 针对实现了接口的类
  2. CGLIB代理: 针对没有实现接口的类

我们来系统性地讲解一下 Spring AOP 的实现机制,包括:

  1. JDK 动态代理
  2. CGLIB 动态代理
  3. 静态代理(对比说明)

🌟 一、Spring AOP 实现机制概述

Spring AOP(面向切面编程)是基于 代理模式 实现的,它通过在目标方法执行前后织入逻辑(增强),实现横切关注点(如日志、安全、事务等)的分离。

Spring AOP 仅支持方法级别的代理(即对方法进行增强),不支持字段、构造器等底层字节码增强(这要用 AspectJ)。


🧩 二、Spring AOP 的两种动态代理方式

1️⃣ JDK 动态代理(基于接口)

✅ 原理:

  • 基于 Java 的 java.lang.reflect.Proxy 类实现。
  • 必须要有接口,Spring 会为接口生成代理类。
  • 代理类在运行时实现接口,并将调用委托给 InvocationHandler。

📦 示例:

1
2
3
4
5
6
7
8
9
public interface UserService {
void addUser();
}

public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("添加用户");
}
}

Spring AOP 为 UserService 创建代理:

1
2
3
4
5
6
7
8
9
10
11
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强");
Object result = method.invoke(userService, args);
System.out.println("后置增强");
return result;
}
});

🎯 特点:

特性 描述
是否依赖接口 ✅ 是
性能 较高(比 CGLIB 略快)
生成的代理类 实现接口

2️⃣ CGLIB 动态代理(基于子类)

✅ 原理:

  • 使用 CGLIB 库在运行时 生成目标类的子类,并重写其方法来实现增强。
  • 适用于 没有接口 的类,或者想对类本身增强而非接口。

🧪 示例:

1
2
3
4
5
public class UserService {
public void addUser() {
System.out.println("添加用户");
}
}

Spring 通过 CGLIB 生成子类:

1
2
3
4
5
6
7
8
9
10
11
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置增强");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置增强");
return result;
}
});
UserService proxy = (UserService) enhancer.create();

🎯 特点:

特性 描述
是否依赖接口 ❌ 否
是否可代理 final 类/方法 ❌ 否(final 无法被继承/重写)
生成的代理类 子类

🏗 三、静态代理(对比动态代理)

✅ 原理:

  • 自己写一个代理类,实现相同接口,将方法调用委托给目标对象,并在调用前后手动添加增强逻辑。

🧪 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface UserService {
void addUser();
}

public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("添加用户");
}
}

// 静态代理类
public class UserServiceProxy implements UserService {
private UserService target;

public UserServiceProxy(UserService target) {
this.target = target;
}

public void addUser() {
System.out.println("前置增强");
target.addUser();
System.out.println("后置增强");
}
}

🎯 特点:

特性 描述
是否自动生成 ❌ 需手动编码
灵活性 差(每个类都要写一个代理类)
是否支持 AOP ❌ 无法动态织入多个切面

🔚 四、三者对比总结

特性 静态代理 JDK 动态代理 CGLIB 动态代理
是否依赖接口 ✅ 是 ✅ 是 ❌ 否
是否自动生成代理类 ❌ 手动编写 ✅ 运行时生成 ✅ 运行时生成
是否可代理类 ❌ 否(接口为主) ❌ 否(接口为主) ✅ 是(类的子类)
性能 一般 较高 略慢(但可接受)
是否支持 final 方法 ❌ 否

✅ Spring 如何选择代理方式?

  • 默认使用 JDK 动态代理(如果目标类实现了接口)。
  • 如果目标类没有接口,Spring 自动切换为 CGLIB。
  • 可以强制使用 CGLIB:
1
@EnableAspectJAutoProxy(proxyTargetClass = true)

AOP核心概念:

  • 切面(Aspect): 横切关注点的模块化
  • 连接点(Joinpoint): 程序执行中的特定点
  • 切点(Pointcut): 连接点的集合
  • 通知(Advice): 切面在特定连接点执行的代码
  • 目标对象(Target): 被代理的对象
  • 代理对象(Proxy): AOP框架创建的对象

JDK动态代理 vs. CGLIB动态代理

  • JDK动态代理

    • 基于接口实现
    • 使用Proxy.newProxyInstance()创建代理
    • 只能代理实现了接口的类
  • CGLIB动态代理

    • 基于继承实现
    • 使用Enhancer类创建代理
    • 可以代理普通类
    • 不能代理final类和方法

1.2 Spring核心注解详解

基础配置注解

1
2
3
4
5
6
@Configuration  // 表示这是一个配置类
@ComponentScan(basePackages = "com.example") // 组件扫描
@EnableAutoConfiguration // 启用自动配置
@SpringBootApplication // SpringBoot主类注解,包含上述三个
@Import(OtherConfig.class) // 导入其他配置类
@PropertySource("classpath:application.properties") // 加载属性文件

Bean定义注解

1
2
3
4
5
6
7
8
9
10
@Component  // 通用组件
@Service // 业务层组件
@Repository // 数据访问层组件
@Controller // 控制层组件
@RestController // RESTful控制器,相当于@Controller + @ResponseBody
@Bean // 方法级别,定义Bean
@Scope("singleton/prototype/request/session") // Bean作用域
@Lazy // 延迟初始化
@Primary // 优先注入
@Qualifier("beanName") // 指定注入的Bean名称

依赖注入注解

1
2
3
4
5
@Autowired  // 自动装配,可用于构造函数、方法、字段
@Resource // JSR-250标准,按名称注入
@Inject // JSR-330标准
@Value("${property.name}") // 注入配置值
@ConfigurationProperties(prefix = "app") // 绑定配置属性

生命周期注解

1
2
@PostConstruct  // 初始化方法
@PreDestroy // 销毁方法

AOP相关注解

1
2
3
4
5
6
7
@Aspect         // 声明切面
@Pointcut // 定义切点
@Before // 前置通知
@After // 后置通知
@AfterReturning // 返回后通知
@AfterThrowing // 异常通知
@Around // 环绕通知

1.3 Spring常见使用场景

场景1:服务层事务管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
@Transactional
public class UserService {

@Autowired
private UserRepository userRepository;

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public User createUser(User user) {
// 业务逻辑
return userRepository.save(user);
}

@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id);
}
}

场景2:缓存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class ProductService {

@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id);
}

@CacheEvict(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
}

1.4 Spring常见面试问题

Q1: Spring IoC容器的初始化过程是怎样的?

A: Spring IoC容器初始化分为三个阶段:

  1. Resource定位: 定位配置文件
  2. BeanDefinition载入: 将配置信息转换为Spring内部数据结构
  3. BeanDefinition注册: 将BeanDefinition注册到IoC容器的HashMap中

具体流程:

  • 创建ApplicationContext
  • 加载配置元数据(XML、注解、Java配置)
  • 解析配置,创建BeanDefinition
  • 注册BeanDefinition到BeanDefinitionRegistry
  • 实例化非懒加载的单例Bean

Q2: Spring中的循环依赖是如何解决的?

A: Spring通过三级缓存解决循环依赖:

  1. singletonObjects: 一级缓存,存放完整的Bean实例
  2. earlySingletonObjects: 二级缓存,存放早期的Bean实例
  3. singletonFactories: 三级缓存,存放Bean工厂

解决过程:

  • A依赖B,B依赖A
  • 创建A时,将A的工厂放入三级缓存
  • A需要注入B,开始创建B
  • B需要注入A,从缓存中获取A的早期实例
  • B创建完成,A继续创建完成

Q3: Spring AOP的实现原理?

A: Spring AOP基于代理模式实现:

  1. JDK动态代理: 目标类实现接口时使用,基于反射机制
  2. CGLIB代理: 目标类没有接口时使用,基于字节码技术

代理创建过程:

  • Spring在Bean初始化后,检查是否需要AOP
  • 如果需要,创建代理对象替换原始Bean
  • 代理对象拦截方法调用,执行切面逻辑

二、Spring Boot深入解析

2.1 Spring Boot核心原理

自动配置原理: Spring Boot通过@EnableAutoConfiguration注解启用自动配置机制。

核心类分析:

  1. SpringBootApplication: 组合注解,包含@Configuration、@EnableAutoConfiguration、@ComponentScan
  2. AutoConfigurationImportSelector: 负责导入自动配置类
  3. spring.factories: META-INF/spring.factories文件定义自动配置类

自动配置流程:

  1. SpringBoot启动时扫描所有jar包下的META-INF/spring.factories文件
  2. 加载文件中定义的自动配置类
  3. 根据条件注解(@ConditionalOnClass等)判断是否生效
  4. 生效的配置类会创建相应的Bean

2.2 Spring Boot启动流程详解

SpringApplication.run()方法执行流程:

  1. 准备阶段
    • 创建SpringApplication实例
    • 确定应用类型(SERVLET、REACTIVE、NONE)
    • 加载ApplicationContextInitializer
    • 加载ApplicationListener
  2. 启动阶段
    • 启动计时器
    • 配置Headless模式
    • 获取并启动监听器
    • 准备环境(Environment)
    • 打印Banner
  3. 容器创建阶段
    • 创建ApplicationContext
    • 准备ApplicationContext
    • 刷新ApplicationContext
    • 刷新后处理
  4. 完成阶段
    • 停止计时器
    • 发布启动完成事件
    • 调用Runners

2.3 Spring Boot核心注解

启动类注解

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
// 等价于以下三个注解的组合
@Configuration
@EnableAutoConfiguration
@ComponentScan

@SpringBootConfiguration // Spring Boot配置类
@EnableScheduling // 启用定时任务
@EnableAsync // 启用异步处理
@EnableCaching // 启用缓存

条件注解

1
2
3
4
5
6
@ConditionalOnClass(DataSource.class)      // 类路径下存在指定类
@ConditionalOnMissingBean(DataSource.class) // 容器中不存在指定Bean
@ConditionalOnProperty(name = "app.enabled", havingValue = "true") // 属性匹配
@ConditionalOnWebApplication // Web应用环境
@ConditionalOnNotWebApplication // 非Web应用环境
@Profile("dev") // 激活的profile

配置属性注解

1
2
3
4
5
6
7
@ConfigurationProperties(prefix = "app.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
// getters and setters
}

2.4 Spring Boot实际应用场景

场景1:微服务架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}

@FeignClient(name = "order-service")
public interface OrderServiceClient {
@GetMapping("/orders/{userId}")
List<Order> getOrdersByUserId(@PathVariable Long userId);
}

场景2:数据访问层整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
@EnableJpaRepositories
public class Application {

@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
}

2.5 Spring Boot面试问题

Q1: Spring Boot的启动原理是什么?

A: Spring Boot启动原理核心在于自动配置:

  1. @SpringBootApplication组合了三个注解,其中@EnableAutoConfiguration是关键
  2. AutoConfigurationImportSelector会扫描所有引入的jar包,查找其META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration键所对应的配置类全限定名列表
  3. 根据条件注解判断哪些自动配置类生效
  4. 生效的配置类会向容器中注册相应的Bean

Q2: Spring Boot如何实现自动配置?

A: 自动配置通过以下机制实现:

  1. 条件注解: @ConditionalOnClass、@ConditionalOnBean等判断配置是否生效
  2. 配置文件: spring.factories定义自动配置类列表
  3. 配置属性: @ConfigurationProperties绑定配置文件中的属性
  4. 默认配置: 提供合理的默认值,用户可覆盖

Q3: Spring Boot Starter的工作原理?

A: Starter是Spring Boot自动配置的载体:

  1. 依赖管理: 通过Maven/Gradle引入相关依赖
  2. 自动配置: 包含AutoConfiguration类
  3. 属性绑定: 提供ConfigurationProperties类
  4. 条件装配: 使用条件注解控制Bean的创建

三、Spring MVC深入解析

3.1 Spring MVC核心组件

DispatcherServlet处理流程:

  1. 接收请求: DispatcherServlet接收HTTP请求
  2. 查找Handler: HandlerMapping查找处理请求的Handler
  3. 获取HandlerAdapter: 获取能够执行Handler的HandlerAdapter
  4. 执行Handler: HandlerAdapter执行Handler(Controller方法)
  5. 处理结果: 返回ModelAndView
  6. 视图解析: ViewResolver解析视图名称
  7. 渲染视图: View渲染模型数据
  8. 返回响应: 响应结果返回给客户端

核心组件详解:

  • DispatcherServlet: 前端控制器,统一处理请求
  • HandlerMapping: 处理器映射器,URL与Handler的映射
  • HandlerAdapter: 处理器适配器,执行Handler
  • Handler: 处理器,即Controller
  • ViewResolver: 视图解析器
  • View: 视图

3.2 Spring MVC核心注解

控制器注解

1
2
3
4
5
6
7
8
@Controller             // 标记控制器类
@RestController // RESTful控制器
@RequestMapping("/api") // 请求映射
@GetMapping("/users") // GET请求映射
@PostMapping("/users") // POST请求映射
@PutMapping("/users/{id}") // PUT请求映射
@DeleteMapping("/users/{id}") // DELETE请求映射
@PatchMapping("/users/{id}") // PATCH请求映射

参数绑定注解

1
2
3
4
5
6
7
8
@RequestParam("name")           // 请求参数
@PathVariable("id") // 路径变量
@RequestBody // 请求体
@RequestHeader("Content-Type") // 请求头
@CookieValue("sessionId") // Cookie值
@ModelAttribute // 模型属性
@SessionAttribute // Session属性
@RequestPart // 文件上传

响应处理注解

1
2
3
4
5
@ResponseBody              // 响应体
@ResponseStatus(HttpStatus.CREATED) // 响应状态码
@ExceptionHandler // 异常处理
@ControllerAdvice // 全局控制器增强
@RestControllerAdvice // RESTful全局异常处理

3.3 Spring MVC实际应用场景

场景1:RESTful API开发

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
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {

@Autowired
private UserService userService;

@GetMapping
public ResponseEntity<PageResult<User>> getUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {

PageResult<User> result = userService.findUsers(page, size, keyword);
return ResponseEntity.ok(result);
}

@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest request) {
User user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
User user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}

场景2:全局异常处理

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
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationException(ValidationException e) {
return ErrorResponse.builder()
.code("VALIDATION_ERROR")
.message(e.getMessage())
.timestamp(LocalDateTime.now())
.build();
}

@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleEntityNotFoundException(EntityNotFoundException e) {
return ErrorResponse.builder()
.code("ENTITY_NOT_FOUND")
.message(e.getMessage())
.timestamp(LocalDateTime.now())
.build();
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGenericException(Exception e) {
log.error("Unexpected error occurred", e);
return ErrorResponse.builder()
.code("INTERNAL_ERROR")
.message("An unexpected error occurred")
.timestamp(LocalDateTime.now())
.build();
}
}

场景3:文件上传处理

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
@RestController
@RequestMapping("/api/files")
public class FileController {

@PostMapping("/upload")
public ResponseEntity<FileUploadResponse> uploadFile(
@RequestPart("file") MultipartFile file,
@RequestParam(required = false) String description) {

// 文件类型验证
if (!isValidFileType(file.getContentType())) {
throw new InvalidFileTypeException("Invalid file type");
}

// 文件大小验证
if (file.getSize() > MAX_FILE_SIZE) {
throw new FileSizeExceededException("File size exceeds limit");
}

String fileName = fileService.saveFile(file, description);

FileUploadResponse response = FileUploadResponse.builder()
.fileName(fileName)
.originalName(file.getOriginalFilename())
.size(file.getSize())
.contentType(file.getContentType())
.uploadTime(LocalDateTime.now())
.build();

return ResponseEntity.ok(response);
}
}

3.4 Spring MVC面试问题

Q1: Spring MVC的执行流程是怎样的?

A: Spring MVC的执行流程如下:

  1. 用户发送请求到DispatcherServlet
  2. DispatcherServlet调用HandlerMapping查找Handler
  3. HandlerMapping返回HandlerExecutionChain(包含Handler和拦截器)
  4. DispatcherServlet调用HandlerAdapter执行Handler
  5. Handler执行完成后返回ModelAndView
  6. DispatcherServlet调用ViewResolver解析视图名称
  7. ViewResolver返回View对象
  8. DispatcherServlet调用View的render方法渲染视图
  9. 响应结果返回给用户

Q2: Spring MVC中的拦截器是如何工作的?

A: 拦截器基于AOP思想,在Handler执行前后进行处理:

  1. HandlerInterceptor接口提供三个方法:
    • preHandle(): 前置处理,返回false则中断请求
    • postHandle(): 后置处理,Handler执行后调用
    • afterCompletion(): 完成处理,视图渲染后调用
  2. 执行顺序
    • 多个拦截器按配置顺序执行preHandle()
    • 按相反顺序执行postHandle()和afterCompletion()

Q3: @RequestBody和@ResponseBody的工作原理?

A: 这两个注解基于HttpMessageConverter工作:

  1. @RequestBody:
    • 使用HttpMessageConverter将HTTP请求体转换为Java对象
    • 常用的转换器:MappingJackson2HttpMessageConverter处理JSON
  2. @ResponseBody:
    • 使用HttpMessageConverter将Java对象转换为HTTP响应体
    • 根据Accept头选择合适的转换器

四、MyBatis深入解析

4.1 MyBatis核心原理

MyBatis架构分析: MyBatis采用分层架构设计:

  1. API接口层: 提供给外部使用的接口API
  2. 数据处理层: 参数映射、SQL解析、结果映射
  3. 基础支撑层: 连接管理、事务管理、配置加载、缓存处理

MyBatis核心组件:

  • SqlSessionFactory: 会话工厂,负责创建SqlSession
  • SqlSession: 会话,执行SQL的核心接口
  • Executor: 执行器,实际执行SQL
  • StatementHandler: 语句处理器,处理SQL语句
  • ParameterHandler: 参数处理器,处理SQL参数
  • ResultSetHandler: 结果集处理器,处理查询结果
  • MappedStatement: 映射语句,封装SQL配置信息

4.2 MyBatis执行流程详解

SQL执行流程:

  1. 解析配置: 解析mybatis-config.xml和Mapper XML文件
  2. 创建会话: 通过SqlSessionFactory创建SqlSession
  3. 获取Mapper: 通过动态代理创建Mapper接口实例
  4. 执行SQL: 调用Mapper方法,转换为MappedStatement执行
  5. 参数处理: ParameterHandler处理输入参数
  6. 执行查询: StatementHandler执行SQL语句
  7. 结果映射: ResultSetHandler处理结果集
  8. 返回结果: 将结果返回给调用方

动态代理原理: MyBatis使用JDK动态代理为Mapper接口创建代理对象:

1
2
3
4
5
6
7
8
// MapperProxy实现InvocationHandler
public class MapperProxy<T> implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 根据方法签名找到对应的MappedStatement
// 执行SQL并返回结果
}
}

4.3 MyBatis核心注解

基础映射注解

1
2
3
4
5
6
7
8
9
10
11
12
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);

@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

@Update("UPDATE users SET name = #{name} WHERE id = #{id}")
int update(User user);

@Delete("DELETE FROM users WHERE id = #{id}")
int delete(@Param("id") Long id);

高级映射注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 结果映射
@Results({
@Result(column = "user_id", property = "id"),
@Result(column = "user_name", property = "name"),
@Result(column = "create_time", property = "createTime")
})
@Select("SELECT user_id, user_name, create_time FROM users")
List<User> findAll();

// 一对一映射
@One(select = "findUserById")
@Result(column = "user_id", property = "user")
Order findOrderById(@Param("id") Long id);

// 一对多映射
@Many(select = "findOrdersByUserId")
@Result(column = "id", property = "orders")
User findUserWithOrders(@Param("id") Long id);

动态SQL注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SelectProvider(type = UserSqlProvider.class, method = "findUsers")
List<User> findUsers(@Param("name") String name, @Param("email") String email);

public class UserSqlProvider {
public String findUsers(Map<String, Object> params) {
return new SQL() {{
SELECT("*");
FROM("users");
if (params.get("name") != null) {
WHERE("name LIKE CONCAT('%', #{name}, '%')");
}
if (params.get("email") != null) {
WHERE("email = #{email}");
}
}}.toString();
}
}

4.4 MyBatis缓存机制

一级缓存(默认开启):

  • 作用域:SqlSession级别
  • 生命周期:与SqlSession相同
  • 存储:HashMap结构,key为CacheKey

二级缓存(需要配置):

  • 作用域:Mapper级别
  • 生命周期:与应用程序相同
  • 配置:@CacheNamespace注解或标签
1
2
3
4
5
6
7
8
9
@CacheNamespace(
eviction = LRU.class,
flushInterval = 60000,
size = 1024,
readWrite = true
)
public interface UserMapper {
// mapper methods
}

4.5 MyBatis实际应用场景

场景1:复杂查询场景

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
@Mapper
public interface OrderMapper {

// 分页查询订单
@Select("""
SELECT o.*, u.name as user_name, u.email as user_email
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = #{status}
AND o.create_time BETWEEN #{startTime} AND #{endTime}
ORDER BY o.create_time DESC
LIMIT #{offset}, #{limit}
""")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "user_id", property = "userId"),
@Result(column = "user_name", property = "user.name"),
@Result(column = "user_email", property = "user.email")
})
List<OrderVO> findOrdersWithUser(
@Param("status") String status,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@Param("offset") int offset,
@Param("limit") int limit
);

// 统计查询
@Select("""
SELECT
COUNT(*) as total_count,
SUM(amount) as total_amount,
AVG(amount) as avg_amount
FROM orders
WHERE status = #{status}
AND create_time >= #{startTime}
""")
OrderStatistics getOrderStatistics(
@Param("status") String status,
@Param("startTime") LocalDateTime startTime
);
}

场景2:批量操作场景

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
@Mapper
public interface BatchMapper {

// 批量插入
@Insert("""
<script>
INSERT INTO users (name, email, create_time) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.createTime})
</foreach>
</script>
""")
int batchInsertUsers(@Param("users") List<User> users);

// 批量更新
@Update("""
<script>
<foreach collection="users" item="user" separator=";">
UPDATE users SET
name = #{user.name},
email = #{user.email},
update_time = NOW()
WHERE id = #{user.id}
</foreach>
</script>
""")
int batchUpdateUsers(@Param("users") List<User> users);
}

场景3:动态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
46
47
48
49
50
51
52
53
54
55
56
@SelectProvider(type = UserSqlProvider.class, method = "searchUsers")
List<User> searchUsers(UserSearchCriteria criteria);

public class UserSqlProvider {

public String searchUsers(UserSearchCriteria criteria) {
return new SQL() {{
SELECT("u.*, p.name as profile_name");
FROM("users u");
LEFT_OUTER_JOIN("user_profiles p ON u.id = p.user_id");

if (StringUtils.hasText(criteria.getName())) {
WHERE("u.name LIKE CONCAT('%', #{name}, '%')");
}

if (StringUtils.hasText(criteria.getEmail())) {
WHERE("u.email = #{email}");
}

if (criteria.getMinAge() != null) {
WHERE("u.age >= #{minAge}");
}

if (criteria.getMaxAge() != null) {
WHERE("u.age <= #{maxAge}");
}

if (criteria.getCreateTimeStart() != null) {
WHERE("u.create_time >= #{createTimeStart}");
}

if (criteria.getCreateTimeEnd() != null) {
WHERE("u.create_time <= #{createTimeEnd}");
}

if (CollectionUtils.isNotEmpty(criteria.getStatuses())) {
WHERE("u.status IN (" +
criteria.getStatuses().stream()
.map(s -> "'" + s + "'")
.collect(Collectors.joining(",")) + ")");
}

// 排序
if (StringUtils.hasText(criteria.getSortField())) {
if ("desc".equalsIgnoreCase(criteria.getSortDirection())) {
ORDER_BY("u." + criteria.getSortField() + " DESC");
} else {
ORDER_BY("u." + criteria.getSortField() + " ASC");
}
} else {
ORDER_BY("u.create_time DESC");
}

}}.toString();
}
}

4.6 MyBatis性能优化

优化策略1:合理使用缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 开启二级缓存,设置合理的缓存策略
@CacheNamespace(
eviction = LRU.class, // 缓存回收策略
flushInterval = 300000, // 缓存刷新间隔(5分钟)
size = 1024, // 缓存大小
readWrite = true, // 读写缓存
blocking = false // 非阻塞缓存
)
public interface ProductMapper {

// 对于频繁查询且变化不大的数据使用缓存
@Select("SELECT * FROM products WHERE category_id = #{categoryId}")
@Options(useCache = true)
List<Product> findByCategory(@Param("categoryId") Long categoryId);

// 对于实时性要求高的数据禁用缓存
@Select("SELECT * FROM products WHERE id = #{id}")
@Options(useCache = false)
Product findRealTimeById(@Param("id") Long id);
}

优化策略2:批量操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用批量插入替代循环插入
@Insert("""
<script>
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES
<foreach collection="items" item="item" separator=",">
(#{item.orderId}, #{item.productId}, #{item.quantity}, #{item.price})
</foreach>
</script>
""")
int batchInsertOrderItems(@Param("items") List<OrderItem> items);

// 批量更新优化
public void batchUpdateProducts(List<Product> products) {
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
for (Product product : products) {
mapper.updateProduct(product);
}
sqlSession.commit();
}
}

优化策略3:延迟加载

1
2
3
4
5
6
7
8
9
10
11
12
13
@ResultMap("userResultMap")
@Select("SELECT * FROM users WHERE id = #{id}")
User findUserById(@Param("id") Long id);

// 在ResultMap中配置延迟加载
<resultMap id="userResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="orders"
select="findOrdersByUserId"
column="id"
fetchType="lazy"/>
</resultMap>

4.7 MyBatis常见问题与解决方案

问题1:N+1查询问题

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
// 问题:会产生N+1次查询
@Select("SELECT * FROM users")
List<User> findAllUsers();

@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> findOrdersByUserId(@Param("userId") Long userId);

// 解决方案1:使用联表查询
@Select("""
SELECT u.*, o.id as order_id, o.amount, o.status as order_status
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
""")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "order_id", property = "orders.id"),
@Result(column = "amount", property = "orders.amount"),
@Result(column = "order_status", property = "orders.status")
})
List<User> findUsersWithOrders();

// 解决方案2:使用批量查询
public List<User> findUsersWithOrdersOptimized(List<Long> userIds) {
List<User> users = userMapper.findUsersByIds(userIds);
if (!users.isEmpty()) {
List<Long> ids = users.stream().map(User::getId).collect(Collectors.toList());
List<Order> orders = orderMapper.findOrdersByUserIds(ids);

// 手动组装数据
Map<Long, List<Order>> orderMap = orders.stream()
.collect(Collectors.groupingBy(Order::getUserId));

users.forEach(user -> user.setOrders(orderMap.get(user.getId())));
}
return users;
}

问题2:大数据量查询内存溢出

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
// 问题:一次性加载大量数据导致内存溢出
@Select("SELECT * FROM large_table")
List<LargeData> findAllData(); // 可能导致OOM

// 解决方案1:分页查询
public List<LargeData> findAllDataWithPaging() {
List<LargeData> allData = new ArrayList<>();
int pageSize = 1000;
int offset = 0;

List<LargeData> pageData;
do {
pageData = mapper.findDataWithLimit(offset, pageSize);
allData.addAll(pageData);
offset += pageSize;
} while (pageData.size() == pageSize);

return allData;
}

// 解决方案2:使用游标查询
@Select("SELECT * FROM large_table WHERE process_status = 'PENDING'")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
Cursor<LargeData> findPendingDataCursor();

public void processLargeData() {
try (Cursor<LargeData> cursor = mapper.findPendingDataCursor()) {
cursor.forEach(this::processData);
}
}

问题3: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
// 危险:直接拼接SQL,容易SQL注入
@Select("SELECT * FROM users WHERE name = '${name}'") // 错误示例
List<User> findByNameUnsafe(@Param("name") String name);

// 安全:使用参数绑定
@Select("SELECT * FROM users WHERE name = #{name}")
List<User> findByNameSafe(@Param("name") String name);

// 动态排序的安全处理
@SelectProvider(type = UserSqlProvider.class, method = "findUsersWithSort")
List<User> findUsersWithSort(@Param("sortField") String sortField,
@Param("sortDirection") String sortDirection);

public class UserSqlProvider {
private static final Set<String> ALLOWED_SORT_FIELDS =
Set.of("id", "name", "email", "create_time", "update_time");

public String findUsersWithSort(Map<String, Object> params) {
String sortField = (String) params.get("sortField");
String sortDirection = (String) params.get("sortDirection");

// 白名单验证
if (!ALLOWED_SORT_FIELDS.contains(sortField)) {
sortField = "id";
}

if (!"DESC".equalsIgnoreCase(sortDirection)) {
sortDirection = "ASC";
}

return "SELECT * FROM users ORDER BY " + sortField + " " + sortDirection;
}
}

4.8 MyBatis面试高频问题

Q1: MyBatis的执行流程是怎样的?

A: MyBatis的执行流程包括以下步骤:

  1. 配置解析: 解析mybatis-config.xml配置文件和Mapper XML文件,创建Configuration对象
  2. SqlSessionFactory创建: 根据Configuration创建SqlSessionFactory
  3. SqlSession创建: 通过SqlSessionFactory.openSession()创建SqlSession
  4. Mapper获取: 通过SqlSession.getMapper()获取Mapper接口的代理对象
  5. 方法调用: 调用Mapper接口方法,通过动态代理转换为SQL执行
  6. SQL执行: 通过Executor执行SQL,包括参数处理、语句执行、结果映射
  7. 结果返回: 将执行结果返回给调用方

Q2: MyBatis的一级缓存和二级缓存有什么区别?

A: 两级缓存的主要区别:

一级缓存(默认开启):

  • 作用域:SqlSession级别
  • 生命周期:与SqlSession相同,SqlSession关闭时缓存清空
  • 存储结构:HashMap,key为CacheKey(由SQL、参数、分页等组成)
  • 失效条件:执行update、insert、delete操作或手动清空

二级缓存(需要配置):

  • 作用域:Mapper级别,多个SqlSession可以共享
  • 生命周期:与应用程序相同
  • 存储结构:可配置(HashMap、LRU、FIFO等)
  • 配置方式:@CacheNamespace注解或标签
  • 注意事项:需要序列化,可能存在脏读问题

Q3: MyBatis如何防止SQL注入?

A: MyBatis通过以下方式防止SQL注入:

  1. 参数绑定: 使用#{}而不是${}
    • #{}:预编译处理,参数作为占位符传递
    • ${}:字符串替换,直接拼接到SQL中(危险)
  2. 类型检查: MyBatis会对参数类型进行检查
  3. 白名单验证: 对于动态排序等场景,使用白名单验证
1
2
3
4
5
6
7
// 安全的写法
@Select("SELECT * FROM users WHERE name = #{name} AND age > #{age}")
List<User> findUsers(@Param("name") String name, @Param("age") Integer age);

// 危险的写法(避免使用)
@Select("SELECT * FROM users WHERE name = '${name}'")
List<User> findUsersUnsafe(@Param("name") String name);

Q4: MyBatis中#{}和${}的区别?

A: 两者的主要区别:

#{}(推荐使用):

  • 预编译处理,生成PreparedStatement
  • 参数会被处理为占位符?
  • 可以防止SQL注入
  • 会进行类型转换
  • 适用于参数值传递

${}(谨慎使用):

  • 字符串替换,直接拼接到SQL中
  • 不会进行预编译
  • 存在SQL注入风险
  • 不会进行类型转换
  • 适用于动态表名、列名等场景

Q5: MyBatis的动态SQL是如何实现的?

A: MyBatis的动态SQL通过以下标签实现:

  1. if标签: 条件判断
  2. choose/when/otherwise: 类似switch-case
  3. where标签: 智能处理WHERE条件
  4. set标签: 智能处理SET语句
  5. foreach标签: 循环处理
  6. trim标签: 去除多余的字符

实现原理:

  • 使用OGNL表达式进行条件判断
  • 在SQL解析阶段根据参数值动态生成SQL
  • 通过SqlNode树结构表示动态SQL
  • 在执行时遍历SqlNode树生成最终SQL

五、框架整合与最佳实践

5.1 Spring Boot + MyBatis整合

完整配置示例

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
46
47
// 主启动类
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// 数据源配置
@Configuration
public class DataSourceConfig {

@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}

@Bean
@Primary
public DataSource primaryDataSource() {
return primaryDataSourceProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);

// MyBatis配置
org.apache.ibatis.session.Configuration config =
new org.apache.ibatis.session.Configuration();
config.setMapUnderscoreToCamelCase(true);
config.setLogImpl(Slf4jImpl.class);
config.setCacheEnabled(true);
config.setLazyLoadingEnabled(true);
config.setAggressiveLazyLoading(false);

factory.setConfiguration(config);
return factory.getObject();
}
}

事务管理配置

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@Service
@Transactional
public class OrderService {

@Autowired
private OrderMapper orderMapper;

@Autowired
private OrderItemMapper orderItemMapper;

@Autowired
private ProductMapper productMapper;

@Transactional(rollbackFor = Exception.class)
public Order createOrder(CreateOrderRequest request) {
// 1. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setStatus("PENDING");
order.setCreateTime(LocalDateTime.now());
orderMapper.insert(order);

// 2. 创建订单项
BigDecimal totalAmount = BigDecimal.ZERO;
List<OrderItem> orderItems = new ArrayList<>();

for (CreateOrderItemRequest itemRequest : request.getItems()) {
// 检查库存
Product product = productMapper.findById(itemRequest.getProductId());
if (product == null) {
throw new ProductNotFoundException("Product not found: " + itemRequest.getProductId());
}

if (product.getStock() < itemRequest.getQuantity()) {
throw new InsufficientStockException("Insufficient stock for product: " + product.getName());
}

// 减库存
productMapper.decreaseStock(itemRequest.getProductId(), itemRequest.getQuantity());

// 创建订单项
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(itemRequest.getProductId());
orderItem.setQuantity(itemRequest.getQuantity());
orderItem.setPrice(product.getPrice());
orderItems.add(orderItem);

totalAmount = totalAmount.add(
product.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity()))
);
}

// 3. 批量插入订单项
if (!orderItems.isEmpty()) {
orderItemMapper.batchInsert(orderItems);
}

// 4. 更新订单总金额
order.setTotalAmount(totalAmount);
orderMapper.updateAmount(order.getId(), totalAmount);

return order;
}

@Transactional(readOnly = true)
public OrderDetailVO getOrderDetail(Long orderId) {
Order order = orderMapper.findById(orderId);
if (order == null) {
throw new OrderNotFoundException("Order not found: " + orderId);
}

List<OrderItem> orderItems = orderItemMapper.findByOrderId(orderId);

return OrderDetailVO.builder()
.order(order)
.items(orderItems)
.build();
}
}

5.2 统一异常处理

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
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(ValidationException e) {
log.warn("Validation error: {}", e.getMessage());
return ApiResponse.error("VALIDATION_ERROR", e.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());

return ApiResponse.error("VALIDATION_ERROR", String.join(", ", errors));
}

@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ApiResponse<Void> handleDataIntegrityViolation(DataIntegrityViolationException e) {
log.error("Data integrity violation", e);
return ApiResponse.error("DATA_CONFLICT", "Data conflict occurred");
}

@ExceptionHandler(OptimisticLockingFailureException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ApiResponse<Void> handleOptimisticLockingFailure(OptimisticLockingFailureException e) {
log.warn("Optimistic locking failure: {}", e.getMessage());
return ApiResponse.error("OPTIMISTIC_LOCK_ERROR", "Resource has been modified by another user");
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleGenericException(Exception e) {
log.error("Unexpected error occurred", e);
return ApiResponse.error("INTERNAL_ERROR", "An unexpected error occurred");
}
}

5.3 接口文档与验证

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 统一响应格式
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String code;
private String message;
private T data;
private Long timestamp;

public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.success(true)
.code("SUCCESS")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}

public static <T> ApiResponse<T> error(String code, String message) {
return ApiResponse.<T>builder()
.success(false)
.code(code)
.message(message)
.timestamp(System.currentTimeMillis())
.build();
}
}

// 请求参数验证
@Data
@Valid
public class CreateUserRequest {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_\\u4e00-\\u9fa5]+$", message = "用户名只能包含字母、数字、下划线和中文")
private String username;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度必须在8-20个字符之间")
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
message = "密码必须包含大小写字母、数字和特殊字符"
)
private String password;

@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;

@NotEmpty(message = "角色不能为空")
private List<@NotBlank(message = "角色名称不能为空") String> roles;
}

5.4 性能监控与优化

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 性能监控切面
@Aspect
@Component
@Slf4j
public class PerformanceMonitorAspect {

@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();

try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;

if (executionTime > 1000) { // 超过1秒记录警告
log.warn("Slow API detected: {} took {}ms", methodName, executionTime);
} else {
log.info("API performance: {} took {}ms", methodName, executionTime);
}

return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
log.error("API error: {} took {}ms, error: {}",
methodName, endTime - startTime, e.getMessage());
throw e;
}
}
}

// 数据库连接池监控
@Component
public class DataSourceHealthIndicator implements HealthIndicator {

@Autowired
private DataSource dataSource;

@Override
public Health health() {
try {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariPoolMXBean poolBean = hikariDataSource.getHikariPoolMXBean();

return Health.up()
.withDetail("database", "MySQL")
.withDetail("activeConnections", poolBean.getActiveConnections())
.withDetail("idleConnections", poolBean.getIdleConnections())
.withDetail("totalConnections", poolBean.getTotalConnections())
.withDetail("threadsAwaitingConnection", poolBean.getThreadsAwaitingConnection())
.build();
}

try (Connection connection = dataSource.getConnection()) {
return Health.up()
.withDetail("database", connection.getMetaData().getDatabaseProductName())
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}

5.5 高频面试综合题

Q1: 在微服务架构中,如何设计一个高并发的订单系统?

A: 设计高并发订单系统需要考虑以下几个方面:

1. 架构设计:

  • 使用分布式架构,订单服务、库存服务、支付服务分离
  • 引入消息队列处理异步任务
  • 使用Redis做缓存和分布式锁
  • 数据库读写分离,分库分表

2. 并发控制:

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
@Service
public class OrderService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Transactional(rollbackFor = Exception.class)
public Order createOrder(CreateOrderRequest request) {
String lockKey = "order:lock:" + request.getUserId();

// 分布式锁防止重复下单
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(30));

if (!locked) {
throw new OrderCreationException("Order creation in progress");
}

try {
// 订单创建逻辑
return processOrder(request);
} finally {
redisTemplate.delete(lockKey);
}
}
}

3. 性能优化:

  • 使用批量操作减少数据库交互
  • 合理使用缓存策略
  • 异步处理非核心业务逻辑
  • 数据库连接池优化

Q2: 如何处理分布式事务?

A: 分布式事务处理方案:

1. 2PC/3PC协议:

  • 强一致性,但性能较差
  • 适用于对一致性要求极高的场景

2. TCC模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@TccTransaction
public class OrderTccService {

public void tryCreateOrder(CreateOrderRequest request) {
// Try阶段:预留资源
orderService.reserveOrder(request);
stockService.reserveStock(request.getItems());
paymentService.reservePayment(request.getPaymentInfo());
}

public void confirmCreateOrder(CreateOrderRequest request) {
// Confirm阶段:确认操作
orderService.confirmOrder(request);
stockService.confirmStock(request.getItems());
paymentService.confirmPayment(request.getPaymentInfo());
}

public void cancelCreateOrder(CreateOrderRequest request) {
// Cancel阶段:回滚操作
orderService.cancelOrder(request);
stockService.cancelStock(request.getItems());
paymentService.cancelPayment(request.getPaymentInfo());
}
}

3. 消息队列最终一致性:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderEventService {

@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
// 异步处理订单相关业务
notificationService.sendOrderNotification(event.getOrder());
inventoryService.updateInventory(event.getOrderItems());
pointsService.addPoints(event.getUserId(), event.getAmount());
}
}

Q3: Spring Boot应用如何优化启动速度?

A: Spring Boot启动优化策略:

1. 依赖优化:

  • 移除不必要的依赖
  • 使用spring-boot-starter-web替代完整的spring-web
  • 避免引入大量自动配置类

2. 配置优化:

1
2
3
4
5
6
7
8
9
10
spring:
main:
lazy-initialization: true # 启用懒加载
jpa:
hibernate:
ddl-auto: none # 禁用DDL自动生成
show-sql: false # 禁用SQL日志
devtools:
restart:
enabled: false # 生产环境禁用热重启

3. JVM优化:

1
2
3
4
5
6
java -XX:+UnlockExperimentalVMOptions 
-XX:+UseZGC
-XX:+UseTransparentHugePages
-XX:MaxMetaspaceSize=256m
-Xms512m -Xmx1024m
-jar application.jar

4. 代码优化:

  • 使用@Lazy注解延迟Bean初始化
  • 避免在@PostConstruct中执行耗时操作
  • 合理使用@ConditionalOn*注解

好的,这是一份详细的解释,我们来逐个分析这些 Java 虚拟机(JVM)参数:

Bash

1
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:+UseTransparentHugePages -XX:MaxMetaspaceSize=256m -Xms512m -Xmx1024m -jar application.jar
  • java: 这是启动 Java 程序的命令。

内存管理和垃圾回收

这部分参数主要用于优化 JVM 的内存使用和垃圾回收机制,以获得更好的性能。

  • -XX:+UnlockExperimentalVMOptions: 这个参数是用来解锁实验性(experimental)的 JVM 选项。ZGC 曾经是实验性功能,所以需要这个参数才能使用。
  • -XX:+UseZGC: 这条命令是启用 Z 垃圾回收器(Z Garbage Collector)。ZGC 是一种为超大堆内存(从几GB到几十TB)设计的、低延迟的垃圾回收器。它的主要目标是在垃圾回收时,尽量减少应用程序的停顿时间,这对于需要极低延迟的应用程序非常重要。
  • -XX:+UseTransparentHugePages: 这个参数开启了透明大页(Transparent Huge Pages)的支持。在操作系统层面,通常内存是以 4KB 的小页来管理的。而大页(通常是 2MB 或 1GB)可以减少 CPU 在内存管理上的开销,从而提升性能,尤其对于内存占用较大的应用效果更明显。
  • -XX:MaxMetaspaceSize=256m: 这个参数设置了元空间(Metaspace)的最大大小为 256MB。元空间是 JVM 存储类的元数据(如类的名称、方法、字段等信息)的地方。在 Java 8 之后,它取代了之前的“永久代”(PermGen)。
  • -Xms512m: 这条命令设定了 JVM 堆的初始内存为 512MB。堆是用来存放 Java 对象的地方。-Xms 决定了 JVM 启动时会申请的最小内存。
  • -Xmx1024m: 这条命令设定了 JVM 堆的最大内存为 1024MB(即 1GB)。这是 JVM 运行时可以使用的最大内存。当堆内存用尽时,JVM 会触发垃圾回收,如果依然不足,就会抛出 OutOfMemoryError

好的,我们来更详细地聊聊 JVM 堆内存的结构,特别是分代设计和具体的划分比例。


堆内存的传统分代结构

在绝大多数的垃圾回收器中(例如 CMS、G1 之前的串行和并行 GC),堆内存被分为以下几个代(Generation):

1. 年轻代(Young Generation)

年轻代是新对象的诞生地。 绝大多数对象,比如局部变量创建的临时对象,都会在年轻代被创建。年轻代被设计成较小且垃圾回收频率高的区域。

  • Eden 区:这是年轻代的主要部分,新创建的对象首先被分配到这里。
  • Survivor 区(幸存者区):有两个大小相等的 Survivor 区,通常命名为 S0S1。它们的作用是保存每次垃圾回收后,还存活的对象。

划分比例

在默认情况下,年轻代内部的划分比例通常是:

  • Eden 区 : S0 区 : S1 区 = 8 : 1 : 1

这个比例可以通过 JVM 参数进行调整,比如使用 -XX:SurvivorRatio=8。这意味着 Eden 区的大小是单个 Survivor 区的 8 倍。

2. 老年代(Old Generation)

老年代用于存放生命周期较长的对象。 当一个对象在年轻代经过多次垃圾回收(通常是 15 次,这个次数也可以通过 -XX:MaxTenuringThreshold 参数调整)后仍然存活,或者年轻代放不下的大对象,就会被“晋升”到老年代。

老年代的垃圾回收频率远低于年轻代,但每次回收的开销更大。

划分比例

堆内存中,年轻代和老年代的默认划分比例通常是:

  • 年轻代 : 老年代 = 1 : 2

例如,如果你设置 -Xms1200m -Xmx1200m,那么年轻代大约是 400MB,老年代大约是 800MB。这个比例可以通过 -XX:NewRatio 参数来调整。例如,-XX:NewRatio=2 表示老年代与年轻代的大小比值为 2:1。


垃圾回收过程(以 Minor GC 为例)

理解了这些区域,我们再来看看垃圾回收是怎么进行的:

  1. Eden 区满了,会触发一次 Minor GC
  2. Minor GC 会检查 Eden 区和其中一个 Survivor 区(比如 S0)。
  3. 它会将所有还存活的对象复制到另一个空的 Survivor 区(比如 S1)。
  4. 同时,那些不再被引用的对象则会被清理。
  5. 所有被复制到 S1 区的对象,它们的年龄(age)会加一。
  6. 下一次 Minor GC 发生时,同样会扫描 Eden 区和 S1 区,将存活对象复制到 S0 区,清空 Eden 和 S1 区,并增加对象的年龄。
  7. 当对象的年龄达到某个阈值时,它就会被晋升到老年代。

现代垃圾回收器(如 G1 和 ZGC)

值得注意的是,像 G1 垃圾回收器已经打破了这种严格的分代比例。它将堆划分为一个个大小相等的区域(Region),每个区域都可以是 Eden、Survivor 或者老年代。G1 能够更智能地选择要回收的区域,从而在保证低停顿的同时,提高吞吐量。

ZGC 则更进一步,如我之前所说,它完全没有年轻代、老年代的概念,而是通过着色指针和读屏障技术,在不中断应用程序的情况下并发地进行垃圾回收,实现了更低的停顿。

因此,当你使用 ZGC 时,传统的分代比例就不适用了。不过,了解传统的分代结构对于理解 Java 内存管理的基础依然非常重要。

以下是针对Spring Cloud Alibaba及其他分布式技术的深度补充,包含实现原理和核心组件的详细说明:


六、Spring Cloud与分布式技术详解

6.1 Spring Cloud Alibaba核心组件

Nacos深度解析

服务发现原理

  1. 注册流程

    • 服务启动时向Nacos Server发送注册请求(HTTP/GRPC)
    • 注册信息包含元数据、健康检查方式(TCP/HTTP/MySQL)
    • 客户端本地缓存服务列表(故障转移)
  2. 健康检查机制

    1
    2
    3
    4
    5
    6
    7
    8
    // Nacos健康检查配置示例
    spring:
    cloud:
    nacos:
    discovery:
    health-check-enabled: true
    health-check-interval: 10s
    health-check-timeout: 5s

配置中心实现

  • 长轮询机制(Push+Pull混合模式):
    • 客户端发起长轮询请求(默认30秒超时)
    • 服务端配置变更时立即响应
    • 客户端收到变更后拉取最新配置
1
2
3
4
5
6
7
// 动态配置刷新示例
@RefreshScope
@RestController
public class ConfigController {
@Value("${app.config.item}")
private String configItem;
}

Sentinel核心原理

流量控制规则

  • 滑动窗口算法
    • 统计周期(1秒)分为多个格子(如20个50ms)
    • 实时淘汰过期格子数据
    • QPS计算基于当前窗口总请求数

熔断降级策略

策略类型 计算公式 适用场景
慢调用比例 响应时间 > RT阈值 && 比例 > 阈值 接口性能波动
异常比例 异常数 / 请求数 > 阈值 依赖服务不稳定
异常数 异常数 > 阈值(时间窗口内) 关键业务熔断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Sentinel规则持久化到Nacos示例
@PostConstruct
public void initRules() {
FlowRuleManager.register2Property(
new NacosDataSourceWrapper(
"nacos-server:8848", "sentinel-flow-rules",
new Converter<String, List<FlowRule>>() {
@Override
public List<FlowRule> convert(String source) {
return JSON.parseArray(source, FlowRule.class);
}
}
)
);
}

RocketMQ集成

消息轨迹追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 生产者配置
@Bean
public RocketMQTemplate rocketMQTemplate() {
RocketMQTemplate template = new RocketMQTemplate();
template.setProducer(new DefaultMQProducer("producer_group") {{
setVipChannelEnabled(false);
setTraceDispatcher(new AsyncTraceDispatcher("trace_topic"));
}});
return template;
}

// 消费者轨迹追踪
@RocketMQMessageListener(
topic = "order_topic",
consumerGroup = "order_consumer",
enableMsgTrace = true
)
public class OrderConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 处理逻辑
}
}

6.2 分布式事务增强(Seata原理)

AT模式工作流程

  1. 一阶段

    • 解析SQL生成前后镜像
    • 注册分支事务到TC(Transaction Coordinator)
    • 本地事务提交前记录undo_log
  2. 二阶段

    • 成功:异步删除undo_log
    • 失败:根据undo_log补偿(反向SQL)

undo_log表示例

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
);

TCC模式最佳实践

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
// 账户服务TCC接口
public interface AccountTccService {
@TwoPhaseBusinessAction(
name = "deduct",
commitMethod = "confirmDeduct",
rollbackMethod = "cancelDeduct"
)
boolean prepareDeduct(
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount
);

boolean confirmDeduct(BusinessActionContext context);

boolean cancelDeduct(BusinessActionContext context);
}

// 业务调用方
@GlobalTransactional
public void placeOrder(Order order) {
// 1. 冻结库存
inventoryTccService.prepare(null, order.getProductId(), order.getCount());

// 2. 预扣款
accountTccService.prepareDeduct(order.getUserId(), order.getAmount());

// 3. 创建订单
orderMapper.insert(order);
}

6.3 分布式缓存深度优化

Redis多级缓存架构

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
// 本地缓存+Caffeine+Redis实现
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return new CaffeineRedisCacheManager(
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000),
RedisCacheWriter.nonLockingRedisCacheWriter(factory),
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
);
}

// 热点Key探测
public <T> T getWithHotspotProtection(String key, Class<T> type) {
// 1. 本地缓存查询
T value = localCache.getIfPresent(key);
if (value != null) return value;

// 2. Redis集群查询(带随机过期时间)
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
return value;
}

// 3. 分布式锁防击穿
String lockKey = "lock:" + key;
try {
if (redisLock.tryLock(lockKey, 3, 30)) {
// 4. 数据库查询
value = databaseLoader.load(key);
redisTemplate.opsForValue().set(
key, value,
ThreadLocalRandom.current().nextInt(30) + 30,
TimeUnit.MINUTES
);
localCache.put(key, value);
}
} finally {
redisLock.unlock(lockKey);
}
return value;
}

6.4 消息队列高级特性

RocketMQ事务消息

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
// 事务消息生产者
public void sendTransactionMessage(Order order) {
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
"order-tx-group",
MessageBuilder.withPayload(order)
.setHeader(RocketMQHeaders.TRANSACTION_ID, order.getId())
.build(),
order
);
}

// 本地事务执行器
@RocketMQTransactionListener(txProducerGroup = "order-tx-group")
public class OrderTransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
Order order = (Order) arg;
orderService.createOrder(order); // 本地事务
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}

@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String orderId = msg.getHeaders().get("TRANSACTION_ID").toString();
return orderService.exists(orderId) ?
RocketMQLocalTransactionState.COMMIT :
RocketMQLocalTransactionState.ROLLBACK;
}
}

Kafka精确一次语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 生产者配置
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configs = new HashMap<>();
configs.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
configs.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "tx-producer-1");
return new DefaultKafkaProducerFactory<>(configs);
}

// 消费者配置
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> configs = new HashMap<>();
configs.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");
return new DefaultKafkaConsumerFactory<>(configs);
}

// 事务性消费
@KafkaListener(topics = "order-topic")
@Transactional
public void processOrder(ConsumerRecord<String, String> record) {
OrderEvent event = parseEvent(record.value());
orderService.process(event); // 数据库操作
}

七、云原生支持(新增)

7.1 Kubernetes集成方案

服务发现适配

1
2
3
4
5
6
7
8
9
10
# Nacos与K8s Service集成
spring:
cloud:
kubernetes:
discovery:
all-namespaces: true
nacos:
discovery:
server-addr: ${NACOS_HOST:nacos-headless}:${NACOS_PORT:8848}
namespace: ${POD_NAMESPACE:default}

配置管理方案

1
2
3
4
5
6
7
8
9
10
11
// 多配置源加载(K8s ConfigMap + Nacos)
@Configuration
@ConfigurationProperties(prefix = "app")
@RefreshScope
public class AppConfig {
@Value("${configFromNacos}")
private String nacosConfig;

@Value("${configFromK8s}")
private String k8sConfig;
}

7.2 Service Mesh整合

Istio流量治理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 金丝雀发布策略
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10

八、性能优化深度实践

8.1 全链路压测方案

影子库表配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ShardingSphere影子库配置
spring:
shardingsphere:
datasource:
names: ds-real,ds-shadow
rules:
shadow:
enable: true
data-sources:
shadow-data-source:
source-data-source-name: ds-real
shadow-data-source-name: ds-shadow
tables:
t_order:
shadow-algorithm-names: [simple-hint-algorithm]

流量染色标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 通过ThreadLocal传递压测标记
public class PressureTestContext {
private static final ThreadLocal<Boolean> FLAG = ThreadLocal.withInitial(() -> false);

public static void markPressureTest() {
FLAG.set(true);
}

public static boolean isPressureTest() {
return FLAG.get();
}
}

// MyBatis拦截器自动路由
@Intercepts(@Signature(type=Executor.class, method="update", args={MappedStatement.class,Object.class}))
public class ShadowDbInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (PressureTestContext.isPressureTest()) {
RoutingContext.setShadowDataSource();
}
return invocation.proceed();
}
}