剑指offer系列
网络
HTTP 状态码
五种可能的取值
- 1xx: 指示信息–表示请求已接受急需处理
- 2xx: 成功–表示请求已被接受, 理解, 接受
- 3xx: 重定向–表示完成请求需必须进行更进一步的操作
- 4xx: 客户端错误–请求有语法错误或请求无法实现
- 5xx: 服务端错误–服务器未能实现合法的请求
GET 请求与 POST 请求的去区别
- HTTP 报文层面: GET 请求将庆区信息放在 URL,POST 放在报文中
- 数据库层面: GET 符合幂等性和安全性, POST 不符合
- 其它层面: GET 可以被浏览器缓存, 被存储, 而 POST 不行
Cookie 和 Session 的区别
- Cookie 数据存放在客户的浏览器上, Session 数据存放在服务器上
- Session 相对于 Cookie 更安全
- 若考虑减轻服务器负担应当使用 Cookie
HTTP 和 HTTPS 的区别
- HTTPS 需要申请 CA 证书, HTTP 不需要
- HTTPS 密文传输, HTTP 明文传输
- 连接方式不同, HTTPS 默认使用 443 端口, HTTP 使用 80 端口
- HTTPS=HTTP + 加密 + 认证 + 完整性保护, 较 HTTP 安全
数据库
RDBMS
- 程序实例
- 存储管理
- 缓存机制
- SQL 解析
- 日志管理
- 权限划分
- 容灾机制
- 索引管理
- 锁管理
- 存储 (文件系统)
为什么要使用索引
- 快速查询数据
索引的数据结构
- 建立二叉树
- 建立 B-Tree
- 建立 B+Tree
- 建立 Hash
缓存中间件
Memchace: 代码层类似 Hash
- 支持简单数据类型
- 不支持数据持久化存储
- 不支持主从同步
- 不支持分片
Redis
- 数据类型丰富
- 支持数据持久化存储
- 支持主从
- 支持分片
为什么 Redis 能这么快
- 完全基于内存, 绝大部分请求纯粹的内存操作, 执行效率高, 不受到磁盘 IO 速度限制
- 数据结构简单对数据操作也简单
- 采用单线程, 单线程也能处理高并发请求, 想多核也可以启动多实例
- 使用多路 IO 复用模型非阻塞 IO
Redis 底层数据类型基础
- 简单的动态字符串
- 链表
- 字典
- 跳跃表
- 整数集合
- 压缩列表
- 对象
使用过的 Redis 的数据类型
- String: 最基本的数据类型, 二进制安全.
- Hash:String 元素组成的字典, 适合用于存储对象
- List: 列表, 按照 String 元素插入顺序排序 后进先出 (可用于排行榜)
- Set:String 元素组成的无序集合, 通过哈希表实现, 不允许重复 (方便获取并集交集等, 应用场景类似于微博粉丝的共同关注等))
- Sorted Set: 通过分数来为集合中的成员进行从小到大的排序.
- 用于计数的 HyperLogLog, 用于支持存储地理位置信息的 Geo
从海量 Key 里查询出某一固定前缀的 Key
- KEYS pattern: 查找所有符合给定模式 pattern 的 key
- keys 指令一次性返回所有匹配的 key
- 数量过大会使服务器卡顿
- SCAN
- 多次查询, 每次查询返回游标, 根据游标二次查询
- 可能会获取到重复的键, 使用 hashset 记录返回的 部分值进行去重
- KEYS pattern: 查找所有符合给定模式 pattern 的 key
如何通过 Redis 实现分布式锁
- 分布式锁需要解决的问题
- 互斥性
- 安全性
- 死锁
- 容错
- SETNX
- 如何解决 SETNX 长期有效的问题
- 通过 expire key 设置过期时间
- 缺点: 原子性得不到满足
- 解决: SET key value[EX seconds][px miliseconds][NX|XX]
- 分布式锁需要解决的问题
大量的 Key 同时过期的注意事项
- 集中过期, 由于清除大量的 key 很耗时, 会出现短暂的卡顿现象
- 解决方案: 在设置 key 的过期时间时, 给每个 key 加上随机值, 分散过期时间.
如何使用 Redis 做异步队列
- 使用 List 作为队列, RPUSH 生产消息, LPOP 消费消息
- 缺点: 没有等待队列里有只就直接消费
- 弥补: 可以通过在应用层引入 Sleep 机制去调用 LPOP 调试
- 使用 BLPOP: 阻塞知道队列有消息或者超时
- 缺点: 只能提供一个消费者消费
- 解决: 使用主题订阅者模式 (pb/sub)
- 发送者 (pub) 发送消息, 订阅者 (sub) 接受消息
- 订阅者可以订阅任意数量的频道
- 使用 List 作为队列, RPUSH 生产消息, LPOP 消费消息
Linux
略
Java
JVM
- 谈谈 Java 的理解
- 平台无关性 (一次编译处处运行)
- GC
- 语言特性
- 面向对象
- 类库
- 异常处理
- JVM 如何加载. class 文件
- Java 虚拟机 (内存虚拟机)
- Class Loader: 依据特定格式加载 class 文件到内存
- Execution Engine: 对命令解析
- Native Interface: 融合不同开发语言的原生库 (一些 Native 方法 [Class.forname])
- Runtime Data Area:JVM 内存空间结构模型
- 反射
- Java 反射机制是在运行状态中, 对于任何一个类, 都能够知道这个类的所有属性和方法; 对于任何一个对象, 都能够调用它的任一方法和属性; 这种动态获取信息以及动态调用方法的功能称为 Java 语言的反射机制
- 反射函数、反射例子
- 类从编译到执行的过程
- 编译器将 java 原文件编译为. class 字节码文件
- ClassLoader 将字节码转换为 JVM 中的 Class 对象
- JVM 利用 Class 对象实例化为 Robot 对象
- ClassLoader
- ClassLoader 在 Java 中有着非常重要的作用, 它主要工作在 Class 装载的加载阶段, 其主要作用是从系统外部获得 Class 二进制数据流. 它是 Java 的核心组件, 所有的 Class 都是由 ClassLoader 进行加载的, ClassLoader 负责将 class 文件里的二进制数据流装载进系统, 然后交给 Java 虚拟机进行连接, 初始化等操作.
- ClassLoader 的种类
- BootStrapClassLoader:C++ 编写, 加载核心库 java.*
- ExtClassLoader:Java 编写,加载 javax.*
- AppClassLoader: 加载程序所在目录
- 自定义 ClassLoader:Java 编写,定制化加载
- 类的加载方式
- 类的加载过程
- 加载: 通过 ClassLoader 加载 class 文件字节码, 生成 Class 对象
- 链接:
- 校验: 将检查加载的 class 的正确性和安全性 (如格式)
- 准备: 为类变量分配存储空间并设置类变量初始值 (存放于方法区中)
- 解析: JVM 将常量池内的符号引用转换为直接引用 ()
- 初始化: 执行类变量赋值和静态代码块
- 方式
- 隐式加载: new
- new 支持调用有参构造
- 显示加载: loadClass,forName
- 显示需要使用反射调用有参构造
- 区别 (通过静态代码块实验)
- Class.forName 得到的 class 是已经初始化完成的
- ClassLoader.loadClass 得到的 class 是还没有链接的
- 隐式加载: new
- 类的加载过程
- Java 内存模型
程序计数器
- 当前线程所执行的字节码行号指示器 (逻辑)
- 改变计数器的值来选取吓一跳需要执行的字节码的命令
- 和线程是一对一的关系” 线程私有”
- 对 Java 方法计数, 如果是 Native 方法则计数器值为 undefined
- 不会发生内存泄漏
局部变量表和操作数栈
- 局部变量表: 包含方法执行过程中的所有变量
- 操作数栈: 入栈, 出栈, 复制, 交换, 产生消费变量
递归引发的 StackOverflowerror
- 递归过深, 超出虚拟机堆栈层数
- 解决方法, 限制递归层数或改用循环
Java 堆 (Heap)
- 对象实例的分配区域
- GC 管理的主要区域
JVM 三大性能调优参数 - Xms -Xmx -Xss 的含义
java -Xms128m -Xmx128m -Xss256K -jar xxx.jar
- -Xss: 规定了每个线程虚拟机栈 (堆栈) 的大小
- -Xms: 堆的初始值
- -Xmx: 堆能达到的最大值
- 一般将 - Xms -Xmx 设置成一样的 因为在 heap 不够用而扩容时会发生内存抖动影响程序稳定性
Java 内存模型中堆和栈的区别
- 内存分配策略
- 静态存储: 编译时必须确定每个数据目标在运行时的存储空间需求
- 栈式存储: 数据区需求在编译时位置, 运行时模块入口前确定
- 堆式存储: 编译时或运行时模块入口都无法确定, 动态分配
- 联系: 引用对象、数据时、栈里定义变量保存堆中目标的首地址
- 管理方式: 栈自动释放, 堆需要 GC
- 空间大小: 栈比堆小
- 碎片相关: 栈产生的碎片远小于堆
- 分配方式: 栈支持静态和动态分配, 而堆只支持动态分配
- 效率: 栈的效率比堆的效率高
- 内存分配策略
Java 垃圾回收机制–GC
- 对象被判定垃圾的标准
没有被其他对象引用
- 如何判定
- 判断对象引用数量
- 通过判断对象的引用数量来决定对象是否可以别回收
- 每个对象都有一个引用计数器, 被引用 + 1, 完成引用则 - 1
- 任何引用计数为 0 的对象实例可以被当成垃圾回收
- 优点: 执行效率高, 程序执行受影响较小
- 缺点: 无法检测出循环引用的情况 (如父对象对子对象的引用, 子对象反过来引用父对象, 所以引用计数不可能为 0), 导致内存泄漏
- 判断对象引用数量
- 如何判定
可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收 (GC Root 记录对象的引用路径)
- 可以作为 GC Root 的对象
- 虚拟机栈中引用的对象 (栈帧中的本地变量表)
- 方法区中的常量引用对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中 JNI(Native 方法) 的引用对象
- 活跃线程的引用对象
- 可以作为 GC Root 的对象
- 垃圾回收算法
标记 - 清除算法 (Mark and Sweep)
- 标记: 从根集合进行扫描, 对存活的对象进行标记
- 清除: 对堆内存从头到尾进行线性遍历, 回收不可达对象内存
- 缺点: 碎片化 清理不可达对象后会产生许多不连续的内存碎片
复制算法 (Copying)
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象清除
- 解决了碎片化问题
- 顺序分配内存, 简单高效
- 适用于对象存活率低的场景
标记 - 整理算法 (Compacting)
- 标记: 从根集合进行扫描, 对存活的对象进行标记
- 整理: 移动所有存活的对象, 且按照内存地址顺序依次排, 然后将末端内存地址以后的所有内存回收
- 避免内存的不连续性
- 不用设置两块内存互换 (复制算法需要有足够内存复制保留)
- 适用于存活率高的场景
分代收集算法 - 主流 (Generational Collector)
- 按照对象生命周期的不同, 划分区域并且 每个区域采用不同的垃圾回收算法
- jdk6 7 有三代 年轻代 老年代 永久代 jdk8 以后只有年轻代 老年代
- 年轻代存活率低可以使用复制算法, 老年代存活率高就可以使用标记清楚和标记整理算法
GC 的分类
Minior GC (年轻代中, 采用复制算法)
年轻代: 尽可能快速的收集掉那些生命周期短的对象
Eden 区 (伊甸 - 起源)
- 对象刚创建会放在这里, 放不下就会放在后面的 Survivor 且年龄 + 1
两个 Survivor 区
- 对象如果存活会在两个 Survivor 之间移动每次移动年龄加 1, 默认最高 15
每次会清除一个或两个区域的内存, 简单高效
ps: 如刚开始对象在 E 区, 经历一次 Minior 如果存活就转移到 S1 区, 年龄 + 1, 此时清除 E 区, 第二次 Minior, 如果 E 和 S1 都有存活, 则移动到 S2 且所有存活对象年龄 + 1, 此时清除 E 和 S1 区, 下一次就存到 S2, 年龄加 + 1, 清除 E 和 S2 区, 周而复始, 当年龄超过一定的限制 默认最大 15 进入老年代
Full GC(父 GC, 老年代中, 标记清理, 标记整理)
- 老年代: 存放生命周期较长的对象
- 经历一定 Minior 次数依然存活的对象
- Survivor 区中放不下的对象
- 新生成的大对象 (-XX:+PretenuerSizeThreadhold 通过此参数设置对象大小, 超过这个值就放入老年代中)
- 触发条件
- 老年代空间不足
- 永久代空间不足 (仅针对 JDK7 及以前版本)
- CMS GC 时 promotion failed,concurrent mode filure
- MinorGC 晋升到老年代时会计算老年代的平均大小大于老年代的剩余空间
- 调用 System.gc(), 显式触发 Full GC, 只是提醒 GC
- 使用 RMI 来进行 RPC 或管理的 JDK 应用, 每小时执行一次 FullGC
- 老年代: 存放生命周期较长的对象
Full GC 和 Major JC(差不多)
- FullGC 比 MinorGC 慢 (大约 10 倍), 但是执行的频率低
Stop-the-World
- JVM 由于要执行 GC 而停止了应用程序的执行
- 任何一种 GC 算法中都会发生
- 多数 GC 优化通过减少 Stop-the-World 发生的时间来提升程序性能
safepoint(所有线程运行到某一个点, 会被冻结, 此时供 GC 清理)
- 可达性分析过程中引用关系不会发生变化的点
- 产生 safepoint 的地方: 方法调用; 循环跳转; 异常跳转等
- 安全点数量适中, 太少 GC 等待时间太多, 太多会增加程序的负荷
常用的调优参数
-XX:SurvivorRatio
:Eden 和 Survivor 的比值, 默认是 8:1-XX:NewRatio
: 老年代和年轻代内存大小的比例-XX:MaxTenuringThreadhold
: 对象从年轻代晋升到老年代经过 GC 次数的最大阈值 (默认是 15)
- 常见的垃圾收集器
JVM 的运行模式
- Server
- Client
- Server 启动较慢, 但启动后进入稳定期运行速度快, 因为采用的是重量级的虚拟机, 对程序有更多优化
年轻代中常见垃圾收集器
Serial 收集器 (
-XX:+UseSerialGC
, 复制算法)- 单线程收集, 进行垃圾收集时, 必须暂停所有工作线程
- 简单高效, Client 模式中默认的年轻代收集器
ParNew 收集器 (
-XX:+UserParNewGC
, 复制算法)- 多线程收集, 其余的行为特点和 Serial 收集器一样
- 单核执行效率不如 Serial, 在多核执行下才有优势 (类似 CPU 单核高主频, 多核低主频)
Parallel Scavenge 收集器 (
+XX:UserParallelGC
, 复制算法)吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
- 比起关注用户现成停顿时间, 更关注系统的吞吐量
- 在多核执行下才有优势, Server 模式下默认的年轻代收集器
- 可配合自适应调节策略使用
-XX:+UserAdaptiveSizePolicy
老年代中常见的垃圾收集器
- Serial Old 收集器 (
-XX:+UseSerialOldGC
, 标记整理算法)- 单线程收集, 进行垃圾收集时, 必须暂停所有工作线程
- 简单高效, Client 模式中默认的老年代收集器
- Parallel Old 收集器 (
-XX:UserParallelOldGC
, 标记整理算法)- 多线程, 吞吐量优先
- CMS 收集器 (
-XX:UseConcMarkSweepGC
, 标记 - 清除算法)- 初始标记: stop-he-World (此时 JVM 会暂停)
- 并发标记: 并发追溯标记, 程序不会停顿
- 并发预清理: 查找执行 并发标记阶段从年轻代晋升到老年代的对象
- 重新标记: 暂停虚拟机, 扫描 CMS 堆中的剩余对象
- 并发清理: 清理垃圾对象
- 并发重置: 重置 CMS 收集器的数据结构, 等待下一次清理工作
- Serial Old 收集器 (
G1 收集器 (
-XX:+UseG1GC
, 复制 + 标记整理算法)特点
并发并行, 缩短 stop-the-world 时间
分代收集, 独立管理堆, 获得更好的收集效果
空间整合, 解决碎片化
可预测的停顿
将整个 Java 堆内存划分成多个大小相等的 Region
年轻代和老年代不再物理隔离
jdk11:EpsilonGC 和 ZGC 了解
Java 线程
- 进程和线程的区别
- 进程是资源分配的最小单位, 线程是 CPU 调度的最小单位
- 所有与进程相关的资源, 都被记录在 PCB 中
- 进程是抢占处理机的调度单位; 线程数与某个进程, 共享其资源
- 线程只由堆栈寄存器, 程序计数器组和 TCB 组成
- 区别
- 线程不能看做独立应用, 而进程可看做独立应用
- 进程有独立的地址空间, 相互不影响, 线程只是进程的不同执行路径
- 线程没有独立的地址空间, 多进程的程序比多线程程序健壮
- 进程的切换比线程的切换开销大
进程和线程的联系
- 运行一个程序会产生一个进程, 进程至少包含一个线程
- 每个进程对应一个 JVM 实例, 多个线程共享 JVM 里的堆
- Java 采用单线程编程模型, 程序会自动创建主线程
- 主线程可以创建子线程, 原则上要后于子线程完成执行
Thread 中 start 和 run 方法的区别
- 调用 start() 方法会创建一个新的子线程启动, 底层调用 native 方法启动一个新的线程
- run 方法只是 Thread 的一个普通方法, 还是从主线程执行
Thread 和 Runnable 的关系
- Thread 是实现了 Runnable 的类,使得 run 支持多线程
- 因为类的单一性原则,推荐使用 Runnable 接口
如何给 run() 方法传参
- 构造函数传参
- 成员变量
- 回调函数传参
如何实现处理线程的返回值
- 主线程等待法 (使用循环实现, 知道有值就跳出循环)
- 使用 Thread 类的 join 方法阻塞当前线程以等待子线程处理完毕
- 通过 Callable 接口实现: 通过 FutureTask Or 线程池获取
线程的状态 (六个状态)
- 新建 -(NEW):创建后尚未启动的线程的状态
- 运行 -(Runnable): 包含 Running 和 Ready, 有可能正在运行也有可能正在等待 CPU 为其分配时间片段
- 无限等待 -(Waiting): 不会被分配 CPU 执行时间, 需要显示被唤醒
- 没有设置 timeout 参数的 Object.wait() 方法
- 没有设置 timeout 参数的 Thread.join() 方法
- LockSupport.park() 方法
- 限期等待 -(Timed Waiting): 在一定时间后会由系统自动唤醒
- Thread.sleep() 方法
- 设置 timeout 参数的 Object.wait() 方法
- 设置 timeout 参数的 Thread.join() 方法
- LockSupport.parkNanos() 方法
- LockSupport.parkUntil() 方法
- 阻塞 -(Blocked): 等待获取排他锁
- 结束 -(Terminated): 已终止线程的状态, 线程已经结束执行
sleep 和 wait 的区别
- sleep 是 Thread 类的方法, wait 是 Object 类的方法
- sleep 方法可以在任何地方使用
- wait 只能在 synchronized 方法或 synchronized 块中使用
- 最本质区别
- Thread.sleep 只会让出 CPU, 不会导致锁行为的改变
- Object.wait 不仅会让出 CPU, 还会释放已经占有的同步资源锁
notify 和 notifyAll 的区别
- 锁池 EntryList
- 等待池 WaitSet
- 区别
- notifyAll 会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
- notify 只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
yield
- 当调用 Thread.yield() 函数时, 会给线程调度器一个当前线程愿意让出 CPU 使用的暗示, 但是线程调度器可能会忽略这个暗示
- yield 不会对锁造成影响
如何中断线程
- 通过调用 stop()方法停止线程 (被抛弃的方法, 类似还有 suspend() 和 resume()方法)
- 调用 interrupt(), 通知线程应该中断了.(并不能真正中断线程)
- 如果线程处于被阻塞状态, 那么线程将立即退出被阻塞状态, 并抛出一个 InterruptedException
- 如果线程处于正常活动状态, 那么会将该线程的中断标志设置为 true, 被设置中断标志的线程将继续正常运行, 不受影响
- 所以需要被调用的线程配合中断
通过 while 循环判断中断标志, 如果为 true, 将线程转态设置成阻塞状态即可退出线程
多线程
线程安全的主要诱因
- 存在共享数据 (也称临界资源)
- 存在多个线程共同操作这些共享数据
解决问题的根本办法:
同一时刻有且只有一个线程在操作共享数据, 其他线程必须等到该线程处理完数据之后再对共享数据进行操作
synchronized
互斥锁的特性
- 互斥性
即在同一时间只允许一个线程持有某个对象锁, 通过这种特性来实现多线程的协调机制, 这样在同一时间只有一个线程对需要同步的代码块 (复合操作) 进行访问. 互斥性也称为操作的原子性 - 可见性
必须确保在所被释放之前, 对共享变量所做的更改, 对于后随后获得该所的另一个线程是可见的 (也就是在获得锁的同时应该获得该共享变量最新的值), 否则另一个线程可能是在本地缓存的某个副本上继续操作, 从而引起不一致.
synchronized 锁的不是代码, 锁的都是对象
- 互斥性
获取的锁的分类:
- 获取对象锁 (两种用法)
- 同步代码块 (synchronized(this),synchronized(类实例对象) ), 锁是小括号() 中的实例对象.
- 同步非静态方法 (synchronized method), 锁的是当前对象的实例对象
- 获取类锁
- 同步代码块 (synchronized(类. class)), 锁是小括号() 中的类对象(Class 对象)
- 同步静态方法 (synchronized static method), 锁是当前对象的类对象 (Class 对象)
- 对象锁和类锁的总结
- 有线程访问对象的同步代码块时, 另外的线程可以访问该对象的非同步代码块;
- 若锁住的是同一个对象, 一个线程在访问对象的同步代码块时, 另一个访问对象的同步代码块的线程会被阻塞;
- 若锁住的是同一个对象, 一个线程在访问对象的同步方法时, 另一个访问对象同步方法的线程会被阻塞;
- 若锁住的是同一个对象, 一个线程在访问对象的同步代码块时, 另一个访问对象同方法的线程会被阻塞, 反之亦然;
- 同一个类的不同对象的对象锁互不干扰
- 类锁由于也是一种特殊的对象锁, 因此表现和上述 1,2,3,4 - 致, 而由于一个类只有一把对象锁, 所以同一个类的不同对象使用类锁将是同步的;
- 类锁和对象锁互不干扰。
Spring
SpringIOC 控制反转
DI (DependencyInjection) 依赖注入
把底层类作为参数传递给上层类, 实现上层对下层的控制
- Setter 注入
- 接口注入
- 注解注入
- 构造器注入
DL (DenpendencyLookup, 例如 EJB)
IOC 容器的优势
- 避免在各处使用 new 来创建类, 并且可以做到维护统一
- 创建实例的时候不需要了解其中的细节
SpringIOC 支持的功能
- 依赖注入
- 依赖检查
- 自动装配
- 支持集合
- 指定初始化方法和销毁方法
- 支持回调方法
SpringIOC 容器的核心接口
- BeanDefinition
主要用来描述 bean 的定义- BeanDefinitionRegistry
提供相 IOC 容器注册 BeanDefinition 对象的方法
- BeanDefinitionRegistry
- BeanFactory
- 提供 IOC 的配置机制
- 包含 Bean 得到各种定义, 便于实例化 Bean
- 建立 Bean 之间的依赖关系
- Bean 生命周期的控制
- ApplicationContext(继承多个接口)
- BeanFactory: 能够管理装配 Bean
- ResourcePatternResolver: 能够加载资源文件
- MessageSource: 能够实现国际化功能
- ApplicationEventPublisher: 能够注册监听器, 实现监听机制
- BeanFactory 与 ApplicationContext 的比较
- BeanFactory 是 Spring 框架的基础设施, 面向 Spring.
- ApplicationContext 面向使用 Spring 框架的开发者.
- getBean 方法的代码逻辑
- 转换 beanName
- 从缓存中加载实例
- 实例化 bean
- 检测 parentBeanFactory
- 初始化依赖的 Bean
- 创建 Bean
- BeanDefinition
Spring Bean 的作用域
- singleton:Spring 的默认作用域, 容器中拥有唯一的 Bean 实例 (适合无状态的 Bean)
- prototype: 针对每个 getBean 的请求, 容器都会创建一个 Bean 实例 (适合有状态的 Bean)
- request: 会为每个 Http 请求创建一个 Bean 实例
- session: 会为每个 session 创建一个 Bean 实例
- globalSession: 会为每个全局 Http Session 创建一个 Bean 实例, 改做用于进队 Protlet 有效
SpringBean 的生命周期
- 创建
- 实例化 Bean
- Aware(注入 BeanID、BeanFactory 和 AppCtx)
- BeanPostProcessor postBeforeInitialization
- InitializingBeans。afterPropertiesSet
- 定制 Bean init 方法
- BeanPostProcessor postAfterInitialization
- Bean 初始化完毕
- 销毁
- 若实现了 DisposableBean 接口,则会调用 destory 方法
- 若配置了 destory-method 属性,则会调用其配置的销毁方法
- 创建
AOP
关注点分离: 不同的问题交给不同的部分去解决
- 面向切面编程 AOP 正式这种技术的体现
- 通用化功能代码的实现, 对应的就是所谓的切面 (Aspect)
- 业务功能代码和切面代码分开后, 架构将变得高内聚低耦合
- 确保功能的完整性: 切面最终需要被合并到业务中 (Weave)
- AOP 的三种织入方式
- 编译时织入: 需要特殊的 Java 编译器如 AspectJ
- 类加载时织入: 需要特殊的 Java 编译器, 如 ApsectJ 和 AspectWerkz
- 运行时织入: Spring 采用的方式, 通过动态代理的方式, 实现简单
- AOP 主要名词概念
- Aspect: 通用功能的代码实现
- Target: 被织入 Aspect 的对象
- Join Point: 可以作为切入点的机会, 所有方法都可以作为切入点
- Pointcut:Apect 实际被用用在 JoinPoint, 支持正则
- Advice: 类的方法以及整个方法如何织入到目标方法的方式
- 前置通知 (Before)
- 后置通知 (AfterReturning)
- 异常通知 (AfterThrowing)
- 最终通知 (After)
- 环绕通知 (Around)
- Weaving:AOP 的实现
- AOP 的实现: JdkProxy 和 Cglib
- 有 AOPProxyFactory 根据 AdvisedSupport 对象的配置来决定
- 默认策略如果目标类是接口, 则用 JDKProxy 实现, 否则用后者
- JDKProxy 的核心: InvocationHandler 接口和 Proxy 类
- 通过 Java 的内部反射机制实现
- 在生成类的过程中比较高效
- Cglib: 以继承方式动态生成目标类的代理 (生成代理类的子类, 通过修改字节码来实现)
- 通过 ASM 实现
- 在生成类之后的执行过程中比较高效
- 代理模式: 接口 + 真实代理类 + 代理类
- 真是类的逻辑包含在 getBean 方法中
- getBean 方法返回的实际上是 Proxy 的实例
- Proxy 实例是 Spring 采用 JDKProxy 或 Cglib 动态生成的
- Spring 事务
- ACID
- 隔离级别
- 事务传播