+-------------------+
| 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
程序编译后生成和平台无关的字节码文件,由针对不同操作系统的JVM
执行,实现一次编译处处运行的跨平台能力
面向对象
健壮性:有垃圾回收和异常处理机制
空指针异常(NullPointerException
),
数组/字符串下标越界(ArrayIndexOutOfBoundsException & StringIndexOutOfBoundsException
)
算术异常(ArithmeticException
)
类型转换异常(ClassCastException
)
private
字段限制直接访问,防止非法修改。++
和 --
运算符可以放在变量之前,也可以放在变量之后:
++a
或 --a
):先自增/自减变量的值,然后再使用该变量,例如,b = ++a
先将 a
增加 1,然后把增加后的值赋给 b
。a++
或 a--
):先使用变量的当前值,然后再自增/自减变量的值。例如,b = a++
先将 a
的当前值赋给 b
,然后再将 a
增加 1。![[Pasted image 20250506142657.png]]
long
类型的数据一定要在数值后面加上 L,否则将作为整型解析。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
则不含此隐式转换,若右侧类型更宽必须显式强转,否则编译报错
<<
)作用:将 a
的二进制位向左移动 n
位,右边补 0
。
效果:相当于 a * 2ⁿ
(在不溢出的情况下)。
示例:
int a = 3; // 二进制:00000011
int b = a << 2; // 左移两位:00001100,即12
System.out.println(b); // 输出12
>>
)作用:将 a
的二进制位向右移动 n
位,左边用原来的符号位(最高位)补齐。
效果:相当于 a / 2ⁿ
,并保留符号(正数补0,负数补1)。
示例:
int a = -8; // 二进制(补码):11111000
int b = a >> 2; // 右移两位:11111110,即-2
System.out.println(b); // 输出-2
>>>
)作用:将 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) {
//......
}
这里输出aaa方法2
.
break
跳出整个循环,不再执行循环continue
结束当前循环,继续执行下次循环return
程序返回,不再执行下面的代码(结束当前的方法 直接返回)this
是指向对象本身的一个指针
用来区分成员变量和局部变量:
public Student(String name) {
// 使用 this 来区分成员变量 name 和局部变量 name
this.name = name;
}
深拷贝是把对象及其引用的所有对象都复制一份,新对象和原对象在内存中没有任何关联。修改新对象不会影响原对象,包括对象中引用的其他对象。
浅拷贝是指对象的字段值的复制,当对象的字段是基本数据类型时,会直接复制数值;当对象的字段是引用类型时,浅拷贝只是复制了引用地址,新对象和原对象仍然引用同一个内存地址
finally
代码块都会执行。JVM
崩溃finally
。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...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
等文本格式解释: JDK
内置的一种 服务提供发现机制
,用来启用框架扩展和替换组件,,比如java.sql.Driver
接口,不同厂商可以针对同一接口做出不同的实现,比如MySQL
和PostgreSQL
都有不同的实现提供给用户
SPI
机制可以为某个接口寻找服务实现。其主要思想是将装配的控制权移到程序之外,它的核心思想是解耦
。
![[Pasted image 20250508111708.png]]
![[Pasted image 20250508083736.png]]
压缩:String
内部用 byte[]
存储,节省空间
常量池:避免重复创建字符串对象
不可变:保证线程安全、哈希值稳定
final char[]
, JDK 9 以后是final byte[]
存储数据的数组被 final
修饰(引用不可变)
String
没有提供修改数组内容的方法
String
类本身被final
修饰
![[Pasted image 20250507091559.png]]
![[Pasted image 20250507093543.png]]
答案:会创建1
或 2
个字符串对象。
如果字符串常量池中已经有一个,则不再创建新的,直接引用;如果没有,则创建一个。
堆中肯定有一个,因为只要使用了new
关键字,肯定会在堆中创建一个
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 |
A
和 B
,需要编写类 C
来扩展它们的功能。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 {
@Override
public void log(String message) {
super.log("[Enhanced] " + message); // 增强日志格式
}
}
public class BChild extends B {
@Override
public 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
初始值是null
, int
初始值是0
;
integer
存放在堆
内存, int
存放在栈
内存
integer
是一个对象类型,封装了很多方法,使用的时候更加灵活
面向过程注重的是解决问题的步骤
, 比如洗衣服,打开洗衣机,放入衣服,启动洗衣机,漂洗,烘干
面向对象关注“参与者”(对象),把“人”“洗衣机”“衣服”都看作对象,赋予它们属性与行为,通过对象协作完成业务。
还有个计算圆面积的例子, 如果使用面向过程的方法, 是直接定义半径啥的进行计算, 但是如果使用面向对象的方式, 首先会定义一个圆类, 然后提供一些方法来计算圆面积,
存在继承关系
子类重写父类的方法
向上转型: 通过父类引用指向子类对象
Animal a = new Dog(); // 向上转型
a.speak(); // 调用 Dog 的 speak()
基本数据类型的性能效率比对象更高,而且占用的内存更小
基本数据类型不需要进行垃圾回收
Java
中将实参传递给方法的方式是值传递
:
子类构造方法默认会调用父类的无参构造方法,来初始化继承的成员。
如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法。
堆内存
中。new
创建的对象,都是一个新的实体。栈内存
中。![[Pasted image 20250511203349.png]]
全对.
Constructor
是否可被override
?为什么?不能,因为 构造器不是继承自父类的方法, 而且构造器方法名要和类名相同, 如果重写的话,子类的构造方法会和父类相同,所以不满足这个条件, 不属于继承体系的一部分
String
, StringBuilder
和 StringBuffer
的区别是什么?String
为什么是不可变的String
不可变, StringBuilder
, StringBuffer
可变
String
天然线程安全, 但是String
拼接生成新对象,性能最差
StringBuffer
线程安全,性能低,
StringBuilder
线程不安全,性能较高
如果是单线程,使用StringBuilder
; 多线程环境下使用StringBuffer
==
进行比较,比较的是两个引用变量的内存地址
是否相同.对象的内容
是否相同,使用 equals()
方法比较。重载发生发生在同一类中(方法与方法之间),方法名相同而参数类型或者参数个数,顺序不同
重写发生在类之间,指的是子类继承父类方法并提供新的实现,方法签名(方法名+参数列表)必须完全相同
静态方法是属于类的,不依赖于任何对象实例;
非静态成员属于对象实例,静态方法在非静态成员存在之前就已经存在了
CPU
调度的基本单位构造方法的作用是在创建对象时初始化对象的状态;
能正常运行,如果一个类没有定义任何构造方法,编译器会自动生成一个无参构造方法;
但是如果类中已经声明了带参数的构造方法,编译器将不会再自动生成默认的无参构造方法。如果还需要使用无参构造方法,必须显式声明;
成员变量定义在类中方法外
, 整个类都可以访问,成员变量储存在堆/方法区
中,有默认初始化值
局部变量定义在方法/代码块
内, 只有在该方法/代码块内才能访问,局部变量储存在栈
中,无默认初始化值
final
是一个修饰符,可以修饰变量、方法和类。如果final
修饰变量,意味着该变量在初始化后不能被改变。修饰类则该类不能被继承; 修饰一个方法时,表明这个方法不能被重写
finalize()
方法用于在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
finally
是一个关键字,与 try
和 catch
一起用于异常的处理。finally
块一定会被执行,无论在 try
块中是否有发生异常.但在调用 System.exit()
、JVM 崩溃、无限循环或进程被强制杀死等极端情况下,finally
不会执行
==
和equals
的区别==
比较基本数据类型时比较的是内容, 比较引用数据类型比较的是地址值(引用)equals
比较的是引用,但是有一些类比如String
重写之后就是比较的是内容(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;
@Override
public 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
用于方法签名中声明该方法可能抛出的异常使用 static
修饰的字段称为静态变量(或类变量),在类加载时分配内存,所有对象实例共享同一份数据。
public class Example {
public static int count = 0; // 静态变量
}
静态方法,属于类本身,可通过 ClassName.method()
调用,不依赖于具体实例。
public class Example {
public static void printHello() { // 静态方法
System.out.println("Hello");
}
}
静态初始化块在类第一次加载时执行一次,用于对复杂静态变量进行初始化。
public class Example {
public static Map<String, String> map;
static {
map = new HashMap<>();
map.put("key", "value");
}
}
![[Pasted image 20250515134740.png]
注意不能在增强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(); // 安全移除
}
}
底层数据结构是动态数组,支持快速随机访问
扩容机制: 使用无参构造方法进行初始化时,默认容量是0
, 第一次添加元素时扩容到 10
, 以后如果容量不足,会触发扩容,新容量为旧容量的1.5
倍.
如果一次添加多个元素,1.5倍扩容后还放不下,则容量大小为实际元素个数为准
时间复杂度: 尾部插入O(1)
,中间插入O(n)
, 随机访问O(1)
, 删除元素O(n)
ArrayList
底层基于动态数组, 适合读多写少的场景,而且线程不安全
LinkedList
底层基于链表,适合写多、读操作不依赖随机访问的场景;
对于 删除 和 新增元素 操作谁的性能更好, 要看情况, 若只对单条数据插入或删除,ArrayList
的速度更好。如果是批量随机的插入删除数据,LinkedList
的速度更好, 因为ArrayList
每插入一条数据,要移动插入点及之后的数据。
RandomAccess
是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引访问元素)。 LinkedList
底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess
接口。
hashCode
值相等,它们也不一定是相等的?答: 因为 hashCode()
使用的哈希算法可能会让不同对象产生相同的哈希值
hashCode
值不相等,则这两个对象不相等hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。hashCode
值相等并且equals()
方法也返回 true
,这两个对象才相等。有规范指定需要同时重写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)null
键取决于具体的 Map
实现;例如HashMap
和 LinkedHashMap
允许一个 null
键,而 Hashtable
、ConcurrentHashMap
、TreeMap
等不允许。HashSet
基于哈希表 实现的,不保证元素的插入顺序。
使用的是哈希值(hashCode)+ equals 方法来判断元素是否重复。
具体如下:
当添加元素时,先计算元素的 hashCode()
值;
然后查找该哈希值所在的桶(bucket);
在桶中会遍历已有元素,通过 equals()
比较是否有“相等”的元素:
如果有,认为是重复元素,不添加;
如果没有,则加入。
增强 for 循环foreach
forEach
循环
普通for
循环
迭代器
Stream API
遍历
相同:
不同 :
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):线程执行完毕或因异常退出,进入终止状态。
反射是指在程序运行时,对于任意一个类,都能获取到这个类的所有属性和方法,对于任意一个对象,都能调用它的任意属性和方法
反射允许程序在运行时动态地获取类的信息并操作类的属性、方法、构造器等
传统方式是 “通过类创建对象”,而反射是 “通过对象反向获取类的信息”
IOC
通过反射,依赖注入,工厂模式,容器机制来实现
AOP
基于动态代理来实现,有JDK
动态代理和CGLB
动态代理
![[Pasted image 20250529104755.png]]
Spring
事务保证一组数据库操作要么全部成功,要么全部失败(原子性)
分为声明式事务和编程式事务,声明式事务通过注解,如 @Transactional
实现。编程式事务通过
TransactionTemplate
或 PlatformTransactionManager
手动控制。
事务属性:
propagation
(传播行为):如 REQUIRED
、REQUIRES_NEW
等
isolation
(隔离级别):见下文
timeout
:超时时间
readOnly
:只读事务(可优化性能)
rollbackFor
:哪些异常触发回滚
数据库隔离级别:
隔离级别 | 说明 | 可避免问题 |
---|---|---|
READ UNCOMMITTED |
读未提交 | 无法避免任何问题 |
READ COMMITTED |
读已提交(Oracle 默认) | 避免脏读 |
REPEATABLE READ |
可重复读(MySQL 默认) | 避免脏读、不可重复读 |
SERIALIZABLE |
串行化(最高级别) | 避免所有并发问题 |
AOP
动态代理管理事务。@Transactional
注解标记方法。beginTransaction()
)。commit()
),失败则回滚(rollback()
)。BeanFactory
和 ApplicationContext
有什么区别?BeanFactory
:Spring
的基础容器,提供基本的依赖注入功能,采用延迟加载。
ApplicationContext
:BeanFactory
的子接口,是更高级的容器,支持国际化、事件发布、AOP
等,采用预加载策略。
![[Pasted image 20250513142459.png]]
bean
时才创建bean
实例AOP
是面向切面编程, 将和业务无关的共性代码逻辑封装起来,减少重复的代码,降低模块之间的耦合度, 使用场景有日志记录
和权限控制
,性能监控
等
AOP
的实现原理是动态代理(JDK动态代理,CGLB代理)
切入点:需要织入逻辑(增强功能)的方法。
通知:织入的具体逻辑(增强功能的代码逻辑),如前置、后置、环绕通知。
切面:绑定通知与切入点的关系。
用途:统一日志处理、性能监控、事务管理
好处: 减少代码冗余,提升可维护性。
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 {
@Autowired
private UserRepository userRepository;
}
IOC
容器中,默认bean
对象只有一个实例对象(单例模式)。
第三方的 bean
不能通过使用@Autowired
,@Componet
等方式注入,这时要采用配置类中使用@Bean
注解的方式:
@Configuration
public class ExternalServiceConfig {
@Bean
public ExternalService externalService() {
// ExternalService 来自第三方库,无法加 @Component
return new ExternalService("apiKey", timeout);
}
}
Spring
的 Bean
,是由 Spring
容器“产生"并管理”的Java
对象,bean
的创建、配置以及生命周期都由容器负责Bean
实例化:通过反射或工厂方法创建Bean
实例。
属性赋值:为Bean
设置相关属性和依赖
初始化:完成Bean
的初始化。
使用:Bean
处于活动状态,可以在应用程序中使用。
销毁:容器关闭时调用销毁方法释放资源
XML
配置:通过
和
标签定义 Bean
。
使用注解(如 @Component
、@Service
、@Controller
、@Repository
) 自动扫描注册Bean
。
使用 @Configuration
配置类结合 @Bean
注解注册Bean
。
Singleton(单例)
: 同一 IoC
容器中仅创建一个实例
Prototype(原型)
: 每次请求(getBean()
或注入)均产生新实例
Request
: 每个 HTTP 请求对应一个实例
Session
: 每个 HTTP 会话(Session)对应一个实例
Application
: 整个 ServletContext
(Web 应用) 中仅一个实例
WebSocket
: 每个 WebSocket
会话对应一个实例
构造器注入:通过构造方法注入
Setter
注入:通过Setter
方法注入
字段注入:直接在字段上使用@Autowired
注解
取决于Bean
的设计。默认情况下,无状态的单例Bean
是线程安全的,而有状态的单例Bean
可能存在线程安全问题。
无状态 : 不能被修改,比如service
层的接口
有状态 : 比如成员变量
不一样, spring只管理单例bean的生命周期,对于非单例的bean,spring创建好之后,就不会管理后续的生命周期了
Spring
通过三级缓存
来解决循环依赖问题.
Spring
创建Bean
的流程:
先去一级缓存
中获取,存在就返回。
如果不存在或者对象正在创建中,去二级缓存
中获取。
如果还没有获取到,就去三级缓存
中获取,通过执行 ObjectFactory
的 getObject()
就可以获取该对象,获取成功之后,从三级缓存
移除,并将该对象加入到二级缓存
。
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。
HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的 Handler
,并会将请求涉及到的拦截器和 Handler
一起封装。
HandlerAdapter
:处理器适配器,根据 HandlerMapping
找到的 Handler
,适配执行对应的 Handler
;
Handler
:请求处理器,处理实际请求的处理器。
ViewResolver
:视图解析器,根据 Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet
响应客户端
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
存储容量更大,更安全。
get
请求参数写在 url
后面,暴露在地址栏,而且url
有长度限制
post
请求的参数写在请求体中,没有长度限制
传输敏感数据时推荐使用 post
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
请求)。
请求类型 | 描述 |
---|---|
GET | 获取资源 |
POST | 创建新资源 |
PUT | 更新现有资源 |
DELETE | 删除资源 |
PATCH | 部分更新现有资源 |
代理模式:比如Spring AOP
,通过JDK
动态代理和CGLIB
代理实现
单例模式:Spring
默认将每个Bean
定义作为单例管理,一个容器中同一Bean
只产生一个实例,便于集中管理和资源复用。
模板方法模式:比如JmsTemplate
、JdbcTemplate
、JpaTemplate
,将固定流程抽象成模板,减少重复代码 。
工厂模式:通过 BeanFactory
/FactoryBean
或自定义工厂方法创建对象,客户端不用关心实例化细节,统一通过接口获取 Bean
实例,实现了解耦与扩展。
Spring
:是核心框架,提供依赖注入(IoC
)和面向切面编程(AOP
)的基础功能,是其他模块的基石。
Spring MVC
:是基于Spring
的Web
框架,专注于MVC
模式(模型-视图-控制器),用于处理 HTTP
请求和响应,支持URL
路由、模板引擎等Web
开发功能。
Spring Boot
:是Spring
的快速开发工具,通过自动配置(如内嵌 Tomcat
)和 Starter
依赖简化项目搭建
转发:返回视图名前加 forward:
,如 return "forward:/path"
。
重定向:返回视图名前加 redirect:
,如 return "redirect:/path"
。
AJAX
返回特殊对象,比如Object
,List
等,需要做什么处理?@ResponseBody
注解将返回值序列化为 JSON/XML
,并配合 @RestController
或配置消息转换器(如 Jackson)。示例:Model
、ModelMap
或 ModelAndView
对象来传递数据@Component
用于类,@Bean
用于方法。
@Component
标注的类不用编写配置, @Bean
定义的Bean
需要在配置类中定义方法来返回对象
@Bean
注解比 @Component
注解的自定义性更强,而且很多地方只能通过 @Bean
注解来注册 bean
。比如注入第三方的类成为bean
时,只能通过 @Bean
来实现。
@Autowired | @Resource | |
---|---|---|
自动装配机制 | 基于类型进行自动装配。如果多个匹配,需结合 @Qualifier 按名称选择。 |
优先按名称装配(指定 name 属性),若找不到对应名称的Bean ,则按类型装配。 |
注入位置 | 可用于字段、构造函数、方法参数等多种场景。 | 常用于字段和方法参数的注入。 |
灵活性 | 配合@Qualifier 可灵活指定注入的 Bean ,适合复杂场景。 |
主要通过名称查找,灵活性稍逊于 @Autowired 和 @Qualifier 的组合。 |
@Controller
返回的是视图名称,需要结合模型和视图解析器进行页面渲染。
@RestController
将方法返回值直接填入HTTP
响应体中,返回的通常是JSON
。
@Component
:通用注解,可标注任意类为 Spring
组件。如果一个不知道Bean
属于哪个层,可以使用@Component
注解。
@Repository
: 持久层 (Dao
层),主要用于数据库相关操作。
@Service
: 服务层,主要涉及一些复杂的逻辑,需要用到Dao
层。
@Controller
: 对应 Spring MVC
控制层,主要用于接受用户请求并调用 Service
层返回数据给前端页面
使用 @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
注解时,只有出现RuntimeException
才回滚异常。rollbackFor
属性用于控制出现何种异常类型时, 回滚事务。
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
// 即使是受检异常,也会触发回滚
throw new Exception("Checked Exception");
}
Spring
开发?在于起步依赖和自动配置两方面, 只需引入SpringBoot
的起动依赖, 就间接引入了很多其他依赖,web
开发所需要的所有的依赖都有了 (得益于Maven
的依赖传递)
自动配置就是当容器启动后,一些配置类、bean
对象就自动存入到了容器中,不需要手动声明,从而简化了开发,省去了繁琐的配置。
如何实现自动配置? 原理就是在配置类中定义一个@Bean
标识的方法, Spring
会自动调用配置类中使用@Bean
标识的方法,并把方法的返回值注入到IOC
容器中
依赖注入(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
覆盖同名配置
@SpringBootApplication
一个组合注解, 组合了@SpringBootConfiguration
、@EnableAutoConfiguration
与 @ComponentScan
,用于标识主配置类并触发自动配置与组件扫描。
@EnableAutoConfiguration
根据类路径中的依赖和已声明的 @Bean
,自动加载并配置常见组件,其实现依赖于 META-INF/spring.factories
中的自动配置类列表。
@ComponentScan
指定包路径下的组件(@Component
、@Service
、@Repository
、@Controller
等)自动注册到IoC
容器,支持基于注解的组件扫描与发现
@ConfigurationProperties
将外部化配置(application.properties
/.yml
)映射到POJO
中,支持类型安全的属性绑定和校验,常与 @EnableConfigurationProperties
一同使用。