目录
1.重载和重写的区别?
2.String 和 StringBuffer、StringBuilder 的区别是什么?
3.== 与 equals 的区别?
4.抽象类和接口的区别是什么?
5. 面向对象的特点
6.Collection 和 Collections 有什么区别?
7.List、Set、Map 之间的区别是什么?
8.HashMap 和 Hashtable 有什么区别?
9.说一下 HashMap 的实现原理?
10.说一下 HashSet 的实现原理?
11.ArrayList和LinkedList的区别?
12.Java集合容器有哪些?
13.哪些集合类是线程安全的?
14.创建线程有哪几种方式?
15.说一下 runnable 和 callable 有什么区别?
16.线程有哪些状态?
17.sleep() 和wait() 有什么区别?
18.notify()和 notifyAll()有什么区别?
19.线程的 run() 和 start() 有什么区别?
20.说一说几种常见的线程池及适用场景?
21.线程池中 submit() 和execute()方法有什么区别?
22.在 Java 程序中怎么保证多线程的运行安全?
23.什么是死锁?
24.怎么防止死锁?
25.ThreadLocal 是什么?有哪些使用场景?
26.synchronized 和 Lock 有什么区别?
27.什么是 Java 序列化?什么情况下需要序列化?
28.动态代理是什么?有哪些应用?
29.怎么实现动态代理?
30.为什么要使用克隆?
32.如何避免SQL 注入?
33.throw 和 throws 的区别?
34.fifinal、fifinally、fifinalize 有什么区别?
35.常见的异常类有哪些?
36.说一下你熟悉的设计模式?
37.spring框架使用了那些设计模式
38.为什么要使用spring?
39.spring 常用的注入方式有哪些?
40.spring 事务实现方式有哪些?
41.并发事务会带来哪些问题?
42.事务的隔离级别有哪些?
43.说一下spring mvc运行流程?
44.spring mvc 有哪些组件?
45.springmvc常见的注解?
46.什么是 spring boot以及作用?
47.spring boot 核心配置文件是什么?
48.springboot自动装配原理?
49.什么是spring cloud?
50.spring cloud 的核心组件有哪些?
51.MyBatis 中 #{}和 ${}的区别是什么?
52.什么地方使用${}
53.当实体类的属性名和表中的字段名不一致如何处理?
54.MyBatis如何实现一对多 / 多对一?
55.ResultType和ResultMap的区别?
56.Redis 是什么?都有哪些使用场景?
57.Redis 有哪些功能?
58.什么是缓存穿透?怎么解决?
59.什么是缓存击穿?如何解决?
60.什么是缓存雪崩?如何解决?
61.Redis 支持的数据类型有哪些?
62.怎么保证缓存和数据库数据的一致性?
63.Redis 持久化有几种方式?
64.Redis 怎么实现分布式锁?
65.Redis 淘汰策略有哪些?
66.Spring的IoC理解:
67.Spring的AOP理解:
68.BeanFactory和ApplicationContext有什么区别?
69.Spring通知(Advice)有哪些类型?
70.Spring中bean的作用域:
71.Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
72.Spring基于xml注入bean的几种方式?
73.Spring的自动装配?
74.@Autowired和@Resource之间的区别?
75.说一下Spring的事务传播行为?
76.通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
77.Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
78.Mybatis的一级、二级缓存:
79.Xml映射文件中,除了常见的select|insert|updae|delete标签外,还有哪些标签?
80.在mapper中如何传递多个参数?
81.Spring @bean 和 @component 注解有什么区别?
82.springbean的生命周期?
83.springbean的循环依赖以及如何解决?
84.spring事务失效的场景?
85.springboot常见注解?
86.未完待续...
重载发生在一个类中,同名的方法如果有不同的参数列表(类型不同、个数不同、顺序不同)则视为重载。
重写发生在子类与父类之间,重写要求子类重写之后的方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常。
1.不可变性:
String类是不可变的,即一旦创建了String对象,就不能修改它的值。每次对String对象的操作都会创建一个新的String对象,导致内存开销较大。
StringBuffer和StringBuilder类是可变的,它们允许修改已有的字符串内容。对于频繁的字符串操作,使用可变的类可以避免频繁地创建新对象,提高性能。
2.线程安全性:
String类是线程安全的,因为它的不可变性使得多个线程可以共享同一个String对象,不会出现并发修改的问题。
StringBuffer类是线程安全的,它的方法都是同步的(synchronized),因此多个线程可以安全地使用它进行字符串操作。但是,由于同步操作的开销,它的性能相对较低。
StringBuilder类是非线程安全的,它的方法没有进行同步。因此,在多线程环境下使用StringBuilder时需要自行保证线程安全。
3.性能:
由于String类的不可变性,每次对字符串进行修改时都会创建一个新的String对象,导致频繁的内存分配和回收,对性能有一定影响。
StringBuffer和StringBuilder类的可变性使得它们在进行字符串操作时不会频繁创建新对象,性能较高。StringBuilder类比StringBuffer类的性能稍高,因为它的方法没有进行同步。
综上所述,如果需要频繁进行字符串操作且不涉及多线程环境,推荐使用StringBuilder类,它具有较高的性能。如果涉及多线程环境或需要线程安全,使用StringBuffer类。String类则适用于不需要修改字符串内容的情况。
"=="操作符:
对于基本数据类型,"=="比较的是值是否相等。
对于引用类型(对象),"=="比较的是对象的引用是否相等,即两个对象是否指向同一个内存地址。"equals()"方法:
"equals()"方法是Object类的方法 , 默认情况下,Object类的"equals()"方法比较的是两个对象的引用是否相等,等价于"=="操作符。 通常,我们需要在自定义类中重写"equals()"方法,根据类的实际需求来定义两个对象是否相等的判断逻辑。 比如:`String str1 = "Hello"; String str2 = new String("Hello"); boolean result = str1.equals(str2); // true`
抽象类(Abstract Class)和接口(Interface)是Java中用于实现抽象类型的两种机制,他们的区别如下:
- 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
- 构造函数:抽象类可以有构造函数;接口不能有。
- 实现数量:类可以实现很多个接口;但只能继承一个抽象类【java只支持单继承】。
- 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的抽象方法可以使用Public和Protected修饰.
- 设计层面:抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
面向对象编程(Object-Oriented Programming,简称OOP)具有以下特征:封装:
封装是将数据和对数据的操作封装在一个单元中,通过定义类来实现。
类通过公共接口(方法)来控制对内部数据的访问,隐藏了内部实现的细节,提供了数据的安全性和保护。
继承:
继承是通过定义一个新的类来继承现有类的特性(属性和方法)。
继承实现了代码的重用,可以建立类之间的层次关系,并通过父类的特性扩展子类的功能。
子类可以继承并重写父类的方法,实现多态性。多态性:
多态性指的是同一类型的对象,在不同的情境下表现出不同的行为。
多态性通过方法的重写和方法的重载实现。
多态性使得程序可以更加灵活和可扩展,提高了代码的可读性和可维护性。这些特征共同构成了面向对象编程的核心思想和基本原则,使得代码具有可重用性、可扩展性、可维护性和可理解性。面向对象编程使得程序更易于设计、实现和维护,提高了软件开发的效率和质量。
Collection:
Collection是Java集合框架中定义的接口,它是所有集合类的根接口。
Collection接口提供了对一组对象进行集合操作的通用方法,如添加、删除、查找等。
Collection接口的常见实现类包括List、Set和Queue等。
Collections:
Collections是Java中提供的一个工具类,包含了各种操作集合的静态方法。
Collections类提供了一系列静态方法,用于对集合进行排序、查找、替换、同步等操作。
Collections类中的方法都是通过传入集合对象作为参数来操作集合。
需要注意的是,Collection和Collections之间没有直接的继承或关联关系。Collection是一个接口,定义了集合的通用行为,而Collections是一个工具类,提供了对集合的操作方法。我们可以使用Collections类的方法对实现了Collection接口的具体集合对象进行操作。
- List是一个有序的集合,允许元素重复,可以通过索引访问和修改元素。
Set是一个不允许重复元素的集合,元素无固定顺序,无法通过索引访问元素。
Map是一种键值对的映射结构,键唯一,值可以重复,可以通过键来获取值。
需要根据具体的需求选择适当的接口和实现类。如果需要有序的集合,可以选择List;如果需要去重的集合,可以选择Set;如果需要根据键查找对应的值,可以选择Map。
- 存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。
- 线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
- 性能:HashMap的性能通常比Hashtable更好。Hashtable的方法是同步的,因此在单线程环境下会产生额外的性能开销。HashMap允许使用null值,避免了对null值的判断和同步操作,有利于性能优化。
HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。 当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 数组 里。当计算出的 hash 值相同且equals不同时,我们称之为 hash 冲突,HashMap 的 做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表当hash冲突大于8且数组长度大于64位时使用红黑树。
HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相 关方法来完成,HashSet 不允许重复的值。
ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量的node对象)
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历,遍历LinkedList必须使用iterator(一特瑞特),不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
Java中的集合容器是用于存储和操作一组对象的数据结构。Java提供了许多内置的集合容器。以下是一些常见的Java集合容器:
Collection:
- List: 有序可重复集合接口
- ArrayList:动态数组,可以根据需要自动增长大小。
- LinkedList:双向链表,可以在任意位置插入和删除元素。
- Set:无序不可重复集合接口
- HashSet:基于哈希表实现的集合,不允许重复元素。
- TreeSet:基于红黑树实现的有序集合,按照元素的自然顺序进行排序。
- LinkedHashSet:基于哈希表和链表实现的集合,保持元素的插入顺序。
Map: 键值对集合对象
HashMap:基于哈希表实现的键值对映射,不允许重复键。
TreeMap:基于红黑树实现的有序键值对映射,按照键的自然顺序进行排序。
LinkedHashMap:基于哈希表和链表实现的键值对映射,保持元素的插入顺序。
以上仅是一些常见的集合容器。每种集合容器都有其特定的用途和适用场景,选择合适的容器可以提高代码效率和可读性。
Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全 的,不过在JDK1.5之后随着 Java.util.concurrent 并发包的出现,它们也有 了 自 己 对 应 的 线 程 安 全 类 , 比 如 HashMap 对 应 的 线 程 安 全 类 就 是 ConcurrentHashMap.
创建线程有三种方式:
- 继承 Thread 重写 run 方法;
- 实现 Runnable 接口;
- 实现 Callable 接口。
runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。
- 新建(New):当线程对象被创建但尚未启动时处于新建状态。
- 可运行(Runnable):线程对象调用了start()方法后,线程处于可运行状态。在可运行状态下,线程可能正在执行,也可能正在等待系统资源(如处理器时间片)。
- 运行(Running):线程获得了处理器时间片并正在执行其任务。
- 阻塞(Blocked):线程被阻塞并暂时停止执行,通常是因为等待某个操作的完成(如等待I/O操作、等待获取锁、等待某个条件满足等)。
- 等待(Waiting):线程等待某个特定条件的发生,需要其他线程显式地唤醒(如通过wait()方法)。
- 超时等待(Timed Waiting):线程等待一段特定的时间,超过时间后会自动唤醒。
- 终止(Terminated):线程执行完毕或出现异常导致终止,不再可运行。
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用 法 不 同 : sleep() 时 间 到 会 自 动 恢 复 ; wait() 可 以 使 用 notify()/notifyAll()直接唤醒。
notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。
notifyAll() 调用后, 会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则 留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个 线程由虚拟机控制。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复 调用,而 start() 只能调用一次。
固定大小线程池(FixedThreadPool):
特点:创建一个固定大小的线程池,线程数量固定,任务提交后会一直保持执行状态,直到线程池关闭。
适用场景:适用于需要限制并发线程数量的场景,如服务器后台处理任务、Web服务器。
缓存线程池(CachedThreadPool):
特点:创建一个可以动态调整大小的线程池,根据任务的数量自动增加或减少线程数量。
适用场景:适用于需要处理大量短期任务的场景,如异步任务处理、大量并发请求的服务器。
单线程线程池(SingleThreadExecutor):
特点:只有一个工作线程的线程池,任务按顺序执行,保证任务的顺序性。
适用场景:适用于需要顺序执行任务的场景。
调度线程池(ScheduledThreadPool):
特点:用于定时执行任务和周期性执行任务的线程池。
适用场景:适用于需要定时执行或周期性执行任务的场景,如定时任务调度、定时数据备份。
启动线程池的方法:execute submit
- execute():只能执行 Runnable 类型的任务。
- submit():可以执行 Runnable 和 Callable 类型的任务。
- Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。
方法一:使用安全类,比如 Java. util. concurrent 下的类。
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock
- 当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,
- 线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,
- 就会发生 AB 两个线程由于互相持有对方需要的锁,而发生 的阻塞现象,我们称为死锁
- 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、 ReentrantReadWriteLock)
- 设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块
- ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独 立地改变自己的副本,而不会影响其它线程所对应的副本。
- ThreadLocal 的经典使用场景是数据库连接和 session 管理等。
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放 锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock() 去释放锁就会造成死锁
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到
Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来
以下情况需要使用 Java 序列化:
想把的内存中的对象状态保存到一个文件中或者数据库中时候;
想用套接字在网络上传送对象的时候;
想通过 RMI(远程方法调用)传输对象的时候
动态代理是运行时动态生成代理类。
应用 : 动态代理的应用有 spring aop、rpc, Java 注解对象获取等
JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的
克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化 时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了
- 存储位置不同:session 存储在服务器端;cookie 存储在浏览器端.
- 安全性不同:cookie 安全性一般,在浏览器存储,可以被伪造和修改
- 容量和个数限制:cookie 有容量限制,每个站点下的 cookie 也有个数限制。
- 存储的多样性:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中
使用预处理 PreparedStatement。
使用正则表达式过滤掉字符中的特殊字符。
throw:是真实抛出一个异常
throws:是声明可能会抛出一个异常。
- final:是修饰符,如果修饰类,此类不能被继承;如果修饰方法和变量,则表示此方法和此变量不能在被改变,只能使用
- finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何情况 都会执行,finally 部分可以省略,但如果 finally 部分存在,则一定会执行 finally 里面的代码。 什么代码:释放资源的代码
- finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。
- NullPointerException 空指针异常
- ClassNotFoundException 指定类不存在
- NumberFormatException 字符串转换为数字异常
- IndexOutOfBoundsException 数组下标越界异常
- ClassCastException 数据类型转换异常
- FileNotFoundException 文件未找到异常
- NoSuchMethodException 方法不存在异常
- IOException IO异常
- SocketException Socket 异常
23种:以下是常用6种:
- 单例模式:保证被创建一次,节省系统开销。单例模式如何实现:有懒汉式和饿汉式两种,懒汉式会出现线程安全问题,需要加双重校验锁。
- 工厂模式(简单工厂、抽象工厂):解耦代码。
- 观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时, 它的所有的依赖者都会收到通知并自动更新。
- 外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了 一个高层的接口,让子系统更容易使用。
- 模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方 法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。
- 代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用
spring的bean的创建默认使用单例模式
spring中获取bean对象通过的是工厂模式
spring的aop采用的是动态代理模式
在各种BeanFactory以及ApplicationContext实现中也都用到模板模式;
- spring 提供 ioc 技术,容器会帮你管理依赖的对象,从而不需要自己创建和 管理依赖对象了,更轻松的实现了程序的解耦。
- spring 提供了事务支持,使得事务操作变的更加方便.
- spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。
- 更方便的框架集成,spring 可以很方便的集成其他框架,比如MyBatis、 hibernate 等。
setter 属性注入
注解方式注入
构造方法注入
声明式事务:声明式事务也有两种实现方式,基于 xml 配置文件的方式和注 解方式(在类上添加 @Transaction 注解)。
编码方式:提供编码的形式管理和维护事务。
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
(扩展:mysql是可重复读隔离级别)
- spring mvc 先将请求发送给 DispatcherServlet(前置控制器)。
- DispatcherServlet 查询一个或多个 HandlerMapping(映射控制器 ),找到处理请求的 Controller(处理器)。
- DispatcherServlet 再把请求提交到对应的 Controller。
- Controller 进行业务逻辑处理后,会返回一个 ModelAndView(模型和视图)。
- Dispathcher(调度员) 查 询 一 个 或 多 个 ViewResolver 视 图 解析 器 ,找到ModelAndView 对象指定的视图对象。
- 视图对象负责渲染返回给客户端。
前置控制器 DispatcherServlet。
映射控制器 HandlerMapping。
处理器 Controller。
模型和视图 ModelAndView。
视图解析器 ViewResolver。
- @Controller 作用在类上表示该类为控制层类
- @RequestMapping用来处理请求地址映射的注解
- @PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数
- @requestParam主要用于在SpringMVC后台控制层获取参数值
- @ResponseBody该注解用于将Controller的方法返回的对象转换为json格式
- @RequestBody用于接收前端传来的json实体,接收参数也是对应的实体
spring boot 是为 spring 服务的,是用来简化新 spring 应用的初始搭建以及开发过 程的。
作用:
- 配置简单
- 独立运行
- 自动装配
- 无代码生成和 xml 配置
- 提供应用监控
- 易上手
- 提升开发效率
spring boot 核心的两个配置文件:
- bootstrap (. yml 或者 . properties):boostrap 由父ApplicationContext 加载的,比 applicaton 优先加载,且bootstrap 里面的属性不能被覆盖 ;
- application (. yml 或者 . properties):用于 spring boot项目的自动化 配置。
主启动类里面有一个main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解的类。
该类包含@EnableAutoConfiguration注解,这个注解会开启自动配置功能,
还包含@Import({AutoConfigurationImportSelector.class})注解,该注解需要导入AutoConfigurationImportSelector自动配置选择器类,该类会自动装载一些自动配置类。而这些配置类会完成相应的自动装配
spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简 化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路 器、数据监控等,都可以用 springboot 的开发风格做到一键启动和部署。
- nacos:服务注册于发现。
- Feign:简单来说就是拼接 url,发 起请求。
- Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
- Sentinel:提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性.
- Gateway:网关管理,由 gateway 网关转发请求给对应的服务。
#{}是预编译处理,${}是字符替换。 在使用 #{}时,MyBatis 会将 SQL 中的 #{} 替换成“?”,配合 PreparedStatement 的 set 方法赋值,这样可以有效的防止 SQL 注入, 保证程序的运行安全。
$的作用是字符拼接,不能防止sql注入,当使用${}参数作为字段名或表名时使用。
比如:
select * from ${tableName}
比如我要用${}在MyBatis的sql中拼接排序类型的时候
通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
通过
类映射字段名和实体类属性名的一一对应的关系
一对多再resultMap中使用collection标签
多对一再resultMap中使用association标签
- resultmap:resultMap如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
- resulttype:resultType使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
Redis 是一个使用 C 语言开发的高速缓存数据库。
Redis 使用场景:
记录帖子点赞数、点击数、评论数;缓存近期热点数据;
记录用户会话信息。
数据缓存功能
分布式锁的功能
解决分布式会话
支持数据持久化
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查 不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓 存穿透
解决:
- 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力。
缓存击穿解决方案:
1.设置永久不过期。【这种只适合热点数据】
2.互斥锁简单来说就是当缓存中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完些,释放锁。若其他线程也在请求该kev时,发现获取锁失败,则先阻塞。
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3.设置热点数据永远不过期。
Redis 支持的数据类型:string(字符串)、list(列表)、hash(字典)、set(集 合)、zset(有序集合)。
合理设置缓存的过期时间。
新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证 数据的一致性。
Redis 的持久化有两种方式,或者说有两种策略:
- RDB(Redis Database):指定的时间间隔能对你的数据进行快照存储。
- AOF(Append Only File):每一个收到的写命令都通过 write 函数追加到文 件中。
Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占 用成功了就可以继续执行,失败了就只能放弃或稍后重试。
占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。
- volatile-lru:从已设置过期时间的数据集(server. db[i]. expires)中挑 选最近最少使用的数据淘汰。
- volatile-ttl:从已设置过期时间的数据集(server. db[i]. expires)中挑 选将要过期的数据淘汰。
- volatile-random:从已设置过期时间的数据集(server. db[i]. expires)中 任意选择数据淘汰。
- allkeys-lru:从数据集(server. db[i]. dict)中挑选最近最少使用的数据 淘汰。
- allkeys-random:从数据集(server. db[i]. dict)中任意选择数据淘汰。
- no-enviction(驱逐):禁止驱逐数据。
(1)什么是IOC:
IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。(2)什么是DI:
IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性.
(3)IoC的原理:
Spring 的 IoC 的实现原理就是工厂模式加反射机制。
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
(1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。(2)BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。
(3)BeanFactory通常以编程的方式创建,ApplicationCotext还能以声明的方式创建。
(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。
(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
(1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
(2)prototype:为每一个bean请求创建一个实例。
(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
- set()方法注入;
构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
静态工厂注入;
实例工厂;
在spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。
(1)在Spring框架xml配置中共有5种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
(2)基于注解的自动装配方式:
使用@Autowired、@Resource注解来自动装配指定的bean。
(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
- REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
- MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
在Java中,接口(Interface)本身是不允许包含具体的方法实现的,它只能定义方法的签名(方法名、参数列表、返回类型),而不包含方法体。因此,在接口中无法直接实现方法重载(Overloading)。
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
延迟加载的基本原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。
其基本原理是在程序运行过程中推迟(延迟)加载某些资源或数据,直到它们被真正需要的时候再进行加载。这样可以避免在程序启动时加载不必要的内容,提高了程序的响应速度,并节省了系统资源。
(1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
、 、 、 、 ,加上动态sql的9个标签 trim | where | set | foreach | if | choose | when | otherwise | bind 等,其中 为sql片段标签,通过 标签引入sql片段, 为不支持自增的主键生成策略标签。
(1)第一种:
//DAO层的函数
Public UserselectUser(String name,String area);
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
(2)第二种: 使用 @param 注解:
public interface usermapper {
user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
}
然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
(3)第三种:多个参数封装成map
try{
//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
//由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数
Mapmap = new HashMap();
map.put("start", start);
map.put("end", end);
return sqlSession.selectList("StudentID.pagination", map);
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();
throw e; }
finally{
MybatisUtil.closeSqlSession();
}
作用对象不同:
@Component
注解作用于类,而@Bean
注解作用于方法、
@Component
通常是通过路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我们需要用它的时候还给我。
@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,只能通过@Bean
来实现。
实例化阶段(Instantiation): 当Spring容器启动时,它会根据配置或注解等方式来实例化Bean对象。在这个阶段,Spring会创建Bean的实例,但还没有初始化Bean的属性。
属性赋值阶段(Population): 在实例化后,Spring会根据配置或注解等方式为Bean的属性设置值,例如依赖注入(DI)或通过构造函数注入等。
初始化阶段(Initialization): 在属性赋值完成后,Spring会调用特定的初始化方法来完成Bean的初始化,这些方法可以通过实现InitializingBean接口或在配置文件中指定。开发者也可以在Bean上使用@PostConstruct注解来标记初始化方法。
使用阶段(In Use): Bean初始化完成后,它处于可用状态,可以被应用程序的其他部分使用。此时,Bean处于正常运行状态,开发者可以通过Spring容器获取和使用它。
销毁阶段(Destruction): 当Spring容器关闭时或者从容器中移除某个Bean时,Spring会调用Bean的销毁方法来释放资源。开发者可以通过实现DisposableBean接口或在配置文件中指定销毁方法。同时,开发者也可以使用@PreDestroy注解来标记销毁方法。
值得注意的是,对于单例模式的Bean,默认情况下Spring容器在关闭时会自动销毁这些Bean,而对于原型模式的Bean,Spring容器不会管理其销毁,需要开发者手动管理。
总结一下,Spring Bean的生命周期包括实例化、属性赋值、初始化、使用和销毁阶段,通过实现特定的接口或标记注解,开发者可以干预Bean的生命周期,实现一些定制化的操作。
循环依赖是指在Spring容器中,两个或多个Bean之间互相依赖,形成一个闭环的依赖关系。例如,Bean A依赖于Bean B,而Bean B又依赖于Bean A,这就是一个循环依赖。
循环依赖可能导致应用程序在初始化阶段出现问题,因为Spring容器无法确定哪个Bean应该先被创建。如果循环依赖不加处理,会导致Bean的创建失败或者应用程序运行时出现不可预料的问题。
Spring提供了三种解决循环依赖的方法:
构造函数注入: 使用构造函数注入可以在Bean创建过程中解决循环依赖。当Bean A依赖于Bean B,而Bean B又依赖于Bean A时,可以使用构造函数注入来让Spring容器在创建Bean A时,将Bean B作为参数传递给Bean A的构造函数,从而打破循环依赖。这样,在Bean A的构造函数中,就可以访问到已经创建好的Bean B。
Setter方法注入: 使用Setter方法注入也可以解决循环依赖。在循环依赖的情况下,Spring容器会先创建所有涉及的Bean,并将它们添加到容器中,然后再进行依赖注入。因此,即使Bean A和Bean B互相依赖,它们都已经在容器中创建好了。接着,Spring容器会调用相应的Setter方法来完成依赖注入。
使用@Lazy注解: 在循环依赖无法通过构造函数注入或Setter方法注入解决的情况下,可以考虑使用@Lazy注解。将@Lazy注解添加到循环依赖的Bean上,表示延迟初始化该Bean,直到它被实际使用时才创建。这样,Spring容器会先创建其他非循环依赖的Bean,然后再在需要时创建循环依赖的Bean。
需要注意的是,尽管上述方法可以解决循环依赖问题,但在应用程序设计时,尽量避免出现循环依赖,因为循环依赖可能导致代码结构复杂、难以维护,且降低了代码的可读性。在实际应用中,适当地设计Bean之间的依赖关系可以有效避免循环依赖的问题。
方法未被声明为
public
: Spring的事务是通过代理来实现的,如果一个方法不是public
的,代理无法拦截该方法的调用,因此事务也就无法生效。自调用方法: 当一个方法在同一个类中通过普通的方法调用(而不是通过代理调用)时,Spring的事务无法生效。这是因为Spring的事务是基于AOP(面向切面编程)实现的,只有通过代理调用的方法才能被事务拦截。
未抛出受检异常: Spring的事务默认只会在遇到运行时异常(RuntimeException)时回滚,如果方法抛出的是受检异常,事务不会回滚。如果需要让事务在受检异常时回滚,可以使用
@Transactional(rollbackFor = YourCheckedException.class)
指定受检异常。异常被捕获并处理: 如果在方法内部捕获了抛出的异常并进行了处理,Spring事务将无法感知到异常的发生,从而无法触发事务回滚。如果希望让事务回滚,异常应该在方法内部被重新抛出。
事务设置为
readOnly
: 将事务的readOnly
属性设置为true
时,事务只能读取数据而不能进行修改操作。如果在readOnly
为true
的情况下进行修改操作,事务将被忽略。使用
this
关键字: 当一个类实现了接口并在该类中使用this
关键字来调用自己的其他方法时,事务也会失效。这是因为this
调用不会经过代理对象,而是直接调用了方法,无法触发事务拦截。未开启事务管理: 在Spring中,需要在配置文件或者类上使用
@EnableTransactionManagement
注解或XML配置来启用事务管理。如果没有正确配置事务管理,事务将无法生效。请注意,上述场景中的问题可能是由于配置问题或代码编写不当导致的,正确使用
@Transactional
注解或配置事务管理器可以解决这些问题,确保Spring事务正确生效。在面试时,理解这些场景并能够提供解决方案会显示出你对Spring事务管理的理解和经验。
在Spring Boot中,有许多常见的注解用于配置、控制应用程序的行为和实现各种功能。以下是一些常见的Spring Boot注解:
@SpringBootApplication
: 这是一个组合注解,它包含了@Configuration
、@EnableAutoConfiguration
和@ComponentScan
三个注解。通常用于标识主启动类,用于启动Spring Boot应用程序。
@RestController
: 用于标识一个类是控制器(Controller),并且其中的方法都返回RESTful风格的数据(例如JSON或XML),省略了@Controller
和@ResponseBody
注解。
@RequestMapping
: 用于映射URL请求到Controller的处理方法上,可以设置HTTP请求的方法、路径、请求参数等条件。
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
: 是@RequestMapping
的缩写形式,用于映射GET、POST、PUT和DELETE请求。
@PathVariable
: 用于将URL中的路径变量绑定到方法的参数上。
@RequestParam
: 用于将HTTP请求中的查询参数绑定到方法的参数上。
@RequestBody
: 用于将HTTP请求体的内容绑定到方法的参数上,常用于接收JSON格式的请求数据。
@Autowired
: 用于自动装配Bean,通过类型匹配进行依赖注入。
@Value
: 用于注入属性值,支持从配置文件中读取配置项。
@ConfigurationProperties
: 用于将配置文件中的属性绑定到配置类中,方便统一管理配置项。
@Component
: 用于将一个类标记为Spring组件,让Spring自动将其纳入到容器中管理。
@Service
: 用于标识一个类是Service层组件,通常用于标记业务逻辑的类。
@Repository
: 用于标识一个类是数据访问层的组件,通常用于标记数据库访问相关的类。
@EnableAutoConfiguration
: 开启Spring Boot的自动配置功能,根据项目的依赖和配置自动配置Spring应用。
@Conditional
: 用于根据特定条件来决定是否创建某个Bean,可以与@ConditionalOnProperty
、@ConditionalOnClass
等一起使用。
@Transactional
: 用于开启事务管理,将标注的方法作为一个事务处理。
@Async
: 用于标识一个方法为异步方法,可以让方法在新线程中执行。以上列举的只是一部分常见的Spring Boot注解,Spring Boot提供了大量的注解用于配置、控制和增强应用程序的功能,帮助开发者更轻松地构建Spring Boot应用。在实际开发中,灵活运用这些注解可以提高开发效率和代码的可读性。
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.