每天学一篇面经(第一天)
说说Java常用的框架
Spring框架:Java企业级项目的脚手架,提供IOC、DI、AOP SpringBoot框架:简化Spring的开发,不用配置一大堆XML,并且支持自动装配 MyBatis框架:常用ORM框架,解决对象关系映射问题
SpringBoot的自动装配是什么?
SpringBoot的自动装配:
- @SpringBootApplication开启自动装配
- @EnableAutoConfiguration中的@Import会触发AutoConfigurationImportSelector
- AutoConfigurationImportSelector去扫描META-INF中的自动装配配置
- 和@ConditionOnxx配合决定哪些自动装配配置生效
- 装配满足条件的Bean
SpringBoot启动的时候会加载什么?
Spring Boot 启动时,先进入 SpringApplication.run(),然后创建 ApplicationContext、读取配置环境、扫描启动类和组件、执行自动装配、刷新容器并实例化 Bean;如果是 Web 项目,还会启动内嵌 Tomcat,最后执行 CommandLineRunner / ApplicationRunner
Bean 的生命周期
- 实例化:通过反射创建 Bean 对象。 Spring 容器会先根据 Bean 的定义信息创建对象,一般是通过反射来完成。这个时候对象虽然已经存在了,但里面的属性通常还没有赋值,所以它还只是一个“空对象”或者“半成品对象”。
- 属性注入:给 Bean 注入依赖和配置。
在对象创建完成之后,Spring 会把这个 Bean 依赖的其他对象或配置值注入进去,比如常见的
@Autowired、@Resource、@Value都是在这个阶段生效。经过属性注入后,Bean 才真正具备完整的依赖关系。 - 初始化:执行 Bean 的初始化逻辑。
当 Bean 的依赖都注入完成后,Spring 会调用初始化相关的方法,比如
@PostConstruct、InitializingBean接口中的afterPropertiesSet(),或者手动配置的init-method。这个阶段一般用来做资源准备、参数检查、连接创建等工作。 - 使用:Bean 已经可以被业务代码正常调用。
初始化完成后,Bean 就进入可用状态了,可以被 Spring 容器提供给业务代码使用。我们平时在项目中通过
@Autowired注入后直接调用的方法,本质上拿到的就是已经完成前面流程的 Bean。 - 销毁:容器关闭时执行 Bean 的清理操作。
当 Spring 容器关闭时,会执行 Bean 的销毁逻辑,比如调用
@PreDestroy、DisposableBean的destroy()方法,或者配置的destroy-method。这个阶段主要用于释放资源,例如关闭数据库连接、线程池、文件流等,避免资源泄漏。
Ioc介绍一下,怎么实现的
IOC即控制反转,是一种设计思想,将对象的创建和管理的权限交给外部容器,减少代码耦合,提高管理效率 IOC的实现原理:工厂模式+反射机制+ 配置文件或注解 详细:
-
读取配置信息
Spring 启动时会读取 XML、注解、JavaConfig 等配置,解析哪些类需要交给容器管理。 -
实例化对象
Spring 容器通过反射创建这些类的对象。 -
保存到容器中
创建好的对象会放到 IOC 容器中,通常是一个类似 Map 的结构,方便后续获取。 -
依赖注入(DI)
如果一个对象依赖另一个对象,Spring 会自动把依赖对象注入进去,而不是让开发者手动new。 -
统一管理生命周期
Spring 会负责 Bean 的初始化、销毁等过程。
DI是什么?和IOC有什么关系?怎么实现的?
DI即依赖注入。由容器在运行时,将对象所依赖的其他对象注入进去,而不是由对象自己创建依赖。DI是IOC思想的一种实现
DI的实现方式
通过构造器注入依赖,优点是安全性高,依赖不可变 通过 set 方法注入,优点是灵活,可选依赖 字段注入(@Autowired、@Resouce),直接在属性上使用注解,写法简单
DI实现原理
- 容器启动,扫描 Bean(@Component / @Bean)
- 创建 Bean 实例(反射)
- 查找该 Bean 依赖的字段或构造器参数(如 @Autowired)
- 从容器中找到对应的依赖 Bean
- 通过反射进行注入(赋值或调用构造器)
补充
@Resource和@Autowired的区别: 来源不同: @Resource是JDK提供的注解,@Autowired是Spring提供的注解。 注入方式不同: @Autowired默认按类型注入,@Resource默认按名称注入 注入流程不同: @Autowired:
- 先按类型找
- 如果有多个,再按名称匹配
- 可以配合 @Qualifier 指定
@Resource: - 先按名称找(name属性或字段名)
- 找不到再按类型
循环依赖是怎么解决的?
Spring 通过 三级缓存 + 提前暴露对象(early exposure) 的机制,解决了 **单例 Bean 的循环依 流程:
- 先从一级缓存找
- 再找二级缓存
- 再通过三级缓存创建早期对象 流程详解:
- 创建 A(实例化,还没注入属性)
- 将 A 的“早期对象”放入三级缓存(singletonFactories)
- A 需要注入 B → 开始创建 B
- 创建 B(实例化)
- B 需要注入 A → 从缓存中获取 A 的“早期对象”
- B 完成初始化
- 回到 A → 注入 B → A 完成初始化
三级缓存
一级缓存(singletonObjects)
- 存放完整初始化后的 Bean
- 二级缓存(earlySingletonObjects)
- 存放提前曝光的“半成品 Bean”
- 三级缓存(singletonFactories)
- 存放 ObjectFactory,用于生成代理对象(解决 AOP 问题)
为什么需要三层缓存?
解决 AOP 代理问题 如果只有二级缓存:
- 可能提前暴露的是“原始对象”
- 但最终应该注入的是“代理对象”
- 会导致 AOP 失效或对象不一致
所以需要三级缓存: - 通过 ObjectFactory 延迟生成“代理对象”
不能解决循环依赖的场景
- 构造器注入的循环依赖
- 因为对象还没创建,无法提前暴露
- 原型(prototype)Bean
- Spring 不缓存 prototype Bean
登录的时候http请求怎么发的
- 用户在前端输入账号和密码后,前端会将登录信息封装成一个 HTTP 请求发送给后端。
- 请求到达后端后,由 Controller 接收请求,Service 处理登录校验逻辑,再与数据库交互验证用户信息。
- 如果验证通过,后端会返回 Token 或 Session 信息给前端,前端保存后,后续请求再携带这个凭证完成身份认证。
Session和JWT的区别
数据存储: Session数据保存在服务端,客户端只保存SessionId JWT的数据保存在客户端,服务端不存储,是无状态的 分布式支持: Session天然不支持分布式 JWT天然支持分布式 数据内容: Session只存SessionId JWT可以存用户信息,不如userId 性能: Session每次请求都要查看服务端存储 JWT服务端只需校验 Token
JWT的组成
- Header(头部)
- Payload(载荷,存放用户信息)
- Signature(签名,防篡改)
JWT的缺点
无法主动失效,泄露后会被伪造
HTTPS加密是怎么做的?
先用非对称加密安全地协商出一个对称密钥,再用这个对称密钥加密后续通信。 详细流程:
- 通过 TLS 握手验证服务端身份
- 协商出一个会话密钥
- 后续数据传输用对称加密
- 同时配合消息认证机制,保证数据没被篡改
为什么不全程加密?
对称加密:加密和解密用同一把密钥,速度快但是需要保证密钥安全 非对称加密:有一对密钥,公钥可以公开,私钥由服务端保存。可以安全传输密钥,但是传输速度慢。
CA
CA是一个受信任的第三方机构,负责给网站签发数字证书,用来证明“某个公钥确实属于某个网站”。浏览器通过信任 CA,从而信任网站的身份。
CA 的工作流程
- 网站向 CA 申请证书
- CA 验证网站身份
- CA 用自己的私钥“签名”证书
- 浏览器内置“受信任的 CA 列表”
- 浏览器验证证书
怎么防中间人攻击?
中间人攻击是指攻击者在客户端和服务端之间拦截并篡改通信数据。防范的核心是:身份认证 + 加密传输 + 完整性校验。
攻击流程
- 攻击者伪装成服务器和客户端通信
- 同时伪装成客户端和服务器通信
- 可以窃听、篡改数据
HTTPS是怎么解决的?
数字证书:防止攻击者伪造服务器身份 非对称加密:中间人无法解密 对称加密:保证数据加密传输
java都有哪些锁,synchronized怎么实现的,能用在哪
Synchronized、ReentrantLock、ReadWriteLock、StampedLock
按竞争策略
| 类型 | 代表 | 特点 |
|---|---|---|
| 悲观锁 | synchronized、ReentrantLock | 先加锁再操作 |
| 乐观锁 | CAS、StampedLock 乐观读 | 假设无冲突,失败重试 |
按是否可重入
| 类型 | 说明 |
|---|---|
| 可重入 | synchronized、ReentrantLock,同线程可多次加锁 |
| 不可重入 | 自旋锁简单实现,容易死锁 |
按公平性
| 类型 | 说明 |
|---|---|
| 公平锁 | new ReentrantLock(true),FIFO 队列,吞吐低 |
| 非公平锁 | 默认,允许插队,吞吐高,可能饥饿 |
// 1. 修饰实例方法 —— 锁是 this 对象
public synchronized void method() { ... }
// 2. 修饰静态方法 —— 锁是 Class 对象
public static synchronized void staticMethod() { ... }
// 3. 修饰代码块 —— 锁是指定对象
synchronized (lockObj) { ... }
① 偏向锁 第一个线程获取时,把 ThreadID 写入 Mark Word 再次进入直接比较 ThreadID,无 CAS 开销 → 适合:单线程反复进入同一锁
② 轻量级锁(自旋锁) 有第二个线程竞争 → 偏向锁撤销 线程在栈帧创建 Lock Record,CAS 替换 Mark Word 失败则自旋重试(JDK6+ 自适应自旋) → 适合:竞争短暂,持锁时间短
③ 重量级锁 自旋达到阈值仍失败 → 膨胀为重量级锁 依赖 OS 的 Mutex,线程挂起进入内核态 → 适合:竞争激烈,持锁时间长
![[Java锁.png]]
Spring里的事务怎么用的
编程式事务(了解即可)
@Autowired
TransactionTemplate transactionTemplate;
public void transfer() {
transactionTemplate.execute(status -> {
accountDao.deduct(1, 100);
accountDao.add(2, 100);
return null;
// 抛异常会自动回滚
});
}
声明式事务
@Service
public class AccountService {
@Transactional // 加这一行,方法变成事务方法
public void transfer(int fromId, int toId, int amount) {
accountDao.deduct(fromId, amount);
accountDao.add(toId, amount);
// 抛 RuntimeException → 自动回滚
// 正常返回 → 自动提交
}
}
声明式事务的底层:
调用 transfer()
↓
[Spring AOP 代理]
↓
TransactionManager.begin() // 开启事务
↓
执行真实方法
↓
成功 → commit() 异常 → rollback()
事务的传播行为
事务方法 A 调用事务方法 B,B 用哪个事务?这就是传播行为。
| 传播行为 | 含义 |
|---|---|
REQUIRED(默认) |
有事务就加入,没有就新建 |
REQUIRES_NEW |
总是新建事务,挂起外层事务 |
NESTED |
嵌套事务,外层回滚影响内层,内层回滚不影响外层(savepoint) |
SUPPORTS |
有事务就加入,没有就非事务执行 |
NOT_SUPPORTED |
非事务执行,挂起已有事务 |
MANDATORY |
必须在事务中,否则抛异常 |
NEVER |
不能在事务中,否则抛异常 |
声明式事务避坑
默认只回滚 RuntimeException(非检查异常) 和 Error。如果抛出的是IOException就会直接提交
坑1:同类内部调用
@Service
public class OrderService {
public void createOrder() {
// ❌ 直接调用同类方法,绕过了 AOP 代理,事务不生效!
this.saveOrder();
}
@Transactional
public void saveOrder() {
// ...
}
}
// ✅ 解决方案:注入自己 或 抽到另一个 Bean
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入代理对象
public void createOrder() {
self.saveOrder(); // 通过代理调用,事务生效
}
@Transactional
public void saveOrder() { ... }
}
坑2:方法不是 public
// ❌ AOP 代理无法拦截 private/protected 方法
@Transactional
private void doSomething() { ... }
// ✅ 必须是 public
@Transactional
public void doSomething() { ... }
坑3:异常被吞掉
// ❌ 异常被 catch 了,Spring 感知不到,不会回滚
@Transactional
public void transfer() {
try {
accountDao.deduct(1, 100);
accountDao.add(2, 100);
} catch (Exception e) {
log.error("出错了", e); // 异常被吃掉,事务提交!
}
}
// ✅ 要么重新抛出,要么手动标记回滚
@Transactional
public void transfer() {
try {
accountDao.deduct(1, 100);
accountDao.add(2, 100);
} catch (Exception e) {
log.error("出错了", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手动标记
// 或者 throw new RuntimeException(e);
}
}
面经链接
https://www.nowcoder.com/feed/main/detail/0dfe5838ae16439f9b2dfa3effb08580
#面经#记录每天Java和Agent面经学习
美团工作强度 2457人发布