面向对象
什么是面向对象?
对比面向过程,是两种不同的处理问题的角度
面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么
比如:洗衣机洗衣服
面向过程会将任务拆解成一系列的步骤(函数),1、打开洗衣机—->2、放衣服—->3、放洗衣粉—–>4、清洗—>5、烘干
面向对象会拆出人和洗衣机两个对象;
人:打开洗衣机 放衣服 放洗衣粉
洗衣机: 清洗 烘干
从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护
JDK JRE JVM
JDK:
Java Develpment Kit java 开发工具
JRE:
Java Runtime Environment java运行时环境
JVM:
java Virtual Machine java 虚拟机
==和equals比较
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
equals: object中默认也是采用==比较,通常会重写
final
最终的
·修饰类:表示类不可被继承
·修饰方法:表示方法不可被子类覆盖,但是可以重载
· 修饰变量:表示变量一旦被赋值就不可以更改它的值。
(1) 修饰成员变量
·如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
·如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。
(2) 修饰局部变量
系统不会为同部变量进行初始化,同部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初
值(仅一次)
(3) 修饰基本类型数据和引用类型数据
·如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
·如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的。
String.、StringBuffer、StringBuilder
String是final修饰的,不可变,每次操作都会产生新的String对象
StringBuffer和StringBuilder都是在原对象上操作
StringBuffer是线程安全的,StringBuilder线程不安全的
StringBuffer方法都是synchronized修饰的
性能:StringBuilder > StringBuffer > String
场景:经常需要改变字符串内容时使用后面两个
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer
重载和重写的区别
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同;方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
抽象类和接口的区别
参数 抽象类 接口 默认的方法实现 可以有默认的方法实现 接口完全是抽象的。根本不存在方法的实现 实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,他需要提供抽象类中所有声明的方法的实现 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 构造器 抽象类可以有构造器 接口不能有构造器 与正常Java类的区别 除了不能实例化抽象类之外,它和普通Java类没有任何区别 接口完全不是同一个类型 访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。不能使用其它的修饰符 main方法 抽象类可以有main方法 接口没有main方法 多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口 速度 比接口速度快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
接口特点
- interface接口是一个完全抽象的类,他不会提供任何方法的实现,只会进行方法的定义
- 接口中只有两种访问修饰符,一种是public,另外一种是default
- 接口只提供方法的定义,接口没有实现,但是接口可以被其他类实现。
- 接口不能被实例化,所以接口不能有任何的构造方法
抽象类特点
- 如果一个类中有抽象方法,那么这个类一定是一个抽象类
- 抽象类中不一定只有抽象方法,也可以有具体的方法,你可以自己去选择是否实现这些方法
- 抽象类中的约束不像接口那么严格,你可以在抽象类中定义构造方法,抽象方法,普通属性,方法,静态属性和静态方法
- 抽象类和接口一样不能被实例化,实例化只能实例化具体的类
List和Set的区别
。List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,
在逐一遍历,还可以使用get(int index)获取指定下表的元素
。Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素
在逐
历各个元素
hashCode与equals
hashCode介绍
hashCode()的作用是获取哈希码,也称为散列码; 它实际上是返回一个int整数。这个哈希码的作用是确定该对象
在哈希表中的索引位置。hashCode0 定义在JDK的Object.java中, Java中的任何类都包含有hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“健”快速的检索出对应的“值”。这其中就利用到了散列
码!(可以快速找到所需要的对象)
为什么要有hashCode
以“HashSet如何检查重复”为例子来说明为什么要有hashCode:
对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没
有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的
相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大
减少了equals的次数,相应就大大提高了执行速度。
· 如果两个对象相等,则hashcode一定也是相同的
。两个对象相等,对两个对象分别调用equals方法都返回true
• 两个对象有相同的hashcode值,它们也不一定是相等的
· 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
.hashCode0的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
Arraylist与LinkedList区别
可以从它们的底层数据结构、效率、开销进行阐述哈
- ArrayList是数组的数据结构,LinkedList是链表的数据结构。
- 随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。
- 插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。
- LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用
HashMap和hashTable有什么区别?其底层实现是什么?
区别:
(1)HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;
(2)HashMap允许key和value为null,而HashTable不允许
2.底层实现:
数组+链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在
·计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
·如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
·如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到
8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
·key为null,存在下标0的位置
数组扩容
ConcurrentHashMap原理,jdk7和jdk8版本的区别
jdk7:
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又
是一个链表结构
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度
为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁, volatile保证
jdk8:
数据结构: synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,赋值操作都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
Java类加载器
JDK自带有三个类加载:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
BootstrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%]AVA_HOME96lib下的jar包和class文
件。
ExtClassLoader是AppClassLoader的父类加载器,负责加载96]AVA_HOME96/lib/ext文件夹下的jar包和class类。
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。
继承ClassLoader实现自定义类加载器
双亲委派模型
向上查找缓存,向下查找加载路径
双亲委派模型的好处:
主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的
ClassLoader加载就是不同的两个类
Java中的异常体系
Java中的所有异常都来自顶级父类Throwable。
Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在
程序编译过程中,会导致程序编译不通过。
GC如何判断对象可以被回收
引用计数法:每个对象有一个引用计数属性,新增—个引用时计数加1,引用释放时计数减1,计数为0时可以
回收,
可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任
何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
引用计数法,可能会出现A引用了 B,B 又引用了A,这时候就算他们都不再使用了,但因为相互引用 计数器=1永远无法被回收。
GC Roots的对象有:
·虚拟机栈(栈帧中的本地变量表) 中引用的对象;
方法区中类静态属性引用的对象
·方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行收,否则,对象“复活”
每个对象只能触发一次finalize()方法
由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
线程的生命周期?线程有几种状态
1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
2.阻塞的情况又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入”等待池”中。进入
这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAII方法才能被唤醒,wait是object类的方
法
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则VM会把该线程放入”锁
池“中。
(3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当
sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的
方法
1.新建状态(New):新创建了一个线程对象。
2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程
池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
sleep()、wait()、join()、yield()的区别
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁, notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
1、sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
s1eep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果s1eep时该线程有锁,那么s1eep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个领。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
6、sleep会让出CPU执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。
yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
Thread、Runable的区别
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
对守护线程的理解
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程
结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠
谱;
守护线程的作用是什么?
举例, GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景:(1)来为其它线程提供服务支持的情况;(2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用; 反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一lllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用java的线程池。
ThreadLocal的原理和使用场景
每一个Thread对象均含有一个ThreadLoca1Map类型的成员变量threadLocals,它存储本线程中所有
ThreadLocal对象及其对应的值
ThreadLocalMap 由一个个Entry对象构成
Entry继承自weakReference<ThreadLocal<7>>,一个Entry由ThreadLoca1对象和object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个3dbc Connection,在整个事务过程都是使用该线程绑定的connection米执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离
ThreadLocal内存泄露原因,如何避免
内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutofMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用
java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本
为什么用线程池?解释下线程池参数?
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
.corePoolsize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
·maxinumpoolsize 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
·keepAliveTime、unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过 setKeepA1iveTime 来设置空闲时间
·workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
·ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
·Handler 任务拒绝策略,有两种情况,第一种是当我们调用shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这是也就拒绝
线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最
大线程?
1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
就好比一个企业里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。
线程池中线程复用原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个”循环任务“中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run方法,将 run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。
mybatis的优缺点
优点;
1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除 sq1 与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态 SQL语句,并可重用。
2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
3、很好的与各种数据库兼容(因为MyBatis 使用JDBC来连接数据库,所以只要JDBC支持的数据库 MyBatis都
支持)。
4、能够与Spring 很好的集成;
5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点:
1、SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
#{}和${}的区别是什么?
#0是预编译处理;
是占位符,$()是字符串替换、是拼接符。
Mybatis在处理#()时,,会将 sql中的#()替换为?号,调用PreparedStatement来赋值;
Mybatis在处理$0时,就是把$0替换成变量的值,调用 Statement 来赋值;
#()的变量替换是在DBMS中.变量替换后,#() 对应的变量自动加上单引号
$()的变量替换是在DBMS外、变量替换后,$()对应的变量不会加上单引号
使用#()可以有效的防止 SQL 注入,提高系统安全性。
简述Mybatis 的插件运行原理,如何编写一个插件。
答:Mybatis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4种接口的插件,Mybatis 使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4 种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的 invoke()方法,拦截那些你指定需要拦截的方法。
编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。
索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
- 把创建了索引的列的内容进行排序
- 对排序结果生成倒排表
- 在倒排表内容上拼上数据地址链
- 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
mysqI聚簇和非聚簇索引的区别
都是B+树的数据结构
聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。
优势:
1、查询通过聚簇索引可以直接获取数据,相比非保簇索引需要第二次查询(非覆盖索引的情况下)效率要高
2、聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合,非聚核索引不适合
劣势:
1、维护索引很昂贵,特别是插入新行或者主键被更新导至要分页(page split)的时候,建议在大量插入新行后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
2、表因为使用UUId(随机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面更慢,所以建议使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值;过长的主键值,会导致非叶子节点占用占用更多的物理空间
InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
MyISM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。
mysql索引的数据结构,各自优劣
索引的数据结构和具体存储引擎的实现有关,在MySOL中使用较多的索引有Hash索引,B+树索引等,InnoDB存储引擎的默认索引实现为: B+树索引。对于哈希索引来说, 底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
B+树:
B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针相互链接。在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+树索引被广泛应用于数据库、文件系统等场景。
索引设计的原则?
查询更快、占用空间更小
- 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
- 基数较小的类,索引效果较差,没有必要在此列建立索引
- 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
- 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
- 定义有外键的数据列一定要建立索引。
- 更新频繁字段不适合创建索引
- 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
- 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
- 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
- 对于定义为text、image和bit的数据类型的列不要建立索引。
锁的类型有哪些
基于锁的属性分类:共享锁、排他锁。
基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)、记录锁、间隙锁、临键锁。
基于锁的状态分类:意向共享锁、意向排它锁。
mysql执行计划怎么看
1.id :是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为NULL最后执行。
2. selectType 表示查询中每个select子句的类型
SIMPLE:表示此查询不包含UNION查询或子查询
PRIMARY: 表示此查询是最外层的查询(包含子查询)
SUBQUERY:子查询中的第一个SELECT
UNION:表示此查询是 UNION的第二或随后的查询
DEPENDENT UNION:UNION中的第二个或后面的查询语句,取决于外面的查询
UNION RESULT, UNION 的结果
DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询 的结果.
DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)
3.table:表示该语句查询的表
4.type:优化sqI的重要字段,也是我们判断sqI性能和优化程度重要指标。他的取值类型范围:
const: 通过索引一次命中,匹配一行数据
system:表中只有一行记录,相当于系统表;
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配
ref:非唯一性索引扫描,返回匹配某个值的所有
range:只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>
index:只遍历索引树;
ALL:表示全表扫描,这个类型的查询是性能最差的查询之一。那么基本就是随着表的数量增多,执行效率越慢。
执行效率:
ALL < index <range< ref < eq_ref < const < system。最好是避免ALL和index5.possible_keys:它表示Mysql在执行该sql语句的时候,可能用到的索引信息,仅仅是可能,实际不一定会用
到。
6.key:此字段是 mysql在当前查询时所真正使用到的素引。他是possible_keys的子集7.key_len:表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用,这也是我们优化sqI时,评估索引的重要指标
9.rows:mysql查询优化器根据统计信息,估算该sql返回结果集需要扫描读取的行数,这个值相关重要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明要优化空间越大
10.fiitered:返回结果的行占需要读到的行(rows列的值)的百分比,就是百分比越高,说明需要查询到数据越准确,百分比越小,说明查询到的数据量大,而结果集很少
11.extra
using filesort:表示 mysqI对结果集进行外部排序,不能通过索引顺序达到排序效果。一般有 using filesort: 都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。
using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。
using temporary:查询有使用临时表,一般出现于排序, 分组和多表join的情况,查询效率不高,建议优化。
using where:sql使用了where过滤,效率较高。
事务的基本特性和隔离级别
事务基本特性ACID分别是:
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
隔离性有4个隔离级别,分别是:
- read uncommit 读未提交,可能会读到其他事务未提交 的数据,也叫做脏读。
用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。 - read commit 读已提交,两次读取结果不一致,叫做不可重复读。
不可重复读解决了脏读的问题,他只会读取已经提交的事务。
用户开启事务读取id=1用户,查询到age=1d,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。 - repeatable read 可重复复读,这是mysqI的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
- serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
关心过业务系统里面的sqI耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
ACID靠什么保证的?
A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sqI
C一致性由其他三大特性保证、程序代码要保证业务上的一致性
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysqI修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log恢复
InnoDB redo 10g 写盘,InnoDB 事务进入 prepare 状态。
如果前面 prepare 成功,bin1og 写盘,再继续将事务日志持久化到 bin1og,如果持久化成功,那么 InnoDB 事务则进入 commit 状态(在 redo 10g 里面写一个 commit 记录)
redolog的刷盘会在系统空闲时进行
什么是MVCC
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链
MVCC只在READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。
聚簇索引记录中有两个必要的隐藏列:
trx_id:用来存储每次对某条聚簇索引记录进行修改的时候的事务id。
roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入损作的undo日志没有这个属性,因为它没有老版本)
已提交读和可重复读的区别就在于它们生成ReadView的策略不同。
开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组
访问数据,获取数据中的事务id(获取的是事务id最大的记录),对比readview:
如果在readview的左边(比readview都小),可以访问(在左边意味着该事务已经提交)
如果在readview的右边(比readview都大)或者就在readview中,不可以访问,获取roll_pointer,取上一版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还未提交)
已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。
这就是MysqI的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。
mysqI主从同步原理
mysqI主从同步的过程:
MysqI的主从复制中主要有三个线程:master(bin1og dump thread)、slave(I/o thread 、SQL
thread),Master—条线程和Slave中的两条线程。
主节点binlog,主从复制的基础是主库记录数据库的所有变更记录到binlog。binlog 是数据库服务器启动的
那一刻起,保存所有修改数据库结构或内容的一个文件。主节点 Iog dump 线程,当binlog 有变动时,Iog dump 线程读取其内容并发送给从节点。
从节点 I/O线程接收 binlog 内容,并将其写入到 relay log 文件中。从节点的SQL线程读取relaylog文件内容对数据更新进行重放,最终保证主从数据库的一致性。
注:主从节点使用binglog文件+position偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发生宕机重启,则会自动从 position 的位置发起同步。
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。
简述MyISAM和InnoDB的区别
MyISAM:
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;
存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
InnoDb:
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里) ,也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;
简述mysqI中索引类型及对数据库的性能的影响
普通索引:允许被索引的数据列包含重复的值。
唯一索引:可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字PRIMARY KEY 来创建。
联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
索引可以极大的提高数据的查询速度。
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。
在哪些情况下会发生针对该列创建了索引但是在查询的时候并没有使用呢?
使用不等于查询
列参与了数学运算或者函数
在字符串like时左边是通配符.类似于’%aaa’
当mysql分析全表扫描比使用索引快的时候不使用索引
当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索 引.
什么是spring?
Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展 是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO 为基础的编程模型促进良好的编程习惯。
Spring里用了哪些设计模式
1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对 象。
2.代理设计模式:Spring AOP功能的实现。
3.单例设计模式:Spring中的bean默认都是单例的。
4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对 数据库操作的类,它们就使用到了模板模式。
5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要 会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也 是用到了适配器模式适配Controller。
ioc和aop的理解?实现原理是什么?
IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对 象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC 容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放 的是各种对象。
将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很 大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一 样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是 如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假 如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这 可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。
Spring时代我们一般通过XML文件来配置Bean,后来开发人员觉得用XML文件来配置不太 好,于是Sprng Boot注解配置就慢慢开始流行起来。
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少 系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使 用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转 而使用CGlib动态代理生成一个被代理对象的子类来作为代理。 当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生 态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到 的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的 扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。
Spring AOP和AspectJ AOP 有什么区别?
Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理 (Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架 了。AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ,它比SpringAOP快很多。
* FactoryBean 和 BeanFactory有什么区别?
BeanFactory 是 Bean 的工厂, ApplicationContext 的父类,IOC 容器的核心,负责生产和管理 Bean 对象。
FactoryBean 是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作。
SpringBean的生命周期说说?
spring bean 容器的生命周期流程如下:
1.Bean容器找到配置文件中Spring Bean的定义。
2.Bean容器利用Java Reflection API创建一个Bean的实例。
3.如果涉及到一些属性值,利用set()方法设置一些属性值。
4.如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入 ClassLoader对象的实例。
6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory()方法,传入 ClassLoader对象的实例。
7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行 postProcessBeforeInitialization()方法。
9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行 postProcessAfterInitialization()方法。
12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指 定的方法。
Spring是怎么解决循环依赖的?
首先,Spring 解决循环依赖有两个前提条件:
- 不全是构造器方式的循环依赖
- 必须是单例
基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。
第一级缓存:用来保存实例化、初始化都完成的对象
第二级缓存:用来保存实例化完成,但是未初始化完成的对象
第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
- 创建对象A,实例化的时候把A对象工厂放入三级缓存
- A注入属性时,发现依赖B,转而去实例化B
- 同样创建对象B,注入属性时发现依赖A,一次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。
- 接着继续创建A,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存
- 最后,一级缓存中保存着实例化、初始化都完成的A、B对象
因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。
* 为什么要三级缓存?二级不行吗?
不可以,主要是为了生成代理对象。
因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。
使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。
假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象,BeanPostProcessor
去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。
Spring事务传播机制有哪些?
在TransactionDefinition接口中定义了7个表示事务传播行为的常量。
支持当前事务的情况:
PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则 创建一个新的事务。
PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则 以非事务的方式继续运行。
PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务, 则抛出异常。(mandatory:强制性)。
不支持当前事务的情况:
PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事 务挂起。
PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前 事务挂起。
PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务 来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
Spring事务的实现方式和原理以及隔离级别?
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式@Transactional注解就是申明式的。
首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败。
在一个方法上加了@Transactional注解后, Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
spring事务隔离级别就是数据库的隔离级别:外加一个默认级别
· read uncommitted (未提交读)
· read committed (提交读、不可重复读)
• repeatable read(可重复读)
• serializable (可串行化)
spring事务什么时候会失效?
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种
1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!
解决方法很简单,让那个this变成UserService的代理类即可!
2、方法不是public的
@Transactiona1 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启Aspect3 代理模式。
3、数据库不支持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)
区分 BeanFactory 和 ApplicationContext
BeanFactory | ApplicationContext |
---|---|
它使用懒加载 | 它使用即时加载 |
它使用语法显式提供资源对象 | 它自己创建和管理资源对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
ApplicationContext提供了一种解决文档信息的方法,一种加载文件资源的方式(如图片),他 们可以向监听他们的beans发送消息。另外,容器或者容器中beans的操作,这些必须以bean 工厂的编程方式处理的操作可以在应用上下文中以声明的方式处理。应用上下文实现了 MessageSource,该接口用于获取本地消息,实际的实现是可选的。
相同点:两者都是通过xml配置文件加载bean,ApplicationContext和BeanFacotry相比, 提供了更多的扩展功能。
不同点:BeanFactory是延迟加载,如果Bean的某一个属性没有注入,BeanFacotry加载 后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身 是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用 ApplicationContext。
Spring中的单例bean的线程安全问题了解吗?
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线 程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操 作会存在线程安全问题。
有两种常见的解决方案:
1.在bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推 荐的一种方式)。
Spring中的bean的作用域有哪些?
1.singleton:唯一bean实例,Spring中的bean默认都是单例的。
2.prototype:每次请求都会创建一个新的bean实例。
3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有 效。
4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有 效。
5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,
Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插 件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个 Portlet都有不同的会话。
你用过哪些重要的 Spring 注解?
- @Controller - 用于 Spring MVC 项目中的控制器类。
- @Service - 用于服务类。
- @RequestMapping - 用于在控制器处理程序方法中配置 URI 映射。
- @ResponseBody - 用于发送 Object 作为响应,通常用于发送 XML 或 JSON 数据作为响应。
- @PathVariable - 用于将动态值从 URI 映射到处理程序方法参数。
- @Autowired - 用于在 spring bean 中自动装配依赖项。
- @Qualifier - 使用 @Autowired 注解,以避免在存在多个 bean 类型实例时出现混淆。
- @Scope - 用于配置 spring bean 的范围。
- @Configuration,@ComponentScan 和 @Bean - 用于基于 java 的配置。
- @Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)。
自动装配有哪些方式?
Spring 容器能够自动装配 bean。也就是说,可以通过检查 BeanFactory 的内容让 Spring 自动解析 bean 的协作者。
自动装配的不同模式:
- no - 这是默认设置,表示没有自动装配。应使用显式 bean 引用进行装配。
- byName - 它根据 bean 的名称注入对象依赖项。它匹配并装配其属性与 XML 文件中由相同名称定义的 bean。
- byType - 它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 bean 名称匹配,则匹配并装配属性。
- 构造函数 - 它通过调用类的构造函数来注入依赖项。它有大量的参数。
- autodetect - 首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。
@Component和@Bean的区别是什么?
1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。
有哪些不同类型的IOC(依赖注入)?
构造器依赖注入:构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数, 每个参数代表注入的对象。
Setter方法依赖注入:首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之 后容器调用bean中的setter方法完成Setter方法依赖注入。
有哪些类型的通知(Advice)?
- Before - 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置。
- After Returning - 这些类型的 Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置。
- After Throwing - 这些类型的 Advice 仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。
- After (finally) - 这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。
- Around - 这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。
Spring MVC 框架有什么用?
Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序。MVC 模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和 UI 逻辑,同时在所有这些元素之间提供松散耦合。
描述一下 SpringMVC 的工作流程
1)用户发送请求至前端控制器 DispatcherServlet。
2)DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3)处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生
成)一并返回给 DispatcherServlet。
4)DispatcherServlet调用HandlerAdapter 处理器适配器。
5)HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)
6)Controller 执行完成返回ModelAndView。
7)HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9)ViewReslover 解析后返回具体 View。
10)DispatcherServlet 根据View进行渲染视图(即将模型数据填充至视图中)。
11)DispatcherServlet 响应用户。
Spring Boot 有哪些优点?
Spring Boot 主要有如下优点:
- 容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。
- 开箱即用,远离繁琐的配置。
- 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
- 没有代码生成,也不需要XML配置。
- 避免大量的 Maven 导入和各种版本冲突。
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
如何实现 Spring Boot 应用程序的安全性?
为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。
比较一下 Spring Security 和 Shiro 各自的优缺点 ?
由于 Spring Boot 官方提供了大量的非常方便的开箱即用的 Starter ,包括 Spring Security 的 Starter ,使得在 Spring Boot 中使用 Spring Security 变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口,所以,如果是 Spring Boot 项目,一般选择 Spring Security 。当然这只是一个建议的组合,单纯从技术上来说,无论怎么组合,都是没有问题的。Shiro 和 Spring Security 相比,主要有如下一些特点:
- Spring Security 是一个重量级的安全管理框架;Shiro 则是一个轻量级的安全管理框架
- Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单
- Spring Security 功能强大;Shiro 功能简单
Spring Boot 中如何解决跨域问题 ?
跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
项目中前后端分离部署,所以需要解决跨域的问题。
我们使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。
当用户登录以后,正常使用;当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。
我们知道一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。
什么是 CSRF 攻击?
CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。
Spring Boot 中的监视器是什么?
Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。
如何在 Spring Boot 中禁用 Actuator 端点安全性?
默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 ACTUATOR 角色的用户才能访问 它们。安全性是使用标准的 HttpServletRequest.isUserInRole 方法实施的。 我们可以使用 来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar
命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。
Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes
目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
微服务中如何实现 session 共享 ?
在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 session 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享 session ,常见的方案就是 Spring Session + Redis 来实现 session 共享。将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session 。这样就实现了 session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。
Spring Boot 中如何实现定时任务 ?
定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。
在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz。
使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。
使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。
为什么我们需要 spring-boot-maven-plugin?
spring-boot-maven-plugin 提供了一些像 jar 一样打包或者运行应用程序的命令。
- spring-boot:run 运行你的 SpringBooty 应用程序。
- spring-boot:repackage 重新打包你的 jar 包或者是 war 包使其可执行
- spring-boot:start 和 spring-boot:stop 管理 Spring Boot 应用程序的生命周期 (也可以说是为了集成测试)。
- spring-boot:build-info 生成执行器可以使用的构造信息。
如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?
这可以使用 DEV 工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式tomcat 将重 新启动。Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产 力。Java 开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。开 发人员可以重新加载 Spring Boot 上的更改,而无需重新启动服务器。这将消除每次手动部署 更改的需要。Spring Boot 在发布它的第一个版本时没有这个功能。这是开发人员最需要的功 能。DevTools 模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供 H2 数据库控制台以更好地测试应用程序。
1 | <dependency> |
Spring Boot 自动配置原理?
@lmport + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META-INF/spring.factories下
使用Spring spi扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
如何理解SpringBoot中的Starter
使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean
starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot–starter,spring-boot-starter-redis
什么是嵌入式服务器?为什么要使用嵌入式服务器?
节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行
只需要一个安装了,java的虚拟机,就可以直接在上面部署应用程序了
springboote经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载springmvc
面试官:springboot和springCloud有什么区别
SpringBoot专注于快速方便的开发单个个体微服务。
SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
为各个服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、精选决策、分布式会话等集成服务。
SpringBoot可以离开SpringCloud独立开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。
SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架
- 本文作者: GHOSTLaycoo
- 本文链接: http://example.com/2022/02/08/面试题/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!