java 跨平台原理
一次编译到处运行,安装 JDK 具备开发环境,hello.java 文件通过 javac.exe 编译成 java.class 的字节码文件。表面通过的是 javac.exe 命令,但是底层是动态调用 jvm, 真正起作用的是 jvm,jvm 是真正的翻译官,将字节码文件翻译成操作系统可以识别的文件格式
Java 源代码 —>编译器 –>jm 可执行的]ava 字节码 (即虛拟指令)—>jvm—>jvm 中解释器 —> 机器可执行的二进制机器码 —->程序运行。
成员变量和局部变量的区别
| 特性 | 局部变量 | 成员变量 (实例变量) |
|---|---|---|
| 定义位置 | 方法、构造方法内部或代码块中 | 类内部,方法外部 |
| 作用域 | 从声明处开始,到它所在的代码块结束 | 整个类内部都可访问 |
| 生命周期 | 方法被调用时创建,方法执行完毕后销毁 | 对象创建时创建,对象被垃圾回收时销毁 |
| 默认值 | 没有默认值,必须手动初始化后才能使用 | 有默认值 (如:int 为 0,对象为 null) |
| 内存位置 | 栈内存 | 堆内存 (对象的一部分) |
| 访问权限 | 无访问修饰符 (如 public, private) | 可以有 (public, protected, private) |
| 语法 | 仅变量名 | 在类的方法外定义 |
作用域的区别

Overload 和 Override 的区别?
重载(Overload): 在同一个类中,当方法名相同,形参列表不同的时候 多个方法构成了重载。
重写 (Override): 在不同的类中,子类对父类提供的方法不满意的时候,要对父类的方法进行重
写。
| 英文 | 位置 | 修饰符 | 返回值 | 方法名 | 参数 | 抛出异常 | 方法体 | |
| 重载 | overloade | 一个类中 | 无关 | 无关 | 必须相同 | 必须不同 | 无关 | 不同 |
| 董写 | override | 子类父类中 | 父类的权限修饰符要低于子类的 | 父类的返回值类型大于子类 | 必须相同 | 必须相同 | 小于等于 | 不同 |
单例模式
饿汉模式: 线程安全(由 JVM 类加载机制保证)、简单直接、在类加载时就初始化,可能会浪费内存、
public class EagerSingleton {
// 类加载时就创建实例
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() {
// 防止通过反射创建实例
if (instance != null) {throw new RuntimeException("单例模式不允许创建多个实例");
}
}
// 全局访问点
public static EagerSingleton getInstance() {return instance;}
}
懒汉模式
- 非线程安全的懒汉模式
public class UnsafeLazySingleton {
private static UnsafeLazySingleton instance;
private UnsafeLazySingleton() {}
public static UnsafeLazySingleton getInstance() {if (instance == null) {instance = new UnsafeLazySingleton();
}
return instance;
}
}
问题: 多线程环境下可能创建多个实例
- 线程安全的懒汉模式
public class DoubleCheckSingleton {
// 使用 volatile 防止指令重排序, 保证顺序
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance() {if (instance == null) { // 第一次检查,在下面加锁竞争
synchronized (DoubleCheckSingleton.class) {if (instance == null) { // 第二次检查
instance = new DoubleCheckSingleton();}
}
}
return instance;
}
}
- 静态内部类(最优实现)
public class InnerClassSingleton {private InnerClassSingleton() {}
// 静态内部类在第一次被引用时才会加载
private static class SingletonHolder {private static final InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {return SingletonHolder.instance;}
}
匿名内部类
匿名内部类是没有名字的内部类,它通常用于创建只需要使用一次的类实例。
class Person {
private String name;
public Person(String name) {this.name = name;}
public void introduce() {System.out.println("我是" + name);
}
}
public class AnonymousWithParams {public static void main(String[] args) {
// 带构造参数的匿名内部类
Person student = new Person("张三") {
@Override
public void introduce() {System.out.println("我是学生:" + super.toString());
}
public void study() {System.out.println("正在学习");
}
};
student.introduce(); // 输出:我是学生:AnonymousWithParams$1@哈希值}
}
- 匿名内部类不能是抽象类
- 匿名内部类不能定义构造器
- 匿名内部类中不能定义静态成员(static final 常量除外)
- 在性能敏感的场景中要谨慎使用,因为每次都会创建新的类对象
finnal
- 修饰类: 表示类不可被继承
- 修饰方法: 表示方法不可被子类覆盖,但是可以重载
- 修饰变量: 表示变量一旦被赋值就不可以更改它的值。
- 如果 final 修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
- 如果 final 修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。
- 系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用 final 修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对 final 变量赋初值(仅一次)
为什么局部内部类和匿名内部类只能访问局部 final 变量?
首先需要知道的一点是: 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题: 当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾: 内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的 ”copy”。这样就好像延长了局部变量的生命周期
throw 和 throws 区别
- 位置不同
- throw: 方法内部
- throws: 方法的签名处,方法的声明处
- 内容不同:
- throw+ 异常对象(检查异常,运行时异常)
- throws+ 异常的类型(可以多个类型,用,拼接)
- 作用不同:
- throw: 异常出现的源头,制造异常。
- throws: 在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理: 要么自己处理要么再继续向外抛出异常
接口和抽象类的区别
- 抽象类可以存在普通成员函数,而接口中只能存在 public abstract 方法。.
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 publicstatic final 类型的。
- 抽象类只能继承一个,接口可以实现多个。
ArrayList 和 LinkedList 区别
- ArrayList: 基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制: 因为数组长度固定,超出长然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往度存数据时需要新建数组,后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过 linkedList(需要创建大量的 node 对象)
- LinkedList: 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询: 需要逐一遍历遍历 LinkedList 必须使用 iterator 不能使用 for 循环,因为每次 for 循环体内通过 get()取得某一元素时都需要对 list 重新进行遍历,性能消耗极大。另外不要试图使用 index0f 等返回元素索引,并利用其进行遍历,使用 indexl0f 对 list 进行了遍历,当结果为空时会遍历整个列表。
如何实现一个 I0C 容器
- 配置文件配置包扫描路径
- 递归包扫描获取.class 文件:1、定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解。2、从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class 结尾的文件添加到一个 Set 集合中进行存储
- 反射、确定需要交给 IOC 管理的类:遍历这个 set 集合,获取在类上有指定注解的类,并将其交给 10C 容器,定义一个安全的 Map 用来存储这些对象
- 对需要注入的类进行依赖注入:遍历这个 I0C 容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入
GC 如何判断对象可以被回收
- 引用计数法: 每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,计数为 0 时可以回收
- 可达性分析法: 从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
引用计数法,可能会出现 A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器 =1 永远无法被回收。
GC Roots 的对象有
- 虚拟机栈 (栈帧中的本地变量表) 中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程: 第一次是经过可达性分析发现没有与 GCRoots 相连接的引用链,第二次是在由虚拟机自动建立的 Finalizer 队列中判断是否需要执行 finalize()方法。
当对象变成 (GC Roots) 不可达时,GC 会判断该对象是否看盖了 finalize 方法,若未覆盖,则直接将其回收。否则, 若对象未执行过 finalize 方法,将其放入 F -Queue 队列,由一低优先级线程执行该队列中对象的 finalize 方法。执行 finalize 方法完毕后,GC 会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”每个对象只能触发一次 finalize()方法
线程的生命周期? 线程有几种状态
- 创建:新创建了一个线程对象
- 就绪:线程对象创建后,其他线程调用了该对象的 start 方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
- 运行:就绪状态的线程获取了 CPU,执行程序代码
- 阻塞:阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
- 等待阻塞: 运行的线程执行 wait 方法,该线程会释放占用的所有资源,IM 会把该线程放入 ” 等待池 ” 中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notifv 或 notifyAl 方法才能被唤醒,wait 是 obiect 类的方法
- 同步阻塞: 运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 VM 会把该线程放入“锁池“中。
- 其他阻塞: 运行的线程执行 sleep 或 ioin 方法,或者发出了!/ 0 请求时,IVM 会把该线程置为阻塞状态。当 sleep 状态超时、ioin 等待线程终止或者超时、或者!/ 0 外理完毕时,线程重新转入就绪状态。sleep 是 Thread 类的方法
- 死亡:线程执行完了或者因异常退出了 run 方法,该线程结束生命周期。
sleep()、wait()、join()、yield()的区别
- 锁池:所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待 cpu 资源分配。
- 等待池:针对 wait, 当我们调用 wait ()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了 notify ()或 notifvAI(0)后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而 notifvAlI0 是将等待池的所有线程放到锁池当中
sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
sleep 方法不会释放 lock,但是 wait 会释放,而且会加入到等待队列中。
sleep 方法不依赖于同步器 synchronized,但是 wait 需要依赖 synchronized 关键字.。
sleep 不需要被唤醒(休眠之后推出阻塞),但是 wait 需要(不指定时间需要被别人中断)
sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。
- yield()执行后线程直接进入就绪状态,马上释放了 cpu 的执行权,但是依然保留了 cpu 的执行资格,所以有可能 cpu 下次进行线程调度还会让这个线程获取到执行权继续执行 join()执行后线程进入阻塞状态,例如在线程 B 中调用线程 A 的
- join(),那线程 B 会进入到阻塞队列,直到线程 A 结束或中断线程
对线程安全的理解
不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在 Java 中,堆是 Java 虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一日的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
为什么用线程池? 解释下线程池参数?
1、降低资源消耗; 提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度; 任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性; 线程是稀缺资源,使用线程池可以统一分配调优监控。
- corePoo1size 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
- maxinumPoo1size 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
- keepA1iveTime、unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除, 我们可以通过 setKeepA1iveTime 来设置空闲时间
- workoueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
- ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,目都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
- Hand1er 任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,: 线程池已经没有能力继续处理新提交的任务时,这是也就拒绝
线程池中阻塞队列的作用? 为什么是先添加列队而不是先创建最大线程?
一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入 wait 状态,释放 cpu 资源。阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时, 线程池利用阻塞队列的 take 方法挂起,从而维持核心线程的存活、不至于一直占用 cpu 资源
在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。就好比一个企业里面有 10 个 (core) 正式工的名额,最多招 10 个正式工,要是任务超过正式工人数 (task>core) 的情况下,工厂领导 (线程池) 不是首先扩招工人,还是这 10 人,但是任务可以稍微积压一下,即先放到队列去 (代价低)。10 个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工) 要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。
谈谈你对 IOC 的理解
- 容器概念
- ioc 容器: 实际上就是个 map(key,value),里面存的是各种对象(在 xml 里配置的 bean 节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的 bean 节点,根据全限定类名使用反射创建对象放到 map 里、扫描到打上上述注解的类还是通过反射创建对象放到 map 里。
- 这个时候 map 里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过 D! 注入(autowired.resource 等注解,xml 里 bean 节点内的 ref 属性,项目启动的时候会读取 xml 节点 ref 属性根据 id 注入,也会扫描这些注解,根据类型或 id 注入;id 就是对象名)。
- 控制反转
- 没有引入 I0C 容器之前,对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。无论是创建还是使用对象 B,控制权都在自己手上。
- 引入 I0C 容器之后,对象 A 与对象 B 之间失去了直接联系,当对象 A 运行到需要对象 B 的时候,10C 容器会主动创建一个对象 B 注入到对象 A 需要的地方。
- 通过前后的对比,不难看出来: 对象 A 获得依赖对象 B 的过程, 由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转“这个名称的由来。
- 全部对象的控制权全部上缴给“第三方 ”0C 容器,所以,10C 容器成了整个系统的关键核心,它起到了一种类似 ” 粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个 ” 粘合剂 ”,对象与对象之间会彼此失去联系,这就是有人把 IOC 容器比喻成“粘合剂“的由来。
- 依赖注入
- “获得依赖对象的过程被反转了 ”。控制被反转之后,获得依赖对象的过程由自身管理变为了由 I0C 容器主动注入。
- 依赖注入是实现 I0C 的方法,就是由 I0C 容器在运行期间,动态地将某种依赖关系注入到对象之中。
BeanFactory 和 Applicationcontext 有什么区别?
Applicationcontext 是 BeanFactory 的子接口,Applicationcontext 提供了更完整的功能:
①继承 Messagesource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册 bean 的事件。
④同时加载多个配置文件。
⑤载入多个 (有继承关系) 上下文,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。
- BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用 getBean(),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入,BeanFacotry 加载后,直至第一次使用调用 getBean 方法才会抛出异常
- ApplicationContext,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检査所依赖属性是否注入。Applicationcontext 启动后预载入所有的单实例 Bean,通过预载入单实例 bean, 确保当你需要的时候,你就不用等待,因为它们已经创建好了。
- 相对于基本的 Beanfactory,Applicationcontext 唯一的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。
- BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader.
- BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 Applicationcontext 则是自动注册。
描述一下 Spring Bean 的生命周期
1、解析类得到 BeanDefinition
2、如果有多个构造方法,,则要推断构造方法
3、确定好构造方法后进行实例化得到一个对象
4、对对象中的加了 @Autowired 注解的属性进行属性填充
5、回调 Aware 方法,比如 BeanNameAware,BeanFactoryAware
6、调用 BeanPostProcessor 的初始化前的方法
7、调用初始化方法
8、调用 BeanPostProcessor 的初始化后的方法,在这里会进行 AOP
9、如果当前创建的 bean 是单例的则会把 bean 放入单例池,
10、使用 bean(程序运行时)
11、Spring 容器关闭时调用 DisposableBean 中 destory()方法
解释下 Spring 支持的几种 bean 的作用域
- singleton: 默认,每个容器中只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护。该对象的生命周期是与 Springl0c 容器一致的(但在第一次被注入时才会创建)。
- ·prototype: 为每一个 bean 请求提供一个实例。在每次注入时都会创建一个新的对象
- request:bean 被定义为在每个 HTTP 请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
- session: 与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean 会随之失效。
- application:bean 被定义为在 Servletcontext 的生命周期中复用一个单例对象。
- ·websocket:bean 被定义为在 websocket 的生命周期中复用一个单例对象。
- global-session: 全局作用域,global-session 和 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时它包含很多 portlet。如果你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。全局作用域与 Servlet 中的 session 作用域效果相同
Spring 框架中的单例 Bean 是线程安全的么
Spring 中的 Bean 默认是单例模式的,框架并没有对 bean 进行多线程的封装处理。
如果 Bean 是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变 bean 的作用域 把 ”singleton” 改为 ”protopyte’ 这样每次请求 Bean 就相当于是 new Bean() 这样就可以保证线程的安全了。
- 有状态就是有数据存储功能
- 无状态就是不会保存数据 controller、service 和 da0 层本身并不是线程安全的,只是如果只是调用里面的. 方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
Dao 会操作数据库 Connection,Connection 是带有状态的,比如说数据库事务,Spring 的事务管理器使用 Threadlocal 为不同线程维护了一套独立的 connection 副本,保证线程之间不会互相影响(Spring 是如何保证事务获取同一个 Connection 的)
不要在 bean 中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用 ThreadLocal 把变量变为线程私有的,如果 bean 的实例变量或类变量需要在多个线程之间共享,那么就只能使用 synchronized、lock、CAS 等这些实现线程同步的方法了。
Spring 框架中都用到了哪些设计模式?
- 简单工厂: 由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
- 工厂方法: 实现了 FactoryBean 接口的 bean 是一类叫做 factory 的 bean。其特点是,spring 会在使用 getBean()调用获得该 bean 时,会自动调用该 bean 的 getobiect()方法,所以返回的不是 factory 这个 bean,而是这个 bean.getoibect)方法的返回值。
- 单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。spring 对单例的实现: spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory。但没有从构造器级别去控制单例,这是因为 spring 管理的是任意的 java 对象。
- 适配器模式:spring 定义了一个适配接口,使得每一种 contro1ler 有一种对应的适配器实现类,让适配器代替 contro11er 执行相应的方法。这样在扩展 contro11er 时,只需要增加一个适配器类就完成了 SpringMvc 的扩展了。
- 装饰器模式: 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。Spring 中用到的包装器模式在类名上有两种表现: 一种是类名中含有 wrapper,另一种是类名中含有 Decorator。
- 动态代理: 切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象创建动态的创建一个代理对象。springAOP 就是以这种方式织入切面的。
- 观察者模式:spring 的事件驱动模型使相的是 观察者模式,Spring 中 observer 模式常用的地方是 1istener 的实现。
- 策略模式:spring 框架的资源访问 Resource 接口。该接口提供了更强的资源访问能力,spring 框架本身大量使用了 Resource 接口来访问底层资源。
spring 事务传播机制
都是在 @Transactional 事件定义下列方式
方法 A 是一个事务的方法,方法 A 执行过程中调用了方法 B,那么方法 B 有无事务以及方法 B 对事务的要求不同都会对方法 A 的事务具体执行造成影响,同时方法 A 的事务对方法 B 的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
- REQUIRED(Spring 默认的事务传播类型): 如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
- SUPPORTS: 当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
- MANDATORY: 当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
- REQUIRES_NEW: 创建一个新事务,如果存在当前事务,则挂起该事务。
- NOT_SUPPORTED: 以非事务方式执行, 如果当前存在事务,则挂起当前事务。
- NEVER: 不使用事务,如果当前事务存在,则抛出异常
- NESTED: 如果当前事务存在,则在嵌套事务中执行,否则 REQUIRED 的操作一样(开启一个事务)
Spring Boot、Spring MVC 和 Spring 有什么区别
- spring 是一个 10C 容器,用来管理 Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供 AOP 机制弥补 OOP 的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
- springmvc 是 spring 对 web 框架的一个解决方案,提供了一个总的前端控制器 Servlet,用来接收请求,然后定义了套路由策略 (url 到 handle 的映射) 及适配执行 handle,将 handle 结果使用视图解析技术生成视图展现给前端
- springboot 是 spring 提供的一个快速开发工具包,让程序员能更方便、更快速的开发 spring+springmvc 应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter 机制)、redis、mongodb、es,可以开箱即用
SpringMVc 工作流程
1)用户发送请求至前端控制器 Dispatcherservlet。
2)DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器
3)处理器映射器找到具体的处理器 (可以根据 xm 配置、注解进行査找),生成处理器及处理器拦截器(如果有则生成) 一并返回给 Dispatcherservlet。
4)DispatcherServlet 调用 HandlerAdapter 处理器适配器,
5)HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)
6)Controller 执行完成返回 ModelAndview。
7)HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet
8)Dispatcherservlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9)ViewReslover 解析后返回具体 View
10)DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
11)DispatcherServlet 响应用户。
Spring Boot 自动配置原理
@lmport+ @configuration + spring spi
动配置类由各个 starter 提供,使用 @Configuration+@Bean 定义配置类,放到 META-INF/spring.factories 下使用 Spring spi 扫描 META-INF/spring.factories 下的配置类
使用 @lmport 导入自动配置类

如何理解 Spring Boot 中的 Starter
使用 spring+springmvc 使用,如果需要引入 mybatis 等框架,需要到 xml 中定义 mybatis 需要的 beanstarter 就是定义一个 starter 的 jar 包,写一个 @Configuration 配置类、将这些 bean 定义在里面,然后在 starter 包的 META-INF/spring.factories 中写入该配置类,springboot 会按照约定来加载该配置类
开发人员只需要将相应的 starter 包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如 mvbatis-spring-boot–starter,spring-boot-starter-redis