Java面试八股(Java基础,Spring,SpringBoot篇)
java基础
- JDK,JRE,JVM
- Java语言的特点
- Java常见的运行时异常
- Java为什么要封装
- 自增自减
- +=的隐式转换
- 移位运算符
- 1. 左移运算符(`<<`)
- 2. 带符号右移运算符(`>>`)
- 3. 无符号右移运算符(`>>>`)
- 可变参数
- break,continue,return 的区别及作用?
- this 关键字有什么作用?
- 深拷贝
- 浅拷贝
- finally 代码块是否一定执行?
- BigDecimal
- try-with-resources语句
- 语法:
- 与传统 `try…finally` 对比
- `Java`序列化中如果有些字段不想进行序列化,怎么办?
- 序列化
- SPI机制
- String的三大技术
- String 底层数据结构是什么?
- String 不可变性如何实现?
- 字符串常量池
- String s1 = new String("abc");这句话创建了几个字符串对象?
- intern()方法
- +号拼接字符串
- 什么是泛型?
- `Exception` 和 `Error`的区别
- 给你个jar包,里面有A类,B类,你要写C类来扩展它们。
- 基本数据类型和引用数据类型对比
- Integer和int的区别
- 如何理解面向对象?
- 多态存在的三个条件
- java面向对象,为什么还保留的基本数据类型?
- 值传递
- 泛型和通配符
- 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
- 对象实体,对象引用的区别
- 对象实体
- 对象引用
- 1. 构造器`Constructor`是否可被`override`?为什么?
- 2.` String`, `StringBuilder`和 `StringBuffer` 的区别是什么?`String`为什么是不可变的
- 3. 对象的相等与指向他们的引用相等,两者有什么不同?
- 4. 重载和重写的区别?
- 5. 在一个静态方法内调用一个非静态成员为什么是非法的?
- 6. 简述线程,进程的基本概念。以及他们之间关系?
- 7. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?
- 9. 成员变量与局部变量的区别有哪些?
- final、finalize 和 finally 的不同之处?
- `==`和`equals`的区别
- Java创建对象的五种方式?
- 接口和抽象类的区别
- 抽象类和普通类的区别?
- throw和throws
- Static可以修饰哪些?
- 1.1 成员变量(静态变量)
- 1.2 成员方法(静态方法)
- 1.3 静态初始化块
- 集合体系结构
- 有一个list,里面的元素是String类型,需要找出里面值为"abc"的并remove掉,该怎么做?
- ArrayList 的底层数据结构是什么?如何扩容?时间复杂度?
- ArrarList和LinkedList区别
- LinkedList 为什么不能实现 RandomAccess 接口?
- HashMap 的底层实现原理是什么?JDK 1.8 之前和之后的区别?
- HashCode方法
- 为什么两个对象的 `hashCode` 值相等,它们也不一定是相等的?
- 总结:
- 为什么重写 equals() 时必须重写 hashCode() 方法?
- 保持哈希契约的一致性
- 保证集合操作的正确性
- map的key可以重复吗,能不能为空?
- 为什么 HashSet 存和取的顺序不一样?
- HashSet 使用什么机制进行数据去重
- 集合遍历的几种方式
- synchronized 和 java.util.concurrent.locks.Lock 的异同?
- 线程的创建方式
- 线程有哪些基本状态?
- 反射是什么?
- IOC和AOP是通过什么机制来实现的?
- Spring的核心思想,说说你的理解?
- Spring事务
- Spring事务的实现原理
- `BeanFactory` 和 `ApplicationContext`有什么区别?
- AOP 面向切面编程
- Spring IOC 是什么?使用场景有哪些?Spring 事务,事务的属性,数据库隔离级别
- IOC中的bean
- 什么是Spring的bean?
- Bean 的生命周期
- Bean 的配置方式:
- Bean的作用域
- 依赖注入的三种方式
- 单例bean是线程安全的吗
- 单例bean和非单例的生命周期是否一样?
- Spring 如何解决循环依赖?
- Spring MVC 的核心组件有哪些?
- MVC分层
- 单点登录
- cookie和session的区别
- get和post请求的区别
- HTTP请求状态码
- 过滤器和拦截器的区别
- HTTP方法
- Spring 设计模式
- Spring Boot、Spring MVC 和 Spring 有什么区别?
- SpringMVC 怎么样设定重定向和转发的?
- 当一个方法向`AJAX`返回特殊对象,比如`Object`,`List`等,需要做什么处理?
- SpringMVC 用什么对象从后台向前台传递数据的?
- @Component 和 @Bean 的区别?
- @Autowired和@Resource注解的区别
- @Controller和@RestController注解的区别
- 将一个类声明为 Bean 的注解有哪些?
- Spring Boot自动装配原理**
- 为什么在`web`开发中,声明控制器 `bean` 只能用 `@Controller`?
- 依赖冲突问题
- @Transactional注解
- SpringBoot原理, SpringBoot为什么大大简化了 `Spring` 开发?
- SpringBoot中哪里用到了反射知识?
- 配置文件相关
- Spring Boot 四大核心注解
JDK,JRE,JVM
+-------------------+
| JDK | ← Java 开发工具包,包含开发和运行 Java 程序所需的工具和环境
| +-------------+ |
| | JRE | | ← Java 运行环境,包含JVM和一些类库
| | +-------+ | |
| | | JVM | | | ← Java 虚拟机,执行 Java 程序的引擎
| | +-------+ | |
| +-------------+ |
+-------------------+
JVM
:是Java
程序运行的引擎,负责将字节码转换为机器码并执行,实现Java
的跨平台特性
![[Pasted image 20250506140234.png]]
![[Pasted image 20250506140625.png]]
Java
在执行流程上兼具“编译”和“解释”两种特性:它首先通过 javac
编译器将源代码编译成与平台无关的字节码(.class
文件),然后由JVM
以解释或即时编译(JIT)的方式将字节码转为本地机器码并执行。因此,Java
通常被称为“编译-解释型”或“混合型”语言.
栈区
: 存储局部变量
、方法调用和返回值。每当一个方法被调用时,JVM
会为该方法创建一个栈帧(stack frame
),并将其压入当前线程的栈中。当方法执行完成后,栈帧会被弹出,释放该方法所占的栈内存。
堆区
: 存放new
创建的对象和数组, 类的实例变量
; JVM不定期检查堆区,如果对象没有引用指向它,内存将被回收.
方法区
: 用于存储类信息、常量池、静态变量
、方法代码等数据。可以认为方法区是类级别的内存区域,而栈区和堆区存储的是实例级别的数据。
成员变量(字段)
指的是在类中、方法外声明的字段,它们包括两大类:实例变量
和 静态变量
Java语言的特点
-
跨平台:
java
程序编译后生成和平台无关的字节码文件,由针对不同操作系统的JVM
执行,实现一次编译处处运行的跨平台能力 -
面向对象
-
健壮性:有垃圾回收和异常处理机制
Java常见的运行时异常
-
空指针异常(
NullPointerException
), -
数组/字符串下标越界(
ArrayIndexOutOfBoundsException & StringIndexOutOfBoundsException
) -
算术异常(
ArithmeticException
) -
类型转换异常(
ClassCastException
)
Java为什么要封装
- 隐藏对象的内部实现细节,仅暴露必要接口,实现“高内聚、低耦合”。
- 作用:保护数据安全:通过
private
字段限制直接访问,防止非法修改。
灵活维护:内部逻辑修改不影响外部调用。
自增自减
++
和 --
运算符可以放在变量之前,也可以放在变量之后:
- 前缀形式(例如
++a
或--a
):先自增/自减变量的值,然后再使用该变量,例如,b = ++a
先将a
增加 1,然后把增加后的值赋给b
。 - 后缀形式(例如
a++
或a--
):先使用变量的当前值,然后再自增/自减变量的值。例如,b = a++
先将a
的当前值赋给b
,然后再将a
增加 1。
![[Pasted image 20250506142657.png]]
- Java 里使用
long
类型的数据一定要在数值后面加上 L,否则将作为整型解析。 - Java 里使用
float
类型的数据一定要在数值后面加上 f 或 F,否则将无法通过编译。
System.out.println(42 == 42.0);// true
当使用 ==
操作符比较 42
和 42.0
时,Java 会进行类型转换。会将 int
类型的 42
自动提升为 double
类型,然后与 42.0
进行比较。
由于 42
转换为double
类型后是 42.0
,与 42.0
的值相等,所以比较结果为 true
。
+=的隐式转换
- 复合赋值(
+=
)会在赋值时对结果做一次“自动缩小转换
a += b; // ⇔ a = (TypeOfA) (a + b);
而普通赋值 a = a + b
则不含此隐式转换,若右侧类型更宽必须显式强转,否则编译报错
移位运算符
1. 左移运算符(<<
)
作用:将 a
的二进制位向左移动 n
位,右边补 0
。
效果:相当于 a * 2ⁿ
(在不溢出的情况下)。
示例:
int a = 3; // 二进制:00000011
int b = a << 2; // 左移两位:00001100,即12
System.out.println(b); // 输出12
2. 带符号右移运算符(>>
)
作用:将 a
的二进制位向右移动 n
位,左边用原来的符号位(最高位)补齐。
效果:相当于 a / 2ⁿ
,并保留符号(正数补0,负数补1)。
示例:
int a = -8; // 二进制(补码):11111000
int b = a >> 2; // 右移两位:11111110,即-2
System.out.println(b); // 输出-2
3. 无符号右移运算符(>>>
)
作用:将 a
的二进制位向右移动 n
位,左边统一补 0
(不管正负)。
适用:适用于 int
和 long
类型,特别是处理位运算时。
示例:
int a = -8; // 补码:11111000
int b = a >>> 2; // 无符号右移:00111110(高位补0)
System.out.println(b); // 输出1073741822(一个大正数)
可变参数
- 可变参数编译后实际会被转换成一个数组
public static void method1(String... args) {//......
}
- 可变参数只能作为方法的最后一个参数
public static void method2(String arg1, String... args) {//......
}
- 如果出现方法重载,会优先匹配固定参数的方法:
![[Pasted image 20250506190537.png]]
这里输出aaa方法2
.
break,continue,return 的区别及作用?
break
跳出整个循环,不再执行循环continue
结束当前循环,继续执行下次循环return
程序返回,不再执行下面的代码(结束当前的方法 直接返回)
this 关键字有什么作用?
-
this
是指向对象本身的一个指针 -
用来区分成员变量和局部变量:
public Student(String name) {// 使用 this 来区分成员变量 name 和局部变量 namethis.name = name;}
- 可用来传递当前对象,引用当前对象的方法等
深拷贝
深拷贝是把对象及其引用的所有对象都复制一份,新对象和原对象在内存中没有任何关联。修改新对象不会影响原对象,包括对象中引用的其他对象。
浅拷贝
浅拷贝是指对象的字段值的复制,当对象的字段是基本数据类型时,会直接复制数值;当对象的字段是引用类型时,浅拷贝只是复制了引用地址,新对象和原对象仍然引用同一个内存地址
finally 代码块是否一定执行?
- 无论是否发生异常,
finally
代码块都会执行。 - 例外情况:
JVM
崩溃- 线程被终止
- 无限循环或死锁导致无法执行到
finally
。
BigDecimal
BigDecimal
对小数进行运算时,不会出现精度损失
使用BigDecimal(String val)
构造方法或者 BigDecimal.valueOf(double val)
静态方法来创建对象:
BigDecimal a = new BigDecimal("1.0");
BigDecimal
类型的数据进行比较时应使用compareTo()
方法,而不是equals()
,因为 equals()
方法不仅仅会比较值的大小(value)
还会比较精度(scale)
,而 compareTo()
方法比较的时候会忽略精度。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.equals(b));//false,不应该使用equals方法
compareTo()
方法可以比较两个 BigDecimal
的值,如果相等就返回0
,如果第1
个数比第2
个数大则返回1
,反之返回-1
。
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
System.out.println(a.compareTo(b));//0
try-with-resources语句
不用再像try...catch...finally
那样手动关闭 (close
) 资源,使用前要实现 AutoCloseable
接口.
语法:
try (ResourceType res = new ResourceType()) {// 使用 res 执行操作
} catch (ExceptionType e) {// 异常处理
}
- 在
try()
中声明资源,调用结束后自动关闭
与传统 try…finally
对比
特性 | try-with-resources | 传统 try…finally |
---|---|---|
资源关闭 | 自动调用 close() 按逆序关闭支持异常抑制 | 手动调用 close() 易漏写或漏捕获 |
代码简洁性 | 声明式管理,省略多余 finally 代码 | 大量样板 finally 和嵌套 try…catch |
异常处理 | 抑制式管理关闭异常,不丢失原抛出异常 | 关闭异常可覆盖原异常,需手动链式处理 |
Java
序列化中如果有些字段不想进行序列化,怎么办?
在Java
原生的序列化机制中(即实现 Serializable
接口的类),可以使用 transient
关键字来标记不需要序列化的字段。被 transient
修饰的字段在序列化时会被忽略,反序列化后其值会被重置为默认值(如 0
、null
等)。
import java.io.Serializable;public class User implements Serializable {private String username;private transient String password; // 不会被序列化// 构造方法、getter 和 setter 省略
}
序列化
- 序列化:将数据结构或对象转换成可以存储或传输的形式,通常是二进制字节流,也可以是
JSON
,XML
等文本格式 - 反序列化:将在序列化过程中所生成的数据转换为原始数据结构或者对象的过程
SPI机制
-
解释:
JDK
内置的一种服务提供发现机制
,用来启用框架扩展和替换组件,,比如java.sql.Driver
接口,不同厂商可以针对同一接口做出不同的实现,比如MySQL
和PostgreSQL
都有不同的实现提供给用户 -
SPI
机制可以为某个接口寻找服务实现。其主要思想是将装配的控制权移到程序之外,它的核心思想是解耦
。
![[Pasted image 20250508111708.png]]
![[Pasted image 20250508083736.png]]
String的三大技术
-
压缩:
String
内部用byte[]
存储,节省空间 -
常量池:避免重复创建字符串对象
-
不可变:保证线程安全、哈希值稳定
String 底层数据结构是什么?
- JDK 8 及之前是
final char[]
, JDK 9 以后是final byte[]
String 不可变性如何实现?
-
存储数据的数组被
final
修饰(引用不可变) -
String
没有提供修改数组内容的方法 -
String
类本身被final
修饰
![[Pasted image 20250507091559.png]]
字符串常量池
![[Pasted image 20250507093543.png]]
String s1 = new String(“abc”);这句话创建了几个字符串对象?
答案:会创建1
或 2
个字符串对象。
如果字符串常量池中已经有一个,则不再创建新的,直接引用;如果没有,则创建一个。
堆中肯定有一个,因为只要使用了new
关键字,肯定会在堆中创建一个
intern()方法
String.intern()
方法可以让 String
对象在运行时加入到JVM
的字符串常量池, 并返回常量池中该字符串的唯一实例引用,从而保证具有相同字符序列的字符串仅被存储一次,可减少内存占用和提高比较效率。
原理:
-
当执行
s.intern()
时,JVM 首先在字符串常量池中查找是否已有与s.equals(...)
相等的字符串: -
若存在,则立即返回该已有实例的引用;
-
否则,将
s
加入常量池,并返回其自身引用。
示例:
String s1 = new String("Hello");
String s2 = "Hello";
String s3 = s1.intern();System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true
s1
是堆上创建的对象, s2
与 s3
都引用常量池中的 "Hello"
实例,因此 s2 == s3
为 true
。
+号拼接字符串
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; // 编译期常量 -> 常量池驻留
String str4 = str1 + str2; // 运行时新建对象
String str5 = "string"; // 直接字面量 -> 常量池驻留System.out.println(str3 == str4); // false
System.out.println(str3 == str5); // true
System.out.println(str4 == str5); // false
什么是泛型?
-
一种参数化类型机制, 允许在定义类,接口,方法的时候使用参数, 可以提高代码的复用性、类型安全性(比如集合使用泛型来约束元素类型)
-
类型擦除 : 代码在编译阶段会移除泛型信息,把泛型信息替换成原始类型(通常是Object)
Exception
和 Error
的区别
Exception
和 Error
都是 Throwable
类的子类
Throwable
├── Error // 严重错误(程序无法处理)
└── Exception // 程序可处理的异常├── RuntimeException // 非受检异常(Unchecked)└── 其他Exception // 受检异常(Checked)
特征 | Error | Exception |
---|---|---|
严重性 | JVM 无法处理的问题 | 程序可处理的异常 |
是否可恢复 | 不可恢复(程序应终止) | 可恢复(通过捕获处理) |
是否强制捕获 | 非受检(无需 try-catch 或 throws ) | 受检异常需处理,非受检异常(如 RuntimeException )不用处理 |
典型示例 | OutOfMemoryError 、StackOverflowError | IOException 、NullPointerException |
给你个jar包,里面有A类,B类,你要写C类来扩展它们。
- 已有类
A
和B
,需要编写类C
来扩展它们的功能。 - Java 不支持多继承,直接继承
A
和B
不可行。 - 最终方案是:
- 创建
A
的子类AChild
和B
的子类BChild
。 - 在
C
类中组合AChild
和BChild
的实例。
- 创建
设计原则:
-
开闭原则:通过子类扩展(而非修改父类)实现功能增强。
-
组合优于继承:用组合实现多逻辑复用,避免多重继承的复杂性。
-
单一职责原则:
AChild
和BChild
各自只负责对父类功能的扩展,C
负责组合逻辑。
// 原有类
public class A {public void log(String message) {System.out.println("Log: " + message);}
}public class B {public boolean validate(String input) {return input != null;}
}// 扩展子类
public class AChild extends A {@Overridepublic void log(String message) {super.log("[Enhanced] " + message); // 增强日志格式}
}public class BChild extends B {@Overridepublic boolean validate(String input) {return super.validate(input) && input.length() > 0; // 增加非空校验}
}// 组合类
public class C {private AChild aChild = new AChild();private BChild bChild = new BChild();public void process(String input) {if (bChild.validate(input)) {aChild.log("Valid input: " + input);} else {aChild.log("Invalid input: " + input);}}
}
基本数据类型和引用数据类型对比
维度 | 基本数据类型 | 对象类型 |
---|---|---|
存储内容 | 直接存储值 | 存储引用 |
内存位置 | 值存储在栈内存 | 引用在栈,对象在堆内存 |
是否可为 null | 不能 | 可以 |
性能 | 操作速度快 | 操作较慢 |
内存占用 | 固定大小(如 int 占 4 字节) | 对象内存包含对象头、字段等,通常更大 |
方法调用 | 按值传递(复制值) | 按引用传递(复制引用地址) |
泛型支持 | 不能直接用于泛型(需包装类) | 可直接用于泛型 |
Integer和int的区别
-
integer
初始值是null
,int
初始值是0
; -
integer
存放在堆
内存,int
存放在栈
内存 -
integer
是一个对象类型,封装了很多方法,使用的时候更加灵活
如何理解面向对象?
-
面向过程注重的是解决问题的
步骤
, 比如洗衣服,打开洗衣机,放入衣服,启动洗衣机,漂洗,烘干 -
面向对象关注“参与者”(对象),把“人”“洗衣机”“衣服”都看作对象,赋予它们属性与行为,通过对象协作完成业务。
-
还有个计算圆面积的例子, 如果使用面向过程的方法, 是直接定义半径啥的进行计算, 但是如果使用面向对象的方式, 首先会定义一个圆类, 然后提供一些方法来计算圆面积,
多态存在的三个条件
-
存在继承关系
-
子类重写父类的方法
-
向上转型: 通过父类引用指向子类对象
Animal a = new Dog(); // 向上转型
a.speak(); // 调用 Dog 的 speak()
java面向对象,为什么还保留的基本数据类型?
-
基本数据类型的性能效率比对象更高,而且占用的内存更小
-
基本数据类型不需要进行垃圾回收
值传递
Java
中将实参传递给方法的方式是值传递
:
- 如果参数是基本类型的话,传递的就是基本类型的字面量值的拷贝,会创建副本。
- 如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。
泛型和通配符
在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
-
子类构造方法默认会调用父类的无参构造方法,来初始化继承的成员。
-
如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法。
对象实体,对象引用的区别
对象实体
- 是类的实例,包含了类定义的属性和方法。
- 存储在
堆内存
中。 - 每次使用
new
创建的对象,都是一个新的实体。
对象引用
- 是指向对象实体的变量,存储在
栈内存
中。 - 保存了对象实体在堆内存中的地址。
- 多个引用变量可以指向同一个对象实体。
![[Pasted image 20250511203349.png]]
全对.
1. 构造器Constructor
是否可被override
?为什么?
不能,因为 构造器不是继承自父类的方法, 而且构造器方法名要和类名相同, 如果重写的话,子类的构造方法会和父类相同,所以不满足这个条件, 不属于继承体系的一部分
2. String
, StringBuilder
和 StringBuffer
的区别是什么?String
为什么是不可变的
-
String
不可变,StringBuilder
,StringBuffer
可变 -
String
天然线程安全, 但是String
拼接生成新对象,性能最差 -
StringBuffer
线程安全,性能低, -
StringBuilder
线程不安全,性能较高 -
如果是单线程,使用
StringBuilder
; 多线程环境下使用StringBuffer
3. 对象的相等与指向他们的引用相等,两者有什么不同?
- 引用相等是指两个引用变量是否指向同一个对象实例,使用
==
进行比较,比较的是两个引用变量的内存地址
是否相同. - 对象相等是指两个
对象的内容
是否相同,使用equals()
方法比较。
4. 重载和重写的区别?
-
重载发生发生在同一类中(方法与方法之间),方法名相同而参数类型或者参数个数,顺序不同
-
重写发生在类之间,指的是子类继承父类方法并提供新的实现,方法签名(方法名+参数列表)必须完全相同
5. 在一个静态方法内调用一个非静态成员为什么是非法的?
-
静态方法是属于类的,不依赖于任何对象实例;
-
非静态成员属于对象实例,静态方法在非静态成员存在之前就已经存在了
6. 简述线程,进程的基本概念。以及他们之间关系?
- 程序是指一组有序的指令集合,表示了某个任务的逻辑和操作步骤,并不具备运行能力。
- 进程是程序的一次执行实例,是资源分配的基本单位,进程之间相互独立
- 线程是进程中的一个执行单元,是
CPU
调度的基本单位 - 一个程序可以对应多个进程(如多次运行),一个进程可以包含多个线程。线程依赖于进程存在,不能独立于进程存在。
7. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?
-
构造方法的作用是在创建对象时初始化对象的状态;
-
能正常运行,如果一个类没有定义任何构造方法,编译器会自动生成一个无参构造方法;
-
但是如果类中已经声明了带参数的构造方法,编译器将不会再自动生成默认的无参构造方法。如果还需要使用无参构造方法,必须显式声明;
9. 成员变量与局部变量的区别有哪些?
-
成员变量定义在
类中方法外
, 整个类都可以访问,成员变量储存在堆/方法区
中,有默认初始化值 -
局部变量定义在
方法/代码块
内, 只有在该方法/代码块内才能访问,局部变量储存在栈
中,无默认初始化值
final、finalize 和 finally 的不同之处?
-
final
是一个修饰符,可以修饰变量、方法和类。如果final
修饰变量,意味着该变量在初始化后不能被改变。修饰类则该类不能被继承; 修饰一个方法时,表明这个方法不能被重写 -
finalize()
方法用于在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。 -
finally
是一个关键字,与try
和catch
一起用于异常的处理。finally
块一定会被执行,无论在try
块中是否有发生异常.但在调用System.exit()
、JVM 崩溃、无限循环或进程被强制杀死等极端情况下,finally
不会执行
==
和equals
的区别
==
比较基本数据类型时比较的是内容, 比较引用数据类型比较的是地址值(引用)equals
比较的是引用,但是有一些类比如String
重写之后就是比较的是内容
Java创建对象的五种方式?
(1)new
关键字
(2)反射 Class.newInstance
和Constructor.newInstance
try {// 方法1:Class.newInstance()(已过时,推荐Constructor)Class<?> clazz = Class.forName("com.example.Employee");Employee emp1 = (Employee) clazz.newInstance(); // Deprecated in Java 9+// 方法2:Constructor.newInstance()Constructor<Employee> constructor = Employee.class.getDeclaredConstructor();Employee emp2 = constructor.newInstance();
} catch (Exception e) {e.printStackTrace();
}
(3)Clone
方法
public class Employee implements Cloneable {private String name;@Overridepublic Employee clone() throws CloneNotSupportedException {return (Employee) super.clone(); // 浅拷贝}
}// 使用克隆创建对象
Employee original = new Employee("Alice");
Employee copy = original.clone();
(4)反序列化
接口和抽象类的区别
-
类是单继承的(包括抽象类), 但一个类可以实现多个接口,接口是多继承;
-
成员变量:接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值。抽象类的成员变量可以有任何修饰符(private
,protected
,public
),可以在子类中被重新定义或赋值。 -
抽象类可以有构造方法, 接口没有构造方法
抽象类和普通类的区别?
-
抽象类用
abstract
关键字定义,不能被实例化,只能作为其他类的父类。普通类没有abstract
关键字,可以实例化。 -
抽象类可以包含
抽象
方法和非抽象
方法。抽象方法没有方法体,必须由子类实现。普通类只能包含非抽象
方法。
abstract class Animal {// 抽象方法public abstract void makeSound();// 非抽象方法public void eat() {System.out.println("This animal is eating.");}
}
throw和throws
throw
用于抛出异常throws
用于方法签名中声明该方法可能抛出的异常
Static可以修饰哪些?
1.1 成员变量(静态变量)
使用 static
修饰的字段称为静态变量(或类变量),在类加载时分配内存,所有对象实例共享同一份数据。
public class Example {public static int count = 0; // 静态变量
}
1.2 成员方法(静态方法)
静态方法,属于类本身,可通过 ClassName.method()
调用,不依赖于具体实例。
public class Example {public static void printHello() { // 静态方法System.out.println("Hello");}
}
1.3 静态初始化块
静态初始化块在类第一次加载时执行一次,用于对复杂静态变量进行初始化。
public class Example {public static Map<String, String> map;static {map = new HashMap<>();map.put("key", "value");}
}
集合体系结构
![[Pasted image 20250515134740.png]
有一个list,里面的元素是String类型,需要找出里面值为"abc"的并remove掉,该怎么做?
-
注意不能在增强
for
循环中直接调用list.remove()
方法,否则会触发ConcurrentModificationException
错误示例:
List<String> list = new ArrayList<>(Arrays.asList("abc", "def", "abc"));
for (String s : list) { if ("abc".equals(s)) {list.remove(s); // 直接调用 list.remove() ❌}
}
- 使用迭代器:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String s = iterator.next();if ("abc".equals(s)) { // 避免空指针异常iterator.remove(); // 安全移除}
}
ArrayList 的底层数据结构是什么?如何扩容?时间复杂度?
-
底层数据结构是动态数组,支持快速随机访问
-
扩容机制: 使用无参构造方法进行初始化时,默认容量是
0
, 第一次添加元素时扩容到10
, 以后如果容量不足,会触发扩容,新容量为旧容量的1.5
倍. -
如果一次添加多个元素,1.5倍扩容后还放不下,则容量大小为实际元素个数为准
-
时间复杂度: 尾部插入
O(1)
,中间插入O(n)
, 随机访问O(1)
, 删除元素O(n)
ArrarList和LinkedList区别
-
ArrayList
底层基于动态数组, 适合读多写少的场景,而且线程不安全 -
LinkedList
底层基于链表,适合写多、读操作不依赖随机访问的场景; -
对于 删除 和 新增元素 操作谁的性能更好, 要看情况, 若只对单条数据插入或删除,
ArrayList
的速度更好。如果是批量随机的插入删除数据,LinkedList
的速度更好, 因为ArrayList
每插入一条数据,要移动插入点及之后的数据。
LinkedList 为什么不能实现 RandomAccess 接口?
RandomAccess
是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引访问元素)。 LinkedList
底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess
接口。
HashMap 的底层实现原理是什么?JDK 1.8 之前和之后的区别?
- 底层实现:- JDK 1.8 前是数组 + 链表(拉链法解决哈希冲突)。JDK 1.8 后是数组 + 链表/红黑树(当链表长度 ≥ 8 时,链表转为红黑树;当树节点数 ≤ 6 时,退化为链表)。
HashCode方法
为什么两个对象的 hashCode
值相等,它们也不一定是相等的?
答: 因为 hashCode()
使用的哈希算法可能会让不同对象产生相同的哈希值
总结:
- 如果两个对象的
hashCode
值不相等,则这两个对象不相等 - 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。 - 如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,这两个对象才相等。
为什么重写 equals() 时必须重写 hashCode() 方法?
有规范指定需要同时重写hashcode
与equals
是方法,许多容器如HashMap
、HashSet
都依赖于这个规范。
因为两个相等的对象的 hashCode
值必须是相等。也就是说如果 equals
方法判断两个对象是相等的,那这两个对象的 hashCode
值也要相等。
如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。
保持哈希契约的一致性
Java
的规范规定:如果两个对象通过 equals()
方法比较相等,那么它们的 hashCode()
方法必须返回相同的整数。这是为了确保在哈希结构中能够正确地存储和检索对象。如果只重写了 equals()
而没有重写 hashCode()
,可能会导致逻辑上相等的对象被分配到不同的哈希桶中,从而无法正确地查找或去重。
保证集合操作的正确性
以 HashMap
为例,其 put()
和 get()
方法首先会根据键对象的 hashCode()
计算哈希值,然后在对应的桶中通过 equals()
方法查找匹配的键。如果 equals()
和 hashCode()
的实现不一致,可能会导致无法找到已存储的键,造成数据丢失或重复存储
map的key可以重复吗,能不能为空?
Map
不允许重复的键(key)- 是否允许
null
键取决于具体的Map
实现;例如HashMap
和LinkedHashMap
允许一个null
键,而Hashtable
、ConcurrentHashMap
、TreeMap
等不允许。
为什么 HashSet 存和取的顺序不一样?
HashSet
基于哈希表 实现的,不保证元素的插入顺序。
HashSet 使用什么机制进行数据去重
使用的是哈希值(hashCode)+ equals 方法来判断元素是否重复。
具体如下:
-
当添加元素时,先计算元素的
hashCode()
值; -
然后查找该哈希值所在的桶(bucket);
-
在桶中会遍历已有元素,通过
equals()
比较是否有“相等”的元素:-
如果有,认为是重复元素,不添加;
-
如果没有,则加入。
-
集合遍历的几种方式
-
增强 for 循环
foreach
-
forEach
循环 -
普通
for
循环 -
迭代器
-
Stream API
遍历
synchronized 和 java.util.concurrent.locks.Lock 的异同?
相同:
- 都用于解决线程安全问题
- 都能保证可见性和原子性
- 都使用互斥机制
不同 :
-
synchronized
是一个关键字,Lock
是一个接口 -
Lock
的锁控制比synchronized
更加灵活,可以更精确的控制锁的范围和粒度 -
synchronized
的使用更加简单
线程的创建方式
-
继承
Thread
类 -
实现
Runnable
接口 -
使用
Callable
接口和Future
接口 -
使用
线程池
创建线程
线程有哪些基本状态?
-
新建(
NEW
):线程刚创建但hai未启动 -
可运行(RUNNABLE):调用线程的
start()
方法后,线程进入可运行状态,等待被线程调度器分配CPU
时间片执行。 -
阻塞(BLOCKED):线程在等待获取一个被其他线程持有的监视器锁时进入阻塞状态。
-
等待(WAITING):线程无限期地等待另一个线程执行特定操作(如
notify()
或notifyAll()
)以唤醒它。 -
计时等待(TIMED_WAITING):线程在指定的时间内等待另一个线程的操作,超过时间后自动唤醒。常见的方法包括
sleep()
、join(long)
、wait(long)
等。 -
终止(TERMINATED):线程执行完毕或因异常退出,进入终止状态。
反射是什么?
-
反射是指在程序运行时,对于任意一个类,都能获取到这个类的所有属性和方法,对于任意一个对象,都能调用它的任意属性和方法
-
反射允许程序在运行时动态地获取类的信息并操作类的属性、方法、构造器等
-
传统方式是 “通过类创建对象”,而反射是 “通过对象反向获取类的信息”
Spring,SpringBoot相关
- JDK,JRE,JVM
- Java语言的特点
- Java常见的运行时异常
- Java为什么要封装
- 自增自减
- +=的隐式转换
- 移位运算符
- 1. 左移运算符(`<<`)
- 2. 带符号右移运算符(`>>`)
- 3. 无符号右移运算符(`>>>`)
- 可变参数
- break,continue,return 的区别及作用?
- this 关键字有什么作用?
- 深拷贝
- 浅拷贝
- finally 代码块是否一定执行?
- BigDecimal
- try-with-resources语句
- 语法:
- 与传统 `try…finally` 对比
- `Java`序列化中如果有些字段不想进行序列化,怎么办?
- 序列化
- SPI机制
- String的三大技术
- String 底层数据结构是什么?
- String 不可变性如何实现?
- 字符串常量池
- String s1 = new String("abc");这句话创建了几个字符串对象?
- intern()方法
- +号拼接字符串
- 什么是泛型?
- `Exception` 和 `Error`的区别
- 给你个jar包,里面有A类,B类,你要写C类来扩展它们。
- 基本数据类型和引用数据类型对比
- Integer和int的区别
- 如何理解面向对象?
- 多态存在的三个条件
- java面向对象,为什么还保留的基本数据类型?
- 值传递
- 泛型和通配符
- 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
- 对象实体,对象引用的区别
- 对象实体
- 对象引用
- 1. 构造器`Constructor`是否可被`override`?为什么?
- 2.` String`, `StringBuilder`和 `StringBuffer` 的区别是什么?`String`为什么是不可变的
- 3. 对象的相等与指向他们的引用相等,两者有什么不同?
- 4. 重载和重写的区别?
- 5. 在一个静态方法内调用一个非静态成员为什么是非法的?
- 6. 简述线程,进程的基本概念。以及他们之间关系?
- 7. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?
- 9. 成员变量与局部变量的区别有哪些?
- final、finalize 和 finally 的不同之处?
- `==`和`equals`的区别
- Java创建对象的五种方式?
- 接口和抽象类的区别
- 抽象类和普通类的区别?
- throw和throws
- Static可以修饰哪些?
- 1.1 成员变量(静态变量)
- 1.2 成员方法(静态方法)
- 1.3 静态初始化块
- 集合体系结构
- 有一个list,里面的元素是String类型,需要找出里面值为"abc"的并remove掉,该怎么做?
- ArrayList 的底层数据结构是什么?如何扩容?时间复杂度?
- ArrarList和LinkedList区别
- LinkedList 为什么不能实现 RandomAccess 接口?
- HashMap 的底层实现原理是什么?JDK 1.8 之前和之后的区别?
- HashCode方法
- 为什么两个对象的 `hashCode` 值相等,它们也不一定是相等的?
- 总结:
- 为什么重写 equals() 时必须重写 hashCode() 方法?
- 保持哈希契约的一致性
- 保证集合操作的正确性
- map的key可以重复吗,能不能为空?
- 为什么 HashSet 存和取的顺序不一样?
- HashSet 使用什么机制进行数据去重
- 集合遍历的几种方式
- synchronized 和 java.util.concurrent.locks.Lock 的异同?
- 线程的创建方式
- 线程有哪些基本状态?
- 反射是什么?
- IOC和AOP是通过什么机制来实现的?
- Spring的核心思想,说说你的理解?
- Spring事务
- Spring事务的实现原理
- `BeanFactory` 和 `ApplicationContext`有什么区别?
- AOP 面向切面编程
- Spring IOC 是什么?使用场景有哪些?Spring 事务,事务的属性,数据库隔离级别
- IOC中的bean
- 什么是Spring的bean?
- Bean 的生命周期
- Bean 的配置方式:
- Bean的作用域
- 依赖注入的三种方式
- 单例bean是线程安全的吗
- 单例bean和非单例的生命周期是否一样?
- Spring 如何解决循环依赖?
- Spring MVC 的核心组件有哪些?
- MVC分层
- 单点登录
- cookie和session的区别
- get和post请求的区别
- HTTP请求状态码
- 过滤器和拦截器的区别
- HTTP方法
- Spring 设计模式
- Spring Boot、Spring MVC 和 Spring 有什么区别?
- SpringMVC 怎么样设定重定向和转发的?
- 当一个方法向`AJAX`返回特殊对象,比如`Object`,`List`等,需要做什么处理?
- SpringMVC 用什么对象从后台向前台传递数据的?
- @Component 和 @Bean 的区别?
- @Autowired和@Resource注解的区别
- @Controller和@RestController注解的区别
- 将一个类声明为 Bean 的注解有哪些?
- Spring Boot自动装配原理**
- 为什么在`web`开发中,声明控制器 `bean` 只能用 `@Controller`?
- 依赖冲突问题
- @Transactional注解
- SpringBoot原理, SpringBoot为什么大大简化了 `Spring` 开发?
- SpringBoot中哪里用到了反射知识?
- 配置文件相关
- Spring Boot 四大核心注解
IOC和AOP是通过什么机制来实现的?
-
IOC
通过反射,依赖注入,工厂模式,容器机制来实现 -
AOP
基于动态代理来实现,有JDK
动态代理和CGLB
动态代理
Spring的核心思想,说说你的理解?
![[Pasted image 20250529104755.png]]
Spring事务
-
Spring
事务保证一组数据库操作要么全部成功,要么全部失败(原子性) -
分为声明式事务和编程式事务,声明式事务通过注解,如
@Transactional
实现。编程式事务通过
TransactionTemplate
或 PlatformTransactionManager
手动控制。
-
事务属性:
propagation
(传播行为):如REQUIRED
、REQUIRES_NEW
等
isolation
(隔离级别):见下文
timeout
:超时时间
readOnly
:只读事务(可优化性能)
rollbackFor
:哪些异常触发回滚 -
数据库隔离级别:
隔离级别 | 说明 | 可避免问题 |
---|---|---|
READ UNCOMMITTED | 读未提交 | 无法避免任何问题 |
READ COMMITTED | 读已提交(Oracle 默认) | 避免脏读 |
REPEATABLE READ | 可重复读(MySQL 默认) | 避免脏读、不可重复读 |
SERIALIZABLE | 串行化(最高级别) | 避免所有并发问题 |
Spring事务的实现原理
- 核心机制:通过
AOP
动态代理管理事务。 - 流程:
- 使用
@Transactional
注解标记方法。 - 生成代理对象,在方法执行前开启事务(
beginTransaction()
)。 - 方法执行成功则提交事务(
commit()
),失败则回滚(rollback()
)。
- 使用
BeanFactory
和 ApplicationContext
有什么区别?
-
BeanFactory
:Spring
的基础容器,提供基本的依赖注入功能,采用延迟加载。 -
ApplicationContext
:BeanFactory
的子接口,是更高级的容器,支持国际化、事件发布、AOP
等,采用预加载策略。
![[Pasted image 20250513142459.png]]
- 延迟加载: 真正需要
bean
时才创建bean
实例
AOP 面向切面编程
-
AOP
是面向切面编程, 将和业务无关的共性代码逻辑封装起来,减少重复的代码,降低模块之间的耦合度, 使用场景有日志记录
和权限控制
,性能监控
等 -
AOP
的实现原理是动态代理(JDK动态代理,CGLB代理) -
切入点:需要织入逻辑(增强功能)的方法。
-
通知:织入的具体逻辑(增强功能的代码逻辑),如前置、后置、环绕通知。
-
切面:绑定通知与切入点的关系。
-
用途:统一日志处理、性能监控、事务管理
-
好处: 减少代码冗余,提升可维护性。
Spring IOC 是什么?使用场景有哪些?Spring 事务,事务的属性,数据库隔离级别
-
IOC
(控制反转) : 对象创建、依赖管理的控制权由程序本身转移到IOC
容器 -
IOC
的好处 : 解耦合,提高程序可维护性和可测试性 -
容器的作用是创建
bean
实例并管理bean
的生命周期,并实现依赖注入 -
DI
(依赖注入)是实现IOC
的一种方式, 就是将依赖对象注入到目标对象中(给对象传递需要使用的其他对象),实现解耦合 -
DI
的实现方式有setter
注入,构造器注入,字段注入(使用@Autowired
注解) -
DI
的使用场景 : 控制对象生命周期,自动注入依赖对象(如@Autowired
), 配置管理(如使用application.yml
或@Value
等)
//Setter 注入
public class UserService {private UserRepository userRepository;public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}
//构造器注入
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}
//字段注入
public class UserService {@Autowiredprivate UserRepository userRepository;
}
IOC中的bean
-
IOC
容器中,默认bean
对象只有一个实例对象(单例模式)。 -
第三方的
bean
不能通过使用@Autowired
,@Componet
等方式注入,这时要采用配置类中使用@Bean
注解的方式:
@Configuration
public class ExternalServiceConfig {@Beanpublic ExternalService externalService() {// ExternalService 来自第三方库,无法加 @Componentreturn new ExternalService("apiKey", timeout);}
}
什么是Spring的bean?
Spring
的Bean
,是由Spring
容器“产生"并管理”的Java
对象,bean
的创建、配置以及生命周期都由容器负责
Bean 的生命周期
-
Bean
实例化:通过反射或工厂方法创建Bean
实例。 -
属性赋值:为
Bean
设置相关属性和依赖 -
初始化:完成
Bean
的初始化。 -
使用:
Bean
处于活动状态,可以在应用程序中使用。 -
销毁:容器关闭时调用销毁方法释放资源
Bean 的配置方式:
-
XML
配置:通过<beans>
和<bean>
标签定义Bean
。 -
使用注解(如
@Component
、@Service
、@Controller
、@Repository
) 自动扫描注册Bean
。 -
使用
@Configuration
配置类结合@Bean
注解注册Bean
。
Bean的作用域
-
Singleton(单例)
: 同一IoC
容器中仅创建一个实例 -
Prototype(原型)
: 每次请求(getBean()
或注入)均产生新实例 -
Request
: 每个 HTTP 请求对应一个实例 -
Session
: 每个 HTTP 会话(Session)对应一个实例 -
Application
: 整个ServletContext
(Web 应用) 中仅一个实例 -
WebSocket
: 每个WebSocket
会话对应一个实例
依赖注入的三种方式
-
构造器注入:通过构造方法注入
-
Setter
注入:通过Setter
方法注入 -
字段注入:直接在字段上使用
@Autowired
注解
单例bean是线程安全的吗
取决于Bean
的设计。默认情况下,无状态的单例Bean
是线程安全的,而有状态的单例Bean
可能存在线程安全问题。
无状态 : 不能被修改,比如service
层的接口
有状态 : 比如成员变量
单例bean和非单例的生命周期是否一样?
不一样, spring只管理单例bean的生命周期,对于非单例的bean,spring创建好之后,就不会管理后续的生命周期了
Spring 如何解决循环依赖?
Spring
通过三级缓存
来解决循环依赖问题.
Spring
创建Bean
的流程:
-
先去
一级缓存
中获取,存在就返回。 -
如果不存在或者对象正在创建中,去
二级缓存
中获取。 -
如果还没有获取到,就去
三级缓存
中获取,通过执行ObjectFactory
的getObject()
就可以获取该对象,获取成功之后,从三级缓存
移除,并将该对象加入到二级缓存
。
Spring MVC 的核心组件有哪些?
-
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。 -
HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装。 -
HandlerAdapter
:处理器适配器,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
; -
Handler
:请求处理器,处理实际请求的处理器。 -
ViewResolver
:视图解析器,根据Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet
响应客户端
MVC分层
-
M是模型,V是视图,C是控制器
-
视图V为用户提供交互界面
-
模型M, 代表存储数据的载体或者
java pojo
, 分为两类:数据承载bean
和业务处理bean
-
数据承载
bean
如数据传输对象(DTO) -
业务处理
bean
如服务层(Service) -
控制器C处理请求
单点登录
-
身份提供者:集中式认证中心,用户首次登录后颁发
JWT Token
。 -
服务提供者:各应用接收并校验
Token
,有效则允许访问。 -
Token
传递:可通过HTTP Header
、Cookie
或重定向参数传递
cookie和session的区别
-
存储位置:
Cookie
在客户端(浏览器),Session
在服务器端。 -
Cookie
占用内存更小,容易暴露,Session
存储容量更大,更安全。
get和post请求的区别
-
get
请求参数写在url
后面,暴露在地址栏,而且url
有长度限制 -
post
请求的参数写在请求体中,没有长度限制 -
传输敏感数据时推荐使用
post
HTTP请求状态码
-
200 OK
:请求成功。 -
301 Moved Permanently
:资源被永久移动到新URI
,应使用新的URI
。 -
302 Found
:资源临时位于不同URI
,应继续使用原URI
发起请求。 -
400 Bad Request
:请求格式错误。 -
401 Unauthorized
:请求需要身份验证。 -
403 Forbidden
:认证成功但权限不足。 -
404 Not Found
:请求的资源不存在。 -
405 Method Not Allowed
:请求方法不允许。 -
500 Internal Server Error
:服务器内部错误 -
502 Bad Gateway
:网关/代理错误,上游服务器无响应
过滤器和拦截器的区别
-
Filter
(过滤器)是Servlet
规范,Interceptor(拦截器)
属于Spring MVC
层 -
Filter
可以拦截所有请求,包括静态资源。 -
Interceptor
只能拦截DispatcherServlet
处理的请求(即控制器Controller
请求)。
HTTP方法
请求类型 | 描述 |
---|---|
GET | 获取资源 |
POST | 创建新资源 |
PUT | 更新现有资源 |
DELETE | 删除资源 |
PATCH | 部分更新现有资源 |
Spring 设计模式
-
代理模式:比如
Spring AOP
,通过JDK
动态代理和CGLIB
代理实现 -
单例模式:
Spring
默认将每个Bean
定义作为单例管理,一个容器中同一Bean
只产生一个实例,便于集中管理和资源复用。 -
模板方法模式:比如
JmsTemplate
、JdbcTemplate
、JpaTemplate
,将固定流程抽象成模板,减少重复代码 。 -
工厂模式:通过
BeanFactory
/FactoryBean
或自定义工厂方法创建对象,客户端不用关心实例化细节,统一通过接口获取Bean
实例,实现了解耦与扩展。
Spring Boot、Spring MVC 和 Spring 有什么区别?
-
Spring
:是核心框架,提供依赖注入(IoC
)和面向切面编程(AOP
)的基础功能,是其他模块的基石。 -
Spring MVC
:是基于Spring
的Web
框架,专注于MVC
模式(模型-视图-控制器),用于处理HTTP
请求和响应,支持URL
路由、模板引擎等Web
开发功能。 -
Spring Boot
:是Spring
的快速开发工具,通过自动配置(如内嵌Tomcat
)和Starter
依赖简化项目搭建
SpringMVC 怎么样设定重定向和转发的?
-
转发:返回视图名前加
forward:
,如return "forward:/path"
。 -
重定向:返回视图名前加
redirect:
,如return "redirect:/path"
。
当一个方法向AJAX
返回特殊对象,比如Object
,List
等,需要做什么处理?
- 使用
@ResponseBody
注解将返回值序列化为JSON/XML
,并配合@RestController
或配置消息转换器(如 Jackson)。示例:
SpringMVC 用什么对象从后台向前台传递数据的?
- 可以使用
Model
、ModelMap
或ModelAndView
对象来传递数据
@Component 和 @Bean 的区别?
-
@Component
用于类,@Bean
用于方法。 -
@Component
标注的类不用编写配置,@Bean
定义的Bean
需要在配置类中定义方法来返回对象 -
@Bean
注解比@Component
注解的自定义性更强,而且很多地方只能通过@Bean
注解来注册bean
。比如注入第三方的类成为bean
时,只能通过@Bean
来实现。
@Autowired和@Resource注解的区别
@Autowired | @Resource | |
---|---|---|
自动装配机制 | 基于类型进行自动装配。如果多个匹配,需结合 @Qualifier 按名称选择。 | 优先按名称装配(指定 name 属性),若找不到对应名称的Bean ,则按类型装配。 |
注入位置 | 可用于字段、构造函数、方法参数等多种场景。 | 常用于字段和方法参数的注入。 |
灵活性 | 配合@Qualifier 可灵活指定注入的 Bean ,适合复杂场景。 | 主要通过名称查找,灵活性稍逊于 @Autowired 和 @Qualifier 的组合。 |
@Controller和@RestController注解的区别
-
@Controller
返回的是视图名称,需要结合模型和视图解析器进行页面渲染。 -
@RestController
将方法返回值直接填入HTTP
响应体中,返回的通常是JSON
。
将一个类声明为 Bean 的注解有哪些?
-
@Component
:通用注解,可标注任意类为Spring
组件。如果一个不知道Bean
属于哪个层,可以使用@Component
注解。 -
@Repository
: 持久层 (Dao
层),主要用于数据库相关操作。 -
@Service
: 服务层,主要涉及一些复杂的逻辑,需要用到Dao
层。 -
@Controller
: 对应Spring MVC
控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面
Spring Boot自动装配原理**
使用 @EnableAutoConfiguration
读取 META-INF/spring.factories
中的配置类,在满足一定条件下将对应 Bean
注入容器,从而实现按需加载的“约定优于配置”
为什么在web
开发中,声明控制器 bean
只能用 @Controller
?
-
Spring MVC
在启动时会扫描所有被@Controller
或@RestController
标注的类,注册为Handler
。 -
如果使用
@Component
,虽然这个类被注入了Spring
容器,但它不会被当作控制器,不能接收HTTP
请求。
依赖冲突问题
@Autowired
注入依赖时,是按类型注入的。如果同一个类型的Bean
有多个,Spring
不知道注入哪一个,会报错, 解决方式如下:
@Primary
: 当存在多个同类型的Bean
时,标注了 @Primary
的Bean
会被默认注入。
@Component
@Primary
public class Apple implements Fruit {//...
}
@Component
public class Banana implements Fruit {//...
}
@Autowired
private Fruit fruit; // 注入 Apple,因为它被标注为 @Primary
@Qualifier
: 明确指定要注入哪一个Bean
(按名称注入),搭配 @Autowired
使用。
@Component("apple")
public class Apple implements Fruit {//...
}@Component("banana")
public class Banana implements Fruit {//...
}@Autowired
@Qualifier("banana") //bean名字默认为类名首字母小写
private Fruit fruit; // 注入 banana Bean
@Resource
:按名称注入,类似于 @Autowired + @Qualifier
。但默认按名称注入,找不到才按类型注入。
@Component("banana")
public class Banana implements Fruit {//...
}@Resource(name = "banana")
private Fruit fruit; // 注入 banana Bean
注意:@Resource
不能和 @Qualifier
一起使用,也不支持 @Primary
。
@Transactional注解
使用 @Transactional
注解时,只有出现RuntimeException
才回滚异常。rollbackFor
属性用于控制出现何种异常类型时, 回滚事务。
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {// 即使是受检异常,也会触发回滚throw new Exception("Checked Exception");
}
SpringBoot原理, SpringBoot为什么大大简化了 Spring
开发?
-
在于起步依赖和自动配置两方面, 只需引入
SpringBoot
的起动依赖, 就间接引入了很多其他依赖,web
开发所需要的所有的依赖都有了 (得益于Maven
的依赖传递) -
自动配置就是当容器启动后,一些配置类、
bean
对象就自动存入到了容器中,不需要手动声明,从而简化了开发,省去了繁琐的配置。 -
如何实现自动配置? 原理就是在配置类中定义一个
@Bean
标识的方法,Spring
会自动调用配置类中使用@Bean
标识的方法,并把方法的返回值注入到IOC
容器中
SpringBoot中哪里用到了反射知识?
依赖注入(DI
): Spring Boot使用反射来实现依赖注入.
组件扫描:SpringBoot
通过扫描包路径来发现和注册组件(例如,@Controller
、@Service
、@Repository
等)。
AOP
面向切面: Spring Boot
使用 AOP
实现一些横切关注点,如事务管理、日志记录等。
动态代理: Spring Boot
中的缓存、事务管理等,使用了动态代理。动态代理是通过反射在运行时创建代理对象的一种机制。
配置属性处理: @ConfigurationProperties
注解将外部配置(如 application.yml
)映射到pojo
中,Spring Boot
通过反射遍历目标类的字段并调用相应的 setter
方法,将值注入到对象中
配置文件相关
-
Spring Boot 配置文件优先级: 命令行参数 > 系统属性参数 >
properties
参数 >yml
参数 >yaml
参数 -
加载顺序:YAML(
.yml
/.yaml
)先加载,随后加载.properties
,以便让.properties
覆盖同名配置
Spring Boot 四大核心注解
-
@SpringBootApplication
一个组合注解, 组合了@SpringBootConfiguration
、@EnableAutoConfiguration
与@ComponentScan
,用于标识主配置类并触发自动配置与组件扫描。 -
@EnableAutoConfiguration
根据类路径中的依赖和已声明的@Bean
,自动加载并配置常见组件,其实现依赖于META-INF/spring.factories
中的自动配置类列表。 -
@ComponentScan
指定包路径下的组件(@Component
、@Service
、@Repository
、@Controller
等)自动注册到IoC
容器,支持基于注解的组件扫描与发现 -
@ConfigurationProperties
将外部化配置(application.properties
/.yml
)映射到POJO
中,支持类型安全的属性绑定和校验,常与@EnableConfigurationProperties
一同使用。