java面试题(中级题汇总)
1. 计算机网络传输层有哪些协议?分别适用于什么场景?
传输层核心协议有两个:
- TCP(传输控制协议):
特点:面向连接、可靠传输(通过三次握手建立连接、四次挥手断开连接,支持重传、拥塞控制等)、字节流传输。
适用场景:对可靠性要求高的场景,如文件传输(FTP)、网页浏览(HTTP/HTTPS)、邮件发送(SMTP)等。
- UDP(用户数据报协议):
特点:无连接、不可靠(不保证送达,无重传机制)、数据报传输、效率高。
适用场景:对实时性要求高但可容忍少量丢包的场景,如视频 / 语音通话(流媒体)、在线游戏、DNS 查询等。
2. 多线程的使用场景有哪些?线程开启多少个合适?判断标准有哪些?
多线程使用场景:
- 异步任务处理(如日志记录、邮件发送,不阻塞主线程);
- 并发 IO 操作(如多文件下载、数据库批量查询,利用 IO 等待时间);
- 实时数据处理(如消息队列消费、实时监控);
- 复杂计算任务拆分(如大数据量排序、分布式计算)。
线程数量没有 “固定答案”,需结合任务类型判断:
- CPU 密集型任务(如数学计算):线程数≈CPU 核心数(避免线程切换开销);
- IO 密集型任务(如网络请求、文件读写):线程数可适当增加(通常为 CPU 核心数 ×2,或根据 IO 等待时间调整)。
判断标准:
- 系统响应时间(避免线程过多导致调度延迟);
- 资源利用率(CPU、内存、IO 不超限);
- 任务类型(CPU/IO 密集);
- 实际压测结果(通过监控工具观察性能瓶颈)。
3. 线程池的提交方式有哪几种?
Java 线程池(ExecutorService)有两种核心提交方式:
- execute(Runnable command):
无返回值,仅执行任务;若任务抛出异常,会直接抛出(需手动捕获)。
- submit(Callable<T> task) 或 submit(Runnable task, T result):
有返回值(Future<T>),可通过Future获取任务结果或异常;任务异常会被封装在Future中(调用get()时抛出)。
4. 锁的实现原理介绍一下?
Java 中锁的实现主要分为两类:
(1)synchronized 关键字
- 底层依赖 对象头(Mark Word) 和 Monitor(监视器锁):
-
- 对象头存储锁状态(无锁、偏向锁、轻量级锁、重量级锁);
-
- 重量级锁通过 Monitor 实现:每个对象关联一个 Monitor,线程竞争锁时需获取 Monitor 的所有权(涉及操作系统互斥锁,开销较大)。
- JDK 6 后优化:引入偏向锁(减少无竞争时的开销)、轻量级锁(自旋锁,避免立即升级为重量级锁)。
(2)java.util.concurrent.locks.Lock 接口(如 ReentrantLock)
- 基于 AQS(AbstractQueuedSynchronizer) 实现:
-
- AQS 通过内置的双向队列管理等待线程,用state变量表示锁的状态(0:无锁,>0:被持有);
-
- 线程竞争锁时,通过 CAS 操作修改state,失败则进入队列等待(可响应中断、设置超时)。
5. TCP 协议中拥塞控制的主要作用是什么?
拥塞控制是 TCP 协议为避免网络拥塞而设计的机制,主要作用:
- 防止发送方发送速率超过网络承载能力,避免数据包大量丢失;
- 维持网络稳定性,保证数据传输的高效性(在拥塞与吞吐量间平衡)。
核心算法包括:慢启动、拥塞避免、快重传、快恢复。
6. String 类的常用函数有哪些?列举十种。
String类提供了丰富的字符串操作方法,常用的有:
- int length():返回字符串长度;
- char charAt(int index):获取指定索引的字符;
- String substring(int beginIndex, int endIndex):截取子串(左闭右开);
- boolean equals(Object anObject):比较字符串内容是否相等;
- int indexOf(String str):查找子串首次出现的索引(未找到返回 - 1);
- String replace(char oldChar, char newChar):替换字符;
- String trim():去除首尾空白字符;
- String toLowerCase() / toUpperCase():转换为小写 / 大写;
- String[] split(String regex):按正则分割字符串;
- boolean startsWith(String prefix):判断是否以指定前缀开头。
7. String、StringBuffer、StringBuilder 的区别有哪些?所有类名包含 Buffer 的类的内部实现原理是什么?有什么优势?
(1)三者区别
特性 | String | StringBuffer | StringBuilder |
可变性 | 不可变(底层数组final) | 可变(数组可修改) | 可变(数组可修改) |
线程安全 | 安全(不可变) | 安全(方法加synchronized) | 不安全(无同步) |
性能 | 差(修改时创建新对象) | 中(同步开销) | 优(无同步开销) |
(2)Buffer 类(如ByteBuffer、StringBuffer)的实现原理与优势
- 原理:内部基于数组存储数据,通过position(当前位置)、limit(可操作上限)、capacity(容量)三个指针控制数据读写,避免频繁创建新对象。
- 优势:
-
- 高效处理数据:通过指针操作减少数组复制,提升 IO / 字符串操作性能;
-
- 支持批量操作:如ByteBuffer可直接与 Channel 交互,适合网络 / 文件传输。
8. String 字符串的不可变是指哪里不可变?
String的不可变性体现在:
- 底层存储数据的数组被final修饰(JDK 8 为char[],JDK 9 + 为byte[]),无法修改数组引用;
- 类中未提供修改数组元素的方法(如substring、replace等方法均返回新String对象)。
即:一旦创建String对象,其内容(字符序列)无法被修改,任何 “修改” 操作都会生成新对象。
9. HTTP 协议有哪些字段,列举 3 个就可以。
HTTP 请求 / 响应头包含多个字段,常用的有:
- Host:指定请求的服务器域名和端口(如Host: www.example.com);
- Content-Type:表示请求 / 响应体的数据格式(如Content-Type: application/json、text/html);
- User-Agent:描述发送请求的客户端(如浏览器型号、操作系统,User-Agent: Mozilla/5.0 ...)。
10. Java 异常类有哪些?分别管理什么异常?
Java 异常体系以Throwable为根,分为两类:
(1)Error(错误)
- 表示 JVM 无法处理的严重错误(如内存溢出),无需捕获,如:
-
- OutOfMemoryError:内存不足;
-
- StackOverflowError:栈溢出(如递归过深)。
(2)Exception(异常)
- 表示程序可处理的异常,分为受检异常(编译时需捕获)和非受检异常(RuntimeException及其子类,编译时无需捕获)。
-
- 受检异常:如IOException(IO 操作错误)、ClassNotFoundException(类未找到);
-
- 非受检异常:如NullPointerException(空指针)、IndexOutOfBoundsException(索引越界)、ArithmeticException(算术错误,如除 0)。
11. Java 反射获取类信息的方式有哪几种?分别是什么?
反射获取类信息(Class对象)的方式有 3 种:
- Class.forName(String className):
通过类的全限定名加载类(触发类初始化),如Class.forName("java.lang.String")。
- 对象.getClass():
通过实例对象获取Class,如"hello".getClass()。
- 类名.class:
直接通过类名获取(不触发类初始化),如String.class。
12. Java 代理的主要方法有哪几种?列举代理的使用场景 2 个。
主要代理方法
- 静态代理:
手动编写代理类(实现与目标类相同的接口),在代理类中调用目标方法并添加增强逻辑(如日志)。缺点:代理类与目标类耦合,不易扩展。
- 动态代理:
-
- JDK 动态代理:基于接口生成代理类(通过Proxy.newProxyInstance()),要求目标类实现接口;
-
- CGLIB 动态代理:通过继承目标类生成代理子类(无需接口),基于 ASM 字节码技术。
使用场景
- AOP(面向切面编程):如 Spring AOP 通过代理实现日志、事务、权限等横切逻辑;
- 远程调用:如 RPC 框架(Dubbo)通过代理封装网络通信细节,让调用远程服务像调用本地方法。
13. equals () 方法的作用是什么?重写 equals 需要注意哪些事项?为什么?
- 作用:判断两个对象的内容是否相等(==判断地址是否相等)。
重写equals的注意事项(需满足 5 大特性)
- 自反性:x.equals(x)必须返回true;
- 对称性:x.equals(y)与y.equals(x)结果一致;
- 传递性:若x.equals(y)且y.equals(z),则x.equals(z);
- 一致性:多次调用结果相同(对象未被修改时);
- 与hashCode()一致:若x.equals(y)为true,则x.hashCode()必须等于y.hashCode()。
原因
- 违反前 4 点会导致逻辑错误(如集合中判断元素相等时出错);
- 违反第 5 点会导致HashMap、HashSet等基于哈希的集合无法正常工作(如两个相等的对象可能被存入不同桶中)。
14. Java 是按值传递还是按引用传递?什么是引用传递,什么是值传递,哪些语言支持引用传递?
- Java 是按值传递:
-
- 基本类型:传递值的副本(修改副本不影响原变量);
-
- 引用类型:传递引用地址的副本(修改副本指向的对象内容会影响原对象,但修改副本的指向不影响原引用)。
概念区分
- 值传递:传递数据的副本,函数内修改副本不影响原数据;
- 引用传递:传递数据的地址(而非副本),函数内修改会直接影响原数据。
支持引用传递的语言
如 C++(通过&声明引用参数)、Python(对象传递本质是引用传递)。
15. 描述 java 的类初始化顺序?如果存在继承,初始化顺序会如何
单个类的初始化顺序(从先到后)
- 静态变量(类变量);
- 静态代码块(static {});
- 实例变量(成员变量);
- 构造代码块({},每次创建实例时执行);
- 构造方法。
存在继承时的初始化顺序
- 父类静态成员 / 静态代码块(只执行一次);
- 子类静态成员 / 静态代码块(只执行一次);
- 父类实例成员 / 构造代码块;
- 父类构造方法;
- 子类实例成员 / 构造代码块;
- 子类构造方法。
规律:静态优先于实例,父类优先于子类。
16. 本地方法栈有什么作用?
本地方法栈是 JVM 内存区域之一,作用是:
- 支持本地方法(native修饰的方法) 的执行,存储本地方法的参数、返回值、局部变量等;
- 本地方法通常用 C/C++ 实现,本地方法栈与操作系统的栈结构相关(不同系统实现不同)。
17. 描述 Java 的双亲委派机制,为什么要用到双亲委派机制
- 双亲委派机制:类加载时,子类加载器先委托父类加载器加载类,若父类加载器无法加载(找不到类),子类加载器才尝试自己加载。
核心加载器(从父到子)
- 启动类加载器(Bootstrap):加载JAVA_HOME/lib下的核心类(如java.lang.String);
- 扩展类加载器(Extension):加载JAVA_HOME/lib/ext下的类;
- 应用类加载器(App):加载应用 classpath 下的类。
作用
- 避免类重复加载:同一类只会被一个加载器加载;
- 保证核心类安全:防止自定义类(如java.lang.String)篡改核心类,避免安全风险。
18. 重写和重载的区别是什么?
维度 | 重写(Override) | 重载(Overload) |
定义 | 子类覆盖父类的方法 | 同一类中方法名相同,参数不同 |
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须相同(个数、类型、顺序) | 必须不同(个数、类型、顺序) |
返回值类型 | 子类返回值需与父类兼容(<= 父类) | 可不同 |
访问修饰符 | 子类修饰符权限 >= 父类 | 无限制 |
异常 | 子类抛出异常 <= 父类 | 无限制 |
作用 | 实现多态(运行时绑定) | 提高方法灵活性(编译时绑定) |
19. 子类构造方法调用父类构造方法的注意事项有哪些?
- 默认调用父类无参构造:子类构造方法第一行隐含super(),若父类无无参构造,编译报错;
- 显式调用父类有参构造:若父类只有有参构造,子类必须在构造方法第一行用super(参数)显式调用;
- 不可调用多次:super()或this()(调用本类其他构造方法)只能出现一次,且必须在第一行。
20. 子类实例初始化是否触发父类实例初始化?
是。
子类实例化时,会先执行父类的实例初始化(实例变量、构造代码块、构造方法),再执行子类的实例初始化。原因是:子类构造方法第一行会调用父类构造方法(super()),触发父类实例初始化。
21. instanceof 关键字的作用是什么?
instanceof用于判断对象是否是某个类(或接口)的实例,包括子类实例。
格式:对象 instanceof 类/接口,返回boolean。
例如:"hello" instanceof String返回true,new Integer(1) instanceof Number返回true(Integer是Number的子类)。
22. 基本类型的强制类型转换是否会丢失精度?引用类型的强制类型转换需要注意什么?
- 基本类型:可能丢失精度。
-
- 如double a = 3.14; int b = (int)a;(b为 3,丢失小数部分);
-
- 整数类型间转换(如long转int)可能溢出(值超出目标类型范围)。
- 引用类型:需确保对象的实际类型是目标类型或其子类,否则会抛出ClassCastException。
例如:Object obj = "hello"; String s = (String)obj;(正确,obj实际是String);若obj是Integer,强转为String则报错。
23. 重入锁有哪些?为什么要有重入锁?
- 重入锁:允许同一线程多次获取同一把锁的锁(避免死锁)。Java 中包括:
-
- synchronized(隐式重入);
-
- ReentrantLock(显式重入,实现Lock接口)。
为什么需要重入锁?
避免同一线程在递归调用或多次获取锁时死锁。例如:
synchronized void methodA() { methodB(); } // 同一线程调用methodA后,可再次获取锁执行methodB
synchronized void methodB() { ... }
若锁不可重入,线程执行methodA时已持有锁,调用methodB会再次请求锁,导致死锁。
24. 指令重排序的优点是什么?由什么原因导致的?
- 优点:提高 CPU 利用率和程序执行效率。例如:编译器或 CPU 可调整指令顺序,避免因 IO、缓存等操作导致的等待。
导致原因
- 编译器优化:编译时调整指令顺序(不改变单线程语义);
- CPU 指令重排序:CPU 为提高流水线利用率,乱序执行指令;
- 内存系统重排序:缓存、写缓冲区导致读 / 写操作顺序与代码顺序不一致。
注意
多线程环境下,指令重排序可能导致可见性问题,需用volatile、synchronized等禁止重排序。
25. Arrays.sort () 的内部原理介绍一下?
Arrays.sort()的实现因 JDK 版本和数据类型而异:
- 基本类型(如int[]、char[]):
JDK 7 + 用双轴快速排序(Dual-Pivot Quicksort),比传统快排更高效(划分更均衡)。
- 对象数组(如String[]):
JDK 7 + 用TimSort(归并排序 + 插入排序的混合算法),针对实际数据中常见的 “部分有序” 场景优化,稳定性更好(相等元素顺序不变)。
26. 堆排序的时间复杂度是多少,空间复杂度是多少?
- 时间复杂度:O(n log n)(最好、最坏、平均均为此值,稳定性优于快排);
- 空间复杂度:O(1)(原地排序,仅需常数级额外空间)。
27. 字符串 “asdasjkfkasgfgshaahsfaf” 经过哈夫曼编码之后存储比特数是多少?
步骤:
- 统计字符频率(遍历字符串,计数每个字符出现次数):
字符串:a s d a s j k f k a s g f g s h a a h s f a f
统计结果(去重后):
a:7, s:6, f:4, g:2, h:2, d:1, j:1, k:2
- 构建哈夫曼树:
每次取频率最小的两个节点合并,新节点频率为两者之和,重复至只剩一个节点。
- 计算编码长度:
哈夫曼编码中,每个字符的编码长度为其在树中的深度。假设最终编码长度:
a:2, s:2, f:3, g:3, h:3, d:4, j:4, k:3
- 总比特数:
7×2 + 6×2 + 4×3 + 2×3 + 2×3 + 1×4 + 1×4 + 2×3 = 14 + 12 + 12 + 6 + 6 + 4 + 4 + 6 = 64。
28. CPU 高速缓存的优点和缺点有哪些?
- 优点:
-
- 解决 CPU 与内存速度不匹配问题(CPU 速度远快于内存),减少 CPU 等待时间;
-
- 提高数据访问效率(缓存速度接近 CPU,远快于内存)。
- 缺点:
-
- 缓存一致性问题:多核心 CPU 的缓存中同一数据可能不一致,需 MESI 等协议解决(开销增加);
-
- 容量有限:缓存容量远小于内存,可能频繁发生缓存失效(Cache Miss);
-
- 硬件复杂度高:需设计缓存映射(直接映射、组相联等)、替换策略(LRU 等)。
29. 线程安全的类有哪些?列举 2 个以上就可以
线程安全类指多线程并发访问时,无需额外同步即可保证数据正确的类:
- Vector:动态数组,方法加synchronized;
- Hashtable:哈希表,方法加synchronized;
- ConcurrentHashMap:并发哈希表(JDK 1.7 分段锁,JDK 1.8 CAS+ synchronized);
- StringBuffer:字符串缓冲区,方法加synchronized。
30. 什么是 LRU 算法?
LRU(Least Recently Used,最近最少使用)是一种缓存淘汰策略:
- 当缓存满时,优先淘汰最久未被使用的缓存项,保留最近使用的项。
- 应用场景:如 Redis 缓存、浏览器缓存、Android 图片缓存等,利用 “局部性原理”(最近使用的项可能再次被使用)提高缓存命中率。
31. 何为 Spring Bean 容器?Spring Bean 容器与 Spring IOC 容器有什么不同吗?
- Spring Bean 容器:管理 Bean 的创建、依赖注入、生命周期的容器,核心是BeanFactory接口,负责 Bean 的实例化和管理。
- Spring IOC 容器:IOC(控制反转)是一种设计思想,Bean 容器是 IOC 思想的实现载体。IOC 容器不仅包括 Bean 容器的功能,还强调 “将对象控制权从代码转移到容器”,通过依赖注入(DI)解耦组件。
- 关系:Bean 容器是 IOC 容器的核心组成部分,IOC 容器 = Bean 容器 + 依赖管理 + 生命周期管理等扩展功能。
32. Spring IOC 如何理解?
IOC(Inversion of Control,控制反转)是 Spring 的核心思想,本质是将对象的创建、依赖关系维护的控制权从代码转移到容器。
- 传统方式:对象 A 依赖对象 B 时,A 需主动创建 B(B b = new B()),耦合度高;
- IOC 方式:容器负责创建 B 并注入到 A 中(依赖注入),A 只需声明依赖,无需关心 B 的创建,降低耦合。
简言之:“以前我找对象,现在对象来找我”。
33. Spring DI 如何理解?
DI(Dependency Injection,依赖注入)是 IOC 的具体实现方式:容器在创建对象时,自动将其依赖的对象(如 Service、DAO)注入到当前对象中。
- 注入方式:
-
- 构造器注入:通过构造方法参数注入;
-
- Setter 注入:通过setXxx()方法注入;
-
- 字段注入:通过@Autowired直接注入字段。
作用:解耦组件依赖,提高代码灵活性和可测试性。
34. Spring 中基于注解如何配置对象作用域?以及如何配置延迟加载机制?
(1)配置对象作用域(@Scope注解)
@Service
@Scope("singleton") // 单例(默认,容器启动时创建,全局唯一)
public class UserService {}
@Service
@Scope("prototype") // 多例(每次获取时创建新对象)
public class OrderService {}
// Web环境特有:
@Scope("request") // 每个请求创建一个实例
@Scope("session") // 每个会话创建一个实例
(2)配置延迟加载(@Lazy注解)
- 作用:单例 Bean 默认在容器启动时创建,@Lazy使其在首次被使用时创建(减少启动时间)。
@Service
@Lazy // 延迟加载
public class UserService {}
35. Spring 工厂底层构建 Bean 对象借助什么机制?当对象不使用了要释放资源,目的是什么?何为内存泄漏?
- 构建 Bean 的机制:反射(通过Class.newInstance()、构造器 / 方法的反射调用创建对象并注入依赖)。
- 释放资源的目的:
回收对象占用的内存、文件句柄、网络连接等资源,避免资源耗尽(如内存溢出、文件描述符超限)。
- 内存泄漏:
对象已不再被使用(无引用),但因未被 GC(垃圾回收器)回收而持续占用内存的现象。常见原因:静态集合持有对象引用、未关闭资源(如Connection、ThreadLocal未清理)。
36. 描述 Spring MVC 处理流程及应用优势?
处理流程
- 用户发送请求:请求被DispatcherServlet(前端控制器)接收;
- 寻找处理器:DispatcherServlet通过HandlerMapping找到处理请求的Controller(处理器);
- 执行处理器:HandlerAdapter适配并调用Controller的方法,返回ModelAndView(数据 + 视图名);
- 解析视图:ViewResolver将视图名解析为View对象;
- 响应结果:View渲染数据并返回给用户。
应用优势
- 松耦合:组件(DispatcherServlet、Controller、View等)通过接口交互,易于扩展;
- MVC 架构:分离模型(Model)、视图(View)、控制器(Controller),职责清晰;
- 灵活配置:支持注解、XML 配置,适配多种视图技术(JSP、Thymeleaf、JSON 等);
- 集成 Spring 生态:无缝整合 Spring 的 IOC、AOP、事务等功能。
37. Spring 中的事务处理方式及优缺点?
Spring 事务处理有两种方式:
(1)编程式事务
通过代码手动控制事务(如TransactionTemplate或PlatformTransactionManager):
transactionTemplate.execute(status -> {
// 业务逻辑
return result;
});
- 优点:灵活,可精确控制事务边界;
- 缺点:代码侵入性强(事务逻辑与业务逻辑混合),繁琐。
(2)声明式事务
通过@Transactional注解或 XML 配置声明事务,底层基于 AOP 实现:
@Transactional
public void saveUser() {
// 业务逻辑(自动开启、提交/回滚事务)
}
- 优点:无代码侵入,配置简单,易于维护;
- 缺点:粒度较粗(基于方法),复杂场景(如部分逻辑不参与事务)需额外处理。
38. MyBatis 应用中 #与 $ 有什么异同点?
维度 | #{} | ${} |
原理 | 预编译参数(占位符?) | 字符串直接替换(拼接 SQL) |
SQL 注入 | 安全(参数不参与 SQL 解析) | 不安全(参数直接拼入 SQL) |
适用场景 | 传递参数(如where id = #{id}) | 动态 SQL 片段(如表名、排序字段) |
类型转换 | 自动转换(如字符串加引号) | 不转换(需手动处理) |
39. MyBatis 应用动态 SQL 解决了什么问题?
动态 SQL 通过<if>、<where>、<foreach>等标签,解决了传统 JDBC 中SQL 拼接的繁琐与风险:
- 避免手动拼接WHERE条件(如多余的AND/OR);
- 简化批量操作(如IN查询、批量插入);
- 减少重复 SQL 代码,提高可维护性;
- 避免因拼接错误导致的 SQL 语法异常。
40. Shiro 框架权限管理时的认证和授权流程描述?
(1)认证流程(登录)
- 前端提交用户名 / 密码,后端封装为UsernamePasswordToken;
- 调用Subject.login(token),委托给SecurityManager;
- SecurityManager调用Authenticator(认证器);
- Authenticator通过Realm(数据源)获取用户信息(如从数据库查密码);
- 比对token中的密码与Realm返回的密码,一致则认证成功,否则抛IncorrectCredentialsException。
(2)授权流程(权限校验)
- 认证成功后,调用Subject.hasRole("admin")或isPermitted("user:delete");
- 请求委托给SecurityManager,再交给Authorizer(授权器);
- Authorizer通过Realm获取用户拥有的角色 / 权限;
- 比对用户请求的角色 / 权限与Realm返回的结果,判断是否授权。
41. BeanFactory 和 ApplicationContext 有什么区别?
维度 | BeanFactory | ApplicationContext |
定义 | 基础接口,IOC 容器的根接口 | 继承 BeanFactory,扩展更多功能 |
加载方式 | 懒加载(获取 Bean 时才创建) | 预加载(启动时创建单例 Bean) |
功能 | 仅提供 Bean 的创建、获取 | 额外支持:事件机制、资源访问、国际化、AOP 等 |
实现类 | DefaultListableBeanFactory | ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等 |
42. 请解释 Spring Bean 的生命周期?
Spring Bean 的生命周期可分为 5 个阶段:
- 实例化(Instantiation):容器通过反射创建 Bean 实例(调用构造方法);
- 属性注入(Population):容器将依赖的 Bean 注入到当前 Bean 的字段或 setter 方法;
- 初始化(Initialization):
-
- 调用BeanNameAware、BeanFactoryAware等接口的回调方法;
-
- 执行@PostConstruct注解的方法;
-
- 调用InitializingBean的afterPropertiesSet();
-
- 执行 XML 中init-method指定的方法;
- 使用(In Use):Bean 被应用程序使用;
- 销毁(Destruction):
-
- 执行@PreDestroy注解的方法;
-
- 调用DisposableBean的destroy();
-
- 执行 XML 中destroy-method指定的方法。
43. Spring Bean 的作用域之间有什么区别?
Spring 定义了 5 种常见作用域(Web 环境特有 3 种):
作用域 | 说明 | 适用场景 |
singleton | 单例(默认),容器中唯一实例 | 无状态 Bean(如 Service、DAO) |
prototype | 多例,每次获取时创建新实例 | 有状态 Bean(如 Request、Session) |
request | 每个 HTTP 请求创建一个实例 | Web 应用,存储请求级数据 |
session | 每个 HTTP 会话创建一个实例 | Web 应用,存储会话级数据 |
globalSession | 全局会话(Portlet 环境) | 分布式 Web 应用 |
44. 使用 Spring 框架的好处是什么?
- 解耦:通过 IOC 容器管理对象依赖,减少硬编码;
- AOP 支持:轻松实现日志、事务、权限等横切逻辑,不侵入业务代码;
- 事务管理:声明式事务简化事务代码,保证数据一致性;
- 一站式框架:整合 ORM(MyBatis)、MVC(Spring MVC)、缓存(Redis)等,降低集成成本;
- 扩展性强:丰富的接口和扩展点,支持自定义组件;
- 测试友好:便于使用 JUnit 进行单元测试(如@MockBean)。
45. Spring 中用到了那些设计模式?
- 工厂模式:BeanFactory、ApplicationContext创建 Bean;
- 单例模式:默认singleton作用域的 Bean;
- 代理模式:AOP 基于 JDK/CGLIB 动态代理;
- 模板方法模式:JdbcTemplate、RestTemplate(固定流程,钩子方法留扩展);
- 观察者模式:事件机制(ApplicationEvent、ApplicationListener);
- 策略模式:Resource接口(不同资源加载策略);
- 适配器模式:HandlerAdapter(适配不同Controller)。
46. Spring 如何保证 Controller 并发的安全?
Spring MVC 的Controller默认是单例(singleton),并发时需避免线程安全问题,解决方案:
- 无状态设计:Controller中不定义成员变量(或仅定义无状态变量),避免多线程共享;
- 使用ThreadLocal:若需存储线程私有数据(如用户上下文),用ThreadLocal隔离线程;
- 原型作用域:将Controller作用域设为prototype(每次请求创建新实例),但会增加内存开销,不推荐。
47. 使用 Spring 框架的好处是什么?(同 44,略)
48. 在 Spring 中如何注入一个 java 集合?
Spring 支持注入List、Set、Map、Properties等集合,通过 XML 或注解实现:
(1)XML 配置
<bean id="userService" class="com.example.UserService">
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<property name="map">
<map>
<entry key="1" value="admin"/>
<entry key="2" value="user"/>
</map>
</property>
</bean>
(2)注解配置
@Service
public class UserService {
@Autowired
private List<User> userList; // 注入容器中所有User类型的Bean
@Value("#{${config.map}}") // SpEL表达式注入Map
private Map<String, String> configMap;
}
49. Spring 支持的事务管理类型?
- 编程式事务:通过TransactionTemplate或PlatformTransactionManager手动控制;
- 声明式事务:
-
- 基于 XML 配置(<tx:advice>);
-
- 基于注解(@Transactional),更常用。
50. Spring 框架的事务管理有哪些优点?
- 简化代码:声明式事务无需手动写begin()、commit()、rollback();
- 一致性:保证事务 ACID 特性(原子性、一致性、隔离性、持久性);
- 灵活性:支持不同隔离级别(READ_UNCOMMITTED至SERIALIZABLE)和传播行为(如REQUIRED、REQUIRES_NEW);
- 整合多种数据源:支持 JDBC、Hibernate、MyBatis 等持久层框架;
- 与 Spring 生态无缝集成:结合 AOP 实现事务切面,不侵入业务代码。
51. Spring MVC 的主要组件?
- DispatcherServlet:前端控制器,接收所有请求,协调其他组件;
- HandlerMapping:映射请求到Controller(如@RequestMapping匹配);
- HandlerAdapter:适配Controller方法,执行并返回ModelAndView;
- Controller:处理器,处理业务逻辑,返回数据和视图名;
- ModelAndView:封装处理结果(Model数据 +View视图名);
- ViewResolver:将视图名解析为View对象(如 JSP、Thymeleaf);
- View:渲染Model数据,生成响应(HTML、JSON 等)。
52. Spring MVC 怎么和 AJAX 相互调用的?
- Controller 返回 JSON:
用@ResponseBody注解(或类上用@RestController),将Model数据转为 JSON:
@GetMapping("/user")
@ResponseBody
public User getUser() {
return new User("张三", 20);
}
- AJAX 发送请求:
前端用jQuery.ajax()或axios发送请求,接收 JSON 并处理:
axios.get("/user")
.then(response => {
console.log(response.data.name); // 输出"张三"
});
- 传递参数:
-
- GET:/user?id=1,后端用@RequestParam接收;
-
- POST:发送 JSON 体,后端用@RequestBody接收。
53. Mybatis 中 #和 $ 的区别?(同 38,略)
54. MyBatis 的缓存机制,一级,二级介绍一下?
(1)一级缓存(SqlSession 级缓存)
- 范围:默认开启,缓存归属于SqlSession(会话);
- 原理:SqlSession执行查询后,结果存入缓存,同一SqlSession内再次执行相同 SQL(参数、SQL 相同),直接从缓存获取;
- 失效:SqlSession关闭、执行insert/update/delete(会清空缓存)。
(2)二级缓存(Mapper 级缓存)
- 范围:需手动开启(在 Mapper XML 中加<cache/>),缓存归属于Mapper(namespace),跨SqlSession共享;
- 原理:SqlSession关闭后,一级缓存数据写入二级缓存,其他SqlSession执行相同 SQL 可命中;
- 注意:缓存对象需实现Serializable接口(支持序列化);可通过useCache="false"禁用某条 SQL 的二级缓存。
55. Spring MVC 与 Struts2 的区别?
维度 | Spring MVC | Struts2 |
核心控制器 | DispatcherServlet(单例) | FilterDispatcher(过滤器) |
控制器设计 | 基于方法(@RequestMapping) | 基于类(Action实例) |
线程安全 | 安全(无状态,单例) | 不安全(Action 多例,需谨慎) |
参数传递 | 方法参数直接接收 | 通过Action成员变量接收 |
拦截器机制 | 基于方法拦截 | 基于类拦截 |
配置方式 | 注解为主(简洁) | XML 为主(繁琐) |
集成性 | 与 Spring 无缝整合 | 需额外配置整合 Spring |
56. MyBatis 的基本工作流程?
- 加载配置:加载mybatis-config.xml(全局配置)和Mapper.xml(SQL 映射);
- 创建SqlSessionFactory:通过SqlSessionFactoryBuilder解析配置,生成SqlSessionFactory(线程安全,全局唯一);
- 获取SqlSession:SqlSessionFactory.openSession()创建SqlSession(非线程安全,代表一次会话);
- 获取Mapper接口:sqlSession.getMapper(UserMapper.class)生成Mapper代理对象;
- 执行 SQL:调用Mapper方法,代理对象通过StatementHandler等组件执行 SQL,返回结果;
- 提交 / 回滚事务:sqlSession.commit()或rollback();
- 关闭SqlSession:sqlSession.close()释放资源。
57. 什么是 MyBatis 的接口绑定,有什么好处?
- 接口绑定:将Mapper接口与Mapper XML(或注解)绑定,无需编写接口实现类,调用接口方法即可执行对应 SQL。
好处
- 简化代码:省去手动实现Mapper接口的代码(传统 JDBC 需写实现类调用 SQL);
- 类型安全:编译时检查方法参数、返回值类型,避免运行时错误;
- 解耦:SQL 与 Java 代码分离(XML 中写 SQL),便于维护和优化;
- 支持动态 SQL:结合 XML 标签实现复杂 SQL 逻辑,灵活性高。
58. MyBatis 的编程步骤?
- 添加依赖:引入mybatis、数据库驱动(如 MySQL)依赖;
- 编写全局配置文件(mybatis-config.xml):配置数据源、事务管理器、Mapper 扫描;
- 定义实体类(如User):与数据库表映射;
- 编写Mapper接口(如UserMapper):声明 CRUD 方法;
- 编写Mapper XML(如UserMapper.xml):绑定接口方法,编写 SQL(select/insert等);
- 编码调用:
// 加载配置,创建SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 获取SqlSession,调用Mapper
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1); // 执行查询
}
59. JDBC 编程有哪些不足之处,MyBatis 是如何解决这些问题的?
JDBC 不足 | MyBatis 解决方案 |
频繁创建 / 关闭连接(开销大) | 集成连接池(如 Druid),复用连接 |
SQL 硬编码在 Java 代码中 | SQL 写在 XML / 注解中,与代码分离 |
参数设置繁琐(setInt()等) | #{}占位符自动设置参数,支持 OGNL 表达式 |
结果集解析繁琐(rs.getString()) | 结果映射(resultMap)自动映射到实体类 |
事务管理手动控制(commit()) | 集成 Spring 事务管理,声明式控制 |
无缓存机制 | 提供一级、二级缓存,减少数据库访问 |
60. MyBatis 的优缺点?
优点
- 灵活:SQL 完全可控,适合复杂查询(存储过程、多表关联);
- 简化 JDBC:自动处理参数设置、结果映射,减少样板代码;
- 低侵入:无需继承特定类,POJO 与 Mapper 接口即可;
- 支持动态 SQL:通过标签简化 SQL 拼接;
- 缓存机制:一级、二级缓存提升查询性能。
缺点
- SQL 编写工作量大:简单 CRUD 也需写 SQL(不如 JPA 自动生成);
- 数据库移植性差:SQL 依赖数据库方言(如 MySQL 的LIMIT、Oracle 的ROWNUM);
- 维护成本高:XML 配置较多,大型项目需规范 SQL 管理。