面试总结
一、@SpringBootApplication注解的作用
@SpringBootApplication是SpringBoot应用的核心入口注解,本质是Spring框架为简化配置提供的"聚合式注解",其设计目标是替代传统Spring项目中XML配置+多注解组合的繁琐模式,核心由三大注解构成,且包含扩展特性:
1. 核心组成注解的深度解析
| 注解 |
底层本质 |
核心能力 |
专业细节 |
| @SpringBootConfiguration |
派生自@Configuration |
标识当前类为SpringBoot的配置类,支持@Bean定义容器Bean |
与普通@Configuration的区别:仅做语义强化,底层均为CGLIB动态代理;可嵌套配置类(@Import) |
| @EnableAutoConfiguration |
基于@Import实现 |
开启自动配置机制,根据依赖自动装配Bean |
1. 底层通过SpringFactoriesLoader.loadFactoryNames()加载META-INF/spring.factories中EnableAutoConfiguration对应的配置类; 2. 支持exclude/excludeName排除指定自动配置类(如@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)); 3. 自动配置类通过@Conditional系列注解实现"条件装配"(如ClassCondition、PropertyCondition) |
| @ComponentScan |
扫描组件核心注解 |
扫描指定路径下的@Component衍生注解(@Controller/@Service/@Repository等) |
1. 默认扫描注解所在类的包及其子包; 2. 支持includeFilters/excludeFilters过滤扫描类; 3. 多模块项目需通过scanBasePackages/scanBasePackageClasses指定扫描范围,避免Bean未加载 |
2. 关键扩展特性
- 元注解支持 :该注解本身被
@Inherited修饰,子类可继承父类的扫描规则(极少用);
- 配置优先级 :自动配置类的优先级低于自定义
@Bean,可通过@Primary调整Bean的优先级;
- 禁用自动配置 :可通过
spring.boot.enableautoconfiguration=false全局关闭自动配置。
二、SpringMVC解决跨域问题的方案
跨域的本质是浏览器的同源策略限制(协议、域名、端口、子域名任一不同即为跨域),SpringMVC针对跨域的解决方案围绕"突破浏览器OPTIONS预检请求限制"设计,核心分为三类,且需理解跨域配置的底层参数含义:
1. 跨域核心概念前置
- 简单请求:满足「GET/HEAD/POST + 无自定义请求头 + Content-Type为application/x-www-form-urlencoded/multipart/form-data/text/plain」,无需预检;
- 非简单请求:PUT/DELETE、自定义头、JSON格式等,浏览器先发送OPTIONS预检请求,验证服务器允许跨域后再发送真实请求;
- 预检缓存 :通过
maxAge配置,减少OPTIONS请求次数,提升性能。
2. 三类解决方案的全维度对比
| 方案类型 |
适用场景 |
核心配置参数及含义 |
优先级 |
生产规范 |
| @CrossOrigin注解 |
单个接口/控制器的局部跨域 |
- origins/allowedOriginPatterns:允许的源(2.4+推荐后者,支持通配符如*.demo.com); - maxAge:预检缓存时间(秒); - allowedMethods:允许的HTTP方法; - allowCredentials:是否允许携带Cookie |
最低 |
禁止使用*,需指定具体域名;方法级注解覆盖控制器级 |
| WebMvcConfigurer配置类 |
全局跨域(无网关场景) |
实现addCorsMappings(CorsRegistry registry),参数与@CrossOrigin一致 |
中等 |
1. 统一配置allowedOriginPatterns为业务域名列表; 2. allowCredentials=true时,allowedOriginPatterns不可为* |
| CorsFilter过滤器 |
全局跨域(网关/细粒度控制) |
基于Servlet Filter实现,直接拦截请求并设置跨域响应头(Access-Control-Allow-Origin等) |
最高 |
1. 配置在Filter链的最前端; 2. 结合网关(Gateway/Nginx)时,优先在网关层配置跨域,避免多层配置冲突 |
3. 生产级跨域配置原则
- 禁止全量放行(
*),需白名单化允许的域名;
- 限制允许的HTTP方法(仅开放业务所需方法);
- 预检缓存时间设置为3600秒以上,减少OPTIONS请求;
- 跨域与认证结合时,确保
allowCredentials=true,且前端请求携带withCredentials: true。
三、SpringBoot实现多套环境配置的方式
SpringBoot的多环境配置核心遵循「配置隔离 + 环境激活 + 配置优先级」三大原则,旨在解决开发/测试/预发/生产等环境的配置隔离问题,且支持多层级配置覆盖:
1. 配置文件的分层与命名规范
| 配置文件类型 |
命名规则 |
作用 |
加载优先级 |
| 主配置文件 |
application.yml/properties |
全局通用配置,用于激活环境、定义公共配置 |
基础层 |
| 环境专属配置文件 |
application-{profile}.yml/properties |
各环境专属配置(如application-prod.yml),覆盖主配置的同名参数 |
激活层 |
| 外部配置文件(服务器/容器) |
服务器指定路径(如/opt/config/) |
生产环境的敏感配置(如数据库密码),无需打包到jar包 |
最高层 |
2. 环境激活的全维度方式(优先级从低到高)
- 配置文件激活 :主配置文件中
spring.profiles.active=prod(基础方式,开发环境用);
- JVM参数激活 :
java -Dspring.profiles.active=prod -jar app.jar(测试环境常用);
- 命令行参数激活 :
java -jar app.jar --spring.profiles.active=prod(生产环境主流);
- 环境变量激活 :设置系统环境变量
SPRING_PROFILES_ACTIVE=prod(容器化部署常用);
- Maven打包激活 :在pom.xml中配置
<profiles>,打包时mvn clean package -Pprod,结合resources过滤替换配置(CI/CD流水线常用);
- 编程式激活 :
SpringApplication.setAdditionalProfiles("prod")(极少用,灵活性低)。
3. 高级特性:多环境配置增强
- 配置继承 :通过
spring.profiles.include=common,prod激活多个环境配置(common为公共配置);
- 占位符引用 :配置中可通过
${spring.profiles.active}引用当前激活的环境,如logging.file.name=app-${spring.profiles.active}.log;
- 配置加密:生产环境敏感配置(密码)需通过Jasypt等工具加密,避免明文存储;
- 配置中心集成:结合Nacos/Apollo等配置中心,实现动态环境配置切换(生产级最佳实践)。
4. 配置加载的完整优先级(高→低)
命令行参数 > 系统环境变量 > JVM参数 > 外部配置文件(服务器路径) > 应用内环境配置文件 > 应用内主配置文件 > 默认配置。
四、final、finally、finalize的深度区别
三者是Java中名称相似但归属、语义、底层机制完全不同的概念,需从JVM层面理解其核心差异:
| 概念 |
语言归属 |
核心语义与底层机制 |
适用场景 |
专业注意事项 |
| final |
关键字(语法) |
1. 修饰类:类不可被继承(JVM层面禁止生成子类的字节码); 2. 修饰方法:方法不可被重写(字节码中标记ACC_FINAL); 3. 修饰变量: - 基本类型:值不可变(编译期常量); - 引用类型:引用地址不可变(对象内容可变); - 局部变量:必须显式赋值; - 成员变量:必须在声明/构造器中赋值 |
定义常量、防止类/方法被篡改、线程安全(不可变对象) |
1. final变量在JVM中存储在常量池,提升访问效率; 2. final方法不支持动态分派(重写),调用效率更高; 3. final类的方法默认final(无需显式声明) |
| finally |
关键字(异常) |
配合try-catch使用,finally块在「try块执行完毕/异常抛出后、方法返回前」执行(JVM通过异常表实现);仅当JVM强制退出(System.exit(0))时不执行 |
资源释放(流、连接、锁) |
1. finally块的执行优先级高于try/catch中的return; 2. finally中抛出异常会覆盖原异常(需避免); 3. try-with-resources可替代finally释放资源(AutoCloseable接口) |
| finalize |
方法(Object) |
1. 属于Object的protected方法,JVM在GC回收对象前调用(仅一次); 2. 底层通过Finalizer线程处理,执行时机不可控; 3. JDK9标记为@Deprecated,JDK17移除 |
历史上用于清理非堆内存资源(如JNI资源) |
1. 执行时机不可控(可能OOM前才执行); 2. 可能导致对象复活(finalize中重新引用对象); 3. 替代方案:Cleaner类(JDK9+)、PhantomReference(虚引用) |
五、List、Set、Map的全维度区别
三者是Java集合框架(Collection/Map体系)的核心接口,其差异源于数据结构设计、语义约束、性能模型的不同,需从底层实现视角理解:
1. 核心体系与底层实现
| 接口 |
父接口 |
核心数据结构(主流实现) |
核心语义约束 |
时间复杂度(平均) |
| List |
Collection |
ArrayList(动态数组)、LinkedList(双向链表) |
有序(插入序)、可重复、索引访问 |
随机访问:ArrayList O(1)、LinkedList O(n);增删:ArrayList尾部O(1)、中间O(n);LinkedList头尾O(1)、中间O(n) |
| Set |
Collection |
HashSet(HashMap底层)、TreeSet(红黑树)、LinkedHashSet(LinkedHashMap) |
无序(默认)、不可重复、无索引 |
添加/查询/删除:HashSet O(1)、TreeSet O(logn);LinkedHashSet O(1)(保留插入序) |
| Map |
无 |
HashMap(数组+链表+红黑树)、TreeMap(红黑树)、LinkedHashMap(HashMap+双向链表) |
键值对、键唯一、值可重复、无索引 |
添加/查询/删除:HashMap O(1)、TreeMap O(logn);LinkedHashMap O(1)(保留插入序) |
2. 关键特性深度解析
- List :
- 有序性:通过索引维护元素顺序,支持
add(int index, E)、get(int index)等索引操作;
- 可重复性:元素的
equals()返回true仍可添加;
- 并发问题:ArrayList/LinkedList非线程安全,并发场景需用
CopyOnWriteArrayList。
- Set :
- 不可重复性:依赖元素的
hashCode()和equals()(HashSet)或Comparable/Comparator(TreeSet);
- 无序性:HashSet的元素顺序由hash值决定,与插入序无关;LinkedHashSet通过双向链表保留插入序;TreeSet按自然序/自定义序排序。
- Map :
- 键唯一性:同Set的不可重复规则;
- 空值支持:HashMap允许1个null键、多个null值;TreeMap不允许null键(需比较);
- 扩容机制:HashMap默认初始容量16,负载因子0.75,扩容为2倍;JDK8中链表长度≥8且数组长度≥64时转为红黑树。
3. 适用场景与选型原则
- List:需按序存储、频繁索引访问、允许重复元素(如订单列表、日志记录);
- Set:需元素唯一性、无需索引(如用户ID集合、标签去重);
- Map:需键值映射、快速查找(如缓存、配置项、数据字典)。
六、ArrayList和LinkedList的深度对比
二者均实现List接口,但底层数据结构的差异导致性能、内存、功能呈现极致分化,需结合JVM内存模型理解:
1. 底层实现与内存模型
| 特性 |
ArrayList |
LinkedList |
| 数据结构 |
动态扩容的Object数组 |
双向链表(每个节点含prev/next/item) |
| 内存布局 |
连续内存块(JVM堆中连续分配) |
非连续内存(节点分散在堆中) |
| 扩容机制 |
1. 空参构造:初始容量0,首次add扩容为10,后续扩容为原容量1.5倍; 2. 指定容量:扩容为原容量1.5倍; 3. 扩容时复制数组(Arrays.copyOf),性能损耗 |
无扩容机制,按需创建节点,无内存冗余 |
| 内存开销 |
数组扩容产生冗余空间(未使用的数组位置) |
每个节点额外存储prev/next指针(约24字节/节点,64位JVM) |
2. 性能特性与测试指标
| 操作类型 |
ArrayList |
LinkedList |
性能结论 |
| 随机访问(get) |
O(1),直接通过索引定位数组元素 |
O(n),需从表头/表尾遍历到目标节点 |
ArrayList秒杀LinkedList |
| 尾部增删(add/remove) |
O(1)(无扩容时),扩容时O(n) |
O(1),直接修改尾节点指针 |
无扩容时性能接近,扩容时ArrayList慢 |
| 中间增删 |
O(n),需移动后续元素 |
O(n),需遍历找到节点+修改指针 |
数据量大时LinkedList略优(移动成本<数组复制) |
| 遍历效率 |
迭代器/普通for循环均高效(连续内存缓存友好) |
迭代器遍历高效,普通for循环(get(index))极低(多次遍历) |
ArrayList遍历效率更高 |
3. 功能扩展与选型
- ArrayList:实现
RandomAccess接口(标记支持快速随机访问),适合大数据量读取、少量尾部增删;
- LinkedList:实现
Deque接口,支持队列/双端队列/栈的所有操作(如offerFirst/pollLast),适合频繁头尾增删(如消息队列、任务栈);
- 并发场景:二者均非线程安全,ArrayList可用
CopyOnWriteArrayList,LinkedList无官方并发实现(需手动加锁)。
七、重载(Overload)和重写(Override)的深度区别
二者是Java多态的两大实现方式,分别对应「编译时多态(静态多态)」和「运行时多态(动态多态)」,需从JVM字节码层面理解其实现机制:
1. 核心机制与底层实现
| 特性 |
重载(Overload) |
重写(Override) |
| 多态类型 |
编译时多态(静态绑定) |
运行时多态(动态绑定) |
| 实现机制 |
编译器根据方法签名(方法名+参数列表)在编译期确定调用的方法(字节码中直接指定方法名) |
JVM在运行时根据对象的实际类型(而非引用类型),通过方法表(vtable)查找并调用方法 |
| 适用范围 |
同一个类(含父类方法在子类的重载) |
父子类(需满足继承关系) |
| 方法签名 |
方法名相同,参数列表(个数/类型/顺序)不同 |
方法签名(方法名+参数列表)完全相同 |
| 返回值 |
无强制约束(可不同) |
需满足「协变返回类型」:子类返回值为父类返回值的子类(JDK5+支持) |
| 访问修饰符 |
无强制约束 |
子类修饰符不能比父类更严格(如父类public→子类不能private) |
| 异常声明 |
无强制约束 |
子类抛出的异常需是父类异常的子类/子集,不可抛出更宽泛的检查异常 |
| 方法绑定 |
编译期绑定(静态分派) |
运行期绑定(动态分派) |
| 关键字 |
无(无需注解) |
推荐使用@Override注解(编译期校验) |
2. 关键注意事项
- 重载的判定仅看参数列表:参数名、返回值、异常、修饰符均不影响重载判定;
- 重写的里氏替换原则:子类重写方法需保证"替换父类对象后程序行为不变";
- 桥接方法:泛型父类的方法在子类重写时,编译器会生成桥接方法(如
Object m(Object)→String m(String)),保证方法签名匹配;
- final/static/private方法不可重写:final方法标记为ACC_FINAL,static方法属于类而非对象,private方法对子类不可见,均无法动态绑定。
八、Java深浅拷贝的深度区别
拷贝的核心是创建对象的独立副本,避免"引用传递"导致的对象修改联动问题,深浅拷贝的本质差异在于是否递归拷贝引用类型成员变量,需结合JVM内存模型理解:
1. 核心定义与内存模型
| 拷贝类型 |
核心语义 |
内存模型(简化) |
| 浅拷贝 |
仅拷贝对象本身(基本类型成员拷贝值,引用类型成员拷贝引用地址),新对象与原对象共享引用类型成员 |
原对象:{基本类型a=1,引用类型b→地址X}; 拷贝对象:{基本类型a=1,引用类型b→地址X} |
| 深拷贝 |
递归拷贝对象本身及所有引用类型成员(包括多层嵌套引用),新对象与原对象完全独立 |
原对象:{基本类型a=1,引用类型b→地址X}; 拷贝对象:{基本类型a=1,引用类型b→地址Y(新对象)} |
2. 实现方式与底层限制
| 拷贝类型 |
主流实现方式 |
优势 |
局限性 |
| 浅拷贝 |
1. 实现Cloneable接口,重写clone()(调用super.clone()); 2. 手动new对象并赋值基本类型成员 |
实现简单、性能高 |
引用类型成员共享,修改会联动;Cloneable是标记接口,无方法,设计不优雅 |
| 深拷贝 |
1. 递归实现Cloneable:所有引用类型成员均实现Cloneable,手动拷贝; 2. 序列化实现:实现Serializable,通过对象流读写; 3. 第三方工具:Apache Commons Lang的SerializationUtils、Gson/Jackson序列化 |
完全隔离对象,无修改联动问题 |
1. 递归Clone实现繁琐(多层嵌套需逐层实现); 2. 序列化无法拷贝transient修饰的成员; 3. 性能低于浅拷贝 |
3. 进阶特性与使用规范
- 不可变对象无需拷贝:如String、Integer等不可变类,引用传递即可,无需拷贝;
- transient关键字:序列化深拷贝时,transient修饰的成员不会被拷贝(可用于跳过无需拷贝的大对象);
- Cloneable的设计缺陷 :未实现
Cloneable调用clone()会抛出CloneNotSupportedException,违反接口设计原则;
- 生产级方案:优先使用序列化(如Jackson)实现深拷贝,代码简洁且适配多层嵌套;浅拷贝仅用于无引用类型成员的简单对象;
- 并发场景:拷贝后的对象若用于多线程,需保证原对象和拷贝对象均线程安全。