JavaGuide
面试题会逐步补充
1. Java基础
1. 接口和抽象类的区别
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法
不同点:
- 接口主要是对类的行为进行约束;抽象类主要用于代码复用
- 一个类可以实现多个接口,但只能继承于一个类
- 接口中的成员变量只能是public static final类型的,不能被修改且必须有初始值;抽象类的成员变量默认为default类型,可在子类中重写
2. 深拷贝和浅拷贝的区别
浅拷贝只是增加了一个指针指向已存在的内存地址
深拷贝是增加了一个指针同时申请了一块新的内存,使这个增加的指针指向这块新的内存
3. String为什么不可变
JDK9之前,Strign的底层是final修饰的char型数组,JDK9之后调整为byte数组
- 保存字符串的数组被声明为私有的且被final修饰,而且String类没有提供修改字符串的方法
- String类被final修饰导致其不能被继承,从而避免了子类破坏String不可变
4. 字符型常量和字符串常量的区别?
- 字符型常量使用单引号引起一个字符,字符串常量使用双引号引起若干个字符
- 字符型常量只占两个字节,字符串常量占若干个字节
- 字符型常量相当于一个整型值(ASCII),字符串常量代表一个地址值
5. String、StringBuffer、StringBuilder的区别
- 可变性
- String对象不可变(原因如上)
- StringBuffer和StringBuilder都继承于AbstratStringBuilde类,这个类也是使用字符数组保存字符串,但没有使用final和private修饰,而且这个类还提供了很多修改字符串的方法
- 线程安全性
- String中的对象是不可变的,可以理解为常量,线程安全
- StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
- StringBuilder没有对方法添加同步锁,所以是线程不安全的
- 性能
- 每次对String类型进行改变的时候,都会生产一个新的String对象,性能较低。StringBuffer和StringBuilder都是对原对象本身进行操作,性能较高。
6. 字符串常量池
JVM为了提升性能和减少内存消耗针对字符串专门开辟的一块区域,主要是为了避免字符串的重复创建。
JDK7之前字符串常量池在方法区中,之后拿到了堆中。
7. 什么是泛型?泛型擦除?常用的通配符?
-
泛型是JDK5引入的一个新特性,提供了编译时类型安全检测机制,确保在泛型类型上只能使用正确类型的对象。
-
泛型擦除:Java在运行期间,所有的泛型信息都会被擦除,只保留原始类型。
-
常用通配符
- T(type):标识一个确定的类型
- ?:表示不确定的java类型
- K V (key,value):分别代表java中的key和value
- E(element):在集合中使用,因为集合中存放的是元素
8. Exception和Error的区别
- Exception:程序本身可以处理的异常,可以通过catch来解决。可以分为Cheked Exception和UnChecked Exception(可以不处理)
- Error:程序无法处理的错误,出现时Jvm一般会选中终止线程
9. Finally中可以使用return吗?
不可以,当try语句和finally语句中都使用了return语句时,try语句中的return会被忽略。try语句中的return返回值会被暂存到一个本地变量中,当执行到finally语句中的return之后,这个本地变量的值就变成了finally语句中的return返回值。
10. Finally中的代码一定会执行吗?
不一定:
- Finally执行之前,java虚拟机被终止运行的话,finally就不会执行
- 程序所在的线程死亡,finally也不会执行
- 关闭CPU
11. 序列化与反序列化
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中生成的二进制字节流转换为数据结构或者对象的过程
12. IO流分为几种?
- 按流向分:输入流和输出流
- 按操作单元分:字节流(无缓冲区 inputStream和outPutStream)和字符流(自带缓冲区 Reader和Writer)
- 按流的角色分:节点流(直接操作数据读写的流类)和处理流( 是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。)
13. IO流的特性?
- 先进先出:最先写入输出流的数据最先被输入流读取到
- 顺序存取:写入和读取都可以按顺序进行,但不可以随机访问中间的数据
- 只读或只写:输入流只能进行都操作,输出流只能进行写操作
14. Comparable与Comparator的区别
- Comparable是lang包下的一个接口,它有一个compareTo(Object object)方法
- Comparator是java.util包下的一个接口,它有一个compare(Object object1,Object object2)方法来排序
15. 重载和重写的区别
重载:发生在同一个类中,方法名相同,形参列表不同,方法的返回值类型和修饰符可以不同
重写:发生在父子类中,方法名,形参列表必须相同,返回值的范围不大于父类,抛出的异常范围不大于父类
16. 双亲委派机制
每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会将请求委派给父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 Bootstrap ClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 Bootstrap ClassLoader
作为父类加载器。
2. 容器
1. Java中的容器有哪些?分别有什么区别?
- List:存储的元素是有序、可重复的
- Set:存储的元素是无序、不可重复的
- Queue:存储的元素是有序、可重复的
- Map:使用键值对存储,key是无序、不可重复的,value是无序、可重复的
2. 集合框架底层数据结构
- List
- ArrayList:数组
- Vector(古老实现):数组
- LinkedList:双向链表
- Set
- HashSet(无序、唯一):底层使用HashMap来保存元素
- LinkedHashSet:HashSet的子类,使用HashMap实现
- TreeSet(有序,唯一):红黑树
- Queue
- PriorityQueue:数组实现二叉堆
- ArrayQueu:数组+双指针
- Map
- HashMap:数组+链表+红黑树
- LinkedHashMap:HashMap的子类,在HashMap的基础上,增加了一条双向链表
- Hashtable:数组+链表
- TreeMap:红黑树(自平衡的排序二叉树)
3. ArrayList和LinkedList的区别
-
底层数据结构不同:ArrayList底层是数组,LinkedList底层是双向链表
-
是否支持快速随机访问:ArrayList支持高效的随机元素访问,而LinkedList不支持
-
插入和删除是否受元素位置的影响:ArrayList受影响,LinkedList不受影响
ArrayList底层是数组,支持高效的随机元素访问,插入和删除数据受元素位置的影响
LinkedList底层是双向链表,不支持随机元素访问,插入和删除不受元素位置的影响
4. HashMap的底层原理?扩容机制?
jdk8之后使用数组+链表+红黑树的底层机制,通过put和get方法存储和获取数据,使用put方法存储数据时,先对键做一个hashcode运算得到它在bucket数组中的位置来存储entry对象 。使用get方法获取对象时,先找到bucket数组的位置,再通过键对象的equals方法找到正确的键值对,返回对象。
hashMap初始长度就是16,负载因子是0.75。HashMap所容纳的最大数据量为:长度*负载因子。即当长度达到这个值的时候就会发生扩容。每次扩容两倍。
5. ArrayList扩容机制
ArrayList扩容时是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。默认情况下,新的容量会是原容量的1.5倍
3. 线程
1. 什么是死锁?解决方式?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或彼此通信而造成的一种阻塞现象。
产生死锁的4个必要条件:
- 互斥条件:一个资源每一次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源阻塞时,不释放已获得的资源
- 不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
如何避免死锁:
- 避免一个线程同时获得多个锁
- 尽量保证每个锁只占用一个资源
- 尝试使用定时锁替代内部锁机制
2. 什么是进程和线程?
进程:CPU执行单个任务的过程,是程序在执行过程中CPU资源分配的最小单位
线程:是CPU调度的最小单位,它可以和属于同一个进程的其他线程共享这个进程的全部资源
协程:是一种用户态的轻量级线程,调度由用户控制。
关系:一个进程包含多个线程,一个线程只能在一个进程中,每一个进程最少包含一个线程
3. 并行与并发
并行:单位时间内,多个任务同时执行
并发:同一时间段,多个任务同时执行
4. Synchronized
synchronized是为了解决多个线程之间访问资源的同步性,它可以保证被修饰的方法或者代码块任意时间只能有一个线程执行
三种使用方式:
- 修饰静态方法(当前类加锁)
- 修饰实例方法(当前对象实例加锁)
- 修饰代码块(指定加锁对象,对给定对象或类加锁)
5. Synchronized和Volatile的区别
- volatile是线程同步的轻量级实现,性能要比synchronized要好,但是只能用于变量
- volatile只能保持数据的可见性,不能保持数据的原子性。synchronized都可以保证
- volatile主要解决变量在多个线程之间的可见性。synchronized解决的是多个线程之间访问资源的同步性。
6. Synchronized和ReentrantLock的区别
- 两者都是可重入锁/system/msgSendRecord
- Synchronized依赖于JVM而ReentrantLock依赖于API
- ReentrantLock比Synchronized增加了一些高级功能
7. ThreadLocal类
实现每个线程都有自己的专属本地变量,避免了线程安全问题,实现线程隔离。
ThreadLocal为什么会导致内存溢出:
线程的ThreadLocals中放入的entry没有被及时的remove掉,导致线程的生命周期持续变长时,积累的对象越来越多而且无法被回收,最终内存溢出。
8. 使用线程池的好处
- 降低资源消耗
- 提升响应速度
- 提高线程的可管理性
9. Runable接口和Callable接口的区别
Runnable不会返回结果或抛出异常提示,Callable接口可以
10. Execute()和Submit()方法的区别
- execute()用于提交不需要返回值的任务,所以无法判断任务是否被线程池成功执行,遇到异常会直接抛出
- submit()用于提交需要返回值的任务,线程池会返回要给Future类型对象,通过这个对象可以判断任务是否执行成功,遇到异常不会直接抛出,只有使用Future的get方法获取返回值时,才会抛出异常
11. 如何创建线程与线程池
线程的创建方式:
- 继承Thread类型,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,重写call方法
- 使用线程池创建线程
线程池的创建方式:
- 通过ThreadPoolExecutor构造方法实现
- 通过Executor框架的工具类Executors实现
12. Sleep和wait方法的区别
Sleep方法声明在Thread类中,必须指定睡眠时间,可以在任意场景下使用,不会释放同步锁
wait方法声明在Object类中,可以指定或不指定睡眠时间,只能使用在同步代码块或者同步方法中,会释放同步锁
13. AQS原理
AQS即AbstractQueuedSynchronizer(抽象队列同步器),这个类在java.util.concurrent.lock
包下面,AQS就是一个抽象类,主要用来构建锁和同步器。
原理:AQS核心思想是,如果请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果请求的共享资源被占用,则需要一套线程阻塞等待以及唤醒时锁分配的机制,这个机制是用CLH队列锁实现的,即将暂时获取不到锁的线程加入队列中。
14. 线程池的7个参数
- corePoolSize:线程池核心线程大小
- maximumPoolSize:线程池最大线程数量
- keepAliveTime:空闲线程存活时间
- unit:空闲线程存活时间单位
- workQueue:工作队列
- threadFactory:线程工厂
- handler:拒绝策略
15. Java中notify()和notifyAll()有什么区别
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候才使用它
notifyAll()方法可以唤醒所有的线程并允许他们争夺锁并确保了至少有一个线程能够执行
16. 为什么 wait/notify/notifyAll 这些方法不在 thread 类里面?
Java提供的锁是对象级的锁,而不是线程级的锁,每个对象都有锁,通过线程获得。
17. 怎么唤醒一个中断的线程
如果线程是调用wait()、sleep()或者join()方法而造成的阻塞,则可以中断线程,并通过抛出interreptedException来唤醒线程。
如果线程遇到了IO阻塞,则无法唤醒。
18. 线程 yield()方法有什么用?
yield()方法可以暂停当前正在执行的线程对象,让其他有相同优先级的线程执行。
19. 一个线程运行时发生异常会怎样?
如果异常没有被捕获,则该线程会停止运行。
20. CountDownLatch(倒计时器)和CyclicBarrier(循环栅栏)的区别
- CountDownLatch:允许多个线程阻塞在一个地方,直至所有线程的任务都执行完毕
- 使用场景:启动一个服务时,主线程需要等待多个组件加载完成之后再继续执行
- CyclicBarrier:让一组线程到达一个同步点时被阻塞 ,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。
- 使用场景:多线程计算数据,最后合并计算结果
CountDownLatch
是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而 CyclicBarrier
更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
21. 如何控制同一时间只有2个线程运行?
- 同时控制两个线程进入临界区,可以用信号量。
- 使用生产者,消费者模型,想要进入临界区的线程先在一个队列中等待,然后消费者每次消费两个。
4. Mysql
1. Drop、Delete、Truncate的区别
Drop:直接将表删除
Delete:删除某一列数据
Truncate:清空表数据时使用
2. Mysql存储引擎
- InnoDB
- MyISAM
- Memory
- Archive
3. InnoDB和MyISAM的区别
- 是否支持行级锁:InnoDB支持行级锁和表级锁,MyISAM支持表级锁
- 是否支持事务:InnoDB支持事务,MyISAM不支持事务
- 是否支持外键:InnoDB支持外键,MyISAM不支持
- 是否支持数据库异常崩溃之后的安全恢复:InnoDB支持,MyISAM不支持
4. 事务的特性
- 原子性:事务是最小的执行的单位,不允许分隔
- 一致性:执行事务前后,数据保持一致
- 持久性:并发事务之间数据库是独立的
- 隔离性:一个事务被提交之后,它对数据库中数据的改变是持久的
5. 并发事务的问题
- 脏读:
- 幻读:
- 不可重复读:
- 丢失修改:
6. 事务的隔离级别与传播属性
隔离级别(4种):
- READ-UNCOMMITTED(读未提交):最低的隔离级别,可能导致脏读、幻读或不可重复读
- READ-COMMITTED(读已提交):允许读取并发事务已提交的数据,可以阻止幻读(脏读,不可重复度)
MySQL默认隔离级别
REPEATABLE-READ(可重复读):对于同一个字段的多次读取结果都是一致的,可以阻止脏读和不可重复读(幻读)- SERIALIZABLE(可串行化):最高隔离级别,所有事务依次逐个执行,可以避免脏读、幻读、不可重复读
传播属性(7种):
- PROPAGATION_REQUIRED——支持当前事务,如果当前没有事务,就新建一个事务
- PROPAGATION_SUPPORTS——支持当前事务,如果当前没有事务,就以非事务方式执行
- PROPAGATION_MANDATORY——支持当前事务,如果当前没有事务,就抛出异常
- PROPAGATION_REQUIRES_NEW——新建事务,如果当前存在事务,则把当前事务挂起
- PROPAGATION_NOT_SUPPORTED——以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- PROPAGATION_NEVER——以非事务方式执行,如果当前存在事务,则抛出异常
- PROPAGATION_NESTED——如果当前存在事务,则在嵌套事务内执行。如果没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
7. 什么是索引?优缺点?
索引是一种用于快速查询和检验数据的数据结构,常见的索引结构为:B树,B+树和Hash
优点:
- 大大加快数据的检索速度
- 通过创建唯一性索引,可以保证数据表中的每一行数据的唯一性
缺点:
- 创建和维护索引需要耗费很多时间
- 索引需要使用物理文件空间存储,会消耗一定空间
索引底层:
B树:叶子节点与非叶子节点都必须存储数据
B+树:只有叶子节点才存储数据,相同层级下,B+树存储的数据更多
索引的类型:
- 普通索引
- 唯一索引
- 主键索引
- 组合索引 (遵循最左匹配原则,且mysql查询时会动态调整查询参数的位置以便更好的使用索引)
- 全文索引
9. 创建索引的方式
- 新建表中添加索引
- 已建表中添加索引
- 以修改表的方式添加索引
10. 脏读、幻读、不可重复读
- 脏读:读取到未提交数据
- 幻读:前后多次读取,数据总量不一致
- 不可重复读:前后多次读取,数据内容不一致
11. 并发三大特性
-
原子性
-
有序性
-
可见性
5. Spring
1. Spring模块
- Spring Core
- Spring Aspects
- Spring AOP
- Spring Web
- Spring Test
- Spring DataAccess
2. IOC(Inverse of control)
IOC(控制反转),是一种设计思想,是将程序中原本手动创建对象的权力交给spring框架管理
控制:创建对象的权力
反转:控制器交给外部环境
3. SpringAOP 和 AspectAOP的区别
SpringAOP:运行时增强,基于动态代理
AspectAOP:编译时增强,基于字节码操作
4. @Autowire和@Resource的区别
@Autowire
是Spring
提供的注解,@Resource
是JDK
提供的注解@Autowire
默认注入方式为byType
(根据类型匹配),@Resource
默认注入方式为byName
(根据名称匹配)- 当一个接口存在多个实现类时,
@Autowire
和@Resource
都需要通过名称才能匹配到正确的Bean。@Autowire
可以使用@Qulifire
注解来显示指定名称,@Resource可以通过name属性显示指定名称
5. Bean的作用域
- singleton:唯一Bean实例,单例模式体现
- prototype:每次请求都会创建一个新的Bean实例
- request:每一次HTTP请求都会创建一个新的Bean实例
- session:每一次来自新session的请求都会创建一个新的Bean实例
- global-session:全局Session作用域,Spring5中已去除
6. Bean的生命周期
- Bean容器找到配置文件中Bean的定义
- 利用反射创建一个Bean的实例
- 如果涉及到一些属性值,利用set方法设置属性值
- (如果Bean实现了
BeanNameAware
接口,调用setBeanName
方法传入Bean的名字 - 如果Bean实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader方法
,传入classLoader
对象的实例 - 如果Bean实现了
BeanFacoryAware
接口,调用setBeanFactory
方法,传入BeanFactory
对象的实例) - 如果Bean实现了一些Aware结尾相关接口,就调用对应的方法
- 如果存在与Bean关联的
BeanPostProcessor
对象,调用postProcessBeforeInitializaiton
方法 - 如果Bean在配置文件中的定义包含
init-method
属性,执行指定的方法 - 初始化之后,如果存在与Bean关联的
BeanPostProcessor
对象,执行postProcessAfterInitialization
方法 - 当要销毁Bean的时候,如果Bean实现了
DisposableBean
接口,执行destroy
方法 - 当要销毁Bean的时候,如果Bean在配置文件中的定义信息包含
destroy-method
属性,执行指定的方法
7. 解决Spring单例Bean的安全问题
分两种情况:
- 强制使用单例Bean
- 使用 ThreadLocal进行线程隔离
- 尽量不要使用成员变量
- 可以创建多个Bean
- 修改类的作用域为prototype
8. IOC的启动过程与底层实现
启动过程:
将容器内部后置处理器的Bean定义信息给注册到IOC容器中,向容器中添加注解类型的扫描器,指定类路径下的Bean扫描策略,将自定义配置类的Bean定义信息注册到IOC容器中,执行容器的核心方法refresh(),启动IOC容器。
实现原理:
先通过createBeanFactory创建一个Bean工厂(DefaultListableBeanFactory),开始循环创建对象,因为容器中的Bean默认都是单例的,所以优先通过getBean,doGetBean从容器中查找,找不到的话再通过createBean,doCreateBean方法以反射的形式创建对象,一般调用的是无参构造方法(getDeclaredConstructor),最后进行对象的属性进行填充与其他初始化操作(initializingBean)。
9. 什么是Spring框架
Spring 是一个以 IOC 和 AOP 为核心的容器框架,用于简化项目的开发,其中IOC是控制反转,即将创建对象的权力交由spring容器管理,降低代码耦合度,AOP是面向切面编程,即将那些与业务无关却为业务逻辑所调用的模块,例如日志记录,权限管理等抽取出来,提高代码复用性,便于维护。
10. 谈谈你对Spirng IOC的理解,原理与底层实现?
IOC即控制反转,将创建对象的权力交由Spirng容器来管理,通过DI(依赖注入)的方式将对应的属性值注入到具体的对象中,它本质是一个存储对象的容器,使用map结构来存储,Bean对象从创建到使用到销毁的过程都由容器来管理。
11. Spring事务什么时候会失效
- 访问权限问题,spring要求被代理方法必须是public的,否则事务会失效。
- 方法被final修饰过
- 未被spring管理
- 方法内部调用
12. Spring是如何解决循环依赖的?
使用三级缓存来解决循环依赖问题,其中一级缓存存储完整的对象,二级缓存存储实例化但未初始化的对象,三级缓存是一个函数式接口用来确保容器的运行过程中同名的Bean对象只有一个。查找的顺序是:一级缓存——>二级缓存——>三级缓存。
为什么需要三级缓存:因为普通对象和代理对象不能同时出现在容器中,如果一个对象需要被代理时,就要使用代理对象覆盖之前的普通对象,由于在实际调用中,无法确定什么时候对象被使用,所以就要求某个对象被调用时,优先判断对象是否需要被代理,因此可以通过lambda表达式来执行对象的覆盖过程。
13. Spring中用到的设计模式
单例模式:Spring容器中的Bean默认都是单例的
原型模式:指定Bean的作用域为prototype
工厂模式:BeanFactory
模板方法:postProcessorBeanFactory
观察者模式:listener,event
代理模式:动态代理
14. Spring的事务传播级别
required,requires_new,nested,supports,not_supported,never,mandatory
事务的传播特性是指不同方法的嵌套调用的过程中,是用同一个事务还是不同的事务,当出现异常时提交还是回滚,在日常工作中,使用较多的时required,requires_new,nested
事务可以分为三类,支持当前事务,不支持当前事务,嵌套事务
- 如果外层方法是required,内层方法是required,requires_new,nested
- 如果外层方式是requires_new,内层方法是required,requires_new,nested
- 如果外层方法是nested,内层方法是required,requires_new,nested
15. Spring事务是如何回滚的
Spring的事务是由AOP来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来具体的操作逻辑,通过TranSactionInterceptor来实现的,然后调用invoke来执行具体的逻辑。
16. @Configuration和@Component的区别
@Configuration中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例,可以保证单例。 @Component 注解每次都会返回一个新的实例。
17. BeanFactory和FactoryBean的区别
相同点:都是用来创建Bean的
不同点:通过BeanFacotory生成的Bean对象要经过完整的生命周期流程,比较麻烦,而FactoryBean可以自定义生成Bean对象的逻辑。
18. Spring中常用的注解
-
声明为Bean的注解
-
- @Component
- @Service
- @Repository
- @Controller
-
注入Bean的注解
-
- @Autowired(Spring提供)
- @Resource (JDK提供)
-
配置类相关注解
-
- @Configuration 声明当前类为配置类
- @Bean 注解在方法上,声明当前方法的返回值为一个bean
- @ComponentScan 用于对Component进行扫描
-
其他
-
- @Value 获取配置文件的值
6. SpringBoot
1. SpringBoot的核心注解是哪个,它由哪几个注解组成?
核心注解:@SpringBootApplication,它由@SpringBootConfiguration和@EnableAutoConfiguration和@CompantScan组成
2. SpringBoot事务的使用
在启动类使用@EnableTransactionManagement开启事务之后,使用@Transaction注解开启事务
3. SpringBoot异步调用方法
在启动类使用@EnableAsync开启异步方法,然后在方法上使用@Async注解即可标注方法为异步方法
4. 什么是Spring自动装配
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器中,并执行类中定义的各种操作。
5. SpringBoot自动装配原理
- 当启动SpringBoot 应用程序的时候,会先创建
SpringApplication
对象,在对象的构造方法中会进行某些参数的初始化工作,最主要是判断应用程序的类型以及初始化器和监听器,在这个过程中会加载应用程序的Spring.factories文件,将文件内容放到缓存中,方便后续获取。 SpringApplicaiton
对象创建完成之后,开始执行run方法来完成启动,启动过程中有两个主要的方法,分别是prepareContext
和refreshContext
,这两个关键方法完成了自动装配的核心功能,处理逻辑包含上下文对象的创建,banner的打印和异常报告期的准备工作,方便后续进行调用。- 在
prepareContext
方法中主要完成上下文对象的初始化操作,包括属性值的设置,比如环境对象。在整个过程中有一个非常重要的方法,叫做load,它主要是将当前启动类作为一个beanDefinition
注册到registry中,方便后续在进行BeanFactoryPostProcessor
调用时,找到对应的类来完成@SpringBootApplication与@EnableAutoConfiguration注解的解析工作。 - 在
refreshContext
方法中会对整个容器进行刷新,调用Spring中的refresh方法,refresh方法中有13个关键的方法来完成整个Spring容器的启动。在自动装配的过程中,会调用invokeBeanFactoryPostProcessor
方法,在此方法中主要是对ConfigurationClassPostProcessor
类的处理,它是BeanFactoryPostProcessor
和BeanDefinitionRegistryPostProcessor
的子类,在调用时会先调用BeanDefinitionRegistryPostProcessor
中的postProcessBeanDefinitionRegistry
方法,然后调用postProcessBeanFactory
方法,在执行postProcessBeanDefinitionRegistry
的时候会解析处理各种注解,包含@ComponentScan、@Bean、@Import 等注解,最主要的是@Import注解的解析 - 在解析@Import注解的时候,会有一个
getImports
的方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processImport
方法中对import的类进行分类,主要识别AutoConfigurationImportSelect
归属于ImportSeclect
的子类,在后续过程中会调用deferredImportSelectorHandler
中的process
方法,来完成EnableAutoConfiguration的加载。
79. SpringBoot主要的注解
SpringBoot启动类的@SpringBootApplilcation注解由@SpringBootConfiguration和@EnableAutoConfiguration和@ComponentScan三个注解共同完成自动装配。其中@SpringBootConfiguration标记启动类为配置类,@ComponentScan实现启动时扫描启动类所在的包以及子包下所有标记为bean的类,并由IOC容器注册为bean,@EnableAutoConfiguration标明开启自动配置
7. Redis
1. Redis常见数据类型
- String
- 使用场景:缓存、计数器、Session
- List
- 使用场景:消息队列、微博TimeLine
- Hash
- 使用场景:缓存,相比string更节省空间
- Set
- 使用场景:标签、点赞、收藏、
- ZSet
- 使用场景:排行榜
- Stream
- 使用场景:消息队列
2. Redis两种持久化方式,有什么区别?
-
RDB持久化:
- RDB持久化是指在指定时间内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,由子进程去做后面的操作。先将数据集写入到临时文件,写入成功后,在替换之前的文件,用二进制压缩存储。触发RDB分为手动触发和自动触发。
-
AOF持久化:
- AOF持久化是以日志的形式记录服务器所处理的每一个写,删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
注:AOF更加倾向于数据的完整性,而RDB系统的性能会比AOF好一点
-
怎么使用这两种持久化
redis 支持同时开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
3. 缓存相关
-
缓存穿透:指请求的是缓存和数据库中都没有的数据
- 解决方法:接口层增加校验,也可以将key-value键值对写为key-null,缓存有效时间可以设置较短,防止用户用一个id反复攻击
-
缓存击穿:指请求的数据缓存中没有,但是数据库中有
- 解决办法:设置热点数据永不过期,接口做熔断降级处理
-
缓存雪崩:缓存中的数据大批量过期,请求直接向数据库中查询
- 解决办法:缓存数据的过期时间设置为随机,设置热点数据永不过期
4. 如何保证Redis与数据库的数据一致性
- 延时双删策略:先删除缓存,再改数据库,然后休眠一小会,再删除缓存。(只保证了最终一致性)(要求写操作不能太频繁)
- 引入MQ保证原子操作,防止删除缓存失败问题出现而引发的业务问题。(只保证了最终一致性)如下图
- 参考链接:https://zhuanlan.zhihu.com/p/346529817
8. MyBatis
1. #{} 和 ${}的区别是什么?
- .
#{}
是sql的占位符,Mybatis会将sql中的#{}
替换为?
号,在sql执行前使用PreparedStatement
方法,按序给sql
的?
号占位符设置参数值,可以避免sql注入 ${}
是Properties
配置文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换。
2. 如果获取生成的主键id
新增标签中添加keyProperty=ID
即可
3. Mybatis如何执行批量操作
4. Mybatis的缓存
MyBatis 的缓存分为一级缓存和二级缓存,一级缓存放在 session 里面,默认就有,二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置
9. JPA
10. 微服务
1. 微服务的架构思想,解决了什么?
将系统业务按照功能拆分为更细粒度的服务,所拆分的每一个服务都是一个独立的应用
解决了什么:降低了业务的复杂度,提升了扩展性与可维护性
2. 微服务项目中使用的组件
nacos:注册中心、发现中心
openFeign:远程调用
gateway:网关
seata:分布式事务
Oauth2+jwt+redis+gateway实现统一鉴权
3. Nacos如何判断服务实例的状态
通过发送心跳包,5秒发送一次,如果15秒没有回应,则说明服务出现了问题,
如果30秒后没有回应,则说明服务已经停止。
11. 项目相关
1. 杭四有几个服务,分别是什么?
共八个服务,分别是:
- auth:认证服务
- gateway:网关服务
- repair:架修服务
- staff:员工服务
- system:系统服务
- vehicle:车辆服务
- warehouse:仓储服务
- jobconvert:文件预览服务
12. Linux
1. 查看日志命令和查进程
tail -f
ps -ef | grep
2. 常用Linux命令
- cd命令
- ls命令
- pwd命令
- mv命令
- cp命令
- cat命令
- tail命令
13. 其他
1. 如何保证接口的幂等性
1、前端拦截
2、使用数据库实现幂等性
3、使用 JVM 锁实现幂等性
4、使用分布式锁实现幂等性
2. SpringMVC执行流程
用户发送请求到前端控制器(DispatcherServlet),前端控制器收到请求调用处理器映射器(HandlerMapping)根据请求URL找到对应的处理器生成处理器执行链并返回给前端控制器,前端控制器再请求处理器适配器(HandlerAdapter)调用相应的Handler(处理器)进行处理并返回ModelAndView给前端控制器(DispatcherServlet),前端控制器请求视图解析器(ViewReslover)解析ModelAndView,返回具体的View视图,最后前端控制器对视图进行渲染,将页面响应给用户。
3. Cookie和Session的区别
- cookie是存储在客户端,可以存储4k的数据
- session存储在服务的,存储数据大小和服务器有关
- cookie支持跨域名访问,session不支持跨域名访问
如果浏览器禁用了cookie如何使用session 或者 保持登录状态?
- 可以使用url地址重写把session id直接附加在URL路径的后面
4. 在地址栏中输入一个地址回车会发生哪些事情?
解析URL——>建立链接——>发送HTTP请求——>返回数据——>渲染页面——>断开连接
介绍:你好,我叫陈鑫宝,今年23岁,有一年的Java开发工作经验,在前公司参与开发过几个项目,其实包括企业定制软件、政府系统服务和自研软件,有SpringBoot,Mybatis,Mysql,Redis等相关技术的开发经验,目前已经离职。
在这基础上, 还可以按如下的要点准备项目的难点,即亮点说辞。
先说下可能被大多数求职者写入简历并在面试时当亮点准备,但其实不是亮点的要素。
1 做了很多的业务模块。其实这些都是属于增删改查的操作,可能业务细节有差别,但从框架层面来上看,其实差别不大。业务开发多了,顶多是熟练初级开发和新手初级开发的差别。
2 同时做了前端和后端,或者前端界面很好看,或者在前端引入了多种效果和框架。其实java主要是负责后端,讲具体点就负责从(控制器层)收到请求开始处理的一系列动作。如果求职者有前端开发经验,这是个加分项,但面试官一定不会因为前端做好但后端不熟而录用这位求职者,相反哪怕求职者只熟悉后端开发,前端一点不懂,这也照样有机会应聘成功。
3 实现了一些较为复杂的功能,比如对日期进行特殊处理的能力,或者能实现很复杂的业务逻辑。上文也说了,能处理较为复杂的业务,依然属于开发项目的能力,有这项技能,顶多能证明求职者属于熟练的初级开发。
况且,上述要点在面试中还未必能很好地展示,比如你说你前端界面做的好看,你不大可能当场演示吧,比如你说你做的业务模块数量多,功能复杂,在面试时也未必能很好地说明,但如下给出的亮点,一方面难度不高,对初级开发也适用,另一方面准备起来也不难,面试前只要方法得当,只要花费少量时间就能达到能侃侃而谈的地步。
亮点1,我熟悉项目开发、测试和部署等流程。
其实这个亮点是白送的,只要是做过项目,那一定经历过这个流程,但面试中很少有人能把这当成亮点说。这块你可以说,在这个项目里,我们是用Maven管理项目,用jenkins部署项目,用git来管理代码。我在这个项目里,除了写代码外,还参与了单元测试和联调。我们代码发布时,最终会把java代码打成jar包并部署到linux服务器上。
说实在,不少面试官也不会认为此项亮点有多值钱,但你这样会给面试官留下“有项目测试、部署和管理等方面的综合能力”的印象。而且,我知道有些求职者,尤其是在校应届生,他们在简历上的的项目可能是学习项目,但如果你在项目里加上这个亮点,那么如果面试官不细问,就会认为这是更加值钱的商业项目。
亮点2,我有过数据性能调优方面的经验。
如果你就说,我用过索引和redis缓存,也有数据库调优经验,那么听起来太空洞,而且面试可能会通过问相关的底层细节来确认。要知道redis和数据库底层细节太繁琐,求职者,尤其是初级开发的求职者被问倒的可能性很大,但如果你按如下的方式植入项目后再说,就能最大程度展现该亮点。
1 我们项目在部署后,会对MySQL数据库进行监控,这块的监控软件有CAT、newrelic或Zabbix等,一旦数据库SQL语句执行时间超过5秒(或其它时间),我们就会收到告警邮件。
对于初级开发来说,你甚至不用说监控的细节,因为这不是初级开发的职责范围,你就说这套监控是运维或项目经理搭建的。不过总是先得知道哪些SQL需要调优,才能继续往下说吧,你说出这套说辞,就能让面试官感觉你的数据库调优经验来自项目。
2 你得把分析过程讲出来,遇到长SQL以后,我们会用执行计划(Execute Plan)分析该sql,这里大家可以去了解下执行计划的知识,比如如何运行,其中包含哪些内容。同时,我们会通过日志,去看当时做了什么业务。
3 经过分析,通过看执行计划和看日志后,我们知道了原因,原因最常见的是索引,或者是存在多张大表关联,或者是同类sql太多,对数据库压力太大,没建Redis缓存。
在准备亮点前,需要看下索引和Redis缓存的概念,比如索引的数据结构,哪些情况下该建索引,复合索引是什么,Redis的数据结构是什么?Spring Boot项目是怎么使用Redis缓存的。
但这里,为了增加可信度,**你更要结合项目需求说。**比如你说,对此我们建了索引,这样就很笼统,但你如果说,经过看执行计划后发现,是因为在从订单明细表里根据用户名去搜索时,对用户名字段没建索引,或者说,在下订单的过程中,需要根据用户ID多次到风控表里去查数据,这块没用缓存,所以导致性能慢。 这样结合业务说,就一定能在潜移默化过程中,向面试官灌输“你调优经验来自项目”的事实。
4 同时再讲下怎么解决的,有始有终。解决方法无非是建索引、建复合索引或引入缓存。
5 你在说好上述整套说辞后,面试官可能会问一些细节,比如索引和Redis的概念等,这块你一定得需要刷八股文来准备。这块如果你心有余力的话,甚至可以准备些值钱技能,比如Redis缓存穿透、Redis缓存的超时时间,或者是Redis持久化技能,或者你还可以再准备,通过Redis集群或MyCAT分库分表组件来提升数据库性能。
其实面试官对性能慢的原因以及解决方法不怎么关注,更关注一整套分析和解决数据库性能问题的步骤和思路。而且上述说辞和准备要点所涉及到的技能初级开发都能准备到位。而且,哪怕简历上的项目是学习项目,或者来自培训班(当然应当尽量让面试官确信是商业项目),照样可以套上上述数据库调优方面的说辞。
亮点3,我有过JVM调优方面的经验。
这里就直接展开说明了。
1 还是先说监控,我们项目在部署后,会对JVM内存进行监控,CAT、newrelic或Zabbix等同样能监控JVM内存,一旦内存用量超过80%,且持续时间超过5分钟,我们就会收到告警邮件。
这里额外说下,为了确保项目的高可用,一般代码是会被部署到多个linux服务器上,比如把spring boot项目打成jar包,再通过jenkins,或干脆手动复制,部署到多个linux服务器上。每台部署项目的服务器,都会部署这套监控系统。
2 再说排查流程。收到告警邮件后,我们会看该服务器的dump文件,其中能看到当时的内存对象,同时会看业务日志,看当时是什么业务导致了内存用量大增。
这里大家可以去看下dump文件的结构,同时可以操作下通过jmat工具打开dump文件的步骤,因为你一旦说了,面试官可能会细问。
3 再结合业务说原因,比如可以说,在导入客户数据的过程中,我们是会从多个文件里读取数据再导入,在导入后,没有关闭IO对象,所以导致内存用量大增。
同样能导致内存用量大增的原因还有,用好HashMap等没有不关掉,或者是缓存Redis数据时没设置超时时间,这就导致缓存对象一直占内存。如果大家要说其它原因,最好也得结合业务说。
4 再说下怎么解决,无非是关掉文件或对象。不过这里,请大家同时要准备下Java虚拟机结构、垃圾回收流程和垃圾回收算法等细节,有可能面试官会进一步提问。相关内容大家可以参考我写的这篇文章。
jvm面试都有什么问题?1 赞同 · 0 评论回答3 赞同 · 0 评论回答4 赞同 · 0 评论回答
亮点4,我有过通过看linux日志分析和解决问题的能力
这个亮点有哪些值钱点呢?大多数初级开发是只会在windows上做业务,不知道项目是部署在linux服务器上,而且缺乏linux的操作经验。这个亮点所涉及的linux技能比较简单,初级开发也能说,但你一旦说出这个亮点,就不仅能进一步证明该项目是商业项目,而且还能说明你有linux开发经验,更不要说你还具有分析和排查问题的能力了。这方面的亮点该怎么说呢?老规矩结合项目说。
1 先说下问题的表现形式。你可以说,在一些下订单的流程中,经常会出现500服务器错误。
2 再说怎么排查。发现问题后,你登录到linux服务器上,用vi命令打开日志文件,再用根据错误关键字和时间,搜索找到上下文,再根据该日志的线程ID去看在该日志文件的其它 日志,如果涉及到其它业务模块,可以用traceID去找。
3 再说下问题原因和怎么解决。原因你怎么说都行,比如价格参数不符规范,或请求类型应该是POST但发GET,往深了说可以说是因高并发导致的问题。怎么解决就一句话的事情,比如发现问题后,我们加了异常处理机制。
4 同时再去看下项目里通过logback输出日志的方式,和linux打开文件搜索关键字等命令,以便面试官细问。
亮点5,我有过通过看底层源码排查问题的能力
对于初级开发,可以说个简单点的,比如在库存盘点的业务流程里,我们是要通过迭代器遍历ArrayList类型的库存信息。在遍历时,我们同时做了修改,所以就导致了“快速失效”的问题。
快速失效的底层源码不难,网上一搜一大堆,而且解决方法也简单,就别边遍历边修改了。
关于底层源码,哪怕是初级开发也可以准备ConcurrentHashMap的,因为其中不仅包含了红黑树等数据结构,还包含了transient和violate等关键字,还包含了线程同步等细节,而且底层源码不难,网上资料很多。
当然面试中可以准备的亮点不止这些,还可以有分布式组件和集群等,不过上文提到的亮点,哪怕是零基础的,准备起来也不难。再啰嗦下,准备时需要注意如下的要点。
1 别单写在简历上,更要按上文给出的思路准备说辞,面试时一定会被问。
2 我知道,哪怕是不少正规项目,也未必能有上述亮点的实践机会,更别提学习项目了。所以在准备时,一方面需要掌握技能,另一方面更得结合项目需求和业务说。
3 得按监控、发现、排查和解决的流程说,同时更要准备相关细节,比如数据库调优方面需要准备Redis和索引等八股文。当然,准备亮点的同时,八股文和算法这类功夫不能少。
4 有时候面试官会质疑,或者细问,比如会问,这么简单的问题为什么你们之前的测试没发现?你或者可以说是在测试环境上发现这个问题的,或者就干脆咬定,是在生产环境上发现。至于为什么之前没测出来,你就说你不知道,毕竟你才是一个初级开发。
或者会问,你说了通过jenkins部署,说了通过new relic监控,你说下细节。或者问些比较深的问题。这些事情其实是资深开发或架构做的,你一个初级开发能知道就不错了,你可以干脆说,这些是我们架构或项目经理做的,面试官听了也就不会再问了。
5 **更重要的是,你得会引导。**比如你在面试中介绍项目时,你说,在这个项目里,我有过数据库性能调优的经验,有过排查内存性能的经验,有通过看linux日志解决线上问题的经验,有通过看底层源码解决实际问题的经验。这样面试官就会继续问了,你就能借机说出来。
或者你可以在回答到相关问题后再提一句,比如你回答到JPA等数据库问题后,你就说,我除了会用JPA连数据,还解决过数据库性能调优方面的问题,然后面试官问了就再展开。
通过上文给出的步骤,大家能发现其实Java项目的亮点不是来自业务,而是来自技术,而且你有亮点的实践经验不会说,这也没用。
但如果你按上文给出的步骤,把这些亮点写入简历,面试前好好准备,面试时全面抛出。这时哪怕你是零项目经验的,面试官也能认为第一你的项目是商业项目,第二你有资深的项目开发经验,这就达到了你准备和抛出亮点的目的了。