1 Java基础
1.1 怎么理解面向对象(封装,继承,多态)
- 封装(Encapsulation):封装是指将数据(属性)和操作数据的方法(行为)组合在一个单元中,并对外部隐藏对象的内部细节。封装可以确保对象的状态不会被意外地改变,并通过公共的方法提供对对象的控制和访问。这样可以降低对其他对象的依赖性,使得代码更加可靠和易维护。
public class Person {
private String name;
public void setName(String newName) {
this.name = newName;
}
public String getName() {
return this.name;
}
}
一个名为 Person 的类中包含私有的属性 name 和公共的方法 setName() 和 getName(),其他类只能通过公共的方法来访问和修改 name 属性,而无法直接访问它。
- 继承(Inheritance):继承是指一个类(子类)可以派生出另一个类(父类)的属性和方法,并可以扩展或修改它们。子类可以继承父类的行为和状态,并且可以定义自己的特定行为和状态。继承可以提高代码的复用性,减少重复编码,并且支持多态。
public class Animal {
public void eat() {
// 吃东西的行为
}
public void sleep() {
// 睡觉的行为
}
}
public class Dog extends Animal {
public void bark() {
// 狗叫的行为
}
}
假设有一个 Animal 类作为父类,其中包含 eat() 和 sleep() 方法,然后有一个 Dog 类作为子类继承自 Animal类。Dog 类可以继承 eat() 和 sleep() 方法,并且可以添加新的方法 bark()。
- 多态(Polymorphism):多态是指同一个方法调用可以根据对象的不同而表现出不同的行为。多态可以通过继承和接口来实现。在编译时并不确定调用的方法,而是在运行时根据对象的实际类型来确定。
public void makeSound(Animal animal) {
animal.makeNoise();
}
使用动物类的例子,可以有一个方法接受 Animal 对象作为参数,当传入 Dog 对象时,调用 bark() 方法,当传入 Cat 对象时,调用 meow() 方法。
1.2 抽象类和接口具体什么区别
- 抽象类
- 抽象类是一种不能被实例化的类,它通常用于定义子类的通用行为。
- 抽象类可以包含抽象方法(没有实际实现的方法)和非抽象方法(有实际实现的方法)。
- 子类必须实现抽象类中的所有抽象方法,否则子类也必须声明为抽象类。
- 抽象类可以包含成员变量、构造函数和普通方法的实现。
- 接口
- 接口是一种完全抽象的类,它只包含方法的声明而没有方法的实现。
- 类可以实现多个接口,从而达到多继承的效果。
- 接口可以用来定义规范,强制实现类提供特定的行为。
在 Java 8 之后,接口也可以包含默认方法和静态方法的实现。
- 使用场景
- 定义一个通用的基类,并且希望子类继承这些通用行为并提供特定实现时,您应该使用抽象类。抽象类适合于需要共享代码和数据的情况。
- 定义一组行为,而不关心实现细节时,或者当类需要实现多个不相关的行为时,您应该使用接口。接口适合于需要定义规范和约定的情况。
抽象类用于表示 is-a 关系,而接口用于表示 has-a 关系。抽象类更关注于共享代码和数据,而接口更关注于定义行为。
1.3 Java的基本数据类型
- 整型:用于表示整数
- byte:8 位,-128 到 127
- short:16 位,-32768 到 32767
- int:32 位,-2^31 到 2^31-1
- long:64 位,-2^63 到 2^63-1
- 浮点型:用于表示带小数部分的数值
- float:32 位,单精度浮点数
- double:64 位,双精度浮点数
- 字符型:用于表示单个字符
- char:16 位,0 到 65535(Unicode 编码)
- 布尔型:用于表示逻辑值
- boolean:表示 true 或 false
1.4 Java的数据结构
- 数组(Array):数组是一种简单的数据结构,它可以存储具有相同数据类型的元素。数组的长度在创建时确定,并且不能更改。可以通过索引来访问和修改数组中的元素。
- 列表(List):列表是一种有序的数据结构,可以按照插入顺序存储元素。Java提供了多种列表实现,如ArrayList和LinkedList。列表可以包含重复的元素,并且可以根据索引进行访问和修改。
- 集合(Set):集合是一种不允许重复元素的数据结构。Java提供了多种集合实现,如HashSet和TreeSet。集合提供了一系列方法来添加、删除和判断元素是否存在。
- 映射(Map):映射是一种键值对存储的数据结构,每个键都唯一对应一个值。Java提供了多种映射实现,如HashMap和TreeMap。映射可以用于高效地查找和检索值。
- 栈(Stack):栈是一种后进先出(LIFO)的数据结构,只允许在顶部进行插入和删除操作。Java提供了Stack类来实现栈。
- 队列(Queue):队列是一种先进先出(FIFO)的数据结构,只允许在队列的一端插入元素,在另一端删除元素。Java提供了Queue接口和其子类,如LinkedList和PriorityQueue。
- 堆(Heap):堆是一种特殊的树状数据结构,具有以下特点:父节点的值总是大于等于/小于等于其子节点的值。Java提供了PriorityQueue类来实现堆。
- 链表(LinkedList):链表是一种动态数据结构,它由节点组成,每个节点包含数据和指向下一个节点的引用。链表可以高效地进行插入和删除操作,但访问元素需要遍历链表。
- 树(Tree):树是一种层次结构的数据结构,由节点和边组成。每个节点可以有多个子节点,除了根节点外,每个节点都有一个父节点。树的种类很多,如二叉树、二叉搜索树(BST)和AVL树等。
1.5 java当中使用什么类型表示价格比较好
在Java中,表示价格可以使用BigDecimal类型来更好地处理精确的十进制数值。BigDecimal提供了高精度的数学运算和舍入规则,适用于表示货币和其他需要精确计算的场景。
1.6 string类的常用方法
方法 | 介绍 |
---|---|
length() | 返回字符串的长度 |
charAt(int index) | 返回指定索引位置的字符 |
substring(int beginIndex) | 返回从指定索引开始到字符串末尾的子字符串 |
substring(int beginIndex, int endIndex) | 返回指定索引范围内的子字符串(不包括 endIndex 处的字符) |
equals(Object anObject) | 比较字符串内容是否相等 |
equalsIgnoreCase(String anotherString) | 忽略大小写,比较字符串内容是否相等 |
indexOf(int ch) | 返回指定字符在字符串中第一次出现的位置 |
indexOf(int ch, int fromIndex) | 从指定位置开始,返回指定字符在字符串中第一次出现的位置 |
indexOf(String str) | 返回子字符串在字符串中第一次出现的位置 |
indexOf(String str, int fromIndex) | 从指定位置开始,返回子字符串在字符串中第一次出现的位置 |
lastIndexOf(int ch) | 返回指定字符在字符串中最后一次出现的位置 |
lastIndexOf(int ch, int fromIndex) | 从指定位置开始,返回指定字符在字符串中最后一次出现的位置 |
lastIndexOf(String str) | 返回子字符串在字符串中最后一次出现的位置 |
lastIndexOf(String str, int fromIndex) | 从指定位置开始,返回子字符串在字符串中最后一次出现的位置 |
replace(char oldChar, char newChar) | 将字符串中所有匹配的旧字符替换为新字符 |
replaceAll(String regex, String replacement) | 使用给定的替换字符串替换所有与给定的正则表达式匹配的子字符串 |
toLowerCase() | 将字符串转换为小写形式 |
toUpperCase() | 将字符串转换为大写形式 |
trim() | 去除字符串两端的空白符 |
split(String regex) | 根据给定的正则表达式拆分字符串 |
startsWith(String prefix) | 判断字符串是否以指定前缀开头 |
endsWith(String suffix) | 判断字符串是否以指定后缀结尾 |
1.7 String,StringBuffer和StringBuilder区别
- 不可变性: String是不可变的,也就是说,一旦创建了一个String对象,就不能修改其内容。任何对String的操作(例如拼接、替换等)都会创建一个新的String对象。而StringBuffer和StringBuilder是可变的,可以对其内容进行修改,而不需要创建新的对象。
- 线程安全性: String是线程安全的,因为它的内容不可变。多个线程可以同时访问和共享相同的String对象,而不会出现并发问题。StringBuffer是线程安全的,它的大多数方法都使用synchronized关键字进行同步,从而确保线程安全。而StringBuilder不是线程安全的,它的方法没有进行同步处理,因此在多线程环境下使用StringBuilder可能会导致不可预测的结果。
- 性能: 由于String是不可变的,每次对String进行修改都会创建一个新的对象,这可能导致频繁的对象创建和垃圾回收,从而影响性能。相比之下,StringBuffer和StringBuilder是可变的,可以避免频繁的对象创建和销毁,因此在频繁操作字符串的场景下,StringBuffer和StringBuilder通常比String更高效。在性能方面,StringBuilder比StringBuffer略优,因为它不需要进行同步操作。
1.8 ==和equls和Hashcode区别
- ==操作符比较的是对象的引用,判断两个对象是否指向同一个内存地址。
- equals()方法比较的是对象的内容,通常需要在类中重写equals()方法,自定义比较规则。
- hashCode()方法返回对象的哈希码值,用于快速查找对象。在重写equals()方法时,也需要同时重写hashCode()方法,以保证逻辑上相等的对象具有相同的哈希码值。
1.9 Override和Overload区别
- 方法重写(Override): 在子类中重新定义父类中已经存在的方法,具有相同的方法名、参数列表和返回类型。通过方法重写,子类可以根据自身的需求对继承的方法进行修改或补充。方法重写用于实现多态性,即子类对象可以以父类类型参与方法调用,但最终执行的是子类重写后的方法。
- 特点:
- 方法名、参数列表和返回类型必须与父类方法一致。
- 访问修饰符可以扩大,但不能缩小。
- 子类方法不能抛出比父类方法更具限制性的异常。
- @Override注解可选,但建议使用,可以帮助编译器检查是否正确重写了父类方法。
- 特点:
- 方法重载(Overload): 在一个类中定义多个具有相同名称但参数列表不同的方法。通过方法重载,可以在同一个类中使用相同的方法名来执行不同的操作。方法重载用于提供更多的灵活性和方便性,使得程序可以根据不同的参数进行不同的处理。
- 特点:
- 方法名相同,参数列表不同(参数的类型、顺序或个数不同)。
- 返回类型可以相同也可以不同。
- 方法重载与方法名、参数列表和返回类型相关,与方法体无关。
- 特点:
1.10 #{}和${}区别
- #{}用于参数的占位符,实现参数化查询,从而防止SQL注入攻击。
- ${}用于从外部环境获取属性值,并注入到配置中。它是用来实现配置参数的动态注入。
1.11 对反射的理解
在Java中,反射机制提供了一组类用于在运行时动态地操作类的成员。
- 获取类的基本信息:可以通过反射获取类的名称、父类、实现的接口等基本信息。
- 获取类的字段信息:可以获取类的各种字段(成员变量)的名称、类型、修饰符等信息,并且可以动态地设置或获取字段的值。
- 获取类的方法信息:可以获取类中定义的方法的名称、参数列表、返回类型等信息,并且可以动态地调用这些方法。
- 动态创建对象:可以使用反射来动态地创建类的实例对象,而不是通过new关键字静态地在编码阶段创建对象。
1.12 final,finalize()和finally{}的区别
- final关键字用于修饰类、方法和变量,分别表示最终类、最终方法和常量。
- finalize()方法是在对象被垃圾回收之前调用的方法,用于执行资源释放或清理操作。
- finally{}块是用于异常处理的代码块,在无论是否发生异常都会被执行,常用于资源清理和确保后续逻辑的执行。
1.13 深拷贝和浅拷贝
- 浅拷贝只复制对象本身和对象包含的引用,不复制引用指向的对象,因此新旧对象的部分数据共享同一块内存。
- 深拷贝不仅复制对象本身和对象包含的引用,还递归复制引用指向的对象,因此新对象的所有数据都是全新的,与原对象完全独立。
1.14 常见异常
- 运行时异常(Runtime Exception)
- NullPointerException:空指针异常
- ArrayIndexOutOfBoundsException:数组下标越界异常
- IllegalArgumentException:非法参数异常
- ArithmeticException:算术异常
- ClassCastException:类型转换异常
- NumberFormatException:数字格式转换异常
- 已检查异常(Checked Exception)
- IOException:输入输出异常
- SQLException:数据库异常
- ClassNotFoundException:类不存在异常
- InterruptedException:线程被中断时抛出的异常
- ParseException:数字转换异常
2 线程
2.1 线程的状态
- 新建(New): 当一个Thread对象被创建但还没有调用start()方法时,线程处于新建状态。
- 可运行(Runnable): 当线程对象调用start()方法后,线程进入可运行状态。此时线程已经分配到了CPU资源,但并不意味着线程一定在执行任务,只是表示线程可以被调度执行。
- 运行(Running): 在可运行状态的线程被调度执行后,线程处于运行状态。线程正在执行自己的任务。
- 阻塞(Blocked): 线程在某些情况下会进入阻塞状态,例如等待获取锁、等待输入输出操作完成、等待其他线程的通知等。在这种情况下,线程无法执行任何任务。
- 等待(Waiting): 线程在调用Object类的wait()方法、Thread类的join()方法或LockSupport类的park()方法时,会进入等待状态。线程将一直等待,直到其他线程唤醒它。
- 计时等待(Timed Waiting): 线程在调用Thread类的sleep()方法、Object类的wait(long)方法、join(long)方法或LockSupport类的parkNanos()、parkUntil()方法时,会进入计时等待状态。线程将在指定的时间内等待,超过时间后将自动唤醒。
- 终止(Terminated): 当线程执行完任务或者发生异常导致线程终止时,线程进入终止状态。
2.2 实现线程的方式
- 继承Thread类并重写run()方法
public class MyThread extends Thread {
public void run() {
// 线程执行的任务代码
System.out.println("线程执行任务");
}
public static void main(String[] args) {
MyThread myThread = new MyThread(); // 创建线程对象
myThread.start(); // 启动线程
}
}
- 实现Runnable接口并将其作为参数传递给Thread类的构造函数
public class MyRunnable implements Runnable {
public void run() {
// 线程执行的任务代码
System.out.println("线程执行任务");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable(); // 创建实现了Runnable接口的对象
Thread thread = new Thread(myRunnable); // 创建线程对象,并将Runnable对象作为参数传递
thread.start(); // 启动线程
}
}
2.3 线程死锁的原因
两个或多个线程在互相等待对方释放资源的情况下,导致它们无法继续执行
- 产生死锁必须具备以下四个条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 如何预防和避免线程死锁(破坏死锁的产生的必要条件)
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
2.4 sleep() 方法和 wait() 方法区别和共同点
- 两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
- 两者都可以暂停线程的执行。
- wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。
2.5 如何唤醒线程
- notify()
唤醒在对象上等待的单个线程
synchronized (lock) {
lock.notify(); // 唤醒一个等待在lock对象上的线程
}
- notifyAll()
唤醒在对象上等待的所有线程
synchronized (lock) {
lock.notifyAll(); // 唤醒所有等待在lock对象上的线程
}
2.6 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法
new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
2.7 线程锁
线程锁就是一种同步机制,用于保护共享资源,确保在同一时刻只有一个线程可以访问它,从而避免数据的混乱和不一致。
- synchronized:可以用来修饰方法或代码块,当某个线程进入synchronized方法或代码块时,会尝试获取对象的锁,其他线程需要等待直到锁被释放才能继续执行。
public synchronized void synchronizedMethod() {
// 同步方法体
}
- ReentrantLock:JDK提供的显示锁,它提供了比synchronized更灵活的锁机制,支持公平锁和非公平锁,也支持可重入特性。
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 执行需要同步的代码块
} finally {
lock.unlock(); // 在finally块中释放锁,确保锁的释放
}
- ReadWriteLock:是基于ReentrantLock实现的读写锁,允许多个线程同时读取共享资源,但在写操作时需要独占锁。
2.8 线程池的常用参数
- corePoolSize(核心线程数):线程池中保持活动状态的最小线程数。
- maximumPoolSize(最大线程数):线程池中允许的最大线程数。
- keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程的存活时间。
- unit(时间单位):用于指定keepAliveTime的时间单位。
- workQueue(任务队列):用于保存等待执行的任务的队列。
- threadFactory(线程工厂):用于创建新线程的工厂类。
- handler(拒绝策略):当任务被拒绝时的处理策略。
- AbortPolicy:拒绝策略
- DiscardPolicy:抛弃策略
- DiscardOldestPolicy:抛弃最老任务策略
- CallerRunsPolicy:调用者执行策略
- 自定义策略
3 集合
3.1 list set Map区别
- List是有序集合,可以包含重复元素
- Set是无序集合,不允许包含重复元素
- Map是键值对的集合,键是唯一的,值可以重复
3.2 list set map底层数据结构
- List
- ArrayList:基于动态数组实现,内部通过数组来存储元素,支持随机访问,但在插入和删除操作时需要移动元素位置。
- LinkedList:基于双向链表实现,内部通过链表结构存储元素,插入和删除操作效率高,但随机访问效率较低。
- Vector:与ArrayList类似,也是基于动态数组实现,但是线程安全的,使用了synchronized关键字进行同步。
- Set
- HashSet:基于哈希表实现,内部使用HashMap来存储元素,无序存储,查找效率高,不允许重复元素。
- LinkedHashSet:基于哈希表和链表实现,内部使用LinkedHashMap来存储元素,按照插入顺序进行迭代。
- TreeSet:基于红黑树实现,内部使用TreeMap来存储元素,有序存储,查找效率较高。
- Map
- HashMap:基于拉链法的哈希表实现,使用数组+链表/红黑树的结构存储键值对,查找效率高,无序存储。
- LinkedHashMap:基于哈希表和双向链表实现,内部使用双向链表维护插入顺序或者访问顺序。
- TreeMap:基于红黑树实现,内部使用红黑树来存储键值对,有序存储。
3.3 线程安全的集合有哪些?hashmap如果线程不安排如何解决?
- Vector:Vector是一个古老的线程安全的动态数组实现,它的方法都是同步的,所以可以确保多线程环境下的安全访问。
- HashTable:HashTable也是一个古老的线程安全的哈希表实现,它的方法也都是同步的,可以确保多线程环境下的安全访问。
- ConcurrentHashMap:ConcurrentHashMap是Java 5引入的线程安全的哈希表实现,它使用了锁分段技术来提高并发访问性能,相比于HashTable,在多线程访问时有更好的性能表现。
- CopyOnWriteArrayList和CopyOnWriteArraySet:这两个集合类是通过写时复制技术实现的,当对集合进行修改时,会先复制一份新的集合,然后再进行修改,这样就可以避免并发修改异常。
对于HashMap而言,如果在多线程环境下使用,可以考虑使用ConcurrentHashMap来替代,因为它提供了更好的并发性能。另外,也可以通过Collections.synchronizedMap来将HashMap包装成线程安全的Map
3.4 集合的扩容问题?list怎么扩容 hashmap的扩容机制?
List 的扩容
在 Java 中,ArrayList 是最常用的 List 实现之一。当向 ArrayList 中添加元素时,如果当前元素个数已经达到了内部数组的容量上限,就会触发扩容操作。ArrayList 会创建一个新的更大的数组,并将所有元素从旧数组复制到新数组中。默认情况下,新数组的大小是原数组容量的 1.5 倍(可以通过 ensureCapacity 方法或者在构造函数中设置初始容量来进行调整)。
HashMap 的扩容
HashMap 使用了链表和红黑树(JDK 8+)来解决哈希冲突,同时也使用了扩容来保证性能。当 HashMap 中的元素个数超过负载因子(默认为 0.75 )与容量的乘积时,HashMap 会进行扩容操作。扩容操作包括创建一个新的数组,将所有的元素重新计算哈希值后放入新的数组中。默认情况下,新数组的大小是原数组容量的两倍。
4 网络协议
4.1 TCP的三次握手和四次挥手
- 三次握手(Three-Way Handshake)
- 第一步:客户端发送同步序列号(SYN)
客户端发送一个带有SYN标志的数据包,指明自己的初始序列号(Sequence Number),然后进入SYN_SENT状态,等待服务器确认。 - 第二步:服务器确认(ACK+SYN)
服务器收到客户端的SYN请求后,会发送一个带有SYN和ACK标志的数据包作为应答,同时也会选择自己的初始序列号,然后进入SYN_RCVD状态。 - 第三步:客户端确认(ACK)
最后,客户端再发送一个带有ACK标志的数据包,表示握手过程完成。服务器收到这个数据包后,也进入ESTABLISHED状态,此时双方都已完成连接的建立。
- 第一步:客户端发送同步序列号(SYN)
- 四次挥手(Four-Way Handshake)
- 第一步:客户端发送断开连接请求(FIN)
客户端发送一个带有FIN标志的数据包,表示数据发送完成,准备断开连接,然后进入FIN_WAIT_1状态。 - 第二步:服务器确认收到请求(ACK)
服务器收到客户端的断开连接请求后,发送一个带有ACK标志的数据包作为确认,表示已经接受到断开请求,但自己还有数据需要发送,然后进入CLOSE_WAIT状态。 - 第三步:服务器发送断开连接请求(FIN)
当服务器的数据发送完成后,会发送一个带有FIN标志的数据包,表示自己也准备断开连接,然后进入LAST_ACK状态。 - 第四步:客户端确认收到请求(ACK)
最后,客户端收到服务器的断开连接请求后,发送一个带有ACK标志的数据包作为确认,然后进入TIME_WAIT状态,等待一段时间后才会进入CLOSED状态。服务器在收到这个确认后,就进入CLOSED状态,连接彻底关闭。
- 第一步:客户端发送断开连接请求(FIN)
4.2 TCP和UDP区别,说说协议的作用和区别
TCP 和 UDP 的作用
- TCP:TCP是一种面向连接的、可靠的、基于字节流的协议。它提供了数据完整性校验、数据重传、数据顺序保证等机制,适用于对数据传输可靠性要求较高的场景,如文件传输、电子邮件发送等。
- UDP:UDP是一种无连接的、不可靠的、面向数据报的协议。它不提供数据重传、数据顺序保证等机制,适用于对实时性要求较高、但对数据丢失或顺序混乱要求较低的场景,如音频/视频流传输、在线游戏等。
TCP 和 UDP 的区别
- 连接性
- TCP是面向连接的,建立连接需要经过三次握手,断开连接需要经过四次挥手。
- UDP是无连接的,发送数据时不需要建立连接,也没有断开连接的过程。
- 可靠性
- TCP提供数据的可靠传输,通过序列号、确认应答、重传机制等确保数据的完整性和可靠性。
- UDP不保证数据的可靠传输,数据报可能丢失或者顺序混乱,不具备可靠性。
- 数据包
- TCP是基于字节流的,发送的数据会被拆分成TCP数据包进行传输。
- UDP是基于数据报的,每个UDP数据包都是独立的,不会像TCP那样被合并或拆分。
- 应用场景
- TCP适用于对数据传输可靠性要求较高的场景,如文件传输、网页访问等。
- UDP适用于对实时性要求较高、但对数据可靠性要求较低的场景,如音频/视频流传输、在线游戏等。
5 前端
5.1 Ajax同步和异步的区别
-
同步(Synchronous): 在同步模式下,客户端发起一个请求后,必须等待服务器返回响应后才能继续执行后续操作。这意味着当发出请求时,客户端处于阻塞状态,直到收到服务器的响应后才能解除阻塞。
-
异步(Asynchronous): 在异步模式下,客户端发起一个请求后,不必等待服务器的响应,而是可以继续执行后续操作。当服务器返回响应时,客户端会通过回调函数或事件处理程序来处理响应数据,从而实现页面内容的更新或其他操作。
同步和异步的区别在于是否需要等待服务器的响应。在同步模式下,客户端需要等待服务器响应;而在异步模式下,客户端可以继续执行其他操作,待服务器响应后再处理数据。
5.2 常用的 HTTP 请求方法
- GET:从服务器获取资源,不应该对服务器上的资源产生任何副作用。通常用于数据检索,不应该用于提交敏感信息。
- POST:向服务器提交数据,可能会对服务器上的资源产生影响。通常用于表单提交、上传文件等需要向服务器发送数据的操作。
- PUT:用于向指定资源位置上传新的内容,也可以用于创建新资源。通常用于更新资源或创建新资源。
- DELETE:请求服务器删除指定的资源。
- PATCH:对资源进行部分修改。常用于更新资源的部分属性。
- HEAD:与 GET 请求类似,但服务器不会返回实际内容,只返回响应头信息。通常用于获取资源的元数据,如大小、类型等。
- OPTIONS:用于获取目标资源支持的通信选项。客户端可以利用这个方法了解服务器支持哪些方法或者对资源支持的方法进行探测。
5.3 get和post区别
- 作用
- GET 请求用于向指定的资源请求数据,请求参数会附在URL之后,以?分隔,多个参数用&连接
- 比如搜索、查看文章等
- POST 请求用于向指定的资源提交要被处理的数据,请求参数通过请求体传输,不会暴露在URL中。
- 比如表单提交、上传文件等
- GET 请求用于向指定的资源请求数据,请求参数会附在URL之后,以?分隔,多个参数用&连接
- 安全性
- GET请求的参数会暴露在URL中,不适合传输敏感信息,因此安全性较低。
- POST请求的参数不会暴露在URL中,适合传输敏感信息,安全性较高。
- 缓存
- GET请求可被浏览器缓存,适合获取不经常改变的数据。
- POST请求不会被浏览器缓存,适合提交可能引起状态改变的数据。
- 传输内容
- 由于URL长度限制,GET请求传输的数据量有限制。
- POST请求传输的数据量一般没有限制。
GET请求用于获取数据,安全性较低;而POST请求用于提交数据,安全性较高。
5.4 jQuery的选择器有哪些
- 基本选择器
- $(“element”):选取指定元素名称的所有元素。
- $(“.class”):选取指定class名称的所有元素。
- $(“#id”):选取指定id名称的元素。
- 层次选择器
- $(“parent > child”):选取指定父元素下的直接子元素。
- $(“ancestor descendant”):选取指定祖先元素下的所有后代元素。
- 过滤选择器
- $(“selector:first”):选取匹配的第一个元素。
- $(“selector:last”):选取匹配的最后一个元素。
- $(“selector:even”):选取偶数位置的元素。
- $(“selector:odd”):选取奇数位置的元素。
- $(“selector:eq(index)”):选取指定索引位置的元素。
- 属性选择器
- $(“element[attribute]”):选取具有指定属性的元素。
- $(“element[attribute=value]”):选取具有指定属性和值的元素。
- $(“element[attribute!=value]”):选取不具有指定属性值的元素。
- 表单选择器
- :input:选取所有input、textarea、select和button元素。
- :text:选取所有type="text"的input元素。
- :checkbox:选取所有type="checkbox"的input元素。
- :checked:选取所有被选中的元素。
- 内容过滤选择器
- :contains(text):选取包含指定文本的元素。
- :empty:选取没有子元素的空元素。
- :has(selector):选取含有指定选择器所匹配的元素的元素。
5.5 前后端跨域如何实现
方案一 vue proxy配置代理跨域
在Vue项目中,可以通过在vue.config.js中配置proxy来实现跨域请求。
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 要代理到哪个域名下
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '', // 将请求路径中的/api替换为空
},
},
},
},
};
方案二 添加映射路径和具体的CORS配置路径
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//放行哪些请求方式
config.addAllowedMethod("*");
//放行哪些原始请求头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
方案三 重写WebMvcConfigurer中addCorsMappings方法(全局跨域)
@Bean
public WebMvcConfigurer MyWebMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//放行哪些原始域
.allowedOrigins("*")
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
};
方案四 添加过滤器
@Component
public class CrosFiter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
方案五 添加注解
@CrossOrigin
//@CrossOrigin(value = "http://localhost:8001") //单独作用在url上
@GetMapping("/Axios")
public String axios(){
return "hello,world";
}
6 数据库
6.1 事务的四大特性及应用
特性
- 原子性(Atomicity):事务是一个原子操作单元,要么全部执行成功,要么全部失败回滚。如果事务中的某个操作失败,整个事务将回滚到初始状态,不对数据库造成任何影响。
- 一致性(Consistency):事务在执行前后,数据库的状态必须保持一致。这意味着事务在执行过程中,不能破坏数据库预设的完整性约束和业务规则。
- 隔离性(Isolation):并发执行的事务之间应该相互隔离,并且互不干扰。一个事务在提交之前,对其他事务是不可见的,保证了事务之间的数据独立性。
- 持久性(Durability):一旦事务提交,其所做的修改将永久保存在数据库中,即使发生系统故障也不会丢失。持久性通过将事务日志写入磁盘或其他持久存储介质来实现。
四大特性保证了数据库操作的可靠性和可恢复性。
应用领域
- 银行系统:银行系统中的转账操作需要保证原子性,一旦发生故障或中断,必须回滚到转账前的状态,避免出现资金不一致的情况。
- 在线购物:在线购物系统中,用户下单、付款、库存扣减等操作应该作为一个事务,保证数据的一致性和可靠性。
- 航空订票:航空订票系统中,用户预订机票、支付费用、生成机票订单等操作需要保证事务的隔离性和持久性,以避免出现重复预订或订单丢失的问题。
- 电子邮件系统:电子邮件系统中的发送邮件操作需要保证原子性,一旦发送失败,邮件不会被丢失,并且可以进行重试或回退。
6.2 事务的隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | ✔ | ✔ | ✔ |
READ-COMMITTED | × | ✔ | ✔ |
REPEATABLE-READ | × | × | ✔ |
SERIALIZABLE | × | × | × |
6.3 数据库的优化经验
- 使用连接池
- 在连接数据库时使用了Druid数据库连接池来合理配置连接池参数,避免频繁地创建和销毁数据库连接,提高数据库连接的复用率和性能。
6.4 索引的使用原则
- 选择合适的字段进行索引:通常情况下,对于经常用于查询、连接、排序和分组的字段,可以考虑创建索引。同时,需要注意不要过度索引,因为每个索引都会占用额外的存储空间,并且在插入、更新和删除数据时也会增加额外的开销。
- 为查询条件经常出现的列创建索引:如果某个字段经常出现在查询的 WHERE 子句中,那么为该字段创建索引可以大大提高查询的效率。
- 结合查询的性质创建联合索引:对于经常以多个条件进行查询的语句,可以考虑创建联合索引,以覆盖多个查询条件。
- 注意索引的顺序:在创建联合索引时,需要考虑字段的顺序,应该将区分度高的字段放在前面。这样可以更好地利用索引来减小数据文件的扫描范围。
- 避免在索引列上使用函数或表达式:当在索引列上使用函数或表达式时,数据库可能无法使用该索引,导致查询性能下降。
- 定期维护索引:随着数据库的使用,索引会产生碎片并且随着数据的更新而失效。定期对索引进行重建或者重新组织是非常重要的,以保持索引的高效性。
- 了解查询优化器的行为:不同的数据库管理系统对于索引的使用有不同的优化策略,了解数据库的查询优化器如何使用索引是非常重要的。
6.5 索引的类型
- 存储形式
- 聚簇索引
- 非聚簇索引
- 数据约束
- 主键索引
- 唯⼀索引
- ⾮唯⼀索引
- 索引列的数量
- 单列索引
- 组合索引
- innoDB可以创建的索引
- 主键索引
- 唯⼀索引
- 普通索引
6.6 索引在什么情况下会失效
- 数据量较小:当数据库表中的数据量非常小的时候,使用索引可能不会带来性能提升,甚至可能会导致性能下降。
- 列数据重复度高:如果索引列的数据重复度非常高,例如性别列只有两种取值,那么使用索引的效果可能不明显。
- 对索引列进行计算或者函数操作:当在查询条件中对索引列进行计算或者函数操作时,数据库可能无法使用索引。
- 数据分布不均匀:如果索引列的数据分布不均匀,导致索引失效的可能性会增加。
- 更新频繁:如果表的某些列上有频繁的更新操作,那么索引可能会失效,因为更新操作需要维护索引结构。
- 不符合最左前缀原则:如果使用了联合索引,并且查询条件不符合最左前缀原则,那么索引可能会失效。
6.7 存储过程的应用场景
- 复杂的业务逻辑:当数据库操作涉及到复杂的业务逻辑时,可以使用存储过程将这些逻辑封装在一起。例如,进行订单处理、库存管理、报表生成等复杂的数据处理操作。
- 提高性能:对于需要频繁执行的数据库操作,可以使用存储过程来提高性能。通过在数据库服务器端执行存储过程,可以减少数据传输和减轻网络负担,从而提高查询性能。
- 安全性要求高的应用:存储过程可以作为数据访问的接口,通过存储过程来访问数据库可以降低直接SQL注入等安全风险。
- 简化应用端逻辑:通过存储过程,可以将复杂的逻辑转移到数据库层,从而简化了应用端的逻辑,提高了应用的可维护性和可扩展性。
- 数据访问权限控制:存储过程可以用于控制用户对数据库的访问权限,限制用户只能通过存储过程进行特定的操作,增加了数据访问的安全性。
- 减少网络开销:存储过程可以减少客户端与服务器之间的通信开销,尤其是在需要频繁调用相同逻辑的场景下可以显著减少网络负担。
6.8 查重复语句和查空语句
查重复语句
重复语句是指代码中多次出现的相同的语句。重复语句可能导致代码冗余,降低代码的可读性和可维护性。检查重复语句可以通过代码重构来消除重复,使代码更加简洁和易于理解。
使用 GROUP BY 和 HAVING 子句进行分组统计
SELECT column, COUNT(*)
FROM table
GROUP BY column
HAVING COUNT(*) > 1;
查空语句
查找表中某一列包含空值(NULL)的记录的 SQL 查询语句。可以使用 WHERE 子句和 IS NULL 条件来筛选出某一列为空值的记录,也可以使用 COALESCE 函数将空值替换为其他数值进行查询。
SELECT *
FROM table
WHERE column IS NULL;
6.9 一张表里面有10万数据,查出姓名是重复的数据如何写sql
可以在姓名上建立索引
CREATE INDEX idx_name ON employee (name);
使用SQL语句中的GROUP BY和HAVING子句来实现
sql
SELECT name, COUNT(*) AS count
FROM employee
GROUP BY name
HAVING COUNT(*) > 1;
也可以采用分批次处理的方式,比如每次查询部分数据,然后合并结果,以减少单次查询的数据量
使用游标(cursor)来实现
6.10 去重语句都有哪些
使用 DISTINCT 关键字
使用 SELECT DISTINCT 语句可以对结果集进行去重,确保查询结果中的行是唯一的。
SELECT
DISTINCT column
FROM table;
使用 GROUP BY
通过将结果集分组并使用聚合函数(如 COUNT、SUM 等),可以去除重复的行。
SELECT
column, COUNT(*)
FROM table
GROUP BY column;
使用窗口函数
如果数据库支持窗口函数(如 ROW_NUMBER()),可以使用它来对结果集进行编号,然后选择编号为 1 的行,达到去重的效果。
SELECT
column, ROW_NUMBER() OVER (PARTITION BY column ORDER BY some_column) AS rn
FROM table
WHERE rn = 1;
使用子查询
通过使用子查询和 NOT IN 或者 NOT EXISTS 来过滤掉重复的行。
SELECT column
FROM table t1
WHERE column
NOT IN (
SELECT column
FROM table t2
WHERE t1.id <> t2.id);
6.11 delete和distinct有什么区别
- DELETE
- DELETE 是用于从表中删除数据的 SQL 操作。它允许你根据指定的条件删除表中的行。
- DELETE 操作是永久性的,它会从表中永久删除符合条件的行,而不是简单地将其标记为删除。
- DISTINCT
- DISTINCT 是用于查询结果去重的关键字。它用于返回结果集中唯一的行。
- DISTINCT 关键字会基于指定的列或表达式对结果集进行去重,并只返回唯一的行。它可以确保结果集中不会包含重复的值。
6.12 表关系有几种
- 一对一关系(One-to-One Relationship)
- 一对一关系表示两个表之间的关系是一对一的。这意味着一个表中的每条记录只能关联到另一个表中的一条记录,反之亦然。在数据库设计中,一对一关系通常用于将可选的或者可变的属性从主表中分离出来,形成一个独立的表。例如,一个人员信息表和一个健康信息表可能会形成一对一关系。
- 一对多关系(One-to-Many Relationship)
- 一对多关系表示一个表中的记录可以关联到另一个表中的多条记录。这是最常见的表关系之一。例如,一个部门表中的部门可以包含多个员工,而每个员工只属于一个部门。
- 多对多关系(Many-to-Many Relationship)
- 多对多关系表示两个表之间的关系是多对多的。这意味着一个表中的记录可以关联到另一个表中的多条记录,并且反之亦然。为了实现多对多关系,通常需要使用一个中间表(关联表或连接表),该中间表存储了两个表之间的对应关系。例如,学生和课程之间的关系就是典型的多对多关系,一个学生可以选择多门课程,而一门课程也可以被多个学生选择。
- 自引用关系(Self-Referencing Relationship)
- 自引用关系表示表中的记录与同一表中的其他记录存在关联。例如,在组织结构中,一个员工可能报告给另一个员工,这种关系就是自引用关系。在数据库设计中,自引用关系通常通过一个外键来实现,该外键指向同一表中的另一条记录。
6.13 左外关联和右外关联有什么区别
- 左外连接(Left Outer Join)
- 左外连接是以左边的表为基础,将左表中的所有记录和右表中匹配的记录连接起来。如果右表中没有匹配的记录,那么会返回NULL值。
- 左外连接的结果集包含了左表中的所有记录,以及与左表中记录匹配的右表中的记录。
SELECT *
FROM 左表
LEFT JOIN 右表
ON 左表.关联字段 = 右表.关联字段;
- 右外连接(Right Outer Join)
- 右外连接是以右边的表为基础,将右表中的所有记录和左表中匹配的记录连接起来。如果左表中没有匹配的记录,那么会返回NULL值。
- 右外连接的结果集包含了右表中的所有记录,以及与右表中记录匹配的左表中的记录。
SELECT *
FROM 左表
RIGHT JOIN 右表
ON 左表.关联字段 = 右表.关联字段;
7 框架
7.1 Spring中的注解有哪些
注解 | 介绍 |
---|---|
@Component | 用于标识一个普通的Java类为Spring的组件,可以被自动扫描并装配到Spring容器中。 |
@Controller | 用于标识一个类为Spring MVC的控制器,处理请求并返回响应。 |
@Service | 用于标识一个类为服务层组件,通常用于定义业务逻辑。 |
@Repository | 用于标识一个类为数据访问层(DAO)组件,用于操作数据库或其他持久化存储。 |
@Autowired | 用于自动装配依赖,通过类型匹配,在Spring容器中查找匹配的Bean,并将其注入到目标对象中。 |
@Qualifier | 与@Autowired一起使用,用于指定具体的Bean名称进行装配。 |
@Value | 用于注入配置文件中的值或者表达式到目标对象中。 |
@RequestMapping | 用于映射请求路径和处理方法,通常在控制器类或方法上使用。 |
@PathVariable | 用于获取URL路径中的变量值。 |
@RequestParam | 用于获取请求参数的值。 |
@ResponseBody | 用于将方法返回的对象作为响应内容返回给客户端。 |
@Configuration | 用于标识一个类为Spring的配置类,替代传统的XML配置文件。 |
@Bean | 用于将方法返回的对象注册为Spring的Bean。 |
@Aspect | 用于定义切面,结合其他注解如@Before、@After等来实现面向切面编程。 |
7.2 对IOC怎么理解
IOC(Inversion of Control,控制反转)将对象的创建、管理和依赖注入的控制权从应用程序代码中转移到外部容器中。
- 在传统的编程模式中,对象之间的依赖关系由开发者手动创建和维护
- 在IOC中,对象的依赖关系由容器负责创建和注入。
IOC核心概念:
- Bean:在IOC容器中被管理的对象称为Bean,它们通常是应用程序的核心组件。
- 容器(Container):容器是IOC的核心,负责创建、管理和注入Bean。常见的IOC容器有Spring容器、CDI容器等。
- 依赖注入(Dependency Injection):依赖注入是IOC的主要实现方式之一,它通过将依赖关系注入到目标对象中,解耦了对象之间的依赖关系。常见的注入方式有构造函数注入、属性注入和方法注入。
- 配置(Configuration):在IOC中,配置描述了Bean之间的关系以及如何创建和管理它们。配置可以使用XML、注解或者Java Config等方式进行定义。
7.3 对AOP怎么理解
AOP(Aspect-Oriented Programming,面向切面编程)将跨越多个不同模块的功能进行分离,并将这些功能横切的关注点(cross-cutting concerns)模块化。
- 在传统的面向对象编程中,某个功能通常会分散在整个应用程序的多个模块中,例如日志记录、性能监控、事务管理等,这样就会导致这些功能的重复出现,难以维护和修改。
- AOP将那些与业务逻辑无关但却分散在各处的横切关注点抽取出来,将其模块化,并在需要的时候将其应用到特定的连接点上,从而提高了代码的重用性、可维护性和可扩展性。
AOP术语:
- 切面(Aspect):切面是一个跨越多个类的模块化单元,它定义了横切关注点和相关的行为。切面通过配置或注解的方式与目标对象进行织入,从而将切面的行为应用到目标对象的方法上。
- 连接点(Join Point):连接点是程序执行过程中可以插入切面的点。在Spring AOP中,连接点通常表示目标对象的方法调用。例如,目标对象的方法执行前、执行后或抛出异常时都可以作为连接点。
- 切点(Pointcut):切点是通过表达式或规则来定义一组连接点的集合。切点指定了在何处应用切面的行为。例如,我们可以使用切点表达式来匹配所有以"get"开头的方法。
- 通知(Advice):通知是切面在连接点处执行的代码。在Spring AOP中,有以下几种类型的通知:
- 前置通知(Before Advice):在目标方法执行前执行的通知。
- 后置通知(After Advice):在目标方法执行后执行的通知,无论方法是否抛出异常。
- 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。
- 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。
- 环绕通知(Around Advice):在目标方法执行前后都可以执行的通知,可以控制目标方法的执行。
7.4 spring怎么实现事务管理,日志管理
事务管理
- 基于注解的事务管理
Spring 提供了 @Transactional 注解,通过在方法上添加该注解,可以将该方法标记为需要进行事务管理的方法。在配置文件中需要启用注解驱动的事务管理器。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void updateUser(User user) {
// 更新用户信息的业务逻辑
}
}
- 基于 XML 配置的事务管理
通过在 Spring 的配置文件中配置事务管理器、事务通知器和切面,可以实现基于 XML 的声明式事务管理。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.example.*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceMethods" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
日志管理
- 集成 Log4j
通过在项目中引入 Log4j 相关的依赖,并配置 Log4j 的配置文件,然后在 Spring 的配置文件中配置 Log4j 的相关内容,就可以实现对 Spring 应用的日志管理。
<bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer"/>
<property name="targetMethod" value="initLogging"/>
<property name="arguments">
<list>
<value>classpath:log4j.properties</value>
</list>
</property>
</bean>
- 集成 Logback
类似地,可以通过引入 Logback 相关的依赖,并配置 Logback 的配置文件,然后在 Spring 的配置文件中配置 Logback 的相关内容来实现对 Spring 应用的日志管理。
7.5 Spring 事务实现方式有哪些
- 编程式事务管理
通过编写代码显式地控制事务的开启、提交、回滚和关闭。使用 Spring 提供的 TransactionTemplate 来进行事务管理,可以在代码中灵活地控制事务的行为。 - 基于注解的声明式事务管理
使用 @Transactional 注解来标记需要进行事务管理的方法或类,通过在配置文件中启用注解驱动的事务管理器来实现声明式事务管理。 - 基于 XML 配置的声明式事务管理
在 Spring 的配置文件中通过 AOP 配置来定义事务通知器和切面,以声明式的方式配置事务管理,可以更好地将事务管理与业务逻辑分离。 - 注解驱动的事务管理
使用 Spring 提供的 @EnableTransactionManagement 注解来启用注解驱动的事务管理功能,这样就可以在项目中直接使用 @Transactional 注解来实现事务管理,而不需要额外的 XML 配置。 - JTA 事务管理
如果应用程序需要在多个数据源或跨多个系统进行分布式事务管理,可以使用 Java 事务 API(JTA)来实现全局事务管理,Spring 对 JTA 提供了良好的支持。
7.6 Spring中的事务传播属性有哪些
- REQUIRED:如果当前存在事务,则加入该事务;否则创建一个新事务。这是默认的传播行为。
- SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式执行。
- MANDATORY:必须在一个已有的事务中执行;否则抛出异常。
- REQUIRES_NEW:创建一个新的事务,并在它自己的事务内执行。如果当前存在事务,则挂起该事务。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行;否则创建一个新事务。嵌套事务是独立于外部事务的,它有自己的提交和回滚操作。如果外部事务回滚,则嵌套事务也会回滚。但是,如果嵌套事务回滚,则只会回滚嵌套事务本身,而不会影响到外部事务。
7.7 Spring 用到了哪些设计模式
- 依赖注入(Dependency Injection)
Spring 通过依赖注入(DI)来管理对象之间的依赖关系,实现解耦和可测试性。它使用了控制反转(IoC)原则,主要采用了构造函数注入、Setter 方法注入和接口注入等方式。 - 工厂模式(Factory Pattern)
Spring 使用工厂模式来创建和管理对象,比如 BeanFactory 和 ApplicationContext。它们隐藏了对象的具体创建逻辑,通过配置文件或注解来声明和实例化对象,提供了更灵活的对象管理方式。 - 代理模式(Proxy Pattern)
Spring AOP(面向切面编程)基于代理模式,通过动态代理技术在运行时生成代理类,并将切面逻辑织入到目标方法中。这样可以实现横切关注点的模块化和重用。 - 模板模式(Template Pattern)
Spring 提供了各种模板类,如 JdbcTemplate、HibernateTemplate 等,封装了底层的复杂操作,简化了开发者的操作流程,提高了开发效率。 - 观察者模式(Observer Pattern)
Spring 的事件驱动机制基于观察者模式,通过 ApplicationContext 发布事件,感兴趣的监听器会接收到相应的事件通知并进行处理。 - 单例模式(Singleton Pattern)
Spring 的默认作用域是单例模式,即每个 Bean 在容器中只有一个实例。这样可以避免多次创建和销毁对象,提高了性能和资源利用率。 - 适配器模式(Adapter Pattern)
Spring MVC 中的处理器适配器就是使用适配器模式来实现的,它将不同类型的处理器适配为统一的处理器接口,以便能够统一处理请求。
7.8 Spring自动装配的方式有哪些
- 按名称自动装配(byName)
Spring 容器会自动识别和装配与目标 bean 名称相匹配的依赖。在 XML 配置中使用 元素的 name 属性来指定自动装配的名称。 - 按类型自动装配(byType)
Spring 容器会自动识别和装配与目标 bean 类型相匹配的依赖。在 XML 配置中使用 元素的 type 属性来指定自动装配的类型。 - 构造函数自动装配(constructor)
Spring 容器会根据构造函数的参数类型自动装配相应的依赖。在 XML 配置中使用 元素进行构造函数自动装配。 - 自动装配模式不使用(no)
在 XML 配置中通过将 autowire 属性设置为 “no” 来禁用自动装配,需要显式通过 或 元素进行手动装配。 - 按注解自动装配
使用 @Autowired 注解或者 JSR-330 的 @Inject 注解可以在字段、构造函数或方法上进行自动装配,Spring 会自动识别并装配相应的依赖。 - 基于 Java 配置的自动装配
使用 Java 配置类并结合 @Autowired、@Bean 等注解,可以实现基于 Java 配置的自动装配。
7.9 @Autowired和@Resource的区别
- 来源
- @Autowired 是 Spring 框架提供的注解,用于自动装配 Spring 容器中的 bean。
- @Resource 是 Java EE 5 提供的注解,由 J2EE 提供,不仅可以用于 Spring 容器中的 bean,还可以用于其他容器或环境中。
- 装配策略
- @Autowired 默认按照类型进行自动装配,如果存在多个类型相同的 bean,可以结合 @Qualifier 注解指定具体的 bean id 进行装配。
- @Resource 默认按照名称进行自动装配,可以通过 name 属性指定要装配的 bean 的名称,也可以通过 type 属性指定要装配的 bean 的类型。
- 可选性
- @Autowired 是非必需的,即被注解的字段或方法可以为 null,如果没有找到匹配的 bean,那么该字段或方法会被忽略。
- @Resource 是强制性的,如果找不到匹配的 bean,则会抛出异常。
- 支持的范围
- @Autowired 可以用在字段、构造函数、Setter 方法以及其他方法上,实现灵活的自动装配方式。
- @Resource 主要用在字段上,也可以用在构造函数或方法上,但通常用于字段的注入。
7.10 Spring怎么解决循环依赖的问题
Spring 使用了三级缓存和前期引用解决循环依赖的问题。当发现循环依赖时,Spring 会进行以下处理:
- 创建对象并放入 “singletonFactories” 缓存
- 当 Spring 创建一个 bean 时,会将正在创建的 bean 对象提前暴露给当前线程。
- 如果发现循环依赖,Spring 会将正在创建的 bean 对象临时存储在 “singletonFactories” 缓存中,而不是等待整个对象创建完成。
- 将对象包装为 “ObjectFactory”
- Spring 将正在创建的 bean 包装在 ObjectFactory 中,以便能够获取到尚未完成的 bean 实例。
- 这样,当其他 bean 需要访问该循环依赖的 bean 时,Spring 会通过 ObjectFactory 提供一个尚未完全初始化的代理对象。
- 完成依赖注入和初始化
- Spring 完成循环依赖的一部分后,会将尚未完成的 bean 注入到其他相关的 bean 中。
- 然后,Spring 继续初始化 bean 的剩余部分,并将其从 “singletonFactories” 缓存中移除。
7.11 Spring 的单例 Bean 是否有线程安全问题
Spring 的单例 Bean 在多线程环境下是共享的,因此可能存在线程安全问题。当多个线程同时访问同一个单例 Bean 时,如果该 Bean 的状态会被修改,就有可能导致线程安全问题。
解决方法:
- 使用线程安全的对象
如果单例 Bean 内部的状态需要被修改,可以考虑使用线程安全的对象,如 ConcurrentHashMap、AtomicInteger 等,或者使用带有同步机制的对象。这样可以确保在多线程环境下对对象状态的修改是安全的。 - 加锁
可以在需要修改状态的关键代码段使用 synchronized 关键字或者 Lock 接口进行加锁,确保在同一时刻只有一个线程能够修改对象的状态,从而避免竞态条件和数据不一致性的问题。 - 避免可变状态
尽量设计无状态的单例 Bean,或者尽量避免在单例 Bean 中修改可变状态。这样可以减少线程安全问题的出现,并且符合“单一职责原则”。 - 使用局部变量
在方法内部使用局部变量而不是共享状态,这样可以避免多个线程之间竞争共享状态的问题。 - 使用线程封闭
将单例 Bean 的状态封闭在单个线程内部,通过线程封闭的方式来避免多线程并发访问造成的问题。
7.12 mybatis里面#{}和${}的区别
见题目1.10
7.13 在mapper中如何传递多个参数
- 使用 @Param 注解
在 Mapper 方法的参数列表中使用 @Param 注解为每个参数指定名称,在 XML 映射文件中引用这些参数名称。
public interface UserMapper {
User selectUserByIdAndName(@Param("id") Long id, @Param("name") String name);
}
- 使用 Map 传参
将多个参数封装到一个 Map 中,然后作为单个参数传递给 Mapper 方法。
public interface UserMapper {
User selectUserByMap(Map<String, Object> map);
}
- 使用 POJO 对象
创建一个专门用于传递参数的 POJO 对象,并将多个参数封装到该对象中,然后作为单个参数传递给 Mapper 方法。
public class UserQuery {
private Long id;
private String name;
// 省略 getter 和 setter
}
public interface UserMapper {
User selectUserByQuery(UserQuery query);
}
7.14 Mybatis如何执行批量操作
在MyBatis中,执行批量操作可以通过使用SqlSession的批量处理方法来实现。
- 使用insert、update或delete的批量方法
在SqlSession接口中,有insert、update和delete方法的批量版本,可以接受一个包含多个参数的集合来进行批量操作。
List<User> userList = new ArrayList<>();
// 填充userList...
// "com.example.UserMapper.insertBatch"是对应的Mapper中定义的批量插入方法
sqlSession.insert("com.example.UserMapper.insertBatch", userList);
- 使用批量操作的Executor方法
MyBatis的Executor接口中也提供了批量操作的方法,可以直接调用batch方法来执行批量操作。
List<User> userList = new ArrayList<>();
// 填充userList...
sqlSession.getConfiguration().getEnvironment().getTransactionFactory().newTransaction(sqlSession.getConnection()).getConnection().setAutoCommit(false);
BatchExecutor batchExecutor = (BatchExecutor) sqlSession.getExecutor();
try {
for (User user : userList) {
batchExecutor.doUpdate(mappedStatement, user);
}
batchExecutor.doFlushStatements(false);
sqlSession.getConfiguration().getEnvironment().getTransactionFactory().newTransaction(sqlSession.getConnection()).getConnection().commit();
} finally {
sqlSession.getConfiguration().getEnvironment().getTransactionFactory().newTransaction(sqlSession.getConnection()).getConnection().rollback();
}
7.15 如何获取自动生成的(主)键值
- 使用KeyGenerator配置主键生成策略
在MyBatis中,可以通过在Mapper XML文件中的插入语句中配置useGeneratedKeys和keyProperty属性来指定使用数据库的主键生成策略,并将生成的主键值设置到指定的属性中。
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
- 使用selectKey元素配置主键生成策略
使用selectKey元素来配置主键生成策略。在Mapper XML文件的插入语句中,可以添加一个selectKey元素来执行主键查询,并将查询结果设置到指定的属性中。
<insert id="insertUser" parameterType="User">
<selectKey resultType="Long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
- 使用SqlSession返回生成的主键值
如果没有配置主键生成策略或者不想在Mapper XML文件中指定主键属性,可以通过SqlSession返回生成的主键值。在执行插入操作后,使用getGeneratedKeys()方法来获取生成的主键值的ResultSet对象,并从中提取主键值。
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
sqlSession.insert("com.example.UserMapper.insertUser", user);
sqlSession.commit();
Long generatedKey = sqlSession.getGeneratedKeys().getLong(1);
user.setId(generatedKey);
}
7.16 Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql
动态SQL的主要用途:
- 条件判断:根据条件的成立与否来动态添加WHERE子句中的条件。
- 循环遍历:对集合类型的参数进行循环遍历,动态生成IN子句或其他需要循环处理的SQL片段。
- 插入或忽略字段:根据参数是否存在来动态插入或忽略某些字段。
- 拼接动态SQL片段:将多个动态SQL片段拼接在一起,形成完整的SQL语句。
MyBatis的动态SQL的执行原理:
- MyBatis的动态SQL是基于OGNL表达式(Object-Graph Navigation Language),它使用OGNL表达式来对条件进行判断和参数的处理,从而动态构建SQL语句。
- 当Mapper接口方法被调用时,MyBatis会解析Mapper XML文件中的动态SQL代码块,并根据参数值动态生成最终的SQL语句。
常见的动态SQL:
- if元素:通过元素来根据条件判断动态添加SQL片段。
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
- choose、when、otherwise元素:类似于Java中的switch语句,根据条件选择不同的分支来生成SQL片段。
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
- trim、set、where、foreach等元素:用于对SQL片段进行修剪、循环处理等操作,以便动态构建SQL语句。
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</set>
WHERE id = #{id}
</update>
7.17 Mybatis是如何进行分页的?分页插件的原理是什么
MyBatis 提供了一些分页插件,比如 MyBatis 分页插件 PageHelper。
分页插件的原理可以简单描述为:
- 拦截 SQL 查询,解析查询语句并获取分页相关的参数,比如当前页数、每页显示数量等。
- 根据分页参数,修改原始的 SQL 查询语句,添加对应的分页逻辑,比如 LIMIT 和 OFFSET 关键字。
- 执行修改后的带有分页逻辑的 SQL 查询语句,并返回结果。
控制层
//设置每页储存几条数据
private static final int PAGE_SIZE = 3;
@RequestMapping("list")
public String list(Integer page, HttpSession session, Items items){
Integer p = 1;
if(page != null){
p = page;
}else {
page = p;
}
//调用方法来获取分页后的数据
PageInfo pageInfo = itemsService.itemsPage(page, PAGE_SIZE, items);
session.setAttribute("pb",pageInfo);
session.setAttribute("items", items);
return "list";
}
业务逻辑实现类
public PageInfo itemsPage(Integer page, int pageSize, Items items) {
//使用分页插件PageHelper(告诉 PageHelper 应该对接下来的查询进行分页处理)
PageHelper.startPage(page, pageSize);
//查询列表
List<Items> itemsList = itemsMapper.selectConList(items);
PageInfo pageInfo = new PageInfo<>(itemsList);
return pageInfo;
}
7.18 Mybatis的一级、二级缓存
- 一级缓存
- 一级缓存是MyBatis中SqlSession对象的缓存。默认情况下,一级缓存是开启的,它保证了在同一个SqlSession中相同的查询SQL只会执行一次,并将结果存储在缓存中。
- 一级缓存的作用范围是SqlSession级别,也就是说在同一个SqlSession中进行的数据库操作可以共享同一个一级缓存。
- 一级缓存的生命周期较短,当SqlSession被关闭、提交或回滚时,一级缓存也会随之失效。
- 二级缓存
- 二级缓存是Mapper级别的缓存,多个SqlSession共享同一个Mapper的二级缓存。
- 开启二级缓存需要在Mapper文件中进行配置,并且对应的POJO需要实现序列化接口。
- <cache> 标签用于配置二级缓存的相关属性。常用的属性包括:
- eviction:指定缓存的回收策略,比如 FIFO(先进先出)、LRU(最近最少使用)等。
- flushInterval:缓存刷新间隔,单位为毫秒。
- size:缓存的最大条目数。
- readOnly:指定缓存是否为只读。
- <cache> 标签用于配置二级缓存的相关属性。常用的属性包括:
- 二级缓存的生命周期较长,它可以跨越多个SqlSession,当不同的SqlSession操作同一个Mapper的数据时,可以共享同一个二级缓存。
7.19 Spring MVC的工作原理
- 请求到达 DispatcherServlet:当客户端发送请求时,请求会首先到达 DispatcherServlet,它充当前端控制器的角色。
- HandlerMapping 寻找处理器:DispatcherServlet 会使用 HandlerMapping 来确定请求对应的处理器(Controller),HandlerMapping 会根据请求的 URL 映射到对应的 Controller 类和方法。
- 执行处理器逻辑:一旦确定了处理器(Controller),DispatcherServlet 就会将请求分派给该处理器,处理器执行具体的业务逻辑,并返回一个 ModelAndView 对象,其中包含了处理结果以及展示该结果的视图名称。
- 渲染视图:DispatcherServlet 会使用 ViewResolver 来解析 ModelAndView 中的视图名,找到对应的视图模板,然后渲染视图并将结果返回给客户端。
- 返回响应:最终,DispatcherServlet 将处理结果返回给客户端,完成整个请求-响应周期。
7.20 Spring MVC的常用注解由有哪些
注解 | 作用 |
---|---|
@Controller | 用于标识一个类是 Spring MVC 中的 Controller,处理客户端的请求。 |
@RequestMapping | 用于将请求映射到对应的处理方法,并可以用在类级别和方法级别上。 |
@PathVariable | 用于将 URL 模板变量绑定到方法参数上。 |
@RequestParam | 用于将请求参数绑定到方法参数上。 |
@ResponseBody | 用于将方法的返回值直接作为 HTTP 响应的内容返回给客户端,通常用于返回 JSON 或 XML 格式的数据。 |
@ModelAttribute | 用于将方法的返回值添加到模型中,在渲染视图时可以使用。 |
@SessionAttributes | 用于指定哪些模型属性需要存储到会话中,通常配合 @ModelAttribute 使用。 |
@InitBinder | 用于初始化数据绑定器,通常用于定制数据绑定规则。 |
@ExceptionHandler | 用于标识异常处理方法,当控制器中抛出指定类型的异常时,会调用对应的异常处理方法进行处理。 |
7.21 @RequestMapping 注解有什么用
@RequestMapping 注解是 Spring MVC 中的一个核心注解,它用于将请求映射到对应的处理方法。
- 处理请求映射:通过 @RequestMapping 注解,可以将一个请求映射到对应的处理方法上。可以用在类级别上,表示该类中的所有方法都处理特定的请求路径;也可以用在方法级别上,表示该方法处理特定的请求路径。
//当请求路径为 "/users/list" 时,会调用 userList() 方法进行处理。
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/list")
public String userList() {
// 处理 "/users/list" 请求
return "userList";
}
}
- 支持不同的 HTTP 方法:@RequestMapping 注解还可以指定处理特定 HTTP 方法的请求。通过设置 method 属性,可以限定只有匹配的 HTTP 方法才会触发处理方法。
//只有当请求路径为 "/users/create" 并且 HTTP 方法为 POST 时,才会调用 createUser() 方法进行处理。
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping(value = "/create", method = RequestMethod.POST)
public String createUser() {
// 处理 POST 请求
return "createSuccess";
}
}
- 支持请求参数绑定:@RequestMapping 注解可以与其他注解配合使用,实现请求参数的绑定。
//结合 @RequestParam 注解可以将请求参数绑定到方法的参数上
//当请求路径为 "/users/info" 并且包含名为 "id" 的请求参数时,会将该参数绑定到 userId 参数上。
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/info")
public String userInfo(@RequestParam("id") int userId) {
// 使用 id 参数进行处理
return "userInfo";
}
}
7.22 SpringMvc的Controller是不是单例模式
Spring MVC 中的 Controller 默认是单例模式。当使用 @Controller 注解标记一个类时,Spring 会自动将该类注册为一个 Bean,并且默认情况下使用单例模式管理这个 Bean。这意味着在整个应用程序的生命周期中,只会创建一个该 Controller 类的实例。
如果需要让 Controller 不使用单例模式,可以使用 Spring 的作用域注解(如 @Scope(“prototype”))来修改默认的单例配置,使其每次请求都创建一个新的实例。
由于 Controller 是单例模式,默认情况下所有请求都会共享同一个实例,这意味着多个请求可能同时访问和修改这些共享的成员变量,存在线程安全问题。
7.23 介绍下 Spring MVC 拦截器?SpringMvc怎么配置拦截器
Spring MVC 拦截器是一种可以拦截请求的组件,它可以在请求到达 Controller 之前或者之后执行一些操作,比如日志记录、权限检查、国际化处理等。拦截器可以用于对请求进行预处理和后处理,而不影响 Controller 的实际处理过程。
要配置 Spring MVC 拦截器步骤:
- 创建拦截器类:首先需要创建一个类,实现 Spring MVC 提供的 HandlerInterceptor 接口,该接口包括了三个方法:preHandle、postHandle 和 afterCompletion。根据需求重写这些方法,在请求处理前后执行相应的逻辑。
- 配置拦截器:在 Spring MVC 的配置文件(比如 XML 配置文件或者使用 Java Config)中配置拦截器。使用 mvc:interceptors 标签(XML 配置)或者实现 WebMvcConfigurer 接口(Java Config)来注册拦截器。
<!-- com.example.MyInterceptor 是自定义的拦截器类-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.example.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
// addPathPatterns("/**") 用于指定拦截的路径
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
- 编写拦截器逻辑:在自定义的拦截器类中编写具体的拦截逻辑,可以在 preHandle 方法中进行请求预处理,比如权限验证;在 postHandle 方法中进行请求后处理,比如添加公共的模型数据;在 afterCompletion 方法中进行请求结束后的处理,比如资源清理工作。
7.24 SpringBoot的核心注解是什么?由那些注解组成
SpringBoot的核心注解是**@SpringBootApplication**,它是由以下3个注解组成:
- @SpringBootConfiguration:它组合了Configuration注解,实现了配置文件的功能。
- @EnableAutoConfiguration:打开自动配置功能。
- @ComponentScan:Spring扫描组件。
7.25 SpringBoot自动配置原理是什么
-
- SpringApplication.run(Application.class, args); 执行流程中 this.refreshContext(context);
-
- this.refreshContext(context); 内部会解析一个携带 @EnableAutoConfiguration 实现自动装配
-
- @EnableAutoConfiguration 这个注解里面会有 一个 @Import 引入配置类 AutoConfigurationImportSelector
-
- AutoConfigurationImportSelector 这个类中 有个方法 loadFactoryNames作用是 读取 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- AutoConfigurationImportSelector 这个类中 有个方法 loadFactoryNames作用是 读取 META-INF/spring.factories
-
- spring.factories 文件中配置的是 自动装配类
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- spring.factories 文件中配置的是 自动装配类
-
- 源码分析[以mybatis为例]
- @Configuration
配置类 帮我们在spring容器中配置对象 - @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
内存中必须有 SqlSessionFactory 和 SqlSessionFactoryBean - @ConditionalOnBean({DataSource.class})
Spring容器中必须有 DataSource ,本配置才能生效 - @EnableConfigurationProperties({MybatisProperties.class})
加载这个配置类 - @AutoConfigureAfter({DataSourceAutoConfiguration.class})
配置需要在 DataSourceAutoConfiguration 创建后才去加载,加载时机比较晚
7.26 你如何理解SpringBoot配置加载顺序
- 命令行参数:首先,Spring Boot会检查命令行参数中是否包含配置属性,命令行参数具有最高优先级。可以通过–指定单个属性,例如:–server.port=8080。
- Java系统属性:如果没有在命令行参数中指定属性,Spring Boot会检查Java系统属性中是否存在对应的配置。可以通过-D指定系统属性,例如:-Dserver.port=8080。
- 操作系统环境变量:如果在Java系统属性中也没有找到配置属性,Spring Boot会尝试从操作系统环境变量中获取对应的配置。
- 配置文件:Spring Boot支持多种类型的配置文件,如application.properties和application.yml。这些配置文件可以放置在不同的位置,按照优先级从高到低依次为:
- 当前目录下的./config/文件夹
- 当前目录
- classpath下的/config/文件夹
- classpath根路径
- 如果有多个配置文件,它们的加载顺序为:
- application.properties或application.yml
- 按照spring.profile.active指定的配置文件,如application-dev.properties或application-dev.yml。
- 默认属性:Spring Boot内置了一些默认属性,它们具有最低优先级。可以在SpringApplication.setDefaultProperties()方法中设置默认属性。
7.27 SpringBoot、Spring MVC和Spring有什么区别
- Spring框架:Spring框架是一个全功能的企业应用程序开发框架,提供了大量的模块和功能,包括控制反转(IoC)、面向切面编程(AOP)、数据访问、事务管理、消息队列等。Spring框架提供了广泛的支持,可以用于构建各种类型的应用程序,从传统的基于服务器的应用程序到云原生的微服务架构。
- Spring MVC:Spring MVC是Spring框架中的一个模块,用于构建Web应用程序。它提供了MVC(Model-View-Controller)架构的支持,通过DispatcherServlet来接收和处理HTTP请求,并通过Controller、ViewResolver等组件来实现请求的处理和响应。Spring MVC通过注解和配置的方式,使得开发Web应用程序变得简单而灵活,可以方便地实现RESTful风格的API。
- Spring Boot:Spring Boot是Spring团队提供的快速开发框架,基于Spring框架,旨在简化Spring应用程序的创建和部署。Spring Boot通过约定大于配置的原则,提供了自动配置、快速启动、内嵌式容器等功能,使得开发者可以更专注于业务逻辑的实现,而不用花费过多精力在配置和搭建环境上。Spring Boot也提供了大量的starter依赖,简化了对各种第三方库和框架的集成。
区别:
- Spring框架是整个框架体系,提供了广泛的功能和模块;
- Spring MVC是Spring框架中用于构建Web应用程序的模块,专注于Web开发;
- Spring Boot是Spring团队提供的快速开发框架,基于Spring框架,旨在简化Spring应用程序的创建和部署。
7.28 SpringBoot中如何解决跨域问题
见题目5.5
7.29 SpringBoot项目如何热部署
- 使用开发工具支持:大多数 Java 开发工具(如 IntelliJ IDEA、Eclipse 等)都支持热部署功能。开发者只需要在工具中启用热部署,并进行相应的配置,就可以在保存代码或资源文件后,自动触发重新加载。
- 使用 Spring Boot DevTools:Spring Boot 提供了一个名为 DevTools 的模块,其中包含了对热部署的支持。通过在 pom.xml 文件中添加 devtools 依赖,启用 DevTools 模块后,修改代码和资源文件后,Spring Boot 会自动触发重新启动应用。此外,DevTools 还提供了其他功能,如自动配置的禁用、静态资源缓存的禁用等。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
- 使用 JRebel:JRebel 是一款商业化的 Java 热部署工具,它可以在开发过程中实现快速重新加载应用程序,而无需重启服务器。只需在 IDE 中安装 JRebel 插件,并进行相应的配置,即可实现热部署功能。
热部署前提条件:
- 确保项目使用了开发模式(dev mode),而不是生产模式(prod mode)。在生产模式下,Spring Boot 会进行优化,不支持热部署。
- 某些修改,如类的结构变更、添加新的实例变量等,可能需要重启才能生效。
7.30 如何使用SpringBoot实现异常处理
在 Spring Boot 中,可以通过使用@ControllerAdvice、@ExceptionHandler和自定义异常类来实现异常处理。
- 创建自定义异常类
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
- 创建全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An internal server error occurred");
}
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(CustomException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
- 在控制器中抛出自定义异常
@RestController
public class MyController {
@GetMapping("/hello")
public String hello() {
// 模拟抛出自定义异常
throw new CustomException("Something went wrong");
}
}
7.31 如何使用SpringBoot实现分页和排序
在 Spring Boot 中,可以使用 Spring Data JPA 和 Pageable 接口来实现分页和排序功能。
- 添加依赖: 在 pom.xml 文件中添加 Spring Data JPA 的依赖,如果已经使用了 Spring Boot Starter,则无需手动添加依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
- 创建数据实体类: 创建一个数据实体类,并使用注解标记实体类和字段与数据库表和列的映射关系。
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 省略其他字段、构造方法和 getter/setter 方法
}
- 创建数据访问接口: 创建一个继承自 JpaRepository 的数据访问接口,用于对 User 实体类进行数据库操作。
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
- 在控制器中使用分页和排序: 在控制器中使用 UserRepository 进行数据查询,并通过 Pageable 接口指定分页和排序的参数。
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/users")
public Page<User> getUsers(@PageableDefault(size = 10, sort = "name") Pageable pageable) {
return userRepository.findAll(pageable);
}
}
7.32 微服务中如何实现 session 共享
- 使用分布式缓存:例如,使用 Redis 或 Memcached 等分布式缓存。所有服务都通过该缓存来共享 session 数据。这种方式需要将 session 数据存储在缓存中,并且需要处理缓存的并发访问和数据一致性问题。
- 使用数据库:可以使用关系型数据库或非关系型数据库来存储 session 数据。每个服务都通过访问数据库来获取和存储 session 数据。这种方式需要处理数据库的并发访问和数据一致性问题。
- 使用消息队列:可以使用消息队列来实现 session 数据共享。每个服务都将 session 数据发送到消息队列,其他服务从消息队列中读取并处理这些数据。这种方式需要处理消息队列的并发访问和数据一致性问题。
- 使用服务注册与发现:可以使用服务注册与发现技术,如 Zookeeper 或 etcd 等。每个服务都向注册中心注册自己,并从注册中心获取其他服务的地址信息。然后,每个服务都直接与其他服务通信,共享 session 数据。这种方式需要处理服务注册与发现的并发访问和数据一致性问题。
- 使用单点登录与令牌(Token):对于一些简单的微服务场景,可以使用单点登录与令牌(Token)来实现 session 数据共享。所有服务都通过验证令牌来验证用户的身份,并获取用户的 session 数据。这种方式需要处理令牌的并发访问和数据一致性问题。
7.33 SpringBoot 中如何实现定时任务
在Spring Boot中实现定时任务可以使用@Scheduled注解和@EnableScheduling注解来实现。
- 在Spring Boot应用程序的启动类上添加@EnableScheduling注解,开启定时任务的支持。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling //开启定时任务的支持
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
- 创建一个包含定时任务方法的类,并在该方法上添加@Scheduled注解,指定定时任务的执行规则。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyTask {
@Scheduled(fixedRate = 5000) // 每隔5秒执行一次任务
public void myTaskMethod() {
// 定时任务的逻辑代码
}
}
@Scheduled注解支持多种属性来定义定时任务的执行规则:
- fixedRate:固定频率执行,单位为毫秒。
- fixedDelay:固定延迟执行,单位为毫秒,表示上一次任务执行完毕后延迟多少时间再执行下一次任务。
- cron:使用Cron表达式定义执行规则,更加灵活。
注意事项:
- 定时任务方法必须是无参的。
- 定时任务类必须使用@Component或其他Spring组件注解进行标记,以便被Spring Boot自动扫描和管理。
8 redis
8.1 redis的常见数据结构
String(字符串)
- 特点
存储单个值 - 使用场景
适用于存储单个值的场景,如缓存、计数器、分布式锁等。
Hash(哈希)
- 特点
键值对存储、无序。 - 使用场景
适用于存储对象或实体的属性信息,以及需要按照键进行快速查找的场景。常见的应用包括缓存对象、存储用户信息、存储配置信息等。
List(列表)
- 特点
有序、可重复。 - 使用场景
适用于需要保持元素顺序、允许重复元素的场景。常见的应用包括消息队列、任务队列、记录操作日志等。
Set(集合)
- 特点
无序、不重复。 - 使用场景
适用于需要存储唯一元素且不关心元素顺序的场景。常见的应用包括关注列表、粉丝列表、标签系统等。
ZSet(有序集合)
- 特点
有序、不重复,每个元素都关联一个分数(score)。 - 使用场景
适用于需要按照分数进行排序和范围查找的场景,如排行榜、计分系统等。
8.2 Redis支持哪几种数据类型
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
8.3 Redis应用场景
- 缓存:Redis最常见的用途是作为缓存层。由于Redis将数据存储在内存中,读取速度非常快,可以显著降低数据库的负载压力。通过将经常访问的数据缓存到Redis中,可以提高应用程序的响应速度和性能。
- 分布式锁:Redis提供了原子性操作和分布式锁的支持,可以实现分布式环境下的互斥访问控制。通过使用Redis的SETNX(set if not exists)命令,可以实现简单有效的分布式锁,避免多个进程或线程同时修改共享资源。
- 计数器和排行榜:Redis支持对数据进行原子性的增减操作,可以用于实现计数器功能。例如,可以用Redis来记录网站的访问次数、用户的点赞数等。此外,Redis还可以根据分数对数据进行排序,用于构建排行榜功能。
- 发布/订阅:Redis支持发布/订阅模式,可以实现消息的发布和订阅机制。通过使用PUBLISH命令发布消息,以及使用SUBSCRIBE和PSUBSCRIBE命令订阅消息,可以实现实时的消息传递和广播功能。
- 数据持久化:Redis提供了两种数据持久化方式,分别是快照(snapshotting)和AOF(Append-Only File)。快照方式可以定期将内存中的数据保存到磁盘上,以防止服务器故障时数据丢失。AOF方式则将每个写操作追加到文件中,以实现更高的数据持久性和灵活的恢复机制。
- 分布式缓存:Redis支持数据的分片和集群,可以构建分布式缓存系统。通过将数据分布在多个Redis节点上,可以扩展缓存容量和吞吐量,提高系统的可用性和性能。
8.4 redis如何保持数据一致是最新的
- 持久化: Redis支持将数据持久化到磁盘,以确保在重启或崩溃后能够恢复数据。Redis提供两种持久化方式:RDB(快照)和AOF(日志)。RDB会定时生成一个快照文件,将数据保存到磁盘上;AOF则将每个写操作追加到日志文件中,以记录数据的变更。通过选择适当的持久化方式,可以确保数据在断电或重启后的一致性。
- 主从复制: Redis支持主从复制机制,即将一个Redis实例(主节点)的数据复制到其他Redis实例(从节点)。主节点负责接收写操作并同步到所有从节点,从节点只负责读取数据。通过主从复制,可以实现数据的备份和故障恢复,并提高读取性能。
- 哨兵模式: Redis的哨兵模式用于监控和管理Redis实例的高可用性。哨兵会监测主节点的状态,并在主节点故障时自动将一个从节点切换为新的主节点。这样可以保证系统的可用性,并确保数据的一致性。
- 集群模式: Redis的集群模式通过将数据分布在多个节点上来提高可用性和扩展性。集群模式将数据分片存储在不同节点上,并使用一致性哈希算法来定位数据的存储位置。通过节点间的数据同步和数据迁移,保持数据的一致性和最新。
8.5 redis的持久化方式有哪些
- RDB(Redis Database)持久化: RDB是Redis默认的持久化方式。它通过创建数据快照来实现持久化,将Redis在内存中的数据按照指定的时间间隔或变更次数保存到磁盘上。RDB持久化生成的文件是一个二进制文件,包含了Redis在某个时间点上的数据快照。在Redis重新启动时,可以使用该文件来恢复数据。
- RDB持久化的优点是生成的快照文件紧凑且恢复速度快,适合用于备份和灾难恢复。缺点是如果Redis发生故障,最后一次持久化之后的数据会丢失。
- AOF(Append-Only File)持久化: AOF是以日志形式记录Redis的每个写操作,并追加到日志文件末尾。Redis重新启动时,会重新执行AOF文件中的写操作来恢复数据。AOF持久化文件是一个文本文件,可以通过配置将AOF文件的大小控制在一定的范围内,防止文件过大。
- AOF持久化的优点是可以保证较低程度的数据丢失,因为每个写操作都会被记录下来,可以通过回放日志来恢复数据。缺点是AOF文件相对于RDB文件更大,恢复速度较慢。
8.6 redis集群有没有接触
Redis提供了集群模式来实现高可用和横向扩展。Redis集群通过分片(Sharding)和复制(Replication)的方式来存储和复制数据,以实现数据的高可用性和负载均衡。
9 Linux
9.1 查看资源使用情况的命令有哪些
- top:实时显示系统的资源占用情况,包括CPU、内存、进程等。
用法:top - htop:类似于top,但提供了更丰富的功能和交互界面。
用法:htop - vmstat:显示系统的虚拟内存、进程、CPU活动等统计信息。
用法:vmstat [间隔时间(秒)] [次数] - iostat:显示系统的磁盘I/O情况,包括读写速度、等待时间等。
用法:iostat [间隔时间(秒)] [次数] - mpstat:显示每个CPU的性能统计信息,包括使用率、上下文切换等。
用法:mpstat [间隔时间(秒)] [次数] - sar:系统活动报告,提供系统的各项性能指标。
用法:sar [选项] [间隔时间(秒)] [次数] - free:显示系统的内存使用情况。
用法:free - df:显示文件系统的磁盘空间使用情况。
用法:df -h - du:显示目录或文件的磁盘空间使用情况。
用法:du -sh [目录或文件]
9.2 删查的命令
文件删除命令
rm: 用于删除文件或目录。语法为:rm [option] file1 file2 …。常用选项包括:
-r:递归删除目录及其内容。
-f:强制删除,不提示用户确认。
文件查找命令
find: 用于在指定目录下搜索文件。语法为:find /path/to/search -options [expression]。常用选项包括:
-name:按照文件名进行搜索。
-type:按照文件类型进行搜索(如f表示普通文件,d表示目录)。
-exec:对搜索到的文件执行指定的命令。
9.3 查看当前进程
ps:显示与当前终端会话相关的进程。它会列出进程的PID(进程ID)、TTY(终端)、STAT(状态)、TIME(CPU占用时间)等信息
top:实时显示系统中各个进程的运行情况,包括进程的PID、用户、CPU占用率、内存占用率等。按下"q"键可以退出top命令。
htop:是top命令的增强版,提供了更加直观和友好的界面,能够方便地查看进程的运行情况。它也显示了CPU和内存的使用情况,以及进程的详细信息。
9.4 查看磁盘空间
df:用于报告文件系统的磁盘空间利用情况。加上"-h"选项可以以人类可读的方式显示磁盘空间大小,以便更容易理解。
du:用于估算文件或目录的磁盘使用情况。加上"-h"选项可以以人类可读的方式显示文件或目录的磁盘使用量。
9.5 查看CUP使用情况
top:实时显示系统中各个进程的运行情况,包括进程的PID、用户、CPU占用率、内存占用率等。按下"q"键可以退出top命令。
htop:是top命令的增强版,提供了更加直观和友好的界面,能够方便地查看进程的运行情况。它也显示了CPU和内存的使用情况,以及进程的详细信息。
mpstat:用于报告多处理器系统中每个处理器的统计信息。它可以显示每个CPU核心的使用率、上下文切换次数、中断次数等。
sar:用于收集、报告和存储系统活动信息。通过添加"-u"选项可以查看CPU使用情况。上述命令中的数字1表示每秒采样一次。
9.6 查看文件内容
cat:用于连接文件并打印到标准输出设备。通过指定文件名作为参数,你可以查看文件的内容。cat命令将整个文件的内容输出到终端。
less:用于按页显示文件内容,并允许向上或向下浏览文件。它适用于大型文件,可以方便地查看文件的内容。使用箭头键进行浏览,按q键退出less命令。
more:也用于按页显示文件内容,类似于less命令。使用空格键向下滚动一页,按q键退出more命令。
head:用于显示文件的开头部分,默认情况下会显示文件的前10行内容。你可以使用"-n"选项来指定显示的行数,例如"head -n 20 filename"将显示文件的前20行。
tail:用于显示文件的最后几行,默认情况下显示文件的最后10行。你可以使用"-n"选项来指定显示的行数,例如"tail -n 20 filename"将显示文件的后20行。