一、基础 - base相关
Java数据类型
基本数据类型:
byte:1字节,8位
short:2字节,16位
int:4字节,32位
long:8字节,64位
float:4字节,32位
double:8字节,64位
char:2字节,16位
boolean:1字节,8位(实际占用取决于JVM实现)
引用数据类型:
类(Class)
接口(Interface)
数组(Array)
枚举(Enum)
注解(Annotation)
Java语言特性
封装:将对象的属性和行为封装在一起,通过访问修饰符控制访问权限,提高代码的安全性和可维护性。
继承:子类继承父类的属性和方法,实现代码的复用和扩展。
多态:同一个方法在不同情况下表现出不同的行为,通过方法重写和方法重载实现。
权限修饰符
public:所有类都可以访问。
protected:同一包内或子类可以访问。
default(默认):同一包内可以访问。
private:仅在类内部可以访问。
适用范围:类、方法、变量、构造器。
final关键字
类:不能被继承。
方法:不能被重写。
变量:值不能被修改(常量)。
static关键字
类:静态变量和静态方法属于类,不属于实例。
方法:可以直接通过类名调用,不需要实例化对象。
代码块:在类加载时执行,用于初始化静态变量。
字符串拼接
+ 号拼接效率不高,因为每次拼接都会创建新的字符串对象。
应该使用 StringBuilder 或 StringBuffer,它们是可变的,不会每次都创建新对象。
Java异常体系
异常体系:Throwable -> Error 和 Exception -> RuntimeException 和其他 Exception。
常见运行时异常:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException 等。
重写与重载
重写:子类重写父类的方法,方法名、参数列表、返回类型必须相同。
重载:同一个类中,方法名相同,参数列表不同。
接口与抽象类
接口:只能包含抽象方法(JDK1.8后可以有默认方法和静态方法),用于定义规范。
抽象类:可以包含抽象方法和具体方法,用于代码复用。
== 和 equals
==:比较对象的引用地址。
equals:比较对象的内容,默认实现与 == 相同,通常需要重写。
&与&&、|与||
& 和 |:逻辑与和逻辑或,不短路。
&& 和 ||:逻辑与和逻辑或,短路。
valueOf和toString
valueOf:将其他类型转换为字符串。
toString:返回对象的字符串表示。
String底层
String 是不可变的,底层使用 char[] 存储。
不可修改:每次修改都会创建新的 String 对象。
StringBuffer和StringBuilder
StringBuffer:线程安全,效率较低。
StringBuilder:非线程安全,效率较高。
创建类实例
使用 new 关键字。
使用反射。
使用克隆。
使用反序列化。
反射
反射:在运行时动态获取类的信息并操作。
应用场景:动态代理、注解处理、ORM框架等。
暴力反射:通过反射访问私有成员。
值传递与引用传递
Java是值传递,传递的是对象引用的副本。
动态代理
方式:JDK动态代理、CGLIB动态代理。
原理:JDK动态代理基于接口,CGLIB基于类继承。
区别:JDK代理要求目标对象实现接口,CGLIB不需要。
二、基础 - 集合相关
Java集合体系
Collection:List、Set、Queue。
Map:HashMap、TreeMap、LinkedHashMap。
List和Set
List:有序、可重复。
Set:无序、不可重复。
去重原理:通过 hashCode 和 equals 方法判断。
hashCode和equals
hashCode:计算对象的哈希值。
equals:比较对象是否相等。
在 Set 中,先比较 hashCode,再比较 equals。
ArrayList底层
add:在数组末尾添加元素,扩容时增加50%。
remove:删除元素并移动后续元素。
扩容原理:默认容量10,扩容时增加50%。
LinkedList底层
add:在链表末尾添加节点。
remove:删除节点并调整前后节点的指针。
get:从头或尾遍历链表。
HashMap
JDK1.7 vs 1.8:1.7使用数组+链表,1.8使用数组+链表/红黑树。
put过程:计算hash值,定位数组索引,插入链表或红黑树。
二次hash:减少hash冲突。
负载因子:0.75,平衡时间和空间。
扩容:2倍,重新计算索引。
链表转红黑树:链表长度大于8且数组长度大于64。
红黑树转链表:链表长度小于6。
并发问题:可能导致死循环或数据丢失。
线程安全集合
Hashtable:线程安全,效率低。
ConcurrentHashMap:线程安全,效率高。
原理:1.7使用分段锁,1.8使用CAS+synchronized。
三、基础 - 其他
JDK1.8新特性
Lambda表达式:简化匿名内部类。
Stream流:简化集合操作。
函数式接口:只有一个抽象方法的接口。
接口默认方法:接口中可以有默认实现。
方法引用:简化Lambda表达式。
算法
冒泡排序:相邻元素比较交换。
选择排序:选择最小元素交换。
快排:分治法,选择基准元素,递归排序。
设计模式
常见设计模式:单例模式、工厂模式、观察者模式、策略模式等。
Socket与IO
Socket:网络通信的端点。
BIO:同步阻塞IO。
NIO:同步非阻塞IO。
AIO:异步非阻塞IO。
HTTP协议
请求内容:请求行、请求头、请求体。
响应内容:状态行、响应头、响应体。
常见响应码:200(成功)、404(未找到)、500(服务器错误)等。
HTTP vs HTTPS:HTTPS使用SSL/TLS加密。
TCP协议:面向连接的可靠传输协议。
四、多线程相关
1. 线程和进程有什么联系和区别?
联系:线程是进程的一部分,一个进程可以包含多个线程。
区别:
进程:是操作系统资源分配的基本单位,拥有独立的内存空间和系统资源。
线程:是CPU调度和分派的基本单位,共享进程的内存空间和系统资源。
2. 创建线程的方式有哪些?线程的状态又有哪些?
创建线程的方式:
继承Thread类,重写run()方法。
实现Runnable接口,实现run()方法。
实现Callable接口,实现call()方法,通过FutureTask包装后创建线程。
使用线程池(如ExecutorService)。
线程的状态:
新建(New):线程被创建但未启动。
就绪(Runnable):线程已启动,等待CPU调度。
运行(Running):线程正在执行。
阻塞(Blocked):线程因等待某些资源(如锁、IO操作)而暂停执行。
等待(Waiting):线程等待其他线程的通知或中断。
超时等待(Timed Waiting):线程在指定时间内等待。
终止(Terminated):线程执行完毕或因异常终止。
3. run()和start()方法有哪些区别?
run():是线程的执行体,直接调用run()方法不会启动新线程,而是在当前线程中执行。
start():启动新线程,并调用run()方法。
4. 实现线程间通讯的方法有哪些?wait、notify、notifyAll分别的作用是什么?可以用在同步代码块之外吗?为什么?
实现线程间通讯的方法:
wait():使当前线程进入等待状态,直到其他线程调用notify()或notifyAll()。
notify():唤醒一个等待的线程。
notifyAll():唤醒所有等待的线程。
作用:
wait():释放锁并进入等待状态。
notify():唤醒一个等待的线程。
notifyAll():唤醒所有等待的线程。
是否可以用在同步代码块之外:
不可以。wait()、notify()、notifyAll()必须在同步代码块(synchronized)中调用,否则会抛出IllegalMonitorStateException。
5. sleep和wait方法有什么区别?
sleep():使当前线程暂停执行指定时间,不释放锁。
wait():使当前线程进入等待状态,释放锁,直到其他线程调用notify()或notifyAll()。
6. 什么是线程安全问题?什么情况下会产生?如何解决?
线程安全问题:多个线程同时访问共享资源时,可能导致数据不一致或错误。
产生原因:多个线程对共享资源进行非原子操作。
解决方法:
使用synchronized关键字。
使用Lock锁。
使用volatile关键字。
使用线程安全的集合类(如ConcurrentHashMap)。
7. 什么是死锁?应该如何防止产生死锁?
死锁:两个或多个线程互相持有对方需要的资源,导致所有线程都无法继续执行。
防止方法:
避免嵌套锁。
按顺序获取锁。
使用超时机制。
使用tryLock()方法。
8. Synchronized关键字的底层原理是什么? 可以用在哪些地方?分别的锁对象是什么?
底层原理:Synchronized通过操作系统的互斥量(Mutex)实现,JVM通过monitorenter和monitorexit指令实现。
使用位置:
方法:锁对象为当前实例对象。
静态方法:锁对象为类的Class对象。
代码块:锁对象为指定的对象。
9. Synchronized在JDK1.6做了哪些优化?结合对象头聊聊Synchronized锁升级的详细过程。
优化:
偏向锁:减少无竞争情况下的锁开销。
轻量级锁:减少竞争情况下的锁开销。
重量级锁:在竞争激烈时使用操作系统互斥量。
锁升级过程:
无锁状态:对象头中无锁标记。
偏向锁:第一次获取锁时,对象头中记录线程ID,后续该线程再次获取锁时无需竞争。
轻量级锁:当有其他线程竞争时,偏向锁升级为轻量级锁,通过CAS操作获取锁。
重量级锁:当轻量级锁竞争激烈时,升级为重量级锁,使用操作系统互斥量。
10. Synchronized是公平锁还是非公平锁?如果获取不到锁时会阻塞吗?
非公平锁:Synchronized是非公平锁,获取不到锁时会阻塞。
11. 同步代码块中执行完wait/notify/notifyAll后会立马释放锁吗,为什么?
会:wait()、notify()、notifyAll()方法会释放锁,因为它们必须在同步代码块中调用,调用后会释放锁以便其他线程获取。
12. Lock锁有哪些实现类?分别的特点是什么?
实现类:
ReentrantLock:可重入锁,支持公平和非公平模式。
ReentrantReadWriteLock:读写锁,允许多个读线程同时访问,但写线程独占。
特点:
ReentrantLock:灵活,支持条件变量、超时获取锁等。
ReentrantReadWriteLock:适用于读多写少的场景,提高并发性能。
13. 什么是线程可重入?Synchronized具备吗?Lock锁呢?
线程可重入:线程可以重复获取已经持有的锁。
Synchronized:具备可重入性。
Lock锁:ReentrantLock具备可重入性。
14. Synchronized关键字和JUC下Lock锁的异同?
相同点:都用于实现线程同步。
不同点:
Synchronized:自动释放锁,无法中断等待锁的线程。
Lock:手动释放锁,支持中断等待锁的线程,支持公平锁和非公平锁。
15. 什么是AQS?说下你对AQS的理解
AQS(AbstractQueuedSynchronizer):是JUC包中实现锁和其他同步组件的基础框架,通过维护一个FIFO队列来管理线程的等待和唤醒。
16. 乐观锁CAS原理了解过吗?什么是CAS的ABA问题?如何解决?
CAS(Compare-And-Swap):是一种乐观锁机制,通过比较内存值和预期值,如果相等则更新。
ABA问题:线程A读取值A,线程B将值改为B再改回A,线程A认为值未变,但实际上值已发生变化。
解决方法:使用版本号机制(如AtomicStampedReference)。
17. 你了解JUC下的哪些常用工具类,分别有什么作用?(CountdownLatch、Cyclicbarrier、Simephore)
CountdownLatch:允许一个或多个线程等待其他线程完成操作。
CyclicBarrier:允许多个线程相互等待,直到所有线程都到达屏障点。
Semaphore:控制同时访问特定资源的线程数量。
18. 说下volatile关键字,有什么作用?原理是什么?
作用:保证变量的可见性,禁止指令重排序。
原理:通过内存屏障(Memory Barrier)实现,确保变量在不同线程间的可见性。
19. 说下ThreadLocal,有什么作用?有哪些主要方法,实现原理是什么?为什么会有内存泄漏问题?如何解决?
作用:为每个线程提供独立的变量副本。
主要方法:
get():获取当前线程的变量副本。
set(T value):设置当前线程的变量副本。
remove():移除当前线程的变量副本。
实现原理:每个线程维护一个ThreadLocalMap,存储线程本地变量。
内存泄漏问题:ThreadLocal引用的对象不会被回收,可能导致内存泄漏。
解决方法:使用完后调用remove()方法。
20. 说下线程池的几大核心参数?分别有什么作用?线程池工作原理是什么样的?有几种默认的线程池?他们的7个核心参数为什么要那么设置?
核心参数:
corePoolSize:核心线程数。
maximumPoolSize:最大线程数。
keepAliveTime:线程空闲时间。
unit:时间单位。
workQueue:任务队列。
threadFactory:线程工厂。
handler:拒绝策略。
工作原理:
当任务提交时,如果线程数小于corePoolSize,创建新线程执行任务。
如果线程数达到corePoolSize,任务进入任务队列。
如果任务队列满,且线程数小于maximumPoolSize,创建新线程执行任务。
如果线程数达到maximumPoolSize,执行拒绝策略。
默认线程池:
FixedThreadPool:固定线程数,任务队列无界。
CachedThreadPool:线程数可变,任务队列无界。
SingleThreadExecutor:单线程,任务队列无界。
ScheduledThreadPool:定时任务线程池。
参数设置原因:根据任务类型和系统资源进行合理配置,避免资源浪费和任务堆积。
21. 单例模式写法有哪几种?(懒汉和饿汉式)懒汉式中保证线程安全的写法是什么?为什么要用双重检查模式?
单例模式写法:
饿汉式:类加载时创建实例。
懒汉式:第一次使用时创建实例。
懒汉式线程安全写法:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查模式原因:
第一次检查避免不必要的同步。
第二次检查确保只有一个线程创建实例。
volatile关键字防止指令重排序,确保实例初始化完成后再赋值。
1. 什么是索引?
索引是一种数据结构,用于加速数据库表中数据的检索速度。它类似于书籍的目录,可以帮助数据库快速定位到特定的数据行,而不需要扫描整个表。
2. Mysql索引有哪些类型?什么场景使用哪种索引?
主键索引(Primary Key):唯一标识表中的每一行数据,不允许为空。
唯一索引(Unique Index):确保索引列中的值是唯一的,但允许为空。
普通索引(Normal Index):最基本的索引类型,没有唯一性限制。
全文索引(Full-Text Index):用于全文搜索,适用于文本字段。
组合索引(Composite Index):在多个列上创建的索引,适用于多条件查询。
使用场景:
主键索引:用于唯一标识每一行数据。
唯一索引:用于确保某些列的值唯一。
普通索引:用于加速查询,适用于频繁查询的列。
全文索引:用于文本搜索。
组合索引:用于多条件查询。
3. Mysql实际使用的数据结构是什么(索引的数据结构)?为什么用这种结构?(如何提高磁盘IO效率)
Mysql(特别是InnoDB)使用B+树作为索引的数据结构。
原因:
B+树的平衡性:B+树是一种平衡树,能够保证树的高度较低,从而减少磁盘IO次数。
叶子节点的链表结构:B+树的叶子节点通过链表连接,适合范围查询。
磁盘IO效率:B+树的节点大小通常与磁盘页大小一致,能够最大限度地减少磁盘IO次数。
4. B+tree和Btree的叶子节点和非叶子节点分别存储什么?他们还有哪些区别?
B树:
叶子节点和非叶子节点都存储数据。
适用于内存中的数据结构,因为每个节点都存储数据,导致树的高度较高。
B+树:
非叶子节点只存储索引信息,不存储数据。
叶子节点存储数据,并且通过链表连接,适合范围查询。
树的高度较低,适合磁盘存储。
区别:
B+树的非叶子节点只存储索引信息,而B树的非叶子节点存储数据。
B+树的叶子节点通过链表连接,适合范围查询。
B+树的高度较低,适合磁盘存储。
5. Mysql两种存储引擎(InnoDB和MyISAM)有哪些区别?
InnoDB:
支持事务(ACID)。
支持行级锁和外键。
使用聚簇索引,数据存储在索引中。
适合高并发、事务性操作。
MyISAM:
不支持事务。
支持表级锁。
使用非聚簇索引,数据和索引分开存储。
适合读密集型应用。
6. 如何进行Mysql优化?(SQL优化层面和服务器优化层面)
SQL优化层面:
使用索引:确保查询中使用的列上有合适的索引。
避免全表扫描:尽量使用索引覆盖查询。
优化查询语句:避免使用子查询、多表连接等复杂操作。
分页查询优化:使用LIMIT和OFFSET时,尽量减少数据量。
服务器优化层面:
硬件升级:增加内存、使用SSD等。
配置优化:调整my.cnf中的参数,如innodb_buffer_pool_size、query_cache_size等。
数据库设计:合理设计表结构,避免冗余数据。
分区表:对于大数据量的表,可以考虑分区。
7. SQL调优你会从何入手?
分析慢查询日志:使用EXPLAIN分析查询计划,找出性能瓶颈。
检查索引:确保查询中使用的列上有合适的索引。
优化查询语句:简化查询,避免不必要的操作。
调整服务器配置:根据实际情况调整my.cnf中的参数。
8. Mysql的数据IO查找流程是什么样的?
查询解析:解析SQL语句,生成查询计划。
查询优化:优化查询计划,选择最优的执行路径。
索引查找:根据索引定位到数据行。
数据读取:从磁盘或缓存中读取数据。
结果返回:将结果返回给客户端。
9. Mysql中如何合理使用索引?有哪些会使索引失效的情况?
合理使用索引:
在频繁查询的列上创建索引。
使用组合索引时,注意索引列的顺序。
避免在索引列上进行函数操作。
索引失效的情况:
在索引列上进行函数操作。
使用!=、<>、NOT IN等操作符。
使用LIKE时,通配符在开头(如LIKE '%abc')。
数据类型不匹配。
10. 什么是聚簇索引、非聚簇索引?什么是覆盖索引?什么是回表查询?
聚簇索引:数据行存储在索引中,索引和数据是一体的。InnoDB的主键索引就是聚簇索引。
非聚簇索引:索引和数据分开存储,索引中存储的是指向数据的指针。MyISAM使用非聚簇索引。
覆盖索引:查询中需要的所有数据都在索引中,不需要回表查询数据行。
回表查询:通过非聚簇索引找到数据行的主键,再通过主键查询数据行。
11. Mysql如何进行慢查询排查(哪个关键字)?分别会列出来哪些信息项?重点关注哪一些信息项?
使用EXPLAIN关键字进行慢查询排查。
信息项:
id:查询的标识符。
select_type:查询的类型。
table:查询的表。
type:连接类型。
possible_keys:可能使用的索引。
key:实际使用的索引。
key_len:索引的长度。
ref:与索引比较的列。
rows:扫描的行数。
Extra:额外的信息。
重点关注:
type:连接类型,如ALL表示全表扫描。
rows:扫描的行数,越少越好。
Extra:额外的信息,如Using filesort、Using temporary等。
12. 事务的特性是什么?Mysql事务隔离级别有哪几种?分别会产生什么问题?Mysql默认隔离级别是什么?Oracle呢?
事务特性(ACID):
原子性(Atomicity):事务是一个不可分割的工作单位,要么全部执行,要么全部不执行。
一致性(Consistency):事务执行前后,数据库的完整性约束没有被破坏。
隔离性(Isolation):事务之间相互隔离,互不影响。
持久性(Durability):事务一旦提交,其结果是永久性的。
Mysql事务隔离级别:
读未提交(Read Uncommitted):允许脏读、不可重复读和幻读。
读已提交(Read Committed):防止脏读,但允许不可重复读和幻读。
可重复读(Repeatable Read):防止脏读和不可重复读,但允许幻读。
串行化(Serializable):防止脏读、不可重复读和幻读,但性能较低。
Mysql默认隔离级别:可重复读(Repeatable Read)。
Oracle默认隔离级别:读已提交(Read Committed)。
13. 分别说下你对Mysql的行锁、表锁,悲观锁、乐观锁、间隙锁的理解
行锁:锁定表中的某一行数据,适用于高并发场景。
表锁:锁定整个表,适用于低并发场景。
悲观锁:假设数据会被并发修改,因此在读取数据时加锁,防止其他事务修改。
乐观锁:假设数据不会被并发修改,因此在提交时检查数据是否被修改,如果没有则提交,否则回滚。
间隙锁:锁定索引记录之间的间隙,防止其他事务插入数据,适用于范围查询。
14. Mysql的varchar和char的区别?
char:固定长度字符串,不足长度时用空格填充,适合存储长度固定的数据。
varchar:可变长度字符串,存储实际长度的数据,适合存储长度不固定的数据。
15. Mysql内连接(inner join)、外连接(left join)分别的写法?
内连接(Inner Join):
SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
左外连接(Left Join):
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;
16. 什么是索引下推?有什么作用?
索引下推(Index Condition Pushdown, ICP)是Mysql 5.6引入的优化技术,它允许在存储引擎层直接过滤数据,而不是在服务器层过滤。这样可以减少服务器层的数据传输量,提高查询性能。
17. SQL解析顺序(FROM JOIN ON WHERE GROUP BY HAVING SELECT DISTINCT ORDER BY LIMIT)
FROM:选择表。
JOIN:连接表。
ON:连接条件。
WHERE:过滤条件。
GROUP BY:分组。
HAVING:分组后的过滤条件。
SELECT:选择列。
DISTINCT:去重。
ORDER BY:排序。
LIMIT:限制结果集。
18. exists和in有哪些区别?
exists:用于检查子查询是否返回结果,返回true或false。
in:用于检查某个值是否在子查询的结果集中。
区别:
exists适用于子查询返回结果集较大的情况,因为它只关心是否返回结果。
in适用于子查询返回结果集较小的情况,因为它需要检查每个值是否在结果集中。
19. count(1)、count(列名)、count(*) 的区别?
count(1):统计行数,忽略列的值。
count(列名):统计列中非NULL值的行数。
count(*):统计所有行数,包括NULL值。
20. where 和 on的区别
where:用于过滤表中的数据,在连接操作之后执行。
on:用于指定连接条件,在连接操作之前执行。
21. union和union all的区别
union:合并两个结果集,并去重。
union all:合并两个结果集,不去重。
22. 什么是当前读?什么是快照读?
当前读:读取最新的数据,使用锁机制保证数据的一致性。
快照读:读取事务开始时的数据快照,不加锁,适用于读已提交和可重复读隔离级别。
23. 听过InnoDB的Mvcc技术吗?说下是什么?
MVCC(Multi-Version Concurrency Control)是InnoDB实现的一种并发控制技术,它通过保存数据的多个版本来实现事务的隔离性。每个事务读取的数据是事务开始时的快照,而不是最新的数据,从而避免了加锁带来的性能问题。
24. Mybatis底层的原理?一级缓存和二级缓存是什么?
Mybatis底层原理:
Mybatis通过XML或注解配置SQL语句,将SQL语句与Java方法映射。
执行SQL语句时,Mybatis通过JDBC与数据库交互,并将结果映射为Java对象。
一级缓存:
默认开启,作用域为SqlSession,同一个SqlSession中相同的查询会使用缓存。
二级缓存:
需要手动开启,作用域为Mapper,不同的SqlSession之间共享缓存。
25. Mybatis #{}和${}的区别?
#{}:预编译处理,防止SQL注入,适用于参数传递。
${}:字符串替换,直接将参数拼接到SQL语句中,存在SQL注入风险,适用于动态表名、列名等。
26. Mybatis mapper层可以进行方法重载吗?为什么?
Mybatis的Mapper层不支持方法重载,因为Mybatis通过方法签名(方法名+参数类型)来唯一标识一个SQL语句。如果方法重载,会导致方法签名冲突,无法唯一标识SQL语句。
27. Mysql存储过程、存储函数、触发器、视图(View)分别用来干嘛的?创建语法是什么?
存储过程:封装一组SQL语句,可以在数据库中重复执行,提高代码复用性。
DELIMITER $$
CREATE PROCEDURE procedure_name(IN param1 INT, OUT param2 INT)
BEGIN
-- SQL statements
END $$
DELIMITER ;
存储函数:封装一组SQL语句,返回一个值,可以在SQL语句中调用。
DELIMITER $$
CREATE FUNCTION function_name(param1 INT) RETURNS INT
BEGIN
-- SQL statements
RETURN result;
END $$
DELIMITER ;
触发器:在特定事件(如插入、更新、删除)发生时自动执行的SQL语句。
CREATE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
-- SQL statements
END;
视图(View):虚拟表,基于查询结果集,可以简化复杂的查询。
CREATE VIEW view_name AS
SELECT column1, column2
FROM table_name
WHERE condition;
这些工具可以帮助开发者更好地管理和优化数据库操作。
好的,以下是对Spring相关问题的详细说明:
1. 说下你对Spring的理解,和SpringBoot有什么关系?
Spring 是一个开源的轻量级Java开发框架,旨在简化企业级应用的开发。它提供了依赖注入(DI)和面向切面编程(AOP)等核心功能,帮助开发者构建松耦合、可测试的应用程序。
Spring Boot 是Spring框架的一个子项目,旨在简化Spring应用的初始搭建以及开发过程。它通过自动配置和约定大于配置的原则,减少了大量的XML配置,使得开发者可以更快速地启动和运行Spring应用。
关系:Spring Boot是基于Spring框架的,它简化了Spring应用的配置和部署,使得开发者可以更专注于业务逻辑的实现。
2. Spring有哪些核心注解?分别有什么作用?
@Component:通用组件注解,用于标记一个类为Spring管理的Bean。
@Controller:用于Web控制器,通常与Spring MVC一起使用。
@Service:用于业务逻辑层,表示一个服务类。
@Repository:用于数据访问层,表示一个数据访问对象(DAO)。
@Autowired:用于自动装配Bean,Spring会自动注入依赖。
@Qualifier:当有多个相同类型的Bean时,用于指定具体的Bean。
@Value:用于注入配置文件中的属性值。
@Configuration:用于标记一个类为配置类,通常与@Bean一起使用。
@Bean:用于在配置类中声明一个Bean。
3. 说下对Spring IOC的理解,怎么理解控制反转?
Spring IOC(Inversion of Control,控制反转) 是一种设计模式,它将对象的创建和依赖关系的管理从应用程序代码中移除,转交给Spring容器来管理。
控制反转:传统的应用程序中,对象的创建和依赖关系的管理是由开发者自己控制的。而在Spring中,这些控制权被反转给了Spring容器,开发者只需要配置好Bean的依赖关系,Spring容器会自动管理这些Bean的生命周期和依赖注入。
4. 说下对Spring AOP的理解、有哪些通知?使用场景有哪些?底层原理是什么?
Spring AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,它允许开发者将横切关注点(如日志、事务管理等)与业务逻辑分离。
通知类型:
@Before:在目标方法执行前执行。
@After:在目标方法执行后执行(无论是否抛出异常)。
@AfterReturning:在目标方法成功执行后执行。
@AfterThrowing:在目标方法抛出异常后执行。
@Around:在目标方法执行前后都可以执行,可以控制目标方法的执行。
使用场景:
日志记录
事务管理
权限控制
性能监控
底层原理:Spring AOP的实现基于动态代理技术。Spring会为目标对象创建一个代理对象,当调用目标方法时,实际上是调用代理对象的方法,代理对象会在目标方法执行前后执行相应的通知逻辑。
5. 说下Spring MVC的流程(从访问一个URL到得到页面结果的具体流程:DispatcherServlet的职责流程)
客户端请求:用户通过浏览器或其他客户端发送请求。
DispatcherServlet:请求首先到达DispatcherServlet,它是Spring MVC的前端控制器。
HandlerMapping:DispatcherServlet通过HandlerMapping找到处理请求的Controller。
Controller:DispatcherServlet将请求转发给对应的Controller。
ModelAndView:Controller处理请求后,返回一个ModelAndView对象。
ViewResolver:DispatcherServlet通过ViewResolver解析ModelAndView中的视图名称,找到对应的视图。
View:DispatcherServlet将ModelAndView中的数据传递给视图,视图渲染后返回给客户端。
6. 对Spring声明式事务的理解?Spring的事务隔离级别有哪些?Spring事务传播行为有哪些?
声明式事务:通过注解或XML配置的方式,将事务管理逻辑与业务逻辑分离,Spring会自动管理事务的开启、提交和回滚。
事务隔离级别:
DEFAULT:使用数据库默认的隔离级别。
READ_UNCOMMITTED:允许读取未提交的数据(脏读)。
READ_COMMITTED:只能读取已提交的数据(防止脏读)。
REPEATABLE_READ:在同一个事务中多次读取相同的数据,结果一致(防止不可重复读)。
SERIALIZABLE:最高隔离级别,防止幻读。
事务传播行为:
REQUIRED:如果当前没有事务,就新建一个事务;如果有,就加入当前事务。
SUPPORTS:如果当前有事务,就加入当前事务;如果没有,就以非事务方式执行。
MANDATORY:如果当前有事务,就加入当前事务;如果没有,就抛出异常。
REQUIRES_NEW:新建一个事务,如果当前有事务,就将当前事务挂起。
NOT_SUPPORTED:以非事务方式执行,如果当前有事务,就将当前事务挂起。
NEVER:以非事务方式执行,如果当前有事务,就抛出异常。
NESTED:如果当前有事务,就在当前事务中嵌套一个新的事务。
7. 什么情况下会让Spring事务失效?
非public方法:Spring事务注解只能作用于public方法。
异常未抛出:如果方法中捕获了异常但没有抛出,事务不会回滚。
自定义异常:默认情况下,Spring事务只对RuntimeException及其子类异常回滚,如果抛出自定义异常,需要配置rollbackFor属性。
事务传播行为:某些传播行为(如NOT_SUPPORTED)会导致事务失效。
多线程:在多线程环境下,事务管理器无法跨线程管理事务。
8. Spring Boot的自动装配原理是什么?
自动装配:Spring Boot通过@EnableAutoConfiguration注解和spring.factories文件,自动扫描并加载项目中的配置类。它会根据项目的依赖和配置文件中的属性,自动配置Spring应用上下文。
原理:
Spring Boot会扫描META-INF/spring.factories文件,找到所有自动配置类。
根据@Conditional注解的条件,判断是否需要加载某个配置类。
自动配置类会根据项目中的依赖和配置文件中的属性,自动配置Spring应用上下文。
9. Spring Boot项目的启动加载流程大概说下
启动类:Spring Boot应用的启动类通常包含@SpringBootApplication注解。
自动配置:Spring Boot会自动扫描并加载META-INF/spring.factories文件中的自动配置类。
Bean加载:Spring Boot会根据自动配置类和项目中的Bean定义,加载并初始化Bean。
Servlet容器启动:如果项目中包含Web依赖,Spring Boot会启动嵌入式的Servlet容器(如Tomcat)。
应用启动:Spring Boot应用启动完成后,可以通过CommandLineRunner或ApplicationRunner接口执行一些初始化逻辑。
10. Spring Boot项目读取配置文件的方式有几种?
@Value注解:通过@Value注解注入配置文件中的属性值。
Environment对象:通过Environment对象获取配置文件中的属性值。
@ConfigurationProperties注解:通过@ConfigurationProperties注解将配置文件中的属性映射到一个Java对象。
PropertySource注解:通过@PropertySource注解指定自定义的配置文件。
11. 如何自定义Spring Boot Starter?
创建Maven项目:创建一个Maven项目,定义好依赖。
编写自动配置类:编写一个自动配置类,使用@Configuration和@Conditional注解。
创建spring.factories文件:在META-INF目录下创建spring.factories文件,指定自动配置类。
打包发布:将项目打包成jar文件,发布到Maven仓库。
使用自定义Starter:在其他Spring Boot项目中引入自定义Starter的依赖,即可使用其功能。
12. BeanFactory和FactoryBean的区别?
BeanFactory:是Spring IOC容器的核心接口,负责Bean的创建、管理和依赖注入。
FactoryBean:是一个特殊的Bean,它本身是一个工厂,可以用来创建其他Bean。通过FactoryBean,开发者可以自定义Bean的创建逻辑。
13. Spring Bean的生命周期是什么?
实例化:Spring容器根据Bean定义创建Bean实例。
属性赋值:Spring容器为Bean的属性注入依赖。
初始化:调用Bean的初始化方法(如@PostConstruct注解的方法)。
使用:Bean被应用程序使用。
销毁:当Spring容器关闭时,调用Bean的销毁方法(如@PreDestroy注解的方法)。
14. Spring如何解决IOC中的循环依赖问题?
循环依赖:当两个或多个Bean相互依赖时,可能会导致循环依赖问题。
解决方法:
三级缓存:Spring通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)来解决循环依赖问题。
提前暴露:在Bean的实例化阶段,Spring会将Bean的引用提前暴露到二级缓存中,使得其他Bean可以引用这个未完全初始化的Bean。
代理对象:对于AOP代理对象,Spring会在Bean实例化后立即创建代理对象,并将其放入缓存中,避免循环依赖。