希望能找到个好实习吧
Java是一种通用的,基于类的,面向对象的编程语言。是世界上使用最广泛的编程语言之一。具有面向对象、平台无关性、简单性、解释执行、多线程、安全性等很多特点,用于应用程序开发的计算平台。
Java代码运行过程中,文件会被 编译成字节码文件 。字节码文件交由 Java虚拟机JVM 来运行,不同的系统平台有着不同的JVMjava虚拟机,从而借助JVM虚拟机来实现跨平台。
类加载过程就是将.class文件中类的元信息加载进内存,创建Class对象并进行解析、初始化类变量等的过程。
类加载分为三个部分:加载、连接、初始化。
符号引用即字符串;直接引用即偏移量,类的元信息位于内存的地址串。
线程是操作系统能够 独立调度 的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。也就是说一个进程可以包含多个线程, 因此线程也被称为轻量级进程。
创建线程的方式有三种:
继承Thread类和实现Runnable接口的区别
最主要的区别就是一个是接口一个是实现类,常用接口可以避免单继承的局限性外。
还有继承Thread类的方式可能会导致类的局部变量不能正确的被共享。因为每个线程都是一个独立的对象,它们之间不能共享实例变量,如果需要共享变量,就必须使用静态变量或共享对象锁。
而使用Runnable接口的方式,多个线程可以共享同一个Runnable实例,从而共享实例变量。
Runnable接口和Callable接口的区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
进程是:一个进程就是CPU执行的单个任务的过程,是程序在执行过程当中CPU 资源分配 的最小单位,并且进程都有自己的地址空间,包含了(操作系统层面)创建态、就绪态、运行态、阻塞态、终止态五个状态。
线程是:CPU 独立调度 的最小单位,它可以和属于同一个进程的其他线程共享这个进程的全部资源。
两者之间的关系:一个进程包含多个线程,一个线程只能在一个进程之中。每一个进程最少包含一个线程。
两者之间的区别:
最根本的区别就是 进程是CPU资源分配的最小单位,线程是CPU独立调度的最小单位;
进程之间的切换开销比较大,但是线程之间的切换开销比较小;
CPU会把资源分配给进程,但是线程几乎不拥有任何的系统资源。因为线程之间是共享同一个进程的,所以线程之间的通信几乎不需要系统的干扰。
HTTP:是 超文本运输协议 ,是实现网络通信的一种规范。在实际应用中,HTTP常被用于在Web浏览器和网站服务器之间 传递信息 ,以 明文方式 发送内容,不提供任何方式的数据加密。
HTTPS:HTTP传递信息是以明文的形式发送内容,这并不安全。而HTTPS出现正是为了解决HTTP不安全的特性。
为了保证这些隐私数据能加密传输,让HTTP运行 安全的SSL/TLS协议 上,即 HTTPS = HTTP + SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。
SSL 协议位于TCP/IP 协议与各种应用层协议之间,浏览器和服务器在使用 SSL 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持。
http和https的区别:
状态码第一位数字决定了不同的响应状态,有如下:
1 表示消息
2 表示成功
3 表示重定向
4 表示请求错误
5 表示服务器错误
区别:
Integer是int类型的包装类,int是基本数据类型
Integer变量需要进行实例化才能进行实用,int不需要
Integer实际上是对象的引用,new一个Integer的时候,是生成一个指针指向这个对象;int则是直接存储数据。
Integer的默认值是null,int是0;
Integer和int的比较
①以下代码输出结果是true
因为当Integer包装类对象与int类型进行比较的时候,Java会将Integer进行拆包比较,变成两个int基本类型的变量比较。
Integer newInteger1 = new Integer(100);
int int1 = 100;
System.out.println(newInteger1 == int1); //true
②以下代码输出结果是flase
因为new出来的是两个对象,内存地址不同
Integer newInteger1 = new Integer(100);
Integer newInteger2 = new Integer(100);
System.out.println(newInteger1 == newInteger2); //false
③以下代码输出结果是flase
因为当变量值再 -128 ~ 127 之间时,非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者的地址也不同
Integer newInteger1 = new Integer(100);
Integer integer3 = 100;
System.out.println(newInteger1 == integer3); //false
④以下代码输出结果是true
非new生成的Integer变量值再 -128 ~ 127 之间时,指向的是同一个java常量池中的对象,内存地址相同
Integer integer3 = 100;
Integer integer4 = 100;
System.out.println(integer3 == integer4); //true
⑤ 与④相反,以下代码输出结果是flase
非new生成的Integer变量值再 -128 ~ 127 之外时,相当于new了两个Integer对象,内存地址不相同
Integer integer5 = 128;
Integer integer6 = 128;
System.out.println(integer5 == integer6); //false
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
具体的不同点:
①接口可以多实现,而抽象类只能单继承
②抽象类中的方法,可以用protect和abstract修饰,而接口中,都是默认的public abstract修饰
③抽象类可以像普通类一样有自己的普通方法,但是接口不可以。
④属性上,接口中的属性只能是public static final修饰的,抽象类中任意
⑤抽象类中可以拥有静态代码块,接口中不可以
⑥抽象类可以拥有自己的构造,接口没有构造,不能拥有
⑦接口在1.8之后可以拥有default修饰的方法,抽象类中没有
final ,是修饰符关键字。
修饰类,表示该类不能在被继承。
修饰方法,表示该方法不能被子类重写。
修饰变量,表示该变量是常量,不能被修改。
finally
通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,可以将释放外部资源的代码写在finally块中。
finalize ,是方法名。
这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
this
在Java语言中,this用来指向当前实例对象,它的一个非常重要的作用就是用来区分对象的成员变量与方法的形参。
super()
super 可以用来访问父类的方法或成员变量。当子类成员变量与父类有相同名字时也会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量只能通过super关键字来访问。
当子类构造函数需要显示调用父类构造函数时,super() 必须为构造函数中的第一条语句。
MVC是一种设计模式,在这种模式下软件被分为三层,Model(模型)、View(视图)、Controller(控制器)。
Model代表的是数据,View代表的是用户界面,Controller代表的是数据处理的逻辑,它是Model和View这两层之间的桥梁。将软件分层,可以将对象之间的耦合度降低,便于代码维护。
Model:指从现实世界中抽象出来的对象模型,是应用逻辑的反应。它封装了数据和对数据的操作,与数据库进行交互。View:是用户界面。Controller:控制器负责视图和模型之间的交互。主要负责把用户的请求分发到相应的模型、把模型的改变及时反映到视图上。
SQL语句的优化:
推荐使用自增id作为主键
1.速度更快,int类型比较速度比字符串快
2.避免过多的磁盘碎片
3.节省磁盘空间
删除操作有三种,DELETE、DROP、TRUNCATE
执行速度不同:DROP > TRUNCATE > DELETE
语言类型不同:DELETE是对表数据进行操作,是DML语言;DROP、TRUNCATE是对表进
行操作,是DDL语言。
原理不同:
DELETE 是对表数据进行一行行的删除,它在执行过程中会触发事务。它不是真正意义的删除,是对这一行的数据进行标记,设为不可读状态,也不会释放存储空间,当有新数据添加的时候,再对这一行的存储空间进行覆盖。
TRUNCATE 是快速的清空一张表,执行过程中不会触发事务,与DROP类似,但是它是先删除一张表再新建一张表,所以执行速度比DROP慢。
DROP 连带着数据一起删除表,不会触发事务,真正意义上的删除,会释放存储空间。
① 因为页中每一行的数据长度不固定,所以不能形成数组结构,使用了链表结构,而链表无法做到随机访问,只能进行 遍历查询,时间复杂度为O(n)。
②所以MySQL运用了 数据冗余建立索引 的思想,在每一页中存储了行数据的索引字段目录,然后把索引字段做成了定长,形成 数组 结构,具备了数组结构就可以对其进行 二分查找算法,时间复杂度为 O ( l o g n ) O(logn) O(logn)。
③但其实并没有冗余所有行的索引字段,而是把 行数据分成一组一组,每组冗余最后一条数据的索引字段,然后通过 二分查找法找到目标组 后,再对目标组里的数据进行 遍历查询,在时间和空间上做权衡。
(cr b站:@大头不摆)
从数据库层面:数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID这四大特性之中,一致性是目的,原子性、隔离性、持久性是手段,是为了保持一致性,数据库提供的手段。
从应用层面:通过代码判断数据库是否有效,然后决定回滚还是提交数据。
回滚日志,是实现原子性的关键,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
利用物理日志,记录的是数据页的物理修改,而不是某一行或者某几行修改成什么样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
脏读:是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
不可重复读:是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
幻读
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
SELECT LAST_INSERT_ID();
如果你一次插入了多条记录,这个 last_insert_id 函数返回的是第一个记录的ID值。
LAST_INSERT_ID 是与table无关的,如果向表a插入数据后,再向表b插入数据,LAST_INSERT_ID会改变。
select max(id) from students;
不适合高并发。如果同时插入的时候返回的值可能不准确。
第一范式:要求表中的数据,每一列都是不可分割的原子项数据。
第二范式:消除部分依赖,要求一张表中的每一列都完全依赖于主键(针对组合主键),不会出现某一列只和部分主键相关。
第三范式:消除传递依赖,要求一张表中的每一列都和主键是直接依赖的,不是间接依赖。
以下内容 参照于https://www.cnblogs.com/wsg25/p/9615100.html
MySQL的索引就像是字典的目录,是一种允许查询操作快速确定哪些符合WHERE子句中的条件,并检索到这些行的其他列值的数据结构
在大数据量的查询中,合理使用索引的优点非常明显:
1.大大加快了数据检索的速度
2.通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
但是它也有坏处:
1.创建索引和维护索引要耗费时间,因为索引需要动态的维护
2.索引会增加存储资源的消耗
扩展:
创建了索引不一定会走索引
比如使用组合索引的时候,如果没用遵从“最左前缀”的原则进行搜索,则索引是不起作用的。
举例,假设在id、name、age字段上已经成功建立了一个名为MultiIdx的组合索引。索引行中按id、name、age的顺序存放,索引可以搜索id、(id,name)、(id, name, age)字段组合。如果列不构成索引最左面的前缀,那么MySQL不能使用局部索引,如(age)或者(name,age)组合则不能使用该索引查询。
算法描述
前提:有序数组A
①定义左边界 L、右边界 R,确定搜索范围,循环执行二分查找(2、3两步)
②获取中间索引 M = Floor((L+R) /2)
③中间索引的值 A[M] 与待搜索的值 T 进行比较
1.如果A[M] == T,表示查找成功,返回M;
2.如果A[M] > T ,表示中间值右侧的值都大于T,无需比较,则右边界设为M - 1;
3.如果A[M] < T ,表示中间值左侧的值都小于T,无需比较,则左边界设为M + 1;
④如果L > R ,则查找失败,退出循环。
时间复杂度度为 O ( l o g n ) O(logn) O(logn)
代码示例
/**
* 非递归的二分查找算法
*
* @param array 有序数组
* @param t 待搜索的值
* @return 若返回-1表示查找失败
反之则表示待搜索的值在该数组的下标
*/
public static int binarySearch1(int[] array, int t) {
int l = 0; //当前比较数组最小的下标
int r = array.length - 1; //当前比较数组最大的下标
int m; //中间索引
while (l <= r) {
m = (l + r) / 2;
if (array[m] == t) {
return m;
} else if (array[m] > t) {
r = m - 1;
} else {
l = m + 1;
}
}
return -1;
}
/**
* 递归二分查找算法
*
* @param array 有序数组
* @param l 当前比较数组最小的下标
* @param r 当前比较数组最大的下标
* @param t 待搜索的值
* @return 若返回-1表示查找失败
反之则表示待搜索的值在该数组的下标
*/
public static int binarySearch2(int[] array, int l, int r, int t) {
int m = (l + r) / 2; //中间索引
if (l > r) {
return -1;
}
if (array[m] == t) {
return m;
} else if (array[m] > t) {
return binarySearch2(array, l, m - 1, t);
} else {
return binarySearch2(array, m + 1, r, t);
}
}
}
解决整数溢出问题
当 l 和 r 都较大时,l + r
有可能超过整数范围,造成运算错误,解决方法有两种:
int m = l + (r - l) / 2;
还有一种是:
int m = (l + r) >>> 1;
算法描述
①依次比较数组中相邻两个元素大小,若 a[ i ] > a[ j ] ,则交换两个元素,两两都比较一遍称为一轮冒泡;
②重复第一步,直到整个数组有序。
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
代码示例
/**
* 冒泡排序
*
* @param array 所需排序的数组
*/
public static void bubble(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
// 一轮冒泡
boolean swapped = false; // 是否发生了交换
for (int j = 0; j < array.length - 1 - j; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
swapped = true;
}
}
System.out.println("第" + i + 1 + "轮冒泡结果:" + Arrays.toString(array));
if (!swapped) {
break;
}
}
}
/**
* 优化后的冒泡排序
*
* @param array 所需排序的数组
*/
public static void bubble2(int[] array) {
int n = array.length - 1;
while (true) {
int last = 0; // 表示最后一次交换索引位置
for (int i = 0; i < n; i++) {
System.out.println("比较次数" + i);
if (array[i] > array[i + 1]) {
swap(array, i, i + 1);
last = i;
}
}
n = last;
System.out.println("第轮冒泡"
+ Arrays.toString(array));
if (n == 0) {
break;
}
}
}
算法描述
①将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集;
②重复以上步骤,直到整个数组有序。
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
可视化示例
i 表示已排序子集, s 表示最小元素下标, j 表示循环查找下标
代码示例
优化点:为减少交换次数,每一轮可以先找最小的索引,在每轮最后再交换元素
/**
* 选择排序
* @param array 需要排序的数组
*/
public static void selection(int[] array) {
// i 代表每轮选择最小元素要交换到的目标索引
for (int i = 0; i < array.length - 1; i++) {
int s = i; //s表示最小元素的索引
for (int j = s + 1; j < array.length - 1; j++) {
if (array[s] > array[j]) {
s = j;
}
}
if (s != i) {
swap(array, i, s);
}
System.out.println("第" + (i + 1) + "轮排序结果为:" + Arrays.toString(array));
}
}
算法描述
①将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需保证顺序)
②重复以上步骤,直到整个数组有序
代码实现
/**
* 插入排序
*
* @param array 所需排序的数组
*/
public static void insert(int[] array) {
// i 代表待插入元素的索引
for (int i = 1; i < array.length - 1; i++) {
int t = array[i]; // 代表待插入的元素值
int j = i; //循环查找的下标
while (j >= 1) {
if (t < array[j - 1]) {
array[j] = array[j - 1];
j--;
} else break;
}
array[j] = t;
System.out.println("第" + i + "轮排序结果:" + Arrays.toString(array));
}
}
提示
插入排序通常被同学们所轻视,其实它的地位非常重要。小数据量排序,都会优先选择插入排序
算法描述
①每一轮排序选择一个基准点(pivot)进行分区
②让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
③当分区完成时,基准点元素的位置就是其最终位置
④在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想 ([divide-and-conquer])
算法描述
① 选择最右元素作为基准点元素
② j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换
③ i 指针维护小于基准点元素的边界,也是每次交换的目标索引
④最后基准点与 i 交换,i 即为分区位置
ArrayList 扩容规则:
①ArrayList() 会使用长度为零的数组
②ArrayList(int initialCapacity) 会使用指定容量的数组
③public ArrayList(Collection extends E> c) 会使用 c 的大小作为数组容量
④add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5 倍
⑤addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)
与ArrayList的区别:
LinkedList
ArrayList
HashMap的底层数据结构,Java1.7与1.8中有什么不同?
树化意义
树化规则
退化规则
索引计算方法
数组容量为何是 2 的 n 次幂
注意
put 流程
1.7 与 1.8 的区别
链表插入节点时,1.7 是头插法,1.8 是尾插法
1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容
1.8 在扩容计算 Node 索引时,会优化
扩容(加载)因子为何默认是 0.75f
如何保证元素不重复
HashSet 在进行添加对象的过程中,首先计算对象的 hashcode,通过 hashcode 来确定对象要添加的位置,同时会与已经加入 HashSet 的对象的 hashcode 进行比较,如果没有相同的hashcode 则表示没有重复,该对象添加进 HashSet ;如果有相同的 hashcode,则会调用对象的 equals() 方法来判断对象是否相同,如果相同,则 HashSet 就不会让重复的对象加入到 HashSet 中,这样就保证了元素的不重复。
对于自定义对象,需要重写equals方法
1.Hashtable 与 ConcurrentHashMap 都是线程安全的Map集合;键和值都不能为空。
2.Hashtable 并发度低,整个Hashtable 对应一把锁,同一时刻,只能有一个线程区操作它
3.1.8之前ConcurrentHashMap使用 Segment + 数组+ 链表的结构,每个Segment对应一把锁,如果多个线程访问不同的Segment,则不会冲突
4.1.8开始ConcurrentHashMap将每个数组的每个头结点作为锁,如果多个线程访问的头结点不同,则不会冲突
内容进行参考,详细请查看:https://blog.csdn.net/mrs_chens/article/details/93986400
一、线程安全
Hashtable 是线程安全的,HashMap不是线程安全的,
Hashtable中所有元素的操作,比如是get、put操作都是用synchronized修饰的。
二、性能优劣
因为Hashtable是线程安全的,如果有多个线程进行同一操作,就会造成线程的阻塞,所有Hashtable性能比较差,HashMap性能比较好,使用更广。
如果又要保证线程安全又要保证性能,可以使用ConcurrentHashMap
三、关于NULL
Hashtable是不允许键或值为NULL的,而HashMap键值都能为NULL
Hashtable中key为NULL会直接抛出空指针异常,value为NULL手动抛出空指针异常;而HashMap会对NULL进行特殊处理。
HashMap hash 方法逻辑:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
四、关于扩容
HashMap的初始容量为16,Hashtable初始容量为11,两者的负载因子默认都是0.75.
当现有容量大于总容量 * 负载因子的时候,HashMap扩容规则为当前容量翻倍,Hashtable扩容规则则是当前容量翻倍 + 1。
1.核心线程数目 corePoolSize :最多保留的线程数
2.最大线程数目 maximumPoolSize :核心线程 + 救急线程
3.生存时间 keepAliveTime :针对救急线程的
4.时间单位 unit :针对救急线程
5.阻塞队列 workQueue
6.线程工厂 threadFactory :可以为线程创建时起个名字
7.拒绝策略 handler
共同点:wait(),wait(long),sleep(long) 的效果都是让当前线程放弃CPU的使用权,进入阻塞状态
不同点:
线程安全主要考虑三个方面:可见性、有序性、原子性
可见性:一个线程对共享变量修改,另一个线程能看到最新的结果
有序性:一个线程内代码按编写顺序执行
原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队
volatile 能够保证共享变量的可见性与有序性,但不能保证原子性
线程直接调用run()方法就相当于一个普通对象调用的他的方法,而只有调用start()方法,线程才会启动,此时才具有抢占CPU资源的资格。当某个线程抢占到 CPU 资源后,会⾃动调⽤ run ⽅法。
Java中比较常用的有三种
原子类:JDK从1.5开始提供了一个atomic包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。在atomic包里,按功能分类可以归纳为4种类型的原子更新方式:原子更新基本类型、原子更新引用类型、原子更新属性、原子更新数组。无论原子更新哪种类型,都遵循“比较和替换”规则,即比较要更新的值是否等于期望值,如果是则更新,如果不是则失败。
volatile:volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”,从而可以保证单个变量读写时的线程安全。当写一个volatile变量时,该线程本地内存中的共享变量的值会被立刻刷新到主内存中;当读一个volatile变量时,该线程本地内存会被置为无效,迫使线程直接从主内存中读取共享变量。
原子类和volatile只能保证单个共享变量的线程安全,锁则可以保证临界区内的多个共享变量的线程安全。Java中加锁的方式有两种,一种是synchronized关键字和Lock接口;
线程是CPU独立调度的最小单位,进程是CPU资源分配的最小单位。一个进程里至少拥有一个线程,可以让一个进程并发地处理多个任务,所以线程也被称为轻量级进程。在同一个进程的多个线程可以共享进程内的资源,从使用者的角度看就像多个线程在同时运行。使用多线程的优点有:1.提高CPU的利用率;2.更快的程序响应时间;3.创建和切换开销小。
死锁是多个线程互相争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态。
死锁产生有四个必要条件,1.互斥条件:一个资源在同一个时刻只能由一个线程执行 2.请求与保持:一个线程在请求被占用资源时,对已经获得的资源保持不放。 3.循环等待:死循环 4.不可剥夺:线程对所获得的资源在未完成时不能被其他线程所剥夺。
乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读,可以使用版本号机制进行控制
悲观锁:悲观锁总是假设最坏的情况,每次去拿数据是都认为别人会修改,所以每次在拿数据时都会上锁,这样别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁和排他锁都是悲观锁的实现。
线程私有的:①程序计数器 ②虚拟机栈
线程共享的:①堆 ②方法区
线程、局部变量、方法参数 使用的内存在 虚拟机栈
类使用的内存在 方法区 中
类对象 使用的内存在 堆 中
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。
singleton 默认值 当Ioc容器一创建就会创建bean实例,而且是单例的,每次得到的都是同一个
prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
IoC Inverse of Control 反转控制的概念:就是将原本在程序中手动创建对象的控制权,交由Spring框架管理
DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件
两者的区别:
IoC 控制反转,指将对象的创建权,反转到Spring容器
DI 依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入
@Autowired 是先根据属性的类型去Spring容器中去找Bean对象,如果找到多个,就会根据属性名去确定其中一个,如果根据属性名字没有找到则报错;
@Resource 是先根据属性名字取去Spring容器中去找Bean对象,如果没有找到,会根据属性类型去找,如果找到多个则会报错;也可以使用 @Resource (name = …)指定name,如果配置了name,则只会根据这个name来找Bean对象,如果没有找到就会直接报错。
@Autowired 是Spring层面提供的,跟Spring强绑定
@Resource 是JDK层面提供的,没有跟Spring强绑定
AOP是面向切面编程,是Spring两大核心之一,是一种编程思想,它可以对业务逻辑的各个部分进行隔离,降低耦合,提高代码的可重用性。
它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。