Java 八股面试题(结合教务系统项目)
Java 八股面试题(结合教务系统项目)
本题集为 Java 八股向,答案尽量精简,并在关键处附【本项目】结合点,便于面试时把八股引到项目上,避免纯背书。 项目技术栈:Spring Boot 2.5.6 / MyBatis / MySQL / Redis / Shiro + JWT / POI / Java 8。
一、Java 基础
Q1. == 和 equals() 的区别?
答:== 比较引用地址(基本类型比值);equals() 默认也是比地址(Object 里就是 ==),String/Integer 等重写后比内容。
【本项目】UserLevelAspect 里 userRole.equals(role) 比较字符串内容,必须用 equals;若误用 == 对非字面量字符串会恒为 false(常量池陷阱)。
Q2. String 为什么不可变?好处是什么?
答:String 用 final 修饰类、private final char[](JDK9 后 byte[])存值,无修改方法。好处:线程安全、可共享常量池、hashCode 可缓存、安全(如作为 HashMap key 不会被改)。
【本项目】JWT token 是 String,不可变保证多线程下解析、验签安全;JwtUtils.SECRET = "sms-panovo" 作为 HMAC 密钥,不可变避免被篡改。
Q3. final / finally / finalize 区别?
答:完全不同的三样东西。final 修饰类(不可继承)、方法(不可重写)、变量(不可重新赋值);finally 是 try-finally 块,异常时也执行;finalize 是 Object 方法,GC 回收前调用,JDK9 已废弃,不推荐用。
【本项目】JwtUtils 中 public static final String SECRET 用 final 修饰常量;POI 导出用 try-with-resources(本质是 finally 关闭流)替代手写 finally。
Q4. 重载(Overload)和重写(Override)的区别?
答:重载是同类中方法名相同、参数列表不同(编译期多态);重写是子类重写父类方法,方法签名需兼容、访问权限不能更严、不能抛更宽异常(运行期多态)。
【本项目】JwtUtils.generateToken(User) 与 generateToken(User, long) 是重载;XSSFWorkbook 等对 Workbook 接口方法的实现是重写。
Q5. Java 异常体系?Checked 和 Unchecked 区别?
答:Throwable → Error(不可恢复,如 OOM)+ Exception。Exception 分 Checked(编译期检查,必须 try/catch 或 throws,如 IOException)和 Unchecked/Runtime(空指针、数组越界等)。
【本项目】ScoreImportExportController 里 parseExcelRows throws Exception(受检异常),调用处 try-catch;NumberFormatException 是运行时异常,Double.parseDouble 抛出后本地 catch。
Q6. try-with-resources 原理?
答:Java 7 引入,自动关闭实现 AutoCloseable 的资源,编译后等价于 try-finally(且能正确处理异常抑制)。资源关闭顺序与声明相反。
【本项目】POI 导出大量用 try (Workbook workbook = new XSSFWorkbook()) {...},确保 Excel 工作簿被关闭,避免大文件内存泄漏。
二、集合
Q7. ArrayList 和 LinkedList 区别?如何选择?
答:ArrayList 底层数组,随机访问 O(1)、增删尾部 O(1) 中间 O(n);LinkedList 双向链表,增删头尾 O(1)、随机访问 O(n)。查询多用 ArrayList,频繁头插多用 LinkedList。
【本项目】ScoreImportExportController 里 List<Map> rawRows = new ArrayList<>() 用 ArrayList,因为按行追加、遍历查询为主。
Q8. HashMap 原理?JDK7 和 JDK8 区别?
答:数组 + 链表(JDK8 链表长度 ≥8 且数组 ≥64 转红黑树)。put 过程:hash → 定位桶 → 空则放 → 有则链表/树插入/覆盖。JDK7 头插法(并发死链),JDK8 尾插法 + 红黑树。
【本项目】OperationLogAspect 用 HashMap 存模块映射 moduleMap,单线程读多写少,合适;SENSITIVE_KEYS 用 HashSet O(1) 判断敏感字段。
Q9. HashMap 默认初始容量和扩容机制?
答:默认 16,负载因子 0.75,超过 容量×0.75 扩容为 2 倍。扩容时 rehash(JDK8 优化:原位置或原位置+旧容量)。指定初始容量建议 预期大小/0.75 + 1 避免扩容。
【本项目】moduleMap 一次性 put 30+ 条,可指定初始容量 64 减少扩容。
Q10. HashMap 和 ConcurrentHashMap 区别?
答:HashMap 线程不安全;ConcurrentHashMap 线程安全。JDK7 分段锁(Segment),JDK8 改为 CAS + synchronized 锁单个桶节点,并发度更高。 【本项目】选课余量、排课资源若有多线程读写共享 map,应用 ConcurrentHashMap;当前模块单请求线程内用 HashMap 即可。
Q11. fail-fast 是什么?怎么触发?
答:集合在迭代时若被结构性修改(增删),迭代器检测到 modCount 变化抛 ConcurrentModificationException。单线程下用 iterator.remove() 修改可避免。
【本项目】遍历 studentList 时若同时 add/remove 会 fail-fast,应使用迭代器 remove 或流式过滤。
三、并发
Q12. 线程的几种状态?
答:NEW(新建)→ RUNNABLE(就绪+运行)→ BLOCKED(阻塞等锁)→ WAITING(无限等待)→ TIMED_WAITING(限时等待)→ TERMINATED(终止)。 【本项目】排课、导入若用线程池,任务线程经历 NEW→RUNNABLE→TIMED_WAITING→TERMINATED。
Q13. synchronized 和 Lock 区别?
答:synchronized 是 JVM 关键字,自动加锁释放,不可中断;Lock 是 API(如 ReentrantLock),手动 lock/unlock,可响应中断、超时、公平锁。 【本项目】选课扣减余量可用 synchronized(简单)或 ReentrantLock(更灵活);ConcurrentHashMap JDK8 内部用 synchronized 锁桶。
Q14. volatile 作用?与 synchronized 区别?
答:volatile 保证可见性(刷主存)和有序性(禁止指令重排),不保证原子性;synchronized 三性都保证。
【本项目】若用 volatile 标志位控制排课线程停止(如 volatile boolean running),保证多线程可见。
Q15. 线程池核心参数?拒绝策略?
答:7 参数:corePoolSize、maxPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。拒绝策略:AbortPolicy(默认抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最老)。 【本项目】批量导入、异步日志保存若用线程池,IO 密集型 corePoolSize 可设 2×CPU;日志保存用 CallerRunsPolicy 防止丢日志。
Q16. ThreadLocal 原理?内存泄漏?
答:每个线程有 ThreadLocalMap,key 是 ThreadLocal 弱引用、value 是强引用。线程复用(如线程池)时 key 被回收成 null 但 value 仍在 → 泄漏。用完调 remove()。 【本项目】Shiro 的 Subject、RequestContextHolder 内部用 ThreadLocal,线程池场景需注意清理。
Q17. CAS 原理?有什么问题?
答:Compare And Swap,三操作数(内存值、期望值、新值),期望值==内存值才更新,否则重试。问题:ABA(用版本号解决)、自旋开销大、只能保证单变量。 【本项目】选课余量扣减可用 CAS(AtomicInteger)实现无锁,避免超卖。
四、JVM
Q18. JVM 内存区域划分?
答:线程私有:程序计数器、虚拟机栈、本地方法栈;线程共享:堆、方法区(JDK8 后元空间 Metaspace)。堆分新生代(Eden + S0 + S1)+ 老年代。
【本项目】POI XSSFWorkbook 加载大 Excel 全在堆,万行数据易 OOM;建议改 SAX 流式解析降低堆压力。
Q19. 对象什么时候进老年代?
答:①大对象(超过 -XX:PretenureSizeThreshold)直接进;②年龄 ≥15(默认 MaxTenuringThreshold);③动态年龄判断(Survivor 中同龄对象超一半,该年龄以上全进);④Survivor 空间不够。
【本项目】大 Excel 解析产生的 Workbook 对象大,可能直接进老年代,频繁 Full GC。
Q20. GC 算法和垃圾回收器?
答:算法:标记-清除、标记-复制、标记-整理、分代收集。回收器:CMS(并发,JDK9 弃用)、G1(JDK9 默认)、ZGC、Parallel Scavenge。 【本项目】Spring Boot 2.5.6 默认 G1,大内存服务端推荐 G1/ZGC,停顿短。
Q21. 类加载过程和双亲委派?
答:加载→验证→准备→解析→初始化。双亲委派:类加载请求先委托父加载器,父加载不到再自己加载,避免重复加载、防止核心类被篡改(如自定义 java.lang.String)。 【本项目】Tomcat 内嵌容器打破双亲委派(每个 webapp 独立 ClassLoader),实现应用隔离。
Q22. 项目中怎么排查 OOM?
答:①加 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 自动 dump;②用 MAT/jvisualvm 分析堆快照找大对象;③定位是缓存堆积、大文件加载、内存泄漏。
【本项目】POI 大文件导入 OOM → 看 dump 是否有巨大 XSSFWorkbook 对象 → 改为 SAX 流式 XSSFReader。
五、Spring
Q23. Spring IOC 和 AOP 理解?
答:IOC 控制反转,对象创建交给容器,DI 注入依赖,解耦。AOP 面向切面,将横切逻辑(日志、权限、事务)从业务代码抽离,通过动态代理织入。
【本项目】@Resource 注入 UserService 是 DI;OperationLogAspect(@Around 记日志)、UserLevelAspect(@Before 鉴权)是 AOP 典型应用。
Q24. Spring Bean 作用域和生命周期?
答:作用域:singleton(默认)、prototype、request、session、application。生命周期:实例化→属性注入→BeanNameAware 等→BeanPostProcessor 前置→初始化(@PostConstruct/InitializingBean/init-method)→BeanPostProcessor 后置→使用→销毁。
【本项目】@Component/@RestController 默认 singleton,OperationLogAspect 全局一个实例,无状态安全。
Q25. Spring 循环依赖怎么解决?
答:singleton + setter/字段注入的循环依赖通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)解决,提前暴露半成品对象。构造器注入无法解决。 【本项目】Controller 注入 Service、Service 注入 Mapper 是单向依赖无循环;若 Service 间循环依赖应重构拆分。
Q26. Spring 常用注解?
答:@Component/@Service/@Repository/@Controller/@RestController 注册 Bean;@Autowired/@Resource 注入;@Configuration/@Bean 配置类;@RequestMapping/@GetMapping/@PostMapping 路由;@RequestParam/@PathVariable/@RequestBody 取参;@RestControllerAdvice/@ExceptionHandler 全局异常。
【本项目】@RestController + @RequestMapping("/api/sms/user");@UserLevel(roles={...}) 是自定义注解;GlobalExceptionHandler 用 @RestControllerAdvice。
Q27. @Autowired 和 @Resource 区别?
答:@Autowired Spring 提供,按类型注入,配合 @Qualifier 指定名;@Resource JSR250,默认按名称,找不到再按类型。
【本项目】大量用 @Resource private UserService userService; 按字段名注入;StringRedisTemplate 也用 @Resource 注入。
Q28. Spring 事务传播行为?
答:7 种,常用:REQUIRED(默认,有则加入无则新建)、REQUIRES_NEW(独立新事务挂起当前)、SUPPORTS、NOT_SUPPORTED。@Transactional 失效场景:自调用、非 public、异常被吞、rollbackFor 未配置。
【本项目】成绩导入涉及多表写入(成绩表+审批表),应加 @Transactional 保证一致性,异常回滚。
六、Spring Boot
Q29. Spring Boot 自动配置原理?
答:@SpringBootApplication 含 @EnableAutoConfiguration,通过 AutoConfigurationImportSelector 加载 META-INF/spring.factories(Boot 2.7+ 改 AutoConfiguration.imports)中的配置类,配合 @ConditionalOnClass/@ConditionalOnMissingBean 按条件装配。
【本项目】引入 mybatis-spring-boot-starter 自动配置 SqlSessionFactory;shiro-spring 自动装配 SecurityManager。
Q30. Spring Boot 启动流程?
答:SpringApplication.run() → 创建应用上下文 → 加载 spring.factories → 注册 Bean → 启动内嵌 Tomcat → 扫描 @Component → 初始化 DispatcherServlet → CommandLineRunner 执行。
【本项目】BackendApplication 的 main 方法启动,端口 9121。
Q31. Spring Boot 配置文件优先级?
答:命令行参数 > 环境变量 > application-{profile}.yml > application.yml > 默认值。application-dev.yml/application-prod.yml 通过 spring.profiles.active 切换。
【本项目】application.yml 主配置,application-dev.yml/application-prod.yml 分环境;captcha.strategy 可被环境变量覆盖。
Q32. Spring Boot 内嵌容器和端口配置?
答:默认内嵌 Tomcat,server.port=9121。可换 Undertow(并发性能更好)或 Jetty。
【本项目】server.port: 9121,max-file-size: 50MB 配置上传限制。
七、MyBatis
Q33. #{} 和 ${} 区别?SQL 注入风险?
答:#{} 预编译占位符(PreparedStatement,自动转义,防注入);${} 字符串拼接,有 SQL 注入风险,仅用于动态表名/列名等无法预编译场景。
【本项目】bug 文件夹的 mapper 里 #{teacherId} 安全;若用 ${} 拼 major_id 会注入。安全测试要 grep 所有 ${} 用法。
Q34. MyBatis 一级二级缓存?
答:一级缓存 SqlSession 级别默认开启,同一 session 相同查询命中;二级缓存 Mapper 级别跨 session,需开启 + 实体序列化。增删改会清空缓存。 【本项目】成绩统计高频查询可开二级缓存,但成绩导入后要 flush,否则脏读。
Q35. MyBatis 动态 SQL 标签?
答:<if>、<choose>/<when>/<otherwise>、<where>、<set>、<foreach>、<trim>。<where> 自动去掉多余 AND;<foreach> 用于 IN 查询和批量插入。
【本项目】成绩导入 upsert、批量插入用 <foreach>;条件查询用 <if> + <where>。
Q36. MyBatis resultMap 和 resultType 区别?
答:resultType 直接映射到简单类型或同名字段 POJO;resultMap 处理字段名不一致、多表关联、嵌套结果(collection/association)。
【本项目】teaching_class 查询关联 profession 名称、多字段驼峰转换用 resultMap。
Q37. PageHelper 分页原理?
答:拦截 SQL 执行,自动在 SQL 后追加 LIMIT,并执行 count 查询返回总数,封装成 PageInfo。注意:分页插件调用紧跟查询前,PageHelper.startPage() 后必须紧跟一次查询。
【本项目】pom 引入 pagehelper-spring-boot-starter:1.4.1,学生列表、成绩列表分页用它,reasonable: true 防页码越界。
八、MySQL
Q38. 索引数据结构?为什么用 B+ 树?
答:InnoDB 用 B+ 树。B+ 树非叶子只存索引、叶子存数据且有序链表连接,范围查询快、磁盘 IO 少(矮胖)。相比 B 树:单次查询稳定到叶子、范围查询快。 【本项目】student.id 主键聚簇索引、score_entry(student_id, class_id) 联合索引用于成绩查询加速。
Q39. 聚簇索引和非聚簇索引区别?
答:聚簇索引叶子节点存整行数据(主键),一张表只有一个;非聚簇(二级)索引叶子存主键值,需回表查数据。 【本项目】按 student_id 查成绩走二级索引再回表;可用覆盖索引(索引含查询列)避免回表。
Q40. 事务四大特性 ACID 和隔离级别?
答:原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability。隔离级别:读未提交、读已提交(Oracle 默认)、可重复读(MySQL 默认 RR)、串行化。MySQL RR 通过 MVCC + 间隙锁解决幻读。 【本项目】成绩导入涉及多表,需事务保证原子性;选课扣余量需事务+行锁防超卖。
Q41. INNER JOIN 和 LEFT JOIN 区别?(结合 bug2)
答:INNER JOIN 只返回两表匹配的行;LEFT JOIN 返回左表全部,右表不匹配填 NULL。
【本项目】bug2 真实案例:teaching_class.major_id 为 NULL 时 INNER JOIN profession 会丢行导致下拉为空,修复方案改为不 JOIN 或 LEFT JOIN。这是测试岗”数据驱动 bug”的经典案例。
Q42. 如何用 explain 分析慢 SQL?
答:看 type(至少 range/index,理想 ref/const/eq_ref)、key(实际用的索引)、rows(扫描行数)、Extra(Using index 覆盖索引好,Using filesort/Using temporary 需优化)。
【本项目】成绩统计分析慢 → explain 看是否全表扫描 type=ALL → 给 student_id/class_id 加联合索引。
Q43. 深分页查询慢怎么优化?
答:LIMIT 1000000, 10 会扫描前 100 万行。优化:①子查询/JOIN 延迟关联,先查主键再 join;②游标分页 WHERE id > last_id LIMIT 10;③覆盖索引。
【本项目】学生表数据量大时深翻页慢,可记录上一页最大 id 翻页。
九、Redis
Q44. Redis 常用数据结构?
答:String(缓存、计数器)、Hash(对象)、List(消息队列)、Set(去重)、ZSet(排行榜)。底层数据结构:SDS、ziplist/listpack、skiplist、dict。 【本项目】验证码用 String 存(uuid → code);用户 token、排课缓存可用 String/Hash。
Q45. Redis 过期策略和淘汰策略?
答:过期策略:惰性删除(访问时检查)+ 定期删除(随机抽样)。淘汰策略(maxmemory-policy):noeviction(默认拒绝写)、allkeys-lru、volatile-lru、allkeys-lfu 等。
【本项目】验证码 set(uuid, code, 5, MINUTES) 设 5 分钟过期;登录后会话可用 volatile-lru 淘汰过期 token。
Q46. 缓存穿透、击穿、雪崩区别和解决?
答:穿透(查不存在数据穿透到 DB)→ 布隆过滤器/空值缓存;击穿(热点 key 过期瞬间高并发)→ 互斥锁/永不过期;雪崩(大量 key 同时过期)→ 过期时间加随机值、多级缓存。 【本项目】排课结果、教学班列表可缓存;缓存击穿用互斥锁防 DB 被打爆。
Q47. Redis 持久化 RDB 和 AOF?
答:RDB 快照(体积小、恢复快、可能丢数据);AOF 追加命令(数据全、文件大、恢复慢,appendfsync 配置刷盘策略)。AOF 重写压缩。 【本项目】Redis 存验证码、token,丢一些可接受,RDB 即可;若 Redis 存重要会话用 AOF。
Q48. 分布式锁怎么实现?选课场景?
答:SET key value NX PX 30000(原子加锁+过期),释放用 Lua 脚本校验 value(防误删别人锁)。进阶用 Redisson(看门狗续期、可重入)。
【本项目】选课扣余量:SET lock:course:{id} NX PX 5000 → 查余量 → 扣减 → 释放锁,防并发超卖。
十、Shiro + JWT
Q49. JWT 由哪几部分组成?
答:Header(算法类型)+ Payload(claim,如 id/level)+ Signature(签名),三部分 Base64URL 拼接用 . 连接。签名 = HMAC(Header.Payload, Secret)。
【本项目】JwtUtils.generateToken 用 JWT.create().withClaim(CLAIM_ID, ...).sign(Algorithm.HMAC256(SECRET)),payload 含 id/username/level/realName。
Q50. JWT 的优势和缺点?
答:优势:无状态、不查库、适合分布式;缺点:无法主动失效(需黑名单)、payload 是 Base64 不加密(别放敏感信息)、续期麻烦。 【本项目】缺点在本项目体现:登出后旧 token 仍有效(无黑名单),是安全测试必提缺陷;level 放 payload 可被解码(虽篡改会被验签拦截)。
Q51. HMAC 和 RSA 签名算法区别?
答:HMAC 对称密钥(同一密钥签发和验证),快但密钥需保密共享;RSA 非对称(私钥签、公钥验),适合多方场景,慢。
【本项目】Algorithm.HMAC256("sms-panovo") 单方签发验证用对称够用;密钥硬编码是风险,应放环境变量。
Q52. Shiro 核心概念?
答:Subject(当前用户)、SecurityManager(核心管理器)、Realm(数据源,认证授权)、Session(会话)、Filter(过滤器链,如 authc/roles/perms)。
【本项目】JwtRealm 自定义 Realm 做认证授权;JwtAuthFilter 自定义过滤器;ShiroConfig 配置 SecurityManager 和过滤链。
Q53. Shiro 认证和授权流程?
答:认证:Subject.login(token) → SecurityManager → Realm.doGetAuthenticationInfo(查库比对)。授权:isPermitted/hasRole → Realm.doGetAuthorizationInfo(查角色权限)。
【本项目】认证靠 JWT 验签(JwtFilter),授权靠 UserLevelAspect AOP 拦截 @UserLevel 注解校验 level/roles。
Q54. 为什么 Shiro 和 JWT 结合?无状态会话怎么处理?
答:JWT 负责”无状态认证”(token 自带身份不查库),Shiro 负责”授权”(角色/权限注解、过滤链)。无状态模式下不用 SessionDAO,token 存客户端。 【本项目】JwtFilter 解析 token → Realm 认证 → @UserLevel/@UserLevelAspect 鉴权,三者分工,符合前后端分离架构。
十一、AOP 与注解(项目深度)
Q55. 自定义注解怎么定义?需要哪些元注解?
答:@Target 修饰目标(METHOD/TYPE)、@Retention 保留策略(RUNTIME 才能反射读到)、@Documented。注解本身没逻辑,需 AOP/反射赋予行为。
【本项目】UserLevel 注解:@Target({METHOD, TYPE}) @Retention(RUNTIME) @Documented,定义 value() 和 roles() 属性。
Q56. AOP 几种通知和切点表达式?
答:通知:@Before/@After/@AfterReturning/@AfterThrowing/@Around。切点:execution(public * com.PanOVO.sc.controller..*.*(..))、@annotation(xxx)。
【本项目】OperationLogAspect 用 @Around("execution(public * com.PanOVO.sc.controller..*.*(..))") 拦截所有 Controller;UserLevelAspect 用 @Before("@annotation(com.PanOVO.sc.shiro.UserLevel)") 拦截带注解的方法。
Q57. Spring AOP 用的是 JDK 动态代理还是 CGLIB?
答:目标实现接口默认用 JDK 动态代理(基于接口),否则用 CGLIB(子类继承)。可用 proxyTargetClass=true 强制 CGLIB。
【本项目】Controller 是类非接口,Spring AOP 用 CGLIB 生成子类代理;UserLevelAspect 织入到 Controller 子类。
Q58. ProceedingJoinPoint 和 JoinPoint 区别?
答:JoinPoint 只能读信息(方法名、参数),用于 @Before/@After;ProceedingJoinPoint 继承它,多了 proceed() 控制目标方法执行,用于 @Around。
【本项目】UserLevelAspect(@Before) 用 JoinPoint 读注解;OperationLogAspect(@Around) 用 ProceedingJoinPoint,proceed() 前后计时、记录日志、捕获异常。
十二、POI / 文件处理
Q59. POI 处理大文件为什么会 OOM?怎么优化?
答:XSSFWorkbook(.xlsx)全量加载到内存,10 万行 Excel 易 OOM。优化:用 SAX 事件模型 XSSFReader + SheetContentsHandler 流式解析,或 EasyExcel(阿里,封装 SAX)。
【本项目】ScoreImportExportController.parseExcelRows 用 new XSSFWorkbook(is) 全量加载,万行数据风险,应改 EasyExcel 或 SAX。
Q60. POI 读取单元格怎么处理不同类型?
答:先判 getCellType()(STRING/NUMERIC/BOOLEAN/FORMULA/BLANK),NUMERIC 还要区分日期和数字(DateUtil.isCellDateFormatted)。学号是数字时直接取会变科学计数,应按字符串读。
【本项目】getCellStringValue 处理 STRING/NUMERIC/BOOLEAN;学号 getCell(0).getNumericCellValue() 直接 (long) 转,避免科学计数法,这是踩坑点。
十三、设计模式(结合项目)
Q61. 单例模式?饿汉和懒汉区别?
答:单例保证一个类仅一个实例。饿汉:类加载即创建(线程安全);懒汉:首次使用创建(需双重检查锁或静态内部类保证线程安全)。 【本项目】JDK Runtime 是饿汉单例;Spring Bean 默认 singleton 由容器管理(非经典单例,容器代劳)。
Q62. 工厂模式?
答:定义创建对象的接口,延迟到子类决定实例化哪个类,解耦创建和使用。简单工厂/工厂方法/抽象工厂。
【本项目】CaptchaUtil.createLineCaptcha 是工厂方法创建验证码对象;Spring BeanFactory 是工厂模式典型。
Q63. 代理模式?静态代理和动态代理?
答:代理模式为目标对象提供代理控制访问。静态代理需手写代理类;动态代理运行时生成(JDK 接口代理/CGLIB 子类代理)。
【本项目】Spring AOP 本质动态代理;UserLevelAspect/OperationLogAspect 通过 CGLIB 代理 Controller,在方法前后织入鉴权/日志。
Q64. 模板方法模式?
答:父类定义算法骨架,子类重写特定步骤不改变结构。
【本项目】AbstractList 的 get/add;POI 解析流程固定(打开→选 sheet→遍历行→取单元格)。
Q65. 责任链模式?
答:请求沿处理者链传递,每个处理者决定处理或传给下一个。解耦发送者和接收者。
【本项目】Shiro Filter Chain(anon → jwtFilter → authc → roles)是责任链;GlobalExceptionHandler 多个 @ExceptionHandler 按类型匹配也是链式。
十四、高频手撕 / 开放题
Q66. HashMap put 流程?
答:①hash(key) 算桶下标;②桶空直接放;③桶非空,key 相等则覆盖 value;④链表长度 ≥8 且数组 ≥64 转红黑树;⑤size > 阈值扩容。
Q67. 线程池工作流程?
答:①核心线程未满创建核心线程;②满了进队列;③队列满创建非核心线程(不超 max);④都满触发拒绝策略。
Q68. Spring 事务 @Transactional 失效场景?
答:①自调用(同类方法间调用,未走代理);②方法非 public;③异常被 catch 吞掉;④默认只回滚 RuntimeException,Checked 异常需 rollbackFor = Exception.class;⑤数据库引擎不支持事务(MyISAM)。
【本项目】成绩导入若 try-catch 吞异常,@Transactional 不回滚,需 rollbackFor 或抛出。
Q69. 一个接口响应慢,怎么排查?
答:①前端看耗时(网络 vs 后端);②后端 APM/日志分段计时(Controller→Service→Mapper);③开 MyBatis debug 看 SQL 耗时和条数(N+1);④DB 看 explain + 慢查询日志;⑤Redis/缓存命中率;⑥GC/线程状态。 【本项目】成绩统计慢 → 多是 SQL 全表扫描或 N+1,加索引+缓存解决。
Q70. 如何用这个项目回答”你用过哪些设计模式”?
答:①代理模式——Spring AOP(UserLevelAspect 鉴权、OperationLogAspect 日志);②工厂模式——CaptchaUtil 创建验证码、Spring BeanFactory;③单例——Spring Bean 默认 singleton;④责任链——Shiro 过滤器链、GlobalExceptionHandler;⑤模板方法——POI 解析流程。结合具体类名讲,比背概念有说服力。
附:八股 → 项目 速查表
| 八股知识点 | 项目结合点 |
|---|---|
| String 不可变 | JWT token、SECRET 密钥 |
| HashMap/HashSet | OperationLogAspect 的 moduleMap、SENSITIVE_KEYS |
| try-with-resources | POI XSSFWorkbook 关闭 |
| 异常体系 | parseExcelRows throws Exception、NumberFormatException |
| AOP 动态代理 | UserLevelAspect(@Before)、OperationLogAspect(@Around) |
| 自定义注解 | @UserLevel(@Target/@Retention) |
| @RestControllerAdvice | GlobalExceptionHandler |
| Spring 事务 | 成绩导入多表写入 |
| 自动配置 | mybatis/shiro starter |
| #{} vs ${} | SQL 注入安全测试 |
| INNER vs LEFT JOIN | bug2 major_id NULL |
| 索引/explain | 成绩统计慢查询 |
| 事务隔离/ACID | 选课扣余量 |
| Redis String/过期 | 验证码 5 分钟 |
| 缓存击穿/穿透 | 排课缓存 |
| 分布式锁 | 选课防超卖 |
| JWT 结构 | JwtUtils.generateToken |
| HMAC256 | JWT 签名 |
| Shiro Realm/Filter | JwtRealm、JwtAuthFilter |
| POI OOM | 大 Excel 导入改 SAX |
| 单例/代理/工厂/责任链 | Spring Bean、AOP、CaptchaUtil、Shiro FilterChain |
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!