MySQL
理想 Java ⾯试
⼀线城市由于限⻋油牌,很多我⾝边的朋友,买的第⼀辆⻋都是电⻋,基本上⼈均特斯拉。国内
也有很多优秀的新能源⻋企,理想、⼩鹏、⽐亚迪、蔚来等等 ,今年⼩⽶也⼊局这个赛道。
这些新能源⻋企中,发现理想的校招薪资开的特别⾼,堪⽐互联⽹⼤⼚⽔平了。
32k x 16 ,base 北京
28k x 16 ,base 北京
话说回来,理想开这么⾼薪资,⾯试难度如何呢?
之前分 享了 很多互联⽹公司后 端⾯经,这次给⼤家分享⼀位同学⾯试理想汽⻋的Java 后端⾯经,
这个⾯经还是⽐较经典,基本后端的知识都问了遍。
我也把问到的知识点,罗列了⼀下
Java :线程池、垃圾 回收、juc 、spring aop
MySQL :索引失效
Redis :缓存三兄弟、布隆过滤器
Kafka :topic 、分区、消费线程的关系
操作系统:线程间通信、socket 编程
⽹络:访问⽹站到显⽰的全流程
MySQL
索引失效的场景知道哪些?
对索引使⽤左或者左右模糊匹配,会索引失效
当我们使 ⽤左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种⽅式都会造
成索引失效。
⽐如下⾯的 like 语句,查询 name 后缀为「林」的⽤⼾,执⾏计划中的 type=ALL 就代表了全表扫描,⽽没有⾛索引。

// name 字段为二级索引
select * from t_user where name like '%林';对索引使⽤函数,会索引失效
有时候我们会 ⽤⼀些 MySQL ⾃带的函数来得到我们想要的结果,这时候要注意了,如果查 询条件
中对索引字段使⽤函数,就会导致索引失效。
⽐如下⾯这条语句查询条件中对 name 字段使⽤了 LENGTH 函数,执⾏计划中的 type=ALL ,代表了全表扫描:

// name 为二级索引
select * from t_user where length(name)=6;对索引进⾏表达式计算,会索引失效
在查询条件中对索引进⾏表达式计算,也是⽆法⾛索引的。

⽐如,下⾯这条查 询语句,执⾏计划中 type = ALL ,说明是 通过全表扫描的⽅式查询数据的:
explain select * from t_user where id + 1 = 10;对索引隐式类型转换,会索引失效
如果索引字段是字符串类型,但是在条件查询中,输⼊的参数是整型的话,你会 在执⾏计划的结
果发现这条语句会⾛全表扫描。
我在原本的 t_user 表增加了 phone 字段,是⼆级索引且类型是 varchar 。

然后我在条件查询中,⽤整型作为输⼊参数,此时执⾏计划中 type = ALL ,所以是通过全表扫描来查 询数据的。

这是因为 phone 字段为字符串,所以 MySQL 要会⾃动把字符串转为数字,所以这条语句相当
于:
select * from t_user where CAST(phone AS signed int) = 1300000001;可以看到,CAST 函数是作⽤在了 phone 字段,⽽ phone 字段是索引,也就是对索引使⽤了函
数!⽽前⾯我们也说了,对索引使⽤函数是会导致索引失效的
联合索引⾮最左匹配,会索引失效
联合索引要能正确使⽤需要遵循最左匹配原则,也就是按照最左优先的⽅式进⾏索引的匹配。
⽐如,如果创建了⼀个 (a, b, c) 联合索引,如果查 询条件是以下这⼏种,就可以匹配上联合索引:
where a=1 ;
where a=1 and b=2 and c=3 ;
where a=1 and b=2 ;
需要注意的是,因为有查 询优化器, 所以 a 字段在 where ⼦句的顺序并 不重要。但是,如果查 询条件是以下这⼏种,因为不 符合最左匹配原则,所以就⽆法匹配上联合索引,联
合索引就会失效:
where b=2 ;
where c=3 ;
where b=2 and c=3 ;Redis
什么 是缓存雪崩、缓存击穿和缓存穿透?怎么解决?
缓存雪崩:当⼤量缓存数据在同⼀时间过期(失效)或者 Redis 故障宕机时,如果此时有⼤量
的⽤⼾请求,都⽆法在 Redis 中处理,于是全部请求都直接访问数据库,从⽽导致数据库的压
⼒骤增,严重的会造成数据库宕机,从⽽形成⼀系列连锁反应,造成整个系统崩溃,这就是缓
存雪崩的问题。

缓存击穿:如果缓存中的某个热点 数据过期了,此时⼤量的请求访问了该热点 数据,就⽆法从
缓存中读取,直接访问数据库,数据库很容易就被⾼并发的请求冲垮,这就是缓存击穿的问
题。

缓存穿透:当⽤⼾访问的数据,既不在缓存中,也不 在数据库中,导致请求在访问缓存时,发
现缓存缺失,再去访问数据库时,发现数据库中也 没有要访问的数据,没办法构建缓存数据,
来服 务后续的请求。那么当有⼤量这样的请求到来时,数据库的压⼒骤增,这就是缓存穿透的
问题。

缓存雪崩解决⽅案:
均匀设置过期时间:如果要给缓存数据设置过期时间,应该避免将⼤量的数据设置成同⼀个过
期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上⼀个随机数,这
样就保证数据不会在同⼀时间过期。
互斥锁:当业务线程在处理⽤⼾请求时,如果发现访问的数据不在 Redis ⾥,就加个互 斥锁,
保证同⼀时间内只有⼀个请求来构 建缓存(从数据库读取数据,再将数据更新到 Redis ⾥),当
缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么
就返回空值或者默认值。实现互斥锁的时候,最好设置超时时 间,不然第⼀个请求拿到了锁,
然后这个请求发⽣了某种意外⽽⼀直阻塞,⼀直不释放锁,这时其他请求也⼀直拿不到锁,整
个系统就会出现⽆响应的现象。
后台 更新缓存:业务线程不再负责 更新缓存,缓存也不 设置有效期,⽽是让缓存“永久有效”,
并将更新缓存的⼯作交由后台 线程定时更新。
缓存击穿解决⽅案:
互斥锁⽅案,保证同⼀时间只有⼀个业 务线程更新缓存,未能获取互斥锁的请求,要么等待锁
释放后重新读取缓存,要么就返回空值或者默认值。
不给热点 数据设置过期时间,由后台 异步更新缓存,或者在热点 数据准备要过期前,提前通知
后台 线程更新缓存以及重新设置过期时间;
缓存穿透解决⽅案:
⾮法请求的限制:当有⼤量恶意请求访问不存在的数据的时候,也会发⽣缓存穿透,因此在
API ⼊⼝处我们要判断求请求参数是否合 理,请求参数是否含 有⾮法值、请求字段是否存在,
如果判断出是恶意请求就直接返回错误,避免进⼀步访问缓存和数据库。
缓存空值或者默认值:当我们线上业 务发现缓存穿透的现象时,可以针对查询的数据,在缓存
中设置⼀个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应
⽤,⽽不会继续查询数据库。
布隆过滤器:我们可以在写⼊数据库数据时,使⽤布隆过滤器做个标记,然后在⽤⼾请求到来
时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数 据是否存在,如果不存
在,就不⽤通过查询数据库来判断数 据是否存在。即使发⽣了缓存穿透,⼤量请求只会查询
Redis 和布隆过滤器, ⽽不会查询数据库,保证了数据库能正常运⾏,Redis ⾃⾝也是⽀持布隆
过滤器的。
布隆过滤器原理是什么 ?
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写⼊数据
库数据时,在布隆过滤器⾥做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤
器, 如果查 询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
第⼀步,使⽤ N 个哈希函数分别 对数据做哈希计算,得到 N 个哈希值;
第⼆步,将第⼀步得到的 N 个哈希值对位图数组的⻓度取模,得到每个哈希值在位图数组的对
应位置。
第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
举个 例⼦,假设有⼀个位图数组⻓度为 8,哈希函数 3 个的布隆过滤器。

在数据库写⼊数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别 计算出 3
个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第
1、4、6 位置的值设置为 1。当应⽤要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数
组的第 1、4、6 位置的值是否全为 1,只要有⼀个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,⾼效查找的同时存在哈希冲突的可能性,⽐如数据 x
和数据 y 可能都落在第 1、4、6 位置,⽽事实上,可能数据库中并不存在数据 y,存在误判的情
况。
所以,查询布隆过滤器说数据存在,并不⼀定证明数据库中存在这个数据,但是查询到数据不存
在,数据库中⼀定就不存在这个数据。
操作系统
线程间有哪些通信⽅式?
共享内存:线程可以通过访问共享的内存区域来进⾏数据交换和共享。Linux 提供了共享内存的
机制,可以使 ⽤ shmget() 、 shmat() 等函数进⾏共享内存的创建和映射。信号量:线程可以使 ⽤信号量来进⾏同步和互斥操作。Linux 提供了信号量机制,可以使 ⽤
sem_init() 、 sem_wait() 、 sem_post() 等函数来操作信 号量。互斥锁:线程可以使 ⽤互斥锁来实现对共享资源的互斥访问。Linux 提供了互 斥锁机制,可以使
⽤ pthread_mutex_init() 、 pthread_mutex_lock() 、 pthread_mutex_unlock() 等函数来操作互斥锁。
条件变量:线程可以使 ⽤条件变量来等待和通知特定的条件。Linux 提供了条件变量机制,可以使⽤ pthread_cond_init() 、 pthread_cond_wait() 、 pthread_cond_signal() 等函数来操作条件变量。
管道:线程可以使 ⽤管道进⾏简单的数据传输。Linux 提供了管道机制,可以使 ⽤ pipe() 函数
来创建管道,并使⽤ read() 和 write() 函数进⾏数据的读写。有了解过Socket ⽹络套接字吗?

基于 TCP 协议的客⼾端和服务端⼯作
服务端和客⼾端初始化 socket ,得到⽂件描述符;
服务端调⽤ bind ,将 socket 绑定在指定的 IP 地址 和端⼝;服务端调⽤ listen ,进⾏监听;
服务端调⽤ accept ,等待客⼾端连接;
客⼾端调⽤ connect ,向服务端的地址 和端⼝发起连接请求;
服务端 accept 返回⽤于传输的 socket 的⽂件描述符;
客⼾端调⽤ write 写⼊数据;服务端调⽤ read 读取数据;
客⼾端断开连接时,会调⽤ close ,那么服务端 read 读取数据的时候,就会读取到了
EOF ,待处理完数据后,服务端调⽤ close ,表⽰连接关闭。
这⾥需要注意的是,服务端调⽤ accept 时,连接成功了会返回⼀个已完成连接的 socket ,后续
⽤来传输数据。
所以,监听的 socket 和真正⽤来传送数据的 socket ,是「两个 」 socket ,⼀个叫作监听 socket ,⼀个叫作已完成连接 socket 。
成功连接建⽴之后,双⽅开始通过 read 和 write 函数来读写数据,就像往⼀个⽂件流⾥⾯写东西
⼀样。
⽹络
键⼊⽹址到浏览器显⽰出来的过程?

应⽤层DNS 解析,传输层TCP 连接,⽹络层IP ,数据链路MAC ,真实物理层,接收到之后再⼀层层 扒⽪。
更详细传输层-> ⽹络层-> 数据链路层-> 路由器的过程,看图解⽹络-> 基础篇-> 键⼊⽹址到⽹⻚显
⽰期间发⽣了什么 ?。

Java 中线程池有哪些?
ScheduledThreadPool :可以设置定期的执⾏任务,它⽀持定时或周期性执⾏任务,⽐如每隔
10 秒钟执⾏⼀次任务,我通过这 个实现类设置定期执⾏任务的策略。
FixedThreadPool :它的核⼼线程数和最⼤线程数是⼀样的,所以可以把它看作是固定线程数的
线程池,它的特点是线程池中的线程数除了初始阶段需要从 0 开始增加外,之后的线程数量就
是固定的,就算任务数超过线程数,线程池也不 会再创建更多的线程来处理任务,⽽是会把超
出线程处理能⼒的任务放到任务队列中进⾏等待。⽽且就算任务队列满了,到了本该继续增加
线程数的时候,由于它的最⼤线程数和核⼼线程数是⼀样的,所以也⽆法再增加新的线程了。
CachedThreadPool :可以称作可缓存线程池,它的特点在于线程数是⼏乎可以⽆限增加的(实
际最⼤可以达到 Integer.MAX_VALUE ,为 2^31-1 ,这个数⾮常⼤,所以基本不可能达到),⽽当线程闲置时还可以对线程进⾏回收。也就是说该线程池的线程数量不是固定不变的,当然它
也有⼀个⽤于存储提交任务的队列,但这个队列是 SynchronousQueue ,队列的容量为0,实际
不存储任何任 务,它只负责 对任务进⾏中转和传递,所以效率⽐较⾼。
SingleThreadExecutor :它会使 ⽤唯⼀的线程去执⾏任务,原理和 FixedThreadPool 是⼀样的,
只不过这 ⾥线程只有⼀个,如果线程在执⾏任务的过程中发⽣异常,线程池也会重新创建⼀个
线程来执⾏后续的任务。这种线程池由于只有⼀个线程,所以⾮常适合⽤于所有任务都需要按
被提交的顺序依次执⾏的场景,⽽前⼏种线程池不⼀定能够保障任务的执⾏顺序等于被提交的
顺序,因为它们是多线程并⾏执⾏的。
SingleThreadScheduledExecutor :它实 际和 ScheduledThreadPool 线程池⾮常相似,它只是
ScheduledThreadPool 的⼀个特例,内部只有⼀个线程。
线程池淘汰策略有哪些?
当线程池的任务队列满了之 后,线程池会执⾏指定的拒绝策略来应对,常⽤的四种拒绝策略包
括:CallerRunsPolicy 、AbortPolicy 、DiscardPolicy 、DiscardOldestPolicy ,此外,还可以通过实现
RejectedExecutionHandler 接⼝来⾃定义拒绝策略。
四种预置的拒绝策略:
CallerRunsPolicy ,使⽤线程池的调⽤者所在的线程去执⾏被拒绝的任务,除⾮线程池被停⽌或
者线程池的任务队列已有空缺。
AbortPolicy ,直接抛出⼀个任务被线程池拒绝的异常。
DiscardPolicy ,不做任何 处理,静默拒绝提交的任务。
DiscardOldestPolicy ,抛弃最⽼的任务,然后执⾏该任务。
⾃定义拒绝策略,通过实现接⼝可以⾃定义任务拒绝策略。
GC 是什么 ?
GC 是垃圾 收集的意思,内存处理是编程⼈员容易出现问题的地⽅,忘记或者错误的内存回收会导
致程序或系统的不稳定甚⾄崩溃。
Java 虚拟机提供的 GC 功能可以⾃动监测对象是否超过作⽤域从⽽达到⾃动回收内存的⽬的,Java
语⾔没有提供释放已分配内存的显⽰操作⽅法。Java 程序员不⽤担⼼内存管理, 因为垃圾 收集器
会⾃动进⾏管理。
说⼀下G1 垃圾 回收器?
G1(Garbage First) 垃圾 收集器,是关注最⼩时延的垃圾 回收器, 也同样适合⼤尺⼨堆内存的垃圾收集,官⽅推荐选择使⽤ G1 来替 代 CMS 。
G1 最⼤的特点是引⼊分区的思路,弱化了分代的概念。合理利⽤垃圾 收集各个周期的资源,解决
了其他收集器、甚⾄ CMS 的众多缺陷。
G1 相⽐ CMS 的改进主要是这⼏个⽅⾯:
算法:G1 基于标记--整理算法, 不会产⽣空间碎⽚,在分配⼤对象时,不会因⽆法得到连续的
空间,⽽提前触发⼀次 FULL GC 。
停顿时间可控:G1 可以通过设置预期停顿时间(Pause Time )来控制垃圾 收集时间避免应⽤雪
崩现象。
并⾏与并发:G1 能更充分的利⽤ CPU 多核环境下的硬件优 势,来缩短 stop the world 的停顿
时间。
G1 收集器的主要应⽤在多 CPU ⼤内存的服务中,在满⾜⾼吞吐 量的同时,尽可能的满⾜垃圾 回收
时的暂停时间。在以下场景中,G1 更适合:
服务端多核 CPU 、JVM 内存占⽤较⼤的应⽤(⾄少⼤于4G );
应⽤在运⾏过程中,会产⽣⼤量内存碎⽚、需要经常压缩空间;
想要更可控、可预期的 GC 停顿周期,防⽌⾼并发下应⽤雪崩现象。
了解volatile 吗?
volatile 关键字保证了两个 性质:
可⻅性:可⻅性是指当多个线程访问同⼀个变量时,⼀个线程修改了这个变量的值,其他线程
能够⽴即看得到修改的值。
有序性:对⼀个volatile 变量的写操作,执⾏在任意后续对这个volatile 变量的读操作之前。
volatile 汇编是怎么实现的?
对于JVM 的内存屏障实现中,也采取了内存屏障。JVM 的内存屏障有四种,我们来看⼀下这四种屏
障和他们的作⽤:
LoadLoad 屏障:对于这样的语句
第一大段读数据指令;
LoadLoad;
第二大段读数据指令;LoadLoad 指令作 ⽤:在第⼆⼤段读数据指 令被访问前,保证第⼀⼤段读数据指 令执⾏完毕
StoreStore 屏障:对于这样的语句
第一大段写数据指令;
StoreStore;
第二大段写数据指令;StoreStore 指令作 ⽤:在第⼆⼤段写数据指 令被访问前,保证第⼀⼤段写数据指 令执⾏完毕
LoadStore 屏障:对于这样的语句
LoadStore;
第二大段写数据指令;LoadStore 指令作 ⽤:在第⼆⼤段写数据指 令被访问前,保证第⼀⼤段读数据指 令执⾏完毕。
StoreLoad 屏障:对于这样的语句
第一大段写数据指令;
StoreLoad;
第二大段读数据指令;StoreLoad 指令作 ⽤:在第⼆⼤段读数据指 令被访问前,保证第⼀⼤段写数据指 令执⾏完毕。
针对volatile 变量,JVM 采⽤的内存屏障是:
- 针对volatile 修饰变量的写操作:在写操作前插⼊StoreStore 屏障,在写操作后插⼊StoreLoad 屏
障;
- 针对volatile 修饰变量的读操作:在每个volatile 读操作前插⼊LoadLoad 屏障,在读操作后插⼊
LoadStore 屏障;
通过这 种⽅式,就可以保 证被volatile 修饰的变量具有线程间的可⻅性和禁⽌指令重排序的功能
了。
Synchronized 和 ReentrantLock 有什么 区别?
主要区别有以下 5 个:
⽤法不同:synchronized 可以⽤来修饰普通⽅法、静态⽅法和代码块,⽽ ReentrantLock 只能
⽤于代码块。
获取锁和释放锁的机制不同:synchronized 是⾃动加 锁和释放锁的,⽽ ReentrantLock 需要⼿
动加 锁和释放锁。
锁类型不同:synchronized 是⾮公平锁,⽽ ReentrantLock 默认为⾮公平锁,也可以⼿动指定
为公平锁。
响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,⽽ synchronized 不能响应中
断。
底层实现不同:synchronized 是 JVM 层⾯通过监视器实现的,⽽ ReentrantLock 是基于 AQS
实现的。
第一大段读数据指令 ;LoadStore;
第二大段写数据指令 ;
第一大段写数据指令 ;StoreLoad;
第二大段读数据指令 ;Kafka
对Kafka 有什么了 解吗?
Kafka 特点如下:
⾼吞吐 量、低延迟:kafka 每秒可以处理⼏⼗万条消息,它的延迟最低只有⼏毫秒,每个topic 可
以分多个partition, consumer group 对partition 进⾏consume 操作。
可扩展性:kafka 集群⽀持热扩展
持久性、可靠性:消息被持久化到本地磁盘,并且⽀持数据备份防⽌数据丢失
容错性:允许集群中节点失败(若副本数量为n, 则允许n-1 个节点失败)
⾼并发:⽀持数千个客⼾端同时读写
如果有 ⼀个消费主题topic ,有⼀个消费组group ,topic 有10 个分区,消费
线程数和分区数的关系是怎么样的?
topic 下的⼀个分区只能被同⼀个consumer group 下的⼀个consumer 线程来消费,但反之并不成
⽴,即⼀个consumer 线程可以消费多个分区的数据,⽐如Kafka 提供的ConsoleConsumer ,默认就只是⼀个线程来消费所有分区的数据。

所以,分区数决定了同组消费者个数的上限。
如果你的分区数是N,那么最好线程数也保持为N,这样通常能够达到最⼤的吞吐 量。超过N的配
置只是浪费系统资源,因为多出的线程不会被分配到任何 分区。
Spring
Spring AOP 的概念了解吗?
Spring AOP 是Spring 框架中的⼀个重要模块,⽤于实现⾯向切⾯编程。
可以把Spring AOP 看作是对Spring 的补充,它使得Spring 不需要EJB 就能提供声明式事务管理;或
者 使⽤Spring AOP 框架的全部功能来实现⾃定义的⽅⾯。
AOP 概念:
⽅⾯(Aspect
):⼀个关注点的模块化,这个关注点实现可能 另外横切多个对象。事务管理是
J2EE 应⽤中⼀个很好的横切关注点例⼦。⽅⾯⽤Spring 的 Advisor 或拦截 器实现。
连接点(Joinpoint
): 程序执⾏过程中明确的点,如⽅法的调 ⽤或特定的异常被抛出。
通知(Advice
): 在特定的连接点,AOP 框架执⾏的动作。各种类 型的通知包
括“around” 、“before” 和“throws” 通知。通知类型将在下⾯讨论 。许多AOP 框架 包括Spring 都是
以
拦截 器做通知模型,维护⼀个“围绕”连接点的拦截 器 链。
切⼊点(Pointcut
): 指定⼀个通知将被引发的⼀系列连接点 的集合。AOP 框架必须允许开发者
指定切⼊点:例如,使⽤正则表达式。
引⼊(Introduction
): 添加⽅法或字段到被通知的类。Spring 允许引⼊新的接⼝到任何 被通知的
对象。例如,你可以使 ⽤⼀个引⼊使任何 对象实现 IsModified 接⼝,来简化缓存。
⽬标对象(Target Object
): 包含连接点的对象。也被称作
被通知或
被代理对象。
AOP
代理(AOP Proxy
): AOP 框架创建的对象,包含通知。在Spring 中,AOP 代理可以是JDK 动
态代理或者CGLIB 代理。
织⼊(Weaving
): 组装⽅⾯来创建⼀个被通知对象。这可以在编译时 完成(例如使⽤AspectJ
编译器) ,也可以在运⾏时完成。Spring 和其他纯Java AOP 框架⼀样, 在运⾏时完成织⼊。
AOP 和OOP 的关系是什么 ?
AOP
与 OOP 不是相互对⽴的关系,可以把 AOP 看作是弥补 OOP 的不⾜,以此之⻓、补彼之
短,两者结合使⽤效果最 佳。
OOP
是针对业务实体及其属性 和⾏为 进⾏抽象封装 ,这个不 难理解,例如:⽤⼾模块、订单
模块 等。
AOP
是针对业务切 ⾯进⾏提取,它所⾯对的是处理过程中的某个 步骤 或 阶段 ,以达到逻辑
处理过程中各部分之间低耦合性的 隔离效果 ,例如:⽇志记录、权限验证 等。
举个 例⼦,如果单纯使⽤ OOP ,需要在⽇志模块、订单模块中进⾏权限验证、⽇志记录怎么
办?难道要在每个⽅法前都加⼊权限验证、⽇志记录的代码吗?那么如果需要在每个⽅法前和⽅
法后都记录⽇志怎么办?
这时如果使⽤ AOP ,就可以借助代理完成这些重复的操作,就可以不在每个⽅法前加 ⼊权限验
证、⽇志记录的代码,降低各部分之间的耦合。
AOP 底层实现是什么 ?
Spring AOP 的底层实现原理主要依赖于动态代理。在Spring AOP 中,通过动态代理技术,可以在
运⾏时动态地创建⼀个代理对象,将切⾯逻辑织⼊到⽬标对象的⽅法调⽤中。
Spring AOP 主要有两种类型的代理:基于接⼝的代理和基于类的代理。
对于基于接⼝的代理,Spring AOP 使⽤JDK 动态代理来实现。JDK 动态代理要求⽬标对象实现⼀
个或多个接⼝,然后通过Proxy 类的静态⽅法创建⼀个代理对象。代理对象实现了⽬标对象的接
⼝,并且在⽅法调⽤前后添加了切⾯逻辑。
对于基于类的代理,Spring AOP 使⽤CGLIB (Code Generation Library )来实现。CGLIB 是⼀个
强⼤的代码⽣成库,它通过继承的⽅式创建⼀个⽬标对象的⼦类,并在⼦类中重写⽬标对象的
⽅法,从⽽实现切⾯逻辑的织⼊。
在运⾏时,当客⼾端调⽤⽬标对象的⽅法时,实际上是调⽤了代理对象的⽅法。代理对象会在⽅
法调⽤前后执⾏切⾯逻辑,并最终将⽅法调⽤委托给⽬标对象。
项⽬
在项⽬中主 要负责 什么 ?
性能调优遇到了什么 瓶颈,以及是如何优 化的?
