Java后端开发SSM框架原理必考题
SSM框架原理必考题
学习目标
- 深入理解Spring IOC容器的工作原理和Bean生命周期
- 掌握Spring AOP的实现原理和应用场景
- 理解Spring事务管理的原理和传播行为
- 掌握SpringMVC的请求处理流程
- 理解MyBatis的工作原理和缓存机制
- 能够在面试中清晰准确地回答SSM框架相关问题
一、Spring IOC容器原理(15-20题)
1.1 什么是IOC(控制反转)?
标准答案:
IOC(Inversion of Control,控制反转)是一种设计思想,将对象的创建、依赖关系的管理从程序代码中剥离出来,交给IOC容器来管理。
核心要点:
- 控制反转:对象的创建控制权从程序代码转移到容器
- 依赖注入:容器负责将依赖对象注入到目标对象中
- 解耦合:降低组件之间的耦合度,提高代码的可维护性
- 配置化:通过配置文件或注解来管理对象
代码示例:
// 传统方式:程序主动创建对象
public class UserService {
// 程序主动创建依赖对象
private UserDao userDao = new UserDaoImpl();
public User getUser(Long id) {
return userDao.findById(id);
}
}
// IOC方式:容器注入依赖对象
@Service
public class UserService {
// 容器负责注入依赖对象
@Autowired
private UserDao userDao;
public User getUser(Long id) {
return userDao.findById(id);
}
}
回答技巧:
- 先说定义,再说好处
- 举例说明传统方式和IOC方式的区别
- 强调解耦合和可维护性的优势
可能的追问:
- IOC和DI的区别是什么?
- Spring IOC容器的实现原理是什么?
1.2 什么是DI(依赖注入)?
标准答案:
DI(Dependency Injection,依赖注入)是IOC的一种实现方式,指容器在运行期间,动态地将依赖对象注入到目标对象中。
核心要点:
- 注入方式:构造器注入、Setter注入、字段注入
- 注入时机:容器启动时或首次使用时
- 注入对象:由容器管理的Bean对象
- 自动装配:通过@Autowired等注解自动注入
三种注入方式对比:
// 1. 构造器注入(推荐)
@Service
public class UserService {
private final UserDao userDao;
// 构造器注入,保证依赖不可变
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
// 2. Setter注入
@Service
public class UserService {
private UserDao userDao;
// Setter注入,依赖可选
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
// 3. 字段注入(不推荐)
@Service
public class UserService {
// 字段注入,简洁但不利于测试
@Autowired
private UserDao userDao;
}
回答技巧:
- 说明DI是IOC的实现方式
- 列举三种注入方式并说明优缺点
- 推荐使用构造器注入
可能的追问:
- 为什么推荐构造器注入?
- @Autowired和@Resource的区别?
1.3 Spring IOC容器的启动流程是什么?
标准答案:
Spring IOC容器的启动流程主要包括以下几个阶段:
核心要点:
- 加载配置:读取XML配置文件或扫描注解配置
- 解析BeanDefinition:将配置信息解析为BeanDefinition对象
- 注册BeanDefinition:将BeanDefinition注册到BeanFactory中
- 实例化Bean:根据BeanDefinition创建Bean实例
- 依赖注入:为Bean注入依赖对象
- 初始化Bean:调用初始化方法
- 容器就绪:容器启动完成,可以使用Bean
流程图:
┌─────────────────────────────────────────────────────────────┐
│ Spring IOC容器启动流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 加载配置文件/扫描注解 │
│ ↓ │
│ 2. 解析配置,生成BeanDefinition │
│ ↓ │
│ 3. 注册BeanDefinition到BeanFactory │
│ ↓ │
│ 4. 调用BeanFactoryPostProcessor │
│ ↓ │
│ 5. 实例化Bean(反射创建对象) │
│ ↓ │
│ 6. 属性赋值(依赖注入) │
│ ↓ │
│ 7. 调用Aware接口方法 │
│ ↓ │
│ 8. 调用BeanPostProcessor.postProcessBeforeInitialization │
│ ↓ │
│ 9. 调用初始化方法(@PostConstruct、InitializingBean) │
│ ↓ │
│ 10. 调用BeanPostProcessor.postProcessAfterInitialization │
│ ↓ │
│ 11. Bean就绪,可以使用 │
│ ↓ │
│ 12. 容器关闭时调用销毁方法 │
│ │
└─────────────────────────────────────────────────────────────┘
代码示例:
// ApplicationContext启动示例
public class SpringIOCDemo {
public static void main(String[] args) {
// 1. 创建IOC容器
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从容器中获取Bean
UserService userService = context.getBean(UserService.class);
// 3. 使用Bean
User user = userService.getUser(1L);
System.out.println(user);
}
}
回答技巧:
- 按照流程顺序逐步说明
- 重点说明BeanDefinition、实例化、依赖注入、初始化几个关键步骤
- 可以画图辅助说明
可能的追问:
- BeanDefinition是什么?
- BeanFactoryPostProcessor和BeanPostProcessor的区别?
- Bean的作用域有哪些?
1.4 Bean的生命周期是什么?
标准答案:
Bean的生命周期是指Bean从创建到销毁的整个过程,主要包括以下阶段:
核心要点:
- 实例化:通过反射创建Bean实例
- 属性赋值:注入依赖对象
- Aware接口回调:注入容器相关对象
- 初始化前处理:BeanPostProcessor前置处理
- 初始化:调用初始化方法
- 初始化后处理:BeanPostProcessor后置处理
- 使用:Bean可以正常使用
- 销毁:容器关闭时调用销毁方法
完整的生命周期代码示例:
@Component
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
// 1. 构造器(实例化)
public LifecycleBean() {
System.out.println("1. 构造器:创建Bean实例");
}
// 2. 属性赋值
@Autowired
public void setName(String name) {
this.name = name;
System.out.println("2. 属性赋值:注入依赖对象");
}
// 3. Aware接口回调
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware:注入Bean名称 = " + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("4. BeanFactoryAware:注入BeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext context) {
System.out.println("5. ApplicationContextAware:注入ApplicationContext");
}
// 4. 初始化前处理(BeanPostProcessor)
// 由容器自动调用,这里省略
// 5. 初始化方法
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct:初始化方法1");
}
@Override
public void afterPropertiesSet() {
System.out.println("7. InitializingBean:初始化方法2");
}
@Bean(initMethod = "init")
public void init() {
System.out.println("8. initMethod:初始化方法3");
}
// 6. 初始化后处理(BeanPostProcessor)
// 由容器自动调用,这里省略
// 7. Bean可以使用
public void doSomething() {
System.out.println("9. Bean正常使用中...");
}
// 8. 销毁方法
@PreDestroy
public void preDestroy() {
System.out.println("10. @PreDestroy:销毁方法1");
}
@Override
public void destroy() {
System.out.println("11. DisposableBean:销毁方法2");
}
@Bean(destroyMethod = "destroyMethod")
public void destroyMethod() {
System.out.println("12. destroyMethod:销毁方法3");
}
}
自定义BeanPostProcessor示例:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("BeanPostProcessor前置处理:" + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("BeanPostProcessor后置处理:" + beanName);
return bean;
}
}
生命周期流程图:
┌──────────────────────────────────────────────────────────────┐
│ Bean生命周期详细流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. 实例化Bean(反射调用构造器) │
│ ↓ │
│ 2. 属性赋值(依赖注入) │
│ ↓ │
│ 3. 调用BeanNameAware.setBeanName() │
│ ↓ │
│ 4. 调用BeanFactoryAware.setBeanFactory() │
│ ↓ │
│ 5. 调用ApplicationContextAware.setApplicationContext() │
│ ↓ │
│ 6. 调用BeanPostProcessor.postProcessBeforeInitialization() │
│ ↓ │
│ 7. 调用@PostConstruct标注的方法 │
│ ↓ │
│ 8. 调用InitializingBean.afterPropertiesSet() │
│ ↓ │
│ 9. 调用自定义的init-method方法 │
│ ↓ │
│ 10. 调用BeanPostProcessor.postProcessAfterInitialization() │
│ ↓ │
│ 11. Bean就绪,可以使用 │
│ ↓ │
│ 12. 容器关闭,调用@PreDestroy标注的方法 │
│ ↓ │
│ 13. 调用DisposableBean.destroy() │
│ ↓ │
│ 14. 调用自定义的destroy-method方法 │
│ │
└──────────────────────────────────────────────────────────────┘
回答技巧:
- 按照时间顺序说明各个阶段
- 重点说明初始化和销毁两个阶段
- 可以说明三种初始化方法的执行顺序
可能的追问:
- 三种初始化方法的执行顺序是什么?
- BeanPostProcessor的作用是什么?
- 如何自定义Bean的初始化和销毁方法?
1.5 Bean的作用域有哪些?
标准答案:
Spring支持6种Bean作用域,常用的有5种:
核心要点:
- singleton(单例):默认作用域,容器中只有一个Bean实例
- prototype(原型):每次获取Bean都创建新实例
- request:每个HTTP请求创建一个Bean实例(Web环境)
- session:每个HTTP Session创建一个Bean实例(Web环境)
- application:整个ServletContext生命周期内只有一个Bean实例
- websocket:每个WebSocket连接创建一个Bean实例
作用域对比表:
| 作用域 | 说明 | 使用场景 | 线程安全 |
|---|---|---|---|
| singleton | 单例,容器中只有一个实例 | 无状态Bean(Service、Dao) | 需要考虑 |
| prototype | 原型,每次获取创建新实例 | 有状态Bean(Action) | 天然线程安全 |
| request | 每个HTTP请求一个实例 | 请求相关数据 | 线程安全 |
| session | 每个HTTP Session一个实例 | 用户会话数据 | 线程安全 |
| application | 整个应用一个实例 | 全局共享数据 | 需要考虑 |
代码示例:
// 1. singleton作用域(默认)
@Service
@Scope("singleton") // 可以省略,默认就是singleton
public class UserService {
// 容器中只有一个UserService实例
}
// 2. prototype作用域
@Component
@Scope("prototype")
public class UserAction {
// 每次getBean都创建新实例
private String username; // 可以有状态
}
// 3. request作用域
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LoginUser {
// 每个HTTP请求创建一个实例
private String username;
private String ip;
}
// 4. session作用域
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
// 每个HTTP Session创建一个实例
private List<CartItem> items = new ArrayList<>();
}
singleton和prototype的区别:
@SpringBootTest
public class ScopeTest {
@Autowired
private ApplicationContext context;
@Test
public void testSingleton() {
// singleton:多次获取是同一个实例
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
System.out.println(service1 == service2); // true
}
@Test
public void testPrototype() {
// prototype:多次获取是不同实例
UserAction action1 = context.getBean(UserAction.class);
UserAction action2 = context.getBean(UserAction.class);
System.out.println(action1 == action2); // false
}
}
回答技巧:
- 先说常用的singleton和prototype
- 再说Web环境下的request和session
- 说明各自的使用场景
可能的追问:
- singleton是线程安全的吗?
- 如何在singleton Bean中注入prototype Bean?
- 什么时候使用prototype作用域?
1.6 @Autowired和@Resource的区别?
标准答案:
@Autowired和@Resource都用于依赖注入,但它们有以下区别:
核心要点:
- 来源不同:@Autowired是Spring提供的,@Resource是JDK提供的(JSR-250)
- 装配方式不同:@Autowired按类型装配,@Resource按名称装配
- 属性不同:@Autowired有required属性,@Resource有name和type属性
- 使用位置不同:@Autowired可用于构造器、字段、方法,@Resource只能用于字段和方法
详细对比表:
| 维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring框架 | JDK标准(JSR-250) |
| 装配方式 | 先按类型,再按名称 | 先按名称,再按类型 |
| 必需属性 | required(默认true) | 无 |
| 指定名称 | 配合@Qualifier使用 | name属性 |
| 使用位置 | 构造器、字段、方法 | 字段、方法 |
| 推荐程度 | 推荐(Spring生态) | 一般 |
代码示例:
@Service
public class UserService {
// 1. @Autowired按类型装配
@Autowired
private UserDao userDao; // 按UserDao类型查找Bean
// 2. @Autowired + @Qualifier按名称装配
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao2; // 按名称"userDaoImpl"查找Bean
// 3. @Autowired的required属性
@Autowired(required = false)
private EmailService emailService; // 找不到Bean不报错
// 4. @Resource按名称装配
@Resource(name = "userDaoImpl")
private UserDao userDao3; // 按名称"userDaoImpl"查找Bean
// 5. @Resource按类型装配
@Resource
private UserDao userDao4; // 按UserDao类型查找Bean
}
装配流程对比:
@Autowired装配流程:
┌─────────────────────────────────────────────────────────┐
│ 1. 按类型查找Bean │
│ ↓ │
│ 2. 找到唯一Bean → 注入成功 │
│ ↓ │
│ 3. 找到多个Bean → 按字段名称匹配 │
│ ↓ │
│ 4. 匹配成功 → 注入成功 │
│ ↓ │
│ 5. 匹配失败 → 配合@Qualifier指定名称 │
│ ↓ │
│ 6. 找不到Bean → 抛出异常(required=true) │
└─────────────────────────────────────────────────────────┘
@Resource装配流程:
┌─────────────────────────────────────────────────────────┐
│ 1. 指定了name属性 → 按名称查找Bean │
│ ↓ │
│ 2. 找到Bean → 注入成功 │
│ ↓ │
│ 3. 找不到Bean → 抛出异常 │
│ ↓ │
│ 4. 未指定name → 按字段名称查找Bean │
│ ↓ │
│ 5. 找到Bean → 注入成功 │
│ ↓ │
│ 6. 找不到Bean → 按类型查找Bean │
│ ↓ │
│ 7. 找到唯一Bean → 注入成功 │
│ ↓ │
│ 8. 找不到或找到多个 → 抛出异常 │
└─────────────────────────────────────────────────────────┘
实际使用建议:
// 推荐:使用@Autowired(Spring生态标准)
@Service
public class UserService {
// 推荐:构造器注入
private final UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 多个实现时,使用@Qualifier指定
@Autowired
@Qualifier("mysqlUserDao")
private UserDao mysqlUserDao;
}
// 不推荐:使用@Resource(除非需要JDK标准)
@Service
public class OrderService {
@Resource(name = "orderDaoImpl")
private OrderDao orderDao;
}
回答技巧:
- 先说来源和装配方式的区别
- 举例说明两者的使用方式
- 说明推荐使用@Autowired
可能的追问:
- 如果有多个相同类型的Bean怎么办?
- @Autowired是如何实现的?
- 为什么推荐使用构造器注入?
1.7 Spring如何解决循环依赖?
标准答案:
Spring通过三级缓存机制解决单例Bean的循环依赖问题。
核心要点:
- 三级缓存:singletonObjects、earlySingletonObjects、singletonFactories
- 提前暴露:Bean实例化后立即放入三级缓存
- 延迟注入:先创建实例,再注入依赖
- 限制条件:只能解决单例Bean的setter注入循环依赖
三级缓存说明:
// Spring容器中的三级缓存
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放提前暴露的Bean(半成品)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:存放Bean工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
循环依赖解决流程:
场景:A依赖B,B依赖A
┌─────────────────────────────────────────────────────────────┐
│ Spring解决循环依赖流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 创建A的实例(调用构造器) │
│ A = new A(); // 此时A还未注入依赖 │
│ ↓ │
│ 2. 将A的ObjectFactory放入三级缓存 │
│ singletonFactories.put("a", () -> getEarlyBeanReference(A));│
│ ↓ │
│ 3. 为A注入依赖,发现需要B │
│ ↓ │
│ 4. 创建B的实例(调用构造器) │
│ B = new B(); // 此时B还未注入依赖 │
│ ↓ │
│ 5. 将B的ObjectFactory放入三级缓存 │
│ singletonFactories.put("b", () -> getEarlyBeanReference(B));│
│ ↓ │
│ 6. 为B注入依赖,发现需要A │
│ ↓ │
│ 7. 从三级缓存获取A的ObjectFactory │
│ ObjectFactory<?> factory = singletonFactories.get("a"); │
│ ↓ │
│ 8. 调用ObjectFactory获取A的早期引用 │
│ A earlyA = factory.getObject(); │
│ ↓ │
│ 9. 将A的早期引用放入二级缓存 │
│ earlySingletonObjects.put("a", earlyA); │
│ singletonFactories.remove("a"); │
│ ↓ │
│ 10. 将A注入到B中 │
│ B.setA(earlyA); │
│ ↓ │
│ 11. B初始化完成,放入一级缓存 │
│ singletonObjects.put("b", B); │
│ ↓ │
│ 12. 将B注入到A中 │
│ A.setB(B); │
│ ↓ │
│ 13. A初始化完成,放入一级缓存 │
│ singletonObjects.put("a", A); │
│ earlySingletonObjects.remove("a"); │
│ ↓ │
│ 14. 循环依赖解决完成 │
│ │
└─────────────────────────────────────────────────────────────┘
代码示例:
// 循环依赖示例
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A依赖B
public void doSomething() {
System.out.println("ServiceA doing something");
serviceB.doSomething();
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // B依赖A
public void doSomething() {
System.out.println("ServiceB doing something");
serviceA.doSomething();
}
}
// Spring可以成功创建这两个Bean,因为使用的是setter注入
无法解决的循环依赖场景:
// 1. 构造器注入的循环依赖(无法解决)
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) { // 构造器注入
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) { // 构造器注入
this.serviceA = serviceA;
}
}
// 报错:BeanCurrentlyInCreationException
// 2. prototype作用域的循环依赖(无法解决)
@Service
@Scope("prototype")
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Scope("prototype")
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
// 报错:BeanCurrentlyInCreationException
解决方案:
// 方案1:使用@Lazy延迟注入
@Service
public class ServiceA {
@Autowired
@Lazy // 延迟注入,注入的是代理对象
private ServiceB serviceB;
}
// 方案2:使用setter注入代替构造器注入
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
// 方案3:重构代码,消除循环依赖(推荐)
// 将公共逻辑提取到第三个类中
@Service
public class CommonService {
public void doCommon() {
// 公共逻辑
}
}
@Service
public class ServiceA {
@Autowired
private CommonService commonService;
}
@Service
public class ServiceB {
@Autowired
private CommonService commonService;
}
回答技巧:
- 先说三级缓存机制
- 画图说明解决流程
- 说明哪些情况无法解决
- 给出解决方案
可能的追问:
- 为什么需要三级缓存,二级不行吗?
- 构造器注入为什么无法解决循环依赖?
- 如何避免循环依赖?
二、Spring AOP原理(10-15题)
2.1 什么是AOP?
标准答案:
AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,通过预编译方式和运行期动态代理实现程序功能的统一维护。
核心要点:
- 横切关注点:分散在各个模块中的相同功能(日志、事务、权限)
- 切面:横切关注点的模块化
- 织入:将切面应用到目标对象的过程
- 解耦:将业务逻辑和系统服务分离
AOP核心概念:
┌─────────────────────────────────────────────────────────────┐
│ AOP核心概念 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Aspect(切面) │
│ - 横切关注点的模块化 │
│ - 由切点和通知组成 │
│ │
│ 2. Join Point(连接点) │
│ - 程序执行的某个特定位置 │
│ - 如方法调用、方法执行、异常抛出等 │
│ │
│ 3. Pointcut(切点) │
│ - 匹配连接点的表达式 │
│ - 定义在哪些连接点上应用通知 │
│ │
│ 4. Advice(通知) │
│ - 在切点上执行的动作 │
│ - 类型:Before、After、AfterReturning、AfterThrowing、Around│
│ │
│ 5. Target Object(目标对象) │
│ - 被一个或多个切面通知的对象 │
│ │
│ 6. AOP Proxy(AOP代理) │
│ - AOP框架创建的对象,用于实现切面契约 │
│ - JDK动态代理或CGLIB代理 │
│ │
│ 7. Weaving(织入) │
│ - 将切面应用到目标对象的过程 │
│ - 时机:编译期、类加载期、运行期 │
│ │
└─────────────────────────────────────────────────────────────┘
代码示例:
// 定义切面
@Aspect
@Component
public class LogAspect {
// 定义切点:匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知:方法执行前执行
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法执行前:" + methodName);
}
// 后置通知:方法执行后执行(无论是否异常)
@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法执行后:" + methodName);
}
// 返回通知:方法正常返回后执行
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法返回值:" + result);
}
// 异常通知:方法抛出异常后执行
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法抛出异常:" + ex.getMessage());
}
// 环绕通知:方法执行前后都执行
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
System.out.println("环绕通知-方法执行前:" + methodName);
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long endTime = System.currentTimeMillis();
System.out.println("环绕通知-方法执行后:" + methodName);
System.out.println("方法执行耗时:" + (endTime - startTime) + "ms");
return result;
}
}
AOP应用场景:
// 1. 日志记录
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.example.annotation.Log)")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
// 记录方法调用日志
log.info("方法调用:{}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
// 2. 性能监控
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info("方法耗时:{}ms", end - start);
return result;
}
}
// 3. 权限校验
@Aspect
@Component
public class AuthAspect {
@Before("@annotation(com.example.annotation.RequireAuth)")
public void checkAuth(JoinPoint joinPoint) {
// 校验用户权限
if (!hasPermission()) {
throw new UnauthorizedException("无权限访问");
}
}
}
// 4. 事务管理
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object transaction(ProceedingJoinPoint joinPoint) throws Throwable {
// 开启事务
beginTransaction();
try {
Object result = joinPoint.proceed();
// 提交事务
commit();
return result;
} catch (Exception e) {
// 回滚事务
rollback();
throw e;
}
}
}
回答技巧:
- 先说定义和作用
- 列举核心概念
- 举例说明应用场景
- 强调解耦的优势
可能的追问:
- AOP的实现原理是什么?
- JDK动态代理和CGLIB代理的区别?
- 五种通知类型的执行顺序是什么?
2.2 Spring AOP的实现原理是什么?
标准答案:
Spring AOP基于动态代理实现,主要有两种代理方式:JDK动态代理和CGLIB代理。
核心要点:
- JDK动态代理:基于接口,使用反射机制
- CGLIB代理:基于继承,使用字节码技术
- 自动选择:有接口用JDK代理,无接口用CGLIB代理
- 代理对象:Spring容器返回的是代理对象,而非目标对象
两种代理方式对比:
| 维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现方式 | 基于接口 | 基于继承 |
| 要求 | 目标类必须实现接口 | 目标类不能是final |
| 性能 | 创建快,调用慢 | 创建慢,调用快 |
| 使用场景 | 有接口的类 | 无接口的类 |
| 底层技术 | Java反射 | ASM字节码 |
JDK动态代理示例:
// 1. 定义接口
public interface UserService {
void addUser(String username);
}
// 2. 实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用户:" + username);
}
}
// 3. 创建代理类
public class JdkProxyFactory {
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 接口数组
new InvocationHandler() { // 调用处理器
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("方法执行前");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("方法执行后");
return result;
}
}
);
}
}
// 4. 使用代理
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) JdkProxyFactory.createProxy(target);
proxy.addUser("张三"); // 调用代理方法
}
}
CGLIB代理示例:
// 1. 目标类(无需实现接口)
public class UserService {
public void addUser(String username) {
System.out.println("添加用户:" + username);
}
}
// 2. 创建代理类
public class CglibProxyFactory {
public static Object createProxy(Object target) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
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;
}
});
// 创建代理对象
return enhancer.create();
}
}
// 3. 使用代理
public class CglibProxyTest {
public static void main(String[] args) {
UserService target = new UserService();
UserService proxy = (UserService) CglibProxyFactory.createProxy(target);
proxy.addUser("张三"); // 调用代理方法
}
}
Spring AOP代理选择流程:
┌─────────────────────────────────────────────────────────────┐
│ Spring AOP代理选择流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 检查目标类是否实现了接口 │
│ ↓ │
│ 2. 实现了接口? │
│ ├─ 是 → 使用JDK动态代理 │
│ │ 创建接口的代理对象 │
│ │ │
│ └─ 否 → 使用CGLIB代理 │
│ 创建子类的代理对象 │
│ │
│ 3. 可以通过配置强制使用CGLIB │
│ @EnableAspectJAutoProxy(proxyTargetClass = true) │
│ │
└─────────────────────────────────────────────────────────────┘
强制使用CGLIB代理:
// 方式1:注解配置
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB
public class AppConfig {
}
// 方式2:XML配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
代理对象的特点:
@Service
public class UserService {
public void addUser(String username) {
System.out.println("添加用户:" + username);
}
}
@SpringBootTest
public class ProxyTest {
@Autowired
private UserService userService;
@Test
public void testProxy() {
// 打印对象类型
System.out.println(userService.getClass());
// 输出:class com.example.service.UserService$$EnhancerBySpringCGLIB$$xxx
// 说明注入的是CGLIB代理对象
// 判断是否是代理对象
boolean isProxy = AopUtils.isAopProxy(userService);
System.out.println("是否是代理对象:" + isProxy); // true
// 判断是否是CGLIB代理
boolean isCglibProxy = AopUtils.isCglibProxy(userService);
System.out.println("是否是CGLIB代理:" + isCglibProxy); // true
// 判断是否是JDK代理
boolean isJdkProxy = AopUtils.isJdkDynamicProxy(userService);
System.out.println("是否是JDK代理:" + isJdkProxy); // false
}
}
回答技巧:
- 先说两种代理方式
- 对比两种方式的区别
- 说明Spring如何选择代理方式
- 举例说明代理的创建过程
可能的追问:
- 为什么JDK代理必须基于接口?
- CGLIB代理为什么不能代理final类?
- 如何获取代理对象的目标对象?
2.3 五种通知类型的执行顺序是什么?
标准答案:
Spring AOP有五种通知类型,它们的执行顺序如下:
核心要点:
- @Before:前置通知,方法执行前执行
- @Around(前半部分):环绕通知的前半部分
- 目标方法执行
- @Around(后半部分):环绕通知的后半部分
- @After:后置通知,方法执行后执行(无论是否异常)
- @AfterReturning:返回通知,方法正常返回后执行
- @AfterThrowing:异常通知,方法抛出异常后执行
正常执行流程:
┌─────────────────────────────────────────────────────────────┐
│ 通知执行顺序(正常情况) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. @Around - 前半部分 │
│ ↓ │
│ 2. @Before - 前置通知 │
│ ↓ │
│ 3. 目标方法执行 │
│ ↓ │
│ 4. @Around - 后半部分 │
│ ↓ │
│ 5. @After - 后置通知 │
│ ↓ │
│ 6. @AfterReturning - 返回通知 │
│ │
└─────────────────────────────────────────────────────────────┘
异常执行流程:
┌─────────────────────────────────────────────────────────────┐
│ 通知执行顺序(异常情况) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. @Around - 前半部分 │
│ ↓ │
│ 2. @Before - 前置通知 │
│ ↓ │
│ 3. 目标方法执行(抛出异常) │
│ ↓ │
│ 4. @After - 后置通知 │
│ ↓ │
│ 5. @AfterThrowing - 异常通知 │
│ ↓ │
│ 6. @Around - 异常处理(如果有) │
│ │
└─────────────────────────────────────────────────────────────┘
完整示例代码:
@Aspect
@Component
public class OrderAspect {
// 环绕通知
@Around("execution(* com.example.service.OrderService.createOrder(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("1. @Around - 前半部分");
try {
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("4. @Around - 后半部分(正常返回)");
return result;
} catch (Exception e) {
System.out.println("4. @Around - 后半部分(异常处理)");
throw e;
}
}
// 前置通知
@Before("execution(* com.example.service.OrderService.createOrder(..))")
public void before(JoinPoint joinPoint) {
System.out.println("2. @Before - 前置通知");
}
// 后置通知
@After("execution(* com.example.service.OrderService.createOrder(..))")
public void after(JoinPoint joinPoint) {
System.out.println("5. @After - 后置通知");
}
// 返回通知
@AfterReturning(pointcut = "execution(* com.example.service.OrderService.createOrder(..))",
returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("6. @AfterReturning - 返回通知,返回值:" + result);
}
// 异常通知
@AfterThrowing(pointcut = "execution(* com.example.service.OrderService.createOrder(..))",
throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("6. @AfterThrowing - 异常通知,异常:" + ex.getMessage());
}
}
// 目标类
@Service
public class OrderService {
public String createOrder(String orderNo) {
System.out.println("3. 目标方法执行:创建订单 " + orderNo);
return "订单创建成功";
}
public String createOrderWithException(String orderNo) {
System.out.println("3. 目标方法执行:创建订单 " + orderNo);
throw new RuntimeException("订单创建失败");
}
}
// 测试类
@SpringBootTest
public class AspectOrderTest {
@Autowired
private OrderService orderService;
@Test
public void testNormalFlow() {
System.out.println("===== 正常执行流程 =====");
orderService.createOrder("ORDER001");
/*
输出:
1. @Around - 前半部分
2. @Before - 前置通知
3. 目标方法执行:创建订单 ORDER001
4. @Around - 后半部分(正常返回)
5. @After - 后置通知
6. @AfterReturning - 返回通知,返回值:订单创建成功
*/
}
@Test
public void testExceptionFlow() {
System.out.println("===== 异常执行流程 =====");
try {
orderService.createOrderWithException("ORDER002");
} catch (Exception e) {
// 捕获异常
}
/*
输出:
1. @Around - 前半部分
2. @Before - 前置通知
3. 目标方法执行:创建订单 ORDER002
5. @After - 后置通知
6. @AfterThrowing - 异常通知,异常:订单创建失败
4. @Around - 后半部分(异常处理)
*/
}
}
多个切面的执行顺序:
// 使用@Order注解指定切面的执行顺序
@Aspect
@Component
@Order(1) // 数字越小,优先级越高
public class FirstAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("FirstAspect - Before");
}
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("SecondAspect - Before");
}
}
// 执行顺序:
// FirstAspect - Before
// SecondAspect - Before
// 目标方法执行
// SecondAspect - After
// FirstAspect - After
回答技巧:
- 按照执行顺序逐一说明
- 区分正常流程和异常流程
- 举例说明实际执行情况
- 说明多个切面的执行顺序
可能的追问:
- @After和@AfterReturning的区别?
- 如何控制多个切面的执行顺序?
- 环绕通知中如何处理异常?
三、Spring事务管理(10-15题)
3.1 Spring事务管理的实现方式有哪些?
标准答案:
Spring提供两种事务管理方式:编程式事务和声明式事务。
核心要点:
- 编程式事务:通过TransactionTemplate或PlatformTransactionManager手动管理事务
- 声明式事务:通过@Transactional注解或XML配置自动管理事务
- 推荐方式:声明式事务,代码侵入性小,易于维护
- 底层实现:基于AOP实现
两种方式对比:
| 维度 | 编程式事务 | 声明式事务 |
|---|---|---|
| 实现方式 | 手动编码 | 注解或XML配置 |
| 代码侵入性 | 高 | 低 |
| 灵活性 | 高 | 中 |
| 维护性 | 差 | 好 |
| 使用场景 | 复杂事务逻辑 | 常规业务场景 |
| 推荐程度 | 不推荐 | 推荐 |
编程式事务示例:
// 方式1:使用TransactionTemplate
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderDao orderDao;
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
try {
// 业务逻辑
orderDao.insert(order);
// 更多操作...
return null;
} catch (Exception e) {
// 回滚事务
status.setRollbackOnly();
throw e;
}
});
}
}
// 方式2:使用PlatformTransactionManager
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private OrderDao orderDao;
public void createOrder(Order order) {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
orderDao.insert(order);
// 更多操作...
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
throw e;
}
}
}
声明式事务示例:
// 方式1:使用@Transactional注解(推荐)
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
// 在方法上添加@Transactional注解
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 扣减库存
productDao.deductStock(order.getProductId(), order.getQuantity());
// 3. 更多操作...
// 如果任何操作失败,整个事务回滚
}
}
// 方式2:在类上添加@Transactional注解
@Service
@Transactional(rollbackFor = Exception.class) // 类上的注解对所有方法生效
public class OrderService {
@Autowired
private OrderDao orderDao;
public void createOrder(Order order) {
orderDao.insert(order);
}
public void updateOrder(Order order) {
orderDao.update(order);
}
// 只读事务,提高性能
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderDao.selectById(id);
}
}
@Transactional注解属性:
@Transactional(
// 事务传播行为
propagation = Propagation.REQUIRED,
// 事务隔离级别
isolation = Isolation.DEFAULT,
// 事务超时时间(秒)
timeout = 30,
// 是否只读事务
readOnly = false,
// 遇到哪些异常回滚
rollbackFor = {Exception.class},
// 遇到哪些异常不回滚
noRollbackFor = {IllegalArgumentException.class}
)
public void createOrder(Order order) {
// 业务逻辑
}
声明式事务的底层实现:
┌─────────────────────────────────────────────────────────────┐
│ 声明式事务的AOP实现原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Spring扫描@Transactional注解 │
│ ↓ │
│ 2. 为标注的类/方法创建AOP代理 │
│ ↓ │
│ 3. 调用方法时,代理拦截 │
│ ↓ │
│ 4. 开启事务(TransactionInterceptor) │
│ - 获取数据库连接 │
│ - 设置autoCommit=false │
│ ↓ │
│ 5. 执行目标方法 │
│ ↓ │
│ 6. 方法正常返回 │
│ - 提交事务(commit) │
│ - 释放连接 │
│ ↓ │
│ 7. 方法抛出异常 │
│ - 回滚事务(rollback) │
│ - 释放连接 │
│ │
└─────────────────────────────────────────────────────────────┘
回答技巧:
- 先说两种方式
- 对比两种方式的优缺点
- 推荐使用声明式事务
- 说明底层基于AOP实现
可能的追问:
- @Transactional注解有哪些属性?
- 事务的传播行为有哪些?
- 事务什么时候会失效?
3.2 Spring事务的传播行为有哪些?
标准答案:
Spring定义了7种事务传播行为,用于控制事务方法之间的调用关系。
核心要点:
- REQUIRED(默认):如果当前有事务,加入该事务;如果没有,创建新事务
- REQUIRES_NEW:总是创建新事务,如果当前有事务,挂起当前事务
- SUPPORTS:如果当前有事务,加入该事务;如果没有,以非事务方式执行
- NOT_SUPPORTED:以非事务方式执行,如果当前有事务,挂起当前事务
- MANDATORY:必须在事务中执行,如果当前没有事务,抛出异常
- NEVER:必须在非事务中执行,如果当前有事务,抛出异常
- NESTED:如果当前有事务,在嵌套事务中执行;如果没有,创建新事务
传播行为详解表:
| 传播行为 | 说明 | 使用场景 |
|---|---|---|
| REQUIRED | 支持当前事务,没有则创建 | 最常用,默认值 |
| REQUIRES_NEW | 总是创建新事务 | 独立事务,不受外部影响 |
| SUPPORTS | 支持当前事务,没有则非事务 | 查询方法 |
| NOT_SUPPORTED | 以非事务方式执行 | 不需要事务的操作 |
| MANDATORY | 必须在事务中执行 | 强制事务约束 |
| NEVER | 必须在非事务中执行 | 禁止事务 |
| NESTED | 嵌套事务 | 部分回滚场景 |
REQUIRED传播行为示例:
@Service
public class OrderService {
@Autowired
private ProductService productService;
// 方法A:有事务
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 调用方法B
productService.deductStock(order.getProductId(), order.getQuantity());
// 方法A和方法B在同一个事务中
// 如果方法B抛出异常,方法A也会回滚
}
}
@Service
public class ProductService {
// 方法B:REQUIRED传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, Integer quantity) {
// 扣减库存
productDao.deductStock(productId, quantity);
// 如果当前有事务(方法A的事务),加入该事务
// 如果当前没有事务,创建新事务
}
}
REQUIRES_NEW传播行为示例:
@Service
public class OrderService {
@Autowired
private LogService logService;
// 方法A:有事务
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 记录日志(独立事务)
logService.saveLog("创建订单:" + order.getOrderNo());
// 3. 模拟异常
if (order.getAmount() > 10000) {
throw new RuntimeException("订单金额过大");
}
// 方法A回滚,但方法B不回滚(独立事务)
// 日志仍然会保存成功
}
}
@Service
public class LogService {
// 方法B:REQUIRES_NEW传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String content) {
// 保存日志
logDao.insert(new Log(content));
// 总是创建新事务,不受外部事务影响
// 即使外部事务回滚,这里的日志也会提交
}
}
NESTED传播行为示例:
@Service
public class OrderService {
@Autowired
private CouponService couponService;
// 方法A:有事务
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 使用优惠券(嵌套事务)
try {
couponService.useCoupon(order.getCouponId());
} catch (Exception e) {
// 优惠券使用失败,只回滚优惠券操作
// 订单创建不受影响
log.warn("优惠券使用失败", e);
}
// 3. 继续其他操作
}
}
@Service
public class CouponService {
// 方法B:NESTED传播行为
@Transactional(propagation = Propagation.NESTED)
public void useCoupon(Long couponId) {
// 使用优惠券
couponDao.updateStatus(couponId, "USED");
// 在嵌套事务中执行
// 如果失败,只回滚嵌套事务,不影响外部事务
}
}
传播行为组合场景:
场景1:REQUIRED + REQUIRED
┌─────────────────────────────────────────────────────────────┐
│ 方法A(REQUIRED) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 开启事务 │ │
│ │ ↓ │ │
│ │ 执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 调用方法B(REQUIRED) │ │
│ │ ↓ │ │
│ │ 方法B加入方法A的事务 │ │
│ │ ↓ │ │
│ │ 执行方法B的业务逻辑 │ │
│ │ ↓ │ │
│ │ 方法B执行完成 │ │
│ │ ↓ │ │
│ │ 继续执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 提交事务(或回滚) │ │
│ └───────────────────────────────────────────────────────┘ │
│ 结论:方法A和方法B在同一个事务中 │
└─────────────────────────────────────────────────────────────┘
场景2:REQUIRED + REQUIRES_NEW
┌─────────────────────────────────────────────────────────────┐
│ 方法A(REQUIRED) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 开启事务A │ │
│ │ ↓ │ │
│ │ 执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 调用方法B(REQUIRES_NEW) │ │
│ │ ↓ │ │
│ │ 挂起事务A │ │
│ │ ↓ │ │
│ │ 开启新事务B │ │
│ │ ↓ │ │
│ │ 执行方法B的业务逻辑 │ │
│ │ ↓ │ │
│ │ 提交事务B │ │
│ │ ↓ │ │
│ │ 恢复事务A │ │
│ │ ↓ │ │
│ │ 继续执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 提交事务A(或回滚) │ │
│ └───────────────────────────────────────────────────────┘ │
│ 结论:方法A和方法B在不同的事务中,互不影响 │
└─────────────────────────────────────────────────────────────┘
场景3:REQUIRED + NESTED
┌─────────────────────────────────────────────────────────────┐
│ 方法A(REQUIRED) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 开启事务 │ │
│ │ ↓ │ │
│ │ 执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 设置保存点(Savepoint) │ │
│ │ ↓ │ │
│ │ 调用方法B(NESTED) │ │
│ │ ↓ │ │
│ │ 执行方法B的业务逻辑 │ │
│ │ ↓ │ │
│ │ 方法B异常 → 回滚到保存点 │ │
│ │ 方法B成功 → 继续执行 │ │
│ │ ↓ │ │
│ │ 继续执行方法A的业务逻辑 │ │
│ │ ↓ │ │
│ │ 提交事务(或回滚) │ │
│ └───────────────────────────────────────────────────────┘ │
│ 结论:方法B在方法A的事务中,但可以部分回滚 │
└─────────────────────────────────────────────────────────────┘
实际应用场景:
// 场景1:订单创建(REQUIRED)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 创建订单、扣减库存、清空购物车
// 这些操作必须在同一个事务中
}
// 场景2:日志记录(REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String content) {
// 日志记录独立事务,不受外部事务影响
// 即使外部事务回滚,日志也要保存
}
// 场景3:查询操作(SUPPORTS)
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Order> listOrders() {
// 查询操作,如果有事务就加入,没有就非事务执行
}
// 场景4:优惠券使用(NESTED)
@Transactional(propagation = Propagation.NESTED)
public void useCoupon(Long couponId) {
// 优惠券使用失败不影响订单创建
// 但订单创建失败会导致优惠券回滚
}
回答技巧:
- 重点说明REQUIRED、REQUIRES_NEW、NESTED三种
- 画图说明不同传播行为的事务关系
- 举例说明实际应用场景
- 说明默认值是REQUIRED
可能的追问:
- REQUIRES_NEW和NESTED的区别?
- 如何选择合适的传播行为?
- 传播行为失效的场景有哪些?
3.3 Spring事务什么时候会失效?
标准答案:
Spring事务在以下场景会失效:
核心要点:
- 方法不是public:@Transactional只能作用于public方法
- 方法被final修饰:CGLIB无法代理final方法
- 同类方法调用:内部方法调用不经过代理
- 异常被捕获:异常被catch后没有抛出
- 异常类型不匹配:默认只回滚RuntimeException和Error
- 数据库不支持事务:如MyISAM引擎
- 没有被Spring管理:类没有@Service等注解
场景1:方法不是public
@Service
public class OrderService {
// ❌ 错误:private方法,事务失效
@Transactional
private void createOrder(Order order) {
orderDao.insert(order);
}
// ✓ 正确:public方法,事务生效
@Transactional
public void createOrderCorrect(Order order) {
orderDao.insert(order);
}
}
场景2:同类方法调用
@Service
public class OrderService {
// 方法A:无事务
public void methodA() {
// 直接调用方法B,不经过代理
this.methodB(); // ❌ 事务失效
}
// 方法B:有事务
@Transactional
public void methodB() {
orderDao.insert(new Order());
}
}
// 解决方案1:注入自己
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己
public void methodA() {
// 通过代理对象调用
self.methodB(); // ✓ 事务生效
}
@Transactional
public void methodB() {
orderDao.insert(new Order());
}
}
// 解决方案2:使用AopContext
@Service
public class OrderService {
public void methodA() {
// 获取代理对象
OrderService proxy = (OrderService) AopContext.currentProxy();
proxy.methodB(); // ✓ 事务生效
}
@Transactional
public void methodB() {
orderDao.insert(new Order());
}
}
// 解决方案3:拆分到不同类(推荐)
@Service
public class OrderService {
@Autowired
private OrderInnerService orderInnerService;
public void methodA() {
orderInnerService.methodB(); // ✓ 事务生效
}
}
@Service
public class OrderInnerService {
@Transactional
public void methodB() {
orderDao.insert(new Order());
}
}
场景3:异常被捕获
@Service
public class OrderService {
// ❌ 错误:异常被捕获,事务不回滚
@Transactional
public void createOrder(Order order) {
try {
orderDao.insert(order);
// 模拟异常
int i = 1 / 0;
} catch (Exception e) {
// 异常被捕获,事务不会回滚
log.error("创建订单失败", e);
}
}
// ✓ 正确:捕获后重新抛出
@Transactional
public void createOrderCorrect(Order order) {
try {
orderDao.insert(order);
int i = 1 / 0;
} catch (Exception e) {
log.error("创建订单失败", e);
throw e; // 重新抛出异常
}
}
// ✓ 正确:手动回滚
@Transactional
public void createOrderManual(Order order) {
try {
orderDao.insert(order);
int i = 1 / 0;
} catch (Exception e) {
log.error("创建订单失败", e);
// 手动标记回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
场景4:异常类型不匹配
@Service
public class OrderService {
// ❌ 错误:抛出检查异常,事务不回滚
@Transactional
public void createOrder(Order order) throws Exception {
orderDao.insert(order);
// 抛出检查异常,默认不回滚
throw new Exception("创建订单失败");
}
// ✓ 正确:指定回滚的异常类型
@Transactional(rollbackFor = Exception.class)
public void createOrderCorrect(Order order) throws Exception {
orderDao.insert(order);
throw new Exception("创建订单失败"); // 会回滚
}
// ✓ 正确:抛出运行时异常
@Transactional
public void createOrderRuntime(Order order) {
orderDao.insert(order);
throw new RuntimeException("创建订单失败"); // 会回滚
}
}
场景5:数据库不支持事务
-- ❌ 错误:MyISAM引擎不支持事务
CREATE TABLE `order` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_no` VARCHAR(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- ✓ 正确:InnoDB引擎支持事务
CREATE TABLE `order` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_no` VARCHAR(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
场景6:没有被Spring管理
// ❌ 错误:没有@Service注解,不被Spring管理
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderDao.insert(order);
}
}
// ✓ 正确:添加@Service注解
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderDao.insert(order);
}
}
事务失效检查清单:
┌─────────────────────────────────────────────────────────────┐
│ Spring事务失效检查清单 │
├─────────────────────────────────────────────────────────────┤
│ │
│ □ 方法是否是public? │
│ □ 方法是否被final修饰? │
│ □ 是否是同类方法调用? │
│ □ 异常是否被捕获? │
│ □ 异常类型是否匹配rollbackFor? │
│ □ 数据库引擎是否支持事务? │
│ □ 类是否被Spring管理? │
│ □ 是否开启了事务管理?(@EnableTransactionManagement) │
│ │
└─────────────────────────────────────────────────────────────┘
回答技巧:
- 列举常见的失效场景
- 每个场景给出错误示例和正确示例
- 说明失效的原因
- 给出解决方案
可能的追问:
- 如何排查事务失效问题?
- 如何验证事务是否生效?
- 事务的最佳实践有哪些?
四、SpringMVC请求处理流程(10-15题)
4.1 SpringMVC的请求处理流程是什么?
标准答案:
SpringMVC的请求处理流程包括以下步骤:
核心要点:
- DispatcherServlet接收请求:前端控制器接收所有请求
- HandlerMapping查找Handler:根据URL查找对应的处理器
- HandlerAdapter执行Handler:适配器执行处理器方法
- ModelAndView返回:处理器返回模型和视图
- ViewResolver解析视图:视图解析器解析视图名称
- View渲染:视图渲染并返回响应
完整流程图:
┌─────────────────────────────────────────────────────────────┐
│ SpringMVC请求处理完整流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 用户发送请求 │
│ ↓ │
│ 2. DispatcherServlet接收请求 │
│ - 前端控制器,统一处理所有请求 │
│ ↓ │
│ 3. DispatcherServlet调用HandlerMapping │
│ - 根据请求URL查找对应的Handler │
│ - 返回HandlerExecutionChain(Handler + 拦截器) │
│ ↓ │
│ 4. DispatcherServlet调用HandlerAdapter │
│ - 适配器模式,适配不同类型的Handler │
│ - 执行拦截器的preHandle方法 │
│ ↓ │
│ 5. HandlerAdapter执行Handler │
│ - 调用Controller的方法 │
│ - 参数解析、数据绑定、参数校验 │
│ ↓ │
│ 6. Handler返回ModelAndView │
│ - Model:模型数据 │
│ - View:视图名称或视图对象 │
│ ↓ │
│ 7. 执行拦截器的postHandle方法 │
│ ↓ │
│ 8. DispatcherServlet调用ViewResolver │
│ - 视图解析器解析视图名称 │
│ - 返回View对象 │
│ ↓ │
│ 9. DispatcherServlet调用View.render() │
│ - 视图渲染,将模型数据填充到视图 │
│ - 生成HTML响应 │
│ ↓ │
│ 10. 执行拦截器的afterCompletion方法 │
│ ↓ │
│ 11. 返回响应给用户 │
│ │
└─────────────────────────────────────────────────────────────┘
核心组件说明:
// 1. DispatcherServlet:前端控制器
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) {
// 查找Handler
HandlerExecutionChain mappedHandler = getHandler(request);
// 获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行Handler
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 渲染视图
render(mv, request, response);
}
}
// 2. HandlerMapping:处理器映射器
public interface HandlerMapping {
// 根据请求查找Handler
HandlerExecutionChain getHandler(HttpServletRequest request);
}
// 3. HandlerAdapter:处理器适配器
public interface HandlerAdapter {
// 执行Handler
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler);
}
// 4. ViewResolver:视图解析器
public interface ViewResolver {
// 解析视图名称
View resolveViewName(String viewName, Locale locale);
}
// 5. View:视图
public interface View {
// 渲染视图
void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response);
}
完整示例代码:
// 1. Controller
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 处理请求
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
// 查询用户
User user = userService.getUser(id);
// 添加模型数据
model.addAttribute("user", user);
// 返回视图名称
return "user/detail";
}
// 返回JSON数据
@GetMapping("/json/{id}")
@ResponseBody
public User getUserJson(@PathVariable Long id) {
return userService.getUser(id);
}
}
// 2. 拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
// 前置处理
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
System.out.println("preHandle:请求处理前");
// 返回true继续执行,返回false中断请求
return true;
}
// 后置处理
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
System.out.println("postHandle:请求处理后");
}
// 完成处理
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
System.out.println("afterCompletion:视图渲染后");
}
}
// 3. 配置拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/register"); // 排除登录注册
}
}
// 4. 视图解析器配置
@Configuration
public class ViewResolverConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/"); // 视图前缀
resolver.setSuffix(".jsp"); // 视图后缀
return resolver;
}
}
请求处理示例:
用户请求:GET /user/1
1. DispatcherServlet接收请求
- URL: /user/1
- Method: GET
2. HandlerMapping查找Handler
- 找到:UserController.getUser()方法
- 返回:HandlerExecutionChain(包含Handler和拦截器)
3. 执行拦截器的preHandle方法
- LoginInterceptor.preHandle()
- 返回true,继续执行
4. HandlerAdapter执行Handler
- 解析参数:@PathVariable Long id = 1
- 调用方法:UserController.getUser(1, model)
- 返回:ModelAndView("user/detail", {user: User对象})
5. 执行拦截器的postHandle方法
- LoginInterceptor.postHandle()
6. ViewResolver解析视图
- 视图名称:"user/detail"
- 解析结果:"/WEB-INF/views/user/detail.jsp"
7. View渲染
- 将User对象填充到JSP页面
- 生成HTML响应
8. 执行拦截器的afterCompletion方法
- LoginInterceptor.afterCompletion()
9. 返回响应给用户
- 返回渲染后的HTML页面
回答技巧:
- 按照流程顺序逐步说明
- 重点说明核心组件的作用
- 画图辅助说明
- 举例说明实际处理过程
可能的追问:
- DispatcherServlet的初始化过程?
- HandlerMapping如何查找Handler?
- 拦截器和过滤器的区别?
4.2 SpringMVC的核心组件有哪些?
标准答案:
SpringMVC的核心组件包括:
核心要点:
- DispatcherServlet:前端控制器,统一处理请求
- HandlerMapping:处理器映射器,查找Handler
- HandlerAdapter:处理器适配器,执行Handler
- Handler:处理器,即Controller
- ViewResolver:视图解析器,解析视图
- View:视图,渲染响应
- HandlerInterceptor:拦截器,拦截请求
组件详解表:
| 组件 | 作用 | 常用实现类 |
|---|---|---|
| DispatcherServlet | 前端控制器 | DispatcherServlet |
| HandlerMapping | 查找Handler | RequestMappingHandlerMapping |
| HandlerAdapter | 执行Handler | RequestMappingHandlerAdapter |
| Handler | 处理请求 | @Controller标注的类 |
| ViewResolver | 解析视图 | InternalResourceViewResolver |
| View | 渲染视图 | JstlView、JsonView |
| HandlerInterceptor | 拦截请求 | 自定义拦截器 |
组件关系图:
┌─────────────────────────────────────────────────────────────┐
│ SpringMVC核心组件关系图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ DispatcherServlet │
│ (前端控制器) │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ HandlerMapping HandlerAdapter ViewResolver │
│ (映射器) (适配器) (视图解析器) │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ Handler Handler View │
│ (处理器) (处理器) (视图) │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ HandlerInterceptor │
│ (拦截器) │
│ │
└─────────────────────────────────────────────────────────────┘
各组件详细说明:
// 1. DispatcherServlet:前端控制器
// 作用:接收所有请求,协调各组件工作
@WebServlet(urlPatterns = "/", loadOnStartup = 1)
public class DispatcherServlet extends FrameworkServlet {
// 核心方法:处理请求
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) {
// 1. 查找Handler
HandlerExecutionChain mappedHandler = getHandler(request);
// 2. 获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 执行拦截器preHandle
if (!mappedHandler.applyPreHandle(request, response)) {
return;
}
// 4. 执行Handler
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 5. 执行拦截器postHandle
mappedHandler.applyPostHandle(request, response, mv);
// 6. 渲染视图
processDispatchResult(request, response, mappedHandler, mv, null);
}
}
// 2. HandlerMapping:处理器映射器
// 作用:根据URL查找对应的Handler
public interface HandlerMapping {
// 查找Handler
HandlerExecutionChain getHandler(HttpServletRequest request);
}
// 常用实现:RequestMappingHandlerMapping
// 处理@RequestMapping注解
public class RequestMappingHandlerMapping extends AbstractHandlerMapping {
// 注册Handler
protected void registerHandlerMethod(Object handler, Method method,
RequestMappingInfo mapping) {
// 将@RequestMapping注解的方法注册为Handler
}
}
// 3. HandlerAdapter:处理器适配器
// 作用:执行Handler,适配不同类型的Handler
public interface HandlerAdapter {
// 是否支持该Handler
boolean supports(Object handler);
// 执行Handler
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler);
}
// 常用实现:RequestMappingHandlerAdapter
// 处理@RequestMapping注解的方法
public class RequestMappingHandlerAdapter extends AbstractHandlerAdapter {
// 执行Handler方法
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) {
// 1. 参数解析
Object[] args = getMethodArgumentValues(request, handlerMethod);
// 2. 调用方法
Object returnValue = handlerMethod.invoke(args);
// 3. 处理返回值
return getModelAndView(returnValue);
}
}
// 4. Handler:处理器
// 作用:处理具体的业务逻辑
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
// 业务逻辑
User user = userService.getUser(id);
model.addAttribute("user", user);
return "user/detail";
}
}
// 5. ViewResolver:视图解析器
// 作用:解析视图名称,返回View对象
public interface ViewResolver {
// 解析视图
View resolveViewName(String viewName, Locale locale);
}
// 常用实现:InternalResourceViewResolver
// 解析JSP视图
public class InternalResourceViewResolver extends UrlBasedViewResolver {
@Override
protected View createView(String viewName, Locale locale) {
// 拼接视图路径
String url = getPrefix() + viewName + getSuffix();
// 创建View对象
return new JstlView(url);
}
}
// 6. View:视图
// 作用:渲染视图,生成响应
public interface View {
// 渲染视图
void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response);
}
// 常用实现:JstlView(JSP视图)
public class JstlView extends InternalResourceView {
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) {
// 将模型数据设置到request
exposeModelAsRequestAttributes(model, request);
// 转发到JSP页面
RequestDispatcher rd = request.getRequestDispatcher(getUrl());
rd.forward(request, response);
}
}
// 7. HandlerInterceptor:拦截器
// 作用:拦截请求,进行预处理和后处理
public interface HandlerInterceptor {
// 前置处理
boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler);
// 后置处理
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView);
// 完成处理
void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);
}
回答技巧:
- 列举核心组件及其作用
- 说明组件之间的关系
- 举例说明各组件的使用
- 画图辅助说明
可能的追问:
- HandlerMapping有哪些实现类?
- HandlerAdapter的作用是什么?
- 如何自定义ViewResolver?
五、MyBatis工作原理(10-15题)
5.1 MyBatis的工作原理是什么?
标准答案:
MyBatis的工作原理包括以下核心流程:
核心要点:
- 加载配置:读取mybatis-config.xml和Mapper.xml
- 创建SqlSessionFactory:通过SqlSessionFactoryBuilder创建
- 创建SqlSession:通过SqlSessionFactory创建
- 获取Mapper代理对象:通过SqlSession.getMapper()获取
- 执行SQL:通过Mapper接口方法执行SQL
- 结果映射:将查询结果映射为Java对象
- 关闭SqlSession:释放资源
完整工作流程图:
┌─────────────────────────────────────────────────────────────┐
│ MyBatis完整工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 加载配置文件 │
│ - mybatis-config.xml(全局配置) │
│ - Mapper.xml(SQL映射文件) │
│ ↓ │
│ 2. 解析配置文件 │
│ - 解析为Configuration对象 │
│ - 包含所有配置信息和SQL语句 │
│ ↓ │
│ 3. 创建SqlSessionFactory │
│ - SqlSessionFactoryBuilder.build(Configuration) │
│ - 单例模式,全局唯一 │
│ ↓ │
│ 4. 创建SqlSession │
│ - SqlSessionFactory.openSession() │
│ - 每次请求创建一个 │
│ ↓ │
│ 5. 获取Mapper代理对象 │
│ - SqlSession.getMapper(UserMapper.class) │
│ - 使用JDK动态代理创建 │
│ ↓ │
│ 6. 执行Mapper方法 │
│ - userMapper.selectById(1L) │
│ ↓ │
│ 7. 代理对象拦截方法调用 │
│ - MapperProxy.invoke() │
│ ↓ │
│ 8. 查找对应的MappedStatement │
│ - 根据方法名查找SQL语句 │
│ - namespace + methodName │
│ ↓ │
│ 9. 执行SQL语句 │
│ - Executor.query() │
│ - 参数处理、SQL拼接、执行查询 │
│ ↓ │
│ 10. 结果映射 │
│ - ResultSetHandler处理结果集 │
│ - 将ResultSet映射为Java对象 │
│ ↓ │
│ 11. 返回结果 │
│ - 返回User对象 │
│ ↓ │
│ 12. 关闭SqlSession │
│ - 释放数据库连接 │
│ │
└─────────────────────────────────────────────────────────────┘
核心组件说明:
// 1. SqlSessionFactoryBuilder:构建器
// 作用:创建SqlSessionFactory
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
// 解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
Configuration config = parser.parse();
// 创建SqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
}
// 2. SqlSessionFactory:会话工厂
// 作用:创建SqlSession
public interface SqlSessionFactory {
// 打开会话
SqlSession openSession();
// 打开会话(指定是否自动提交)
SqlSession openSession(boolean autoCommit);
}
// 3. SqlSession:会话
// 作用:执行SQL、获取Mapper、管理事务
public interface SqlSession {
// 查询单个对象
<T> T selectOne(String statement, Object parameter);
// 查询列表
<E> List<E> selectList(String statement, Object parameter);
// 插入
int insert(String statement, Object parameter);
// 更新
int update(String statement, Object parameter);
// 删除
int delete(String statement, Object parameter);
// 提交事务
void commit();
// 回滚事务
void rollback();
// 获取Mapper
<T> T getMapper(Class<T> type);
// 关闭会话
void close();
}
// 4. Executor:执行器
// 作用:执行SQL语句
public interface Executor {
// 查询
<E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler);
// 更新
int update(MappedStatement ms, Object parameter);
// 提交
void commit(boolean required);
// 回滚
void rollback(boolean required);
}
// 5. MappedStatement:映射语句
// 作用:封装SQL语句和映射信息
public class MappedStatement {
private String id; // namespace + methodName
private SqlSource sqlSource; // SQL语句
private SqlCommandType sqlCommandType; // SQL类型(SELECT、INSERT等)
private ResultMap resultMap; // 结果映射
// ...
}
// 6. StatementHandler:语句处理器
// 作用:处理JDBC Statement
public interface StatementHandler {
// 准备语句
Statement prepare(Connection connection);
// 参数化
void parameterize(Statement statement);
// 查询
<E> List<E> query(Statement statement, ResultHandler resultHandler);
// 更新
int update(Statement statement);
}
// 7. ParameterHandler:参数处理器
// 作用:设置SQL参数
public interface ParameterHandler {
// 设置参数
void setParameters(PreparedStatement ps);
}
// 8. ResultSetHandler:结果集处理器
// 作用:处理查询结果
public interface ResultSetHandler {
// 处理结果集
<E> List<E> handleResultSets(Statement stmt);
}
完整使用示例:
// 1. mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- Mapper映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
// 2. Mapper接口
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int delete(Long id);
}
// 3. Mapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="com.example.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 查询单个 -->
<select id="selectById" resultMap="BaseResultMap">
SELECT id, username, email, create_time
FROM user
WHERE id = #{id}
</select>
<!-- 查询列表 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT id, username, email, create_time
FROM user
</select>
<!-- 插入 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, email, create_time)
VALUES (#{username}, #{email}, #{createTime})
</insert>
<!-- 更新 -->
<update id="update">
UPDATE user
SET username = #{username},
email = #{email}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="delete">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
// 4. 使用MyBatis
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1. 加载配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 3. 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 4. 获取Mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行SQL
User user = userMapper.selectById(1L);
System.out.println(user);
// 6. 提交事务
sqlSession.commit();
} finally {
// 7. 关闭SqlSession
sqlSession.close();
}
}
}
// 5. Spring集成MyBatis
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource)
throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return factory.getObject();
}
}
// 6. 使用Mapper(Spring环境)
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUser(Long id) {
return userMapper.selectById(id);
}
}
Mapper代理对象的创建过程:
// MapperProxy:Mapper代理类
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 1. 如果是Object的方法,直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 2. 获取MapperMethod
MapperMethod mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
// 3. 执行SQL
return mapperMethod.execute(sqlSession, args);
}
}
// MapperMethod:Mapper方法
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.insert(command.getName(), param);
break;
case UPDATE:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.update(command.getName(), param);
break;
case DELETE:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.delete(command.getName(), param);
break;
case SELECT:
if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else {
result = sqlSession.selectOne(command.getName(), param);
}
break;
default:
throw new BindingException("Unknown execution method");
}
return result;
}
}
回答技巧:
- 按照流程顺序逐步说明
- 重点说明核心组件的作用
- 画图辅助说明
- 举例说明实际使用过程
可能的追问:
- Mapper接口如何生成代理对象?
- MyBatis如何处理SQL参数?
- MyBatis如何映射查询结果?
5.2 MyBatis的缓存机制是什么?
标准答案:
MyBatis提供两级缓存机制:一级缓存和二级缓存。
核心要点:
- 一级缓存:SqlSession级别,默认开启,不能关闭
- 二级缓存:Mapper级别,需要手动开启
- 缓存查询顺序:二级缓存 → 一级缓存 → 数据库
- 缓存失效:增删改操作会清空缓存
一级缓存和二级缓存对比:
| 维度 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用域 | SqlSession级别 | Mapper级别 |
| 生命周期 | SqlSession的生命周期 | 应用的生命周期 |
| 默认状态 | 默认开启,不能关闭 | 默认关闭,需要手动开启 |
| 缓存对象 | 查询结果对象 | 查询结果对象的副本 |
| 线程安全 | 线程不安全 | 线程安全 |
| 失效时机 | SqlSession关闭或清空 | 增删改操作 |
一级缓存示例:
@Test
public void testFirstLevelCache() {
SqlSession 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);
// 验证是同一个对象
System.out.println(user1 == user2); // true
} finally {
sqlSession.close();
}
}
// 输出:
// 第一次查询:User(id=1, username=张三) // 执行SQL
// 第二次查询:User(id=1, username=张三) // 从缓存获取
// true
一级缓存失效场景:
@Test
public void testFirstLevelCacheInvalid() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 场景1:不同的SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper.selectById(1L); // 查询数据库
User user2 = userMapper2.selectById(1L); // 再次查询数据库(不同SqlSession)
System.out.println(user1 == user2); // false
// 场景2:执行增删改操作
User user3 = userMapper.selectById(1L); // 查询数据库
userMapper.update(new User(1L, "李四")); // 更新操作,清空缓存
User user4 = userMapper.selectById(1L); // 再次查询数据库
System.out.println(user3 == user4); // false
// 场景3:手动清空缓存
User user5 = userMapper.selectById(1L); // 查询数据库
sqlSession.clearCache(); // 清空缓存
User user6 = userMapper.selectById(1L); // 再次查询数据库
System.out.println(user5 == user6); // false
// 场景4:查询条件不同
User user7 = userMapper.selectById(1L); // 查询id=1
User user8 = userMapper.selectById(2L); // 查询id=2(不同条件)
// 两次都查询数据库
} finally {
sqlSession.close();
}
}
二级缓存配置:
<!-- 1. mybatis-config.xml中开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 2. Mapper.xml中配置缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache/>
<!-- 或者自定义缓存配置 -->
<cache
eviction="LRU" <!-- 缓存淘汰策略:LRU、FIFO、SOFT、WEAK -->
flushInterval="60000" <!-- 刷新间隔:60秒 -->
size="512" <!-- 缓存大小:512个对象 -->
readOnly="true"/> <!-- 只读缓存 -->
<!-- SQL语句 -->
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
二级缓存示例:
// 实体类需要实现Serializable接口
public class User implements Serializable {
private Long id;
private String username;
private String email;
// ...
}
@Test
public void testSecondLevelCache() {
// 第一个SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
try {
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById(1L); // 查询数据库
System.out.println("第一次查询:" + user1);
} finally {
sqlSession1.close(); // 关闭SqlSession,数据进入二级缓存
}
// 第二个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectById(1L); // 从二级缓存获取
System.out.println("第二次查询:" + user2);
} finally {
sqlSession2.close();
}
}
// 输出:
// 第一次查询:User(id=1, username=张三) // 执行SQL
// 第二次查询:User(id=1, username=张三) // 从二级缓存获取
缓存查询流程:
┌─────────────────────────────────────────────────────────────┐
│ MyBatis缓存查询流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 执行查询 │
│ ↓ │
│ 2. 查询二级缓存 │
│ ├─ 命中 → 返回结果 │
│ └─ 未命中 → 继续 │
│ ↓ │
│ 3. 查询一级缓存 │
│ ├─ 命中 → 返回结果 │
│ └─ 未命中 → 继续 │
│ ↓ │
│ 4. 查询数据库 │
│ ↓ │
│ 5. 将结果放入一级缓存 │
│ ↓ │
│ 6. SqlSession关闭时,将结果放入二级缓存 │
│ ↓ │
│ 7. 返回结果 │
│ │
└─────────────────────────────────────────────────────────────┘
回答技巧:
- 先说两级缓存的概念
- 对比两级缓存的区别
- 举例说明缓存的使用
- 说明缓存失效的场景
可能的追问:
- 为什么需要两级缓存?
- 二级缓存的淘汰策略有哪些?
- 如何自定义缓存实现?
六、练习题
6.1 基础练习
练习1:实现一个简单的IOC容器
要求:
- 支持Bean的注册和获取
- 支持单例和原型作用域
- 支持依赖注入
练习2:实现一个简单的AOP代理
要求:
- 使用JDK动态代理
- 实现方法执行前后的日志记录
- 实现方法执行时间统计
练习3:实现一个简单的事务管理器
要求:
- 支持事务的开启、提交、回滚
- 支持事务的传播行为
- 支持异常回滚
6.2 进阶练习
练习4:实现一个简单的DispatcherServlet
要求:
- 支持@RequestMapping注解
- 支持参数解析
- 支持视图渲染
练习5:实现一个简单的MyBatis
要求:
- 支持Mapper接口代理
- 支持SQL执行
- 支持结果映射
6.3 综合练习
练习6:实现一个完整的SSM项目
要求:
- 使用Spring IOC管理Bean
- 使用Spring AOP实现日志记录
- 使用Spring事务管理
- 使用SpringMVC处理请求
- 使用MyBatis操作数据库
七、学习检查清单
- 理解Spring IOC容器的工作原理
- 掌握Bean的生命周期
- 理解Spring AOP的实现原理
- 掌握五种通知类型的执行顺序
- 理解Spring事务管理的实现方式
- 掌握七种事务传播行为
- 了解事务失效的场景
- 理解SpringMVC的请求处理流程
- 掌握SpringMVC的核心组件
- 理解MyBatis的工作原理
- 掌握MyBatis的缓存机制
- 能够在面试中清晰准确地回答SSM框架相关问题
文档说明:
- 本文档包含SSM框架原理的核心面试题
- 每道题都包含标准答案、核心要点、代码示例、回答技巧
- 建议结合实际项目经验进行学习
- 面试前重点复习标准答案和核心要点
更多推荐




所有评论(0)