万字长文 JAVA面试篇(基础)

JAVA集合

Collection 接口的接口 对象的集合(单列集合)

├——-List 接口:元素按进入先后有序保存,可重复

│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全

│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全

│—————-└ Vector 接口实现类 数组, 同步, 线程安全

│ ———————-└ Stack 是Vector类的实现类

└——-Set 接口: 仅接收一次,不可重复,并做内部排序

├—————-└HashSet 使用hash表(数组)存储元素

│————————└ LinkedHashSet 链表维护元素的插入次序

└ —————-TreeSet 底层实现为二叉树,元素排好序

Map 接口 键值对的集合 (双列集合)

├———Hashtable 接口实现类, 同步, 线程安全

├———HashMap 接口实现类 ,没有同步, 线程不安全-

│—————–├ LinkedHashMap 双向链表和哈希表实现

├ ——–TreeMap 红黑树对所有的key进行排序

JUC集合包中的List和Set

JUC集合包中的List和Set实现类包括: CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet

CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口。CopyOnWriteArrayList是支持高并发的

CopyOnWriteArraySet相当于线程安全的HashSet,它继承于AbstractSet类。CopyOnWriteArraySet 内部包含一个CopyOnWriteArrayList对象(聚合关系),它是通过CopyOnWriteArrayList实现的

ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。

JUC集合包中Map

JUC集合包中Map的实现类包括: ConcurrentHashMap和ConcurrentSkipListMap

ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的,它支持并发

ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。

什么是哈希碰撞(hash碰撞)

hash碰撞指的是,两个不同的值(比如张三、李四的学号)经过hash计算后,得到的hash值相同,后来的李四要放到原来的张三的位置,但是数组的位置已经被张三占了,导致冲突

解决Hash冲突的方法

线性探测法

当我们的所需要存放值的位置被占了,我们就往后面一直加1并对m取模直到存在一个空余的地址供我们存放值,取模是为了保证找到的位置在0~m-1的有效空间之中。

平方探测法(二次探测)

当我们的所需要存放值的位置被占了,会前后寻找而不是单独方向的寻找。

公式:h(x)=(Hash(x) +i)mod (Hashtable.length);(i依次为+(i^2)和-(i^2))

再哈希法

同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。

MySql调优

  • Explain + SQL分析,字段type、index 查看争对查询条件是否走索引,type=all表示全表扫描需要优化
  • 创建索引,离散性很低没必要建索引,几个字段一起查询建组合索引,索引数量不能超过5个,
  • 查询条件虽然是索引字段,但是左like、函数、隐式类型转换、计算都会导致索引失效
  • 两表数据量很大,不能用join,改成单表查询,场景:A表20-60万,B表2万到-5万,AB表join耗时分页查询800毫秒,如果拆成单表分开查询耗时几十毫秒
  • 单表数据很大,考虑借助ES、Mongdb,也可以用sharding-jdbc分库分表

一、监控工具

(Prometheus+Grafana)监控MySQL,发现查询性能变慢,报警提醒运维人员

二、分析查询计划 

explan分析sql执行计划(访问类型、记录条数、索引长度等);主要关注字段:

possible_keys:查询可能用到的索引

key:实际使用的索引

key_len:实际使用的索引的字节数长度。

type:访问类型,看有没有走索引。all(全表扫描),ref(命中非唯一索引),const(命中主键/唯一索引)、range(范围索引查询)、index_merge(使用多个索引)、 system(一行记录时,快速查询)。

Extra:额外信息。看有没有走索引。

using index:覆盖索引,不回表。

using filesort:需要额外的排序。排序分为索引排序和filesort排序,索引排序一般更快,深分页等查询数据量大时filesort更快。

using index condition:索引下推。MySQL5.6开始支持。联合索引某字段是模糊查询(非左模糊)时,该字段进行条件判断后,后面几个字段可以直接条件判断,判断过滤后再回表对不包含在联合索引内的字段条件进行判断。

using where:不走索引,全表扫描。

三、使用合适的存储引擎

MyISAM:适合读取频繁,写入较少的场景(因为表级锁、B+树叶存地址)

InnoDB:适合并发写入的场景(因为行级锁、B+树叶存记录)。

四、分库分表

分库分表:数据量级到达千万级以上后,垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)。

概念:

只分表:单表数据量大,读写出现瓶颈,这个表所在的库还可以支撑未来几年的增长。

只分库:整个数据库读写出现性能瓶颈,将整个库拆开。

分库分表:单表数据量大,所在库也出现性能瓶颈,就要既分库又分表。

垂直拆分:把字段分开。例如spu表的pic字段特别长,建议把这个pic字段拆到另一个表(同库或不同库)。

水平拆分:把记录分开。例如表数据量到达百万,我们拆成四张20万的表。

JAVA 锁

一、可重入锁

什么是可重入锁

可重入锁也称递归锁,指的是同一线程 外层函数获得锁之后,内层递归函数任然有获取该锁的代码,但不受影响,对同一条线程来说是可重入的。

ReentrantLock和Synchroized都是可重入锁

ReentrantLock和Synchroized有什么区别

用法不同:synchroized可用来修饰普通方法、静态方法和代码块,而ReentrantLock只能用于代码块上。ReentrantLock在使用前需要创建ReentrantLock对象,然后使用lock方法进行加锁,使用完之后使用unlock方法释放锁。

获取锁和释放锁的方式不同:synchroized会自动加锁和释放锁,当进入synchroized修饰的代码块时会自动加锁,当离开synchroized的代码块时会自动释放锁。而ReentrantLock需要手动创建和释放锁。

锁类型不同:synchroized属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁。默认情况下 ReentrantLock 为非公平锁。

响应中断不同:ReentrantLock 可以使用 lockInterruptibly 获取锁并响应中断指令,而 synchronized 不能响应中断,也就是如果发生了死锁,使用 synchronized 会一直等待下去,而使用 ReentrantLock 可以响应中断并释放锁,从而解决死锁的问题。

底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的

为什么要用可重入锁

因为如果一个线程对一个函数进行了加锁,然后执行另一个函数的时候又掉了第一个函数,这个时候锁没有释放,是同一个线程的锁,这样就造成了自己等待自己释放锁,造成了死锁。

可重入锁是怎么实现的或者说ReentrantLock是怎么实现可重入锁的? – 重点

线程调用加锁时:锁的状态为0表示可以加锁,使用CAS将锁的状态设置成1(使用CAS是因为是个原子操作),进行锁的争抢,记录当前持有锁的线程,锁重入时,判断当前线程是否为可重入锁的线程(也就是是否等于上一个加锁的线程)如果是,状态自增+1。

解锁时:状态-1,减到0时,释放锁,唤醒阻塞队列的线程,进行CAS锁争抢。

CAS又是什么

CAS(Compare-and-Swap)比较并交换

CAS指令需要三个操作数,分别是内存位置(在java中可以简单的理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和准备设置的新值(用B表示)。CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则他就比执行更新。但是,不管是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作,执行期间不会被其他线程中断。

CAS缺点

首先我们可以看到getAndAddInt执行时,有个do while方法如果CAS失败他就会一直尝试,如果CAS长时间不成功,可能会给cpu造成很大的开销

二、公平锁非公平锁

公平锁

所谓公平,就是排队的时候讲究先来后到,先来的先获取到锁,优点是不会线程饥饿,缺点是系统线程上下文切换次数很高,从而降低吞吐量

非公平锁

不要求先来先获取锁,阻塞队列存在等待锁,新来的线程也可以立马获取到锁,很大程度上解决上下文的切换,优点是吞吐量高,缺点是可能存在 线程饥饿

默认是非公平锁

什么又是线程饥饿

比如线程A现在正在阻塞队列里,然后如果是非公平锁,那很有可能锁一直被后面的线程争抢到,导致线程A一直拿不到锁,我们就称线程A为饥饿

分布式锁是如何实现可重入锁的,或者你怎么设计一个分布式锁的可重入锁?

我们知道可重入锁主要是通过CAS计数器来完成,他最大的特性就是计数,计算加锁的次数,所以当可重入锁需要在分布式环境实现时,我们也就需要统计加锁的次数。

分布式锁的实现方案有两种:

基于ThreadLocal实现方案

基于RedisHash实现方案

基于ThreadLocal实现方案

Java 中 ThreadLocal可以使每个线程拥有自己的实例副本,我们可以利用这个特性对线程重入次数进行计数,我们定义一个ThreadLocal的全局变量 LOCKS,内存存储 Map 实例变量

每个线程都可以通过 ThreadLocal获取自己的 Map实例,Map 中 key 存储锁的名称,而 value存储锁的重入次数。

ThreadLocal

1、ThreadLocal是什么?

ThreadLocal即线程本地变量,如果你创建了一个ThreadLocal的变量,那么访问这个变量的每一个线程都会有这个变量的一份本地拷贝,多个线程操作这个变量的时候,实际上是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。

2、为什么要使用ThreadLocal?

并发场景下,会存在多个线程同时修改一个共享变量的场景,这就有可能出现线程安全的问题。

我们虽然可以通过加锁的方式来避免线程安全,但是加锁会导致系统变慢,阻塞其他线程,导致切换到cpu内核调度线程(java使用的是cpu内核线程),还会造成上下文切换。

3、ThreadLocal的实现原理?

使用ThreadLocal类来访问共享变量时,会在每个线程的本地都保存一份共享变量的副本。

每一个Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的k-v值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程k-v值对中找到本地对应的线程变量。

volatile

1、volatile是什么解释一下?

关键字volatile是java虚拟机提供的最轻量级的同步机制,被volatile关键字修饰的变量对所有线程是立即可见的,对volatile变量所有的写操作都都能立刻反映到其他线程之中。保证变量的及时可见性。

2、那为什么普通变量修改对其他线程不是立即可见的?

因为普通变量在的值在线程间传递时均需要通过主内存来完成,比如,线程A修改了一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写之后再对主内存进行读取,新变量的值才会对线程B可见。

3、volatile是怎么做到对其他线程及时可见的?

每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作的数据并且回写到了主内存,其他已经读取的线程的变量副本就会失效,需要对数据操作就又要去主内存中读取了,说白了就是,一个线程改变了一个共享变量,会告诉其他线程,让他们的变量失效。

当cpu写数据的时候,如果发现操作的变量是共享变量,即在其他cpu中也存在该变量的副本,这个时候会发出信号通知其他cpu将该变量的缓存置为无效转态,因此当其他cpu需要读取这个变量的时候,发现自己缓存中缓存该变量的缓存行是无效的,那么他就会从主内存进行读写。这就是缓存一致性协议。

4、那是怎么发现数据是否是失效了呢?

嗅探

每个处理器通过嗅探在总线上传播的数据来检查自己的缓存的值是否过期了,当处理器发现自己缓存行对应的地址被修改,就会将自己处理器的缓存行置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

5、为什么volatile不能保证原子性?

对于i=1这种赋值操作,由于其本身是原子操作,因此在多线程中不会出现不一致的问题,但是对于i++这种复和操作,即使用volatile修饰也不能保证原子性,可能会引发数据不一致的情况。我们可以是使用AtmoicInteger() 类来实现自增。

高并发下的锁的几种优化方案?

1、业务中常见的几种锁优化方案?

减少锁的持有时间:避免给整个方法加锁,

减小锁的粒度:将大对象,拆成小对象,大大增加并行度,降低锁竞争。如此一来偏向锁,轻量级锁成功率提高,一个简单的例子就是 jdk 内置的 ConcurrentHashMap 与 SynchronizedMap

使用读写分离替代独占锁:顾名思义,用 ReadWriteLock 将读写的锁分离开来,尤其在读多写少的场合,可以有效提升系统的并发能力。

读-读不互斥:读读之间不阻塞。

读-写互斥:读阻塞写,写也会阻塞读。

写-写互斥:写写阻塞。

锁分离:在读写锁的思想上做进一步的延伸,根据不同的功能拆分不同的锁,进行有效的锁分离。一个典型的示例便是 LinkedBlockingQueue,在它内部,take 和 put 操作本身是隔离的。有若干个元素的时候,一个在 queue 的头部操作,一个在 queue 的尾部操作,因此分别持有一把独立的锁。

锁粗话:通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短。

即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。

而凡事都有一个度,如果对同一个锁不停的进行请求同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。

使用TheadLocal:除了控制有限资源访问外,我们还可以增加资源来保证对象线程安全。

对于一些线程不安全的对象,例如 SimpleDateFormat,与其加锁让 100 个线程来竞争获取。

不如准备 100 个 SimpleDateFormat,每个线程各自为营,很快的完成 format 工作。

使用无锁操作,例如CAS,Atomic:与锁相比,使用 CAS 操作,由于其非阻塞性,因此不存在死锁问题,同时线程之间的相互影响,也远小于锁的方式。使用无锁的方案,可以减少锁竞争以及线程频繁调度带来的系统开销。

2、讲一下ConcurrentHashMap的分段锁?

分段锁其实是一种锁的设计,并不是一种具体的锁,对与ConcurrentHashMap来说其并发的实现就是通过分段锁的形式来实现的高效的并发操作。

ConcurrentHashMap的分段锁称为Segment,他类似与HashMap的结构,即内部拥有一个Entry数组,数组中的每一个元素又是一个链表,同时又是一个ReentrantLock。

当需要put的时候,并不是对整个HashMap来加锁,而是先通过hashCode来知道他要放到哪个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是在一个分段中,就实现了真正的并发插入。

但是,值统计size的时候,可就是过去hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

JAVA多线程

进程与线程的区别

进程:是实现某个独立功能的程序,它是操作系统(如windows 系统)进行资源分配和调度的一个独立单位,也是可以独立运行的一段程序。

线程:是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使⽤用多线程对进⾏行行运算提速。比如,如果⼀个线程完成⼀个任务要100毫秒,那么用十个线程完成改任务只需10毫秒

守护线程与用户线程有什么区别

守护线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作。

用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

形成死锁的四个必要条件是什么

  1. 互斥条件:在一段时间内,某个资源只由一个进程占用。如果此时其他进程请求资源,就只能等待,直到占有资源的进程释放。
  2. 占有且等待条件:进程至少保持一个资源,但又提出了新的资源的请求,而请求的资源被其他进程占有,此时请求进程阻塞,对自己已经获取的资源保持不放。
  3. 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A)

如何避免死锁

  1. 避免一个线程同时获得多个锁
  2. 避免一个线程在锁内同时占有多个资源,尽量保证每个锁只占有一个资源
  3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

创建线程的四种方式

继承Thread类

步骤:

  1. 定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体;
  2. 创建Thread子类的实例,也就是创建了线程对象;
  3. 启动线程,调用线程的start()方法。

实现 Runnable 接口

步骤:

  1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体;
  2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象;
  3. 调用线程对象的start()方法来启动线程。

使用 Callable 和 Future 创建线程

步骤:

创建实现 Callable 接口的类 MyCallable;

以 myCallable 为参数创建 FutureTask 对象;

将 FutureTask 作为参数创建 Thread 对象;

调用线程对象的 start() 方法

使用线程池创建线程

主要有四种:

  1. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  2. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  3. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  4. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。

实现Runnable接口与实现Callable接口有什么区别

相同点:

  • 都是接口
  • 都是编写多线程程序
  • 都采用Thread.start()启动程序

主要区别:

  • Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

线程有哪5个状态

线程5个状态:创建、就绪、运行、阻塞和死亡。

  1. 创建状态(new):在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  2. 就绪状态(Runnable):当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态,等待线程被调度选中,获取CPU的使用权。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  3. 运行状态(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
  4. 阻塞状态(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。

阻塞的情况:

    • 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waittingqueue)中,使本线程进入到等待阻塞状态;
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
    • 其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
  1. 死亡状态(dead):

线程run()、main()方法执行结束,或者因异常退出了run()方法,或调用stop()方法,则该线程结束生命周期。死亡的线程不可再次复生。

线程同步以及线程调度相关的方法有哪些

wait():使一个线程处于等待或阻塞状态,并且释放所持有对象的锁

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常

notify():唤醒一个处于等待的线程,在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪一个线程,而且跟线程的优先级有关。

notifyAll():唤醒所有处于等待的线程,该方法并不是将对象的锁给所有线程,而是由他们竞争,只有获得锁的线程才会进入就绪状态。

Spring 特性

说一下Spring中用到的设计模式

单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。

工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。

代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。

模板方法模式:主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法。

spring中的两大核心

AOP

aop是面向切面编程,用于将一些与业务无关的,但却对多个对象产生影响的公共行为和逻辑,抽取出来封装成一个模块,而这个模块就叫做切面。优点:减少系统中的重复代码,降低了模块间的耦合度,提高了系统的可维护性。主要用权限认证、日志、事务处理

aop的实现关键主要在于代理模式,分为JDK动态代理和CGLIB动态代理

JDK动态代理和CGLIB动态代理对比

JDK动态代理值提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过的是invoke()方法反射来代用目标类中的代码的。动态将逻辑和业务绑定在一起 。Proxy是利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象

如果代理类中没实现InvocationHandler接口,那么Aop会选择使用CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时动态生成指定类的一个子类对象,并且覆盖其中特定的方法来实现的aop。CGLIB是通过继承的方式做的动态代理。所以如果某各类被 标记为final,那么他是无法使用CGLIB代理的

aop中切面、切点、连接点、通知之间的关系

1、切面:就是将公共行为封装成模块之后的类

2、切点:描述何时执行通知的规则

3、连接点:被切点匹配执行通知的位置

4、通知:是程序公共行为执行的代码操作

IOC

IOC是控制反转,指的是创建对象的控制权利的转移,以前创建对象的主动权是自己,而现在 是将主动权交给了spring去管理,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,有利用功能的复用。DI依赖注入,和IOC是同一个概念的不同角度的描述,一个程序在运行时依赖IOC容器来动态注入对象 需要的外部资源

IOC就是 创建对象的时候不用在去new了,可以通过spring自动生产,使用java的反射机制,根据配置文件运行时动态去创建和管理,之后再去调用

spring的IOC是由三种注入方式:构造器注入、set方法注入、根据注解注入

AOP的应用场景有哪些呢

1、日志记录

2、权限验证

3、效率检查(个人在代码上,喜欢用注解+切面,实现校验,redis分布式锁等功能)

4、事务管理(spring 的事务就是用AOP实现的)

Spring Boot 的底层原理

Spring Boot 底层实际上主要的有三个注解:

configuration

EnableAutoConfiguation

ComponentScan

读取 mate-inf下的 spring.factories 文件信息

Spring Bean 的作用域范围

1、singleton(单例模式):一个bean中只有一个实例 (默认)

2、prototype:一个bean的定义可以有多 个实例

3、request

4、session

5、global Session

SpringMVC 的原理

客户端发起请求到dispatchservlet,dispatchservlet根据请求调用handlerMappering,解析出一个对应的handler,解析出的handler由handlerAdapter适配器去处理完成之后得到对应的moudleAndView,最后将view返回给客户

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值