每日面试题整理
1、什么是死锁?
解答:死锁是由二个线程或者多个线程相互持有所需要的资源,导致线程一直处于等待其他线程释放资源的状态,无法继续执行,如果都不主动去释放所占用的资源,将会产生死锁。
/**
* 死锁案例,测试死锁
*/
public class TestDeadLock {
final static Object o1 = new Object();
final static Object o2 = new Object();
public static void main(String[] args) {
//先持有 o1 的锁,再去获取 o2 的锁
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (o1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 o1 对象的锁");
try {
System.out.println("休眠1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + " 去获取 o2 对象的锁");
synchronized (o2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 成功获取 o2 对象的锁");
}
}
}
};
//先持有 o2 的锁,再去获取 o1 的锁
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (o2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 o2 对象的锁");
try {
System.out.println("休眠1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + " 去获取 o1 对象的锁");
synchronized (o1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 成功获取 o1 对象的锁");
}
}
}
};
t1.start();
t2.start();
}
}
2、springboot自动装配原理
解答:
Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个 注解是对三个注解进行了封装,分别是: @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
其中 @EnableAutoConfiguration 是实现自动化配置的核心注解。 该注解通过 @Import 注解导入对应的配置选择器。关键的是内部就是读取了 该项目和该项目引用的Jar包的的classpath路径下METAINF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要 将其导入到Spring容器中。 一般条件判断会有像 @ConditionalOnClass 这样的注解,判断是否有对应的 class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器 中使用。
3、ThreadLocal 是什么?有哪些使用场景?
解答:
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
4、synchronized 和 ReentrantLock 区别是什么?
解答:synchronized是关键字, ReentrantLock是接口类,synchronized是在JVM层面实现的,是Java内置的关键字,可以直接使用。ReentrantLock则是通过Java API实现,需要基于Lock接口使用,并且可以使用更加丰富的锁操作方法。
5、说说线程的生命周期和状态?
解答:
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
NEW: 初始状态,线程被创建出来但没有被调用 start() 。
RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
BLOCKED :阻塞状态,需要等待锁释放。
WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
TERMINATED:终止状态,表示该线程已经运行完毕。
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。

6、Java双亲委派机制原理
解答:
类加载机制其实就是虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.lang.Class。
装载->链接->初始化

双亲委派机制的出现原因 :
双亲委派机制向上委派机制保证先加载JDK的核心类,再加载应用程序的类,有效防止了因为应用程序中因为某个类的存在一些不安全问题,导致JVM变得不安全。
向下委派机制保证需要加载的类,都得到了加载。
例子: 不管是在哪个jar包下的类最终都会委派给启动类加载器进行加载。比如我自定义
public class StringBuffer {
public StringBuffer(){
}
}
如果没有向下委派一直到启动类加载器,那StringBuffer就变成我这个了。安全性出来了。 如果在JVM存在包名和类名完全相同的两个类,这个类就无法被加载。
7、什么时候会导致fullgc
解答:
FullGC是Java虚拟机中的一种垃圾回收方式。
Full GC会在整个堆空间中执行垃圾回收,清理所有不再被引用的对象所占用的内存空间。Full
GC通常是在年老代空间或者整个堆空间被占满时触发的,它会清理所有的年老代对象和永久代对象。
以下情况可能会导致fullgc:
对象过多:当JVM中存在大量对象时,Full GC可能会被触发。如果这些对象不能通过Minor GC释放,则会由Full GC回收这些对象。
长时间停顿:在执行Full GC期间,应用程序将停止响应。此时,请求将继续积累,以至于JVM可能触发下一个Full GC。长时间停顿可能会导致Full GC,除非使用CMS GC(Concurrent Mark-Sweep Garbage Collector)或G1 GC(Garbage-First Garbage Collector)。
频繁创建对象:如果应用程序在JVM的生命周期中频繁创建对象,则会产生大量垃圾。这会使Minor GC减少高速,进而调用Full GC。
8、JVM中的堆空间
老年代。
永久代(在JDK 8版本之后被元空间取代)。
新生代(Young)又被划分为:Eden、From Survivor和To Survivor三个区域。老年代(Old)主要用于存放已经经过垃圾回收的Java对象。永久代(HotSpot的永久代)被彻底移除,取而代之的是元空间,元空间使用的是直接内存(直接在内存空间里)。
9、Java创建对象的流程
解答:
Java创建对象的流程可以 2. 验证类。 3. 准备类。 4. 解析类。 5. 初始化类。 6. 创建对象。
具体来说,在Java中创建对象需要先加载类,然后进行验证和准备阶段,这两个阶段完成后,会将类的静态变量和静态初始化块加载到内存中。接下来是解析阶段,将类中的符号引用转换为直接引用。然后是初始化阶段,执行静态初始化块和初始化静态变量。最后,通过调用构造方法创建对象。
10、如何保证消息队列的高可用性?
要确保消息队列的高可用性,可以采取以下措施:
1. 集群部署:在生产环境中,建立一个由多台机器组成的消息队列集群。这样可以提高可用性,一台机器出现故障时仍然能够正常运行。
2. 数据备份和恢复:定期对消息队列中的数据进行备份,以防止数据丢失。在发生故障时,可以快速恢复备份数据,并保持业务的连续性。
3. 数据复制和同步:将消息队列中的数据进行复制和同步,确保多个节点之间的数据一致性。这样即使某个节点发生故障,其他节点仍然可以继续处理消息。
4. 故障转移和容错:在消息队列集群中,可以设置故障转移机制,当节点出现故障时,自动将任务转移到其他正常的节点上处理,确保系统的可用性和稳定性。
5. 监控和报警:建立健全的监控系统,及时监测消息队列的运行状态和性能指标。当发现异常或故障时,及时发送报警信息,以便及时处理和排除故障。
6. 水平扩展:随着业务的增长,可以通过水平扩展来增加消息队列的处理能力。通过增加更多的节点和资源,可以提高消息处理的并发性和吞吐量。
7. 灰度发布和版本管理:在更新版本或进行系统升级时,采用灰度发布策略,逐步切换到新版本,以减少对系统的影响。同时,建立版本管理机制,及时修复和更新消息队列软件的漏洞和问题。
以上是一些常用的措施,可以帮助提高消息队列的高可用性。根据具体的需求和实际情况,还可以进一步调整和优化这些措施。
11、jdk8新特性Stream
解答:
Java 8 API添加了一个新的抽象称为流Stream把真正的函数式编程风格引入到Java中,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API极大简化了集合框架的处理,这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
List<String> ls = Arrays.asList("1,小明,67", "2,小红,71", "3,小红,61","4,小明,78","5,小绿,69","6,小绿,67");
//使用steam流来求,每个人平均成绩,并根据平均成绩来进行降序排序
List<Map.Entry<String, Double>> collect = ls.stream()
.collect(Collectors.groupingBy(e -> e.split(",")[1],//分组,求平均值
Collectors.averagingInt(e -> Integer.parseInt(e.split(",")[2]))))
.entrySet()
.stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))//进行排序
.collect(Collectors.toList());//转回集合
System.out.printf(collect.toString());
12、sql笔试题

解答:
select year,
sum(case month when 1 then amount else 0 end) m1,
sum(case month when 2 then amount else 0 end) m2,
sum(case month when 3 then amount else 0 end) m3,
sum(case month when 4 then amount else 0 end) m4
from test
group by year
13、什么是缓存雪崩,如何解决
解答:
缓存雪崩是指在缓存系统中,大量缓存失效或同时过期时所发生的现象。这可能导致大量请求直接访问后端系统,从而使后端系统负载过高,甚至崩溃。
缓存雪崩通常发生在以下情况下:
1. 缓存数据的过期时间设置不合理:如果大量缓存数据在同一时间过期,就会导致并发请求频繁地访问后端系统。
2. 缓存系统故障:当缓存系统发生故障,无法提供缓存服务时,所有的请求都将直接访问后端系统。
3. 数据热点:当某些热门数据集中在一部分缓存节点上时,这些节点可能会因请求压力过大而崩溃,从而影响整个系统。
为了解决缓存雪崩问题,可以考虑以下几个方法:
1. 合理设置缓存的过期时间:将缓存数据的过期时间设置为随机的时间,避免大量缓存同时失效。也可以使用滑动窗口或定时刷新等方法,确保缓存数据在不同时间失效。
2. 实施缓存高可用:使用分布式缓存架构,将缓存数据分散存储在不同的缓存节点上,当部分节点失效时,其他节点可以继续提供缓存服务,降低单点故障风险。
3. 数据预加载:在缓存数据过期之前,提前触发异步加载缓存数据的任务,保证缓存数据的即时性,避免大量请求直接访问后端系统。
4. 限流和熔断:通过限制请求的并发数或采用熔断机制,当缓存系统压力过大时,及时限制请求访问,避免对后端系统造成过大压力。
5. 合理分配热点数据:通过数据分片等方式,将热点数据均匀分布在多个缓存节点上,避免某个节点负载过大,提高系统的整体并发能力。
综上所述,通过合理设置缓存策略、实施高可用、预加载数据,以及限流和熔断等手段,可以有效预防和解决缓存雪崩问题。
14、单表一亿条数据如何进行优化
解答:
处理一亿条数据的单表可以是一项挑战,但可以通过以下几种优化方法提高查询和性能:
1. 索引优化:为重要的查询字段添加索引,可以大幅提高查询性能。根据具体查询场景和查询频率,选择合适的索引类型,如哈希索引、B+树索引等。同时,避免创建过多的索引,因为索引的更新操作也会对性能产生影响。
2. 分区和分片:根据数据的特点,可以考虑将表进行水平分区,将数据分散存储在不同的物理存储区域中。分区可以基于时间、范围或其他条件进行,能够提高查询效率和管理数据的灵活性。
3. 垂直拆分和水平拆分:如果表中包含大量的冗余数据或者某些字段的访问频率远大于其他字段,可以考虑将相关的字段拆分成多个表,以减少查询的数据量。水平拆分可以将数据按照某个条件均匀地拆分成多个表,每个表的数据量相对较小,提高查询性能。
4. 缓存和缓存预热:对于常用的查询结果或者耗时较长的查询,可以使用缓存技术,将查询结果缓存起来,加快后续的查询响应速度。同时,可以通过缓存预热,在系统启动或低峰期预先加载热门数据到缓存中,减少对数据库的直接访问。
5. 查询优化和分页:对于复杂的查询语句,可以通过优化查询语句、使用合适的查询方式(如JOIN、子查询等)以及合理使用分页来减少返回的数据量,提高查询效率。
6. 数据归档和清理:对于不再活跃或者过期的数据,可以进行归档或者定期清理,将其移至历史表或者删除,以减少表的数据量,提高查询性能。
7. 异步处理和延迟加载:对于不需要立即返回结果的耗时操作,可以考虑将其异步化,通过消息队列等方式进行处理,减少对数据库的直接访问。同时,对于大字段或者冷数据可以使用延迟加载的方式,减少数据的读取和传输时间。
8. 硬件优化:在确保其他优化手段充分利用的基础上,可以考虑使用更高性能的硬件设备,如更快的存储设备、更高的内存容量等,来提升数据库的性能。
需要根据具体的业务情况、数据特点和查询要求来选择和实施适当的优化方法。同时,优化数据库性能也需要综合考虑系统的整体架构和资源配置,以达到最佳的效果。
15、Java BeanUtils工具类实现原理
解答:
Java BeanUtils工具类是Apache Commons BeanUtils项目中的一部分,它提供了一组用于操作Java Bean(POJO)的工具方法。BeanUtils工具类的实现原理如下:
1. 反射:BeanUtils使用Java的反射机制来实现对Java Bean的操作。通过反射,BeanUtils可以动态地检查和获取Java Bean的属性、方法和字段,并通过调用相应的方法来完成对属性的读取和写入操作。
2. Introspector:BeanUtils使用Java的Introspector机制来获取Java Bean的属性信息。Introspector可以分析Java Bean的类结构,获取其所有的属性描述符(PropertyDescriptor)。PropertyDescriptor包含了属性名称、属性的读取方法(getter)和写入方法(setter),以及一些其他属性的特性。
3. 属性拷贝:BeanUtils提供了属性拷贝的功能,即将一个Java Bean的属性值复制到另一个Java Bean中。在拷贝属性时,BeanUtils使用反射来获取源对象和目标对象的属性描述符,并通过调用相应的getter和setter方法来完成属性值的拷贝。
4. 类型转换:BeanUtils可以自动进行类型转换。当源对象和目标对象的属性类型不匹配时,BeanUtils会尝试将属性值按照目标类型进行自动转换。例如,将字符串类型的属性赋值给整数类型的属性时,BeanUtils会自动进行类型转换。
5. 工具方法:除了属性拷贝之外,BeanUtils还提供了其他一些常用的工具方法,如获取Java Bean的属性、设置属性值、获取属性类型等。
需要注意的是,由于使用了反射机制,BeanUtils在性能上可能不如手动编写赋值代码高效。因此,在对性能要求较高的场景下,可以考虑手动编写赋值逻辑。此外,BeanUtils也存在一些安全风险,因为它可以访问和修改Java Bean中的任意属性。因此,在使用BeanUtils时需要确保对输入数据进行适当的验证和过滤,以防止潜在的安全问题。
16、在MySQL中,常见的索引类型有以下几种:
解答:
1. B-Tree索引:B-Tree(即平衡树)索引是最常见和最常用的索引类型。它可以应用于所有的列类型,包括整数、字符串等。B-Tree索引使用二叉搜索树的结构,使得查询、插入和删除都能在O(log n)的时间复杂度内完成。在B-Tree索引中,叶子节点存储了完整的记录,通过树的节点层级,可以快速定位到目标记录。
2. 哈希索引:哈希索引使用哈希算法将索引值映射到索引位置。相对于B-Tree索引,哈希索引的查询速度非常快,通常在O(1)时间复杂度内完成。然而,哈希索引仅支持等值查询,并且对索引值进行哈希函数计算和存储,不能按照顺序进行范围查询。
3. 全文索引:全文索引用于对文本内容进行快速搜索。通常,B-Tree索引只能支持对整个词或短语的搜索,而无法支持对文本内容的分词和模糊匹配。全文索引使用全文搜索引擎,在文本内容上进行分词,并生成索引。MySQL的全文索引可以实现对全文搜索的支持,例如对文章标题、摘要等进行关键字搜索。
4. 空间索引:空间索引用于支持地理空间数据的查询。MySQL提供了R-Tree空间索引的实现,可以对存储了地理坐标数据的列进行索引,从而支持距离计算、位置搜索等功能。
此外,MySQL还支持组合索引(即多列索引),可以在一个索引中包含多个列。组合索引可以有效地优化多列的查询条件,提高查询性能。
选择适当的索引类型取决于具体的业务需求和查询场景。通常,B-Tree索引是最常用和通用的索引类型,可以满足大部分查询需求。如果需要进行全文搜索、地理空间查询或者只有等值查询,可以考虑使用全文索引、空间索引或哈希索引。合理选择和优化索引,可以显著提升数据库的查询性能。
时间复杂度定义:
O(1):根据下标查询,一次就能查到
O(n): 最坏的情况,会全部遍历循环一次取值
O(log n):
O(log n) 是一个表示算法复杂度的符号,它表示随着输入数据量的增加,算法的执行时间将以对数的方式增长。换句话说,如果一个算法的时间复杂度是 O(log n),那么当输入数据量翻倍时,执行时间将只增加一个常数倍。
举个例子,假设你有一个数组,需要查找其中是否存在某个元素。如果你使用线性搜索(即从头到尾逐个检查每个元素),那么当数组大小翻倍时,你需要花费的时间也会翻倍。但是,如果你使用二分搜索(即每次都在剩余的一半中查找),那么当数组大小翻倍时,你需要花费的时间只增加一个常数倍。因此,二分搜索的时间复杂度是 O(log n)。
时间复杂度是衡量算法执行速度的一个指标,通常用大O符号(O)表示。它表示随着输入数据量的增加,算法执行时间的增长趋势。时间复杂度可以分为以下几种:
1. O(1):常数时间复杂度。这意味着无论输入数据量如何,算法的执行时间都是固定的。例如,访问数组中的一个元素、赋值操作等。
2. O(log n):对数时间复杂度。这表示算法的执行时间与输入数据量的对数成正比。例如,二分查找算法。
3. O(n):线性时间复杂度。这表示算法的执行时间与输入数据量成正比。例如,遍历一个数组或链表。
4. O(n log n):线性对数时间复杂度。这表示算法的执行时间与输入数据量的乘积成正比,但增长速度小于对数时间复杂度。例如,归并排序和快速排序。
5. O(n^2):平方时间复杂度。这表示算法的执行时间与输入数据量的平方成正比。例如,冒泡排序和插入排序。
6. O(n^3):立方时间复杂度。这表示算法的执行时间与输入数据量的立方成正比。例如,三重嵌套循环。
7. O(2^n):指数时间复杂度。这表示算法的执行时间与输入数据量的指数成正比。例如,递归实现的阶乘计算。
8. O(n!):阶乘时间复杂度。这表示算法的执行时间与输入数据量的阶乘成正比。例如,全排列问题求解。
了解不同时间复杂度的含义和特点,可以帮助我们选择合适的算法来解决实际问题,提高程序的执行效率。
17、vue界面的组成部分
解答:
Vue界面通常由以下几个核心组成部分构成:
1. 模板(Template):Vue使用基于HTML的模板语法,模板用于定义界面的结构与布局。通过Vue的指令和插值表达式,可以在模板中动态地绑定数据、响应用户的交互操作,并生成最终的HTML结构。
2. 数据(Data):在Vue中,数据被称为响应式数据。可以将数据对象绑定到Vue实例中的data属性上,在模板中使用数据对象的属性进行数据绑定。当数据发生变化时,Vue将负责自动更新界面上的相关部分。
3. 组件(Components):组件是Vue中的可复用和自定义元素,它有自己的模板、数据和方法。Vue的组件可以嵌套使用,形成一个层级的组件树。组件的封装和复用提高了代码的可维护性和可重用性。
4. 指令(Directives):指令是Vue提供的特殊属性,用于在模板中添加特定的行为和功能。指令可以用于控制DOM的显示和隐藏、绑定事件、修改样式等。Vue提供了一些内置的指令,例如v-if、v-for、v-bind、v-on等,同时也支持自定义指令。
5. 方法(Methods):在Vue实例中,可以定义一些方法用于处理界面的交互逻辑。通过调用这些方法,可以响应用户的事件和操作,修改数据或处理业务逻辑。
6. 计算属性(Computed Properties):计算属性是根据数据的变化动态生成的属性。计算属性可以根据数据的变化自动更新,可以用于完成一些复杂的数据处理或者返回派生数据。
7. 过滤器(Filters):过滤器可以用于在模板中对数据进行处理和格式化。通过过滤器,可以在模板中对数据进行常见的格式化操作,例如日期格式化、文本截断、大小写转换等。
除了上述核心组成部分外,Vue还提供了路由(Vue Router)、状态管理(Vuex)、动画(Vue Transition)等扩展功能,用于构建复杂和高效的交互界面。
总体而言,Vue界面由模板、数据、组件、指令、方法等组成,通过这些部分的组合和交互,实现了一个响应式、可复用和可扩展的用户界面。
18、redis的底层原理
解答:
Redis 是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。它支持多种类型的数据结构,如字符串、哈希表、列表、集合、有序集合等。
Redis 之所以快,主要有以下几个原因:
1. 基于内存操作:Redis 将所有数据存储在内存中,这意味着读写操作的速度非常快。这是因为相比于磁盘 I/O,内存 I/O 要快得多。
2. 非阻塞 I/O:Redis 使用单线程模型进行网络通信,并采用非阻塞 I/O 技术。这意味着当 Redis 执行 I/O 操作时(例如读取或写入数据),它将不会阻塞其他客户端的请求,而是立即返回一个错误信息。这样,即使某个客户端的网络请求需要很长时间才能完成,也不会影响其他客户端的请求。
3. 事件驱动模型:Redis 使用事件驱动模型来处理并发请求。每当有新的连接请求到达时,Redis 都会创建一个新的事件循环来处理这个连接的所有请求。这样,即使有大量的并发连接,每个连接也可以独立地处理自己的请求,而不会影响其他连接。
4. 数据结构简单且紧凑:Redis 的数据结构非常简单且紧凑,这使得它可以快速地进行数据查找和操作。此外,Redis 还使用了高效的数据编码和解码算法,进一步提高了性能。
5. 持久化策略:Redis 提供了两种持久化策略:RDB(快照)和 AOF(追加文件)。RDB 会在指定的时间间隔内生成数据集的时间点快照;AOF 则会记录所有写操作到文件末尾。这两种策略都可以保证在发生故障时数据的完整性。
以上就是 Redis 底层原理以及为什么那么快的一些解释。希望对你有所帮助!
19、什么是NIO?
解答:
Java NIO(New Input/Output)是Java编程语言中的一组API,用于实现高效的、非阻塞的IO操作。它是Java标准库中对传统的Java IO(InputStream和OutputStream)的补充和增强。
Java NIO提供了一种基于通道和缓冲区的IO模型,可以在一个线程中处理多个IO操作,从而实现了非阻塞IO。它的核心概念是通道(Channel)和缓冲区(Buffer)。通道是与文件、网络套接字等IO源进行交互的对象,而缓冲区是用于数据存储和传输的对象。
与传统的Java IO相比,Java NIO具有以下几个重要特点:
1. 非阻塞IO:允许一个线程处理多个IO操作,避免了线程阻塞和上下文切换的开销。
2. 选择器(Selector):提供了一种高效的多路复用IO模型,可以在一个线程中同时管理多个通道的IO事件。
3. 更快的速度:Java NIO的IO操作速度通常比传统的Java IO更快,特别是在处理大量连接或大量数据时。
4. 支持文件锁定:Java NIO提供了对文件锁定的支持,可以实现文件的排他性访问。
总的来说,Java NIO提供了更灵活和高效的IO操作方式,适用于需要处理大量连接和大量数据的场景,如网络编程、多线程服务器等。
AIO、BIO和NIO是Java中三种不同的IO模型,它们之间有以下区别:
1. BIO (Blocking IO,阻塞IO):
- 传统的IO模型,也称为同步IO模型。
- 在执行IO操作时,线程会被阻塞,直到IO操作完成。
- 每个IO操作需要一个独立的线程来处理,导致线程资源的浪费。
- 适用于连接数较少且数据量较小的场景,如简单的客户端/服务器应用。
2. NIO (Non-blocking IO,非阻塞IO):
- 引入了通道-缓冲区模型,是同步非阻塞的IO模型。
- IO操作不会导致线程阻塞,线程可以继续执行其他任务。
- 通过选择器(Selector)实现了多路复用,一个线程可以管理多个通道的IO事件。
- 适用于需要处理大量连接和大量数据的场景,如高性能网络服务器。
3. AIO (Asynchronous IO,异步IO):
- 异步非阻塞的IO模型。
- IO操作不会阻塞线程,并且可以通过回调函数的方式获取IO操作的结果。
- 在IO操作完成后,系统会通知应用程序进行处理。
- 适用于需要实现高并发、高吞吐量的场景,如高性能的网络应用。
总的来说,BIO适用于连接数较少、数据量较小的场景;NIO适用于需要处理大量连接和大量数据的场景;AIO适用于需要实现高并发、高吞吐量的场景。选择合适的IO模型可以根据应用程序的具体需求来决定。
20、NIO
Channel
- FileChannel:从文件中读写数据
- new FileInputStream("xx.txt") # 流方式进行读写
- FileChannel channel = file.getChannel() # 获取通道
- new RandomAccessFile("xx.txt", "rw") # 随机访问通道进行读写
- FileChannel channel = file.getChannel() # 获取通道
- file.seek(aFile.length()); # 移动文件指针到尾部,追加写入
- channel.position(); # 获取当前位置
- channel.position(pos + 123) # 设置位置
- channel.size() # 获取文件大小
- channel.truncate(1024) # 截取掉文件前1024字节
- channel.force() # 强制写入到磁盘,参数true是否将权限等信息也写入
- channel.transferFrom(fromChannel, position, size) # 写入通道 from to
- channel.transferTo(position, size, toChannel) # 从通道写入from to
- DatagramChannel:能通过UDP读写网络中的数据
- SocketChannel:能通过TCP读写网络中的数据
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样,对每一个新进来的连接都会创建一个
上面三个都实现了AbstractSelectableChannel
所有通道类的超类SelectableChannel
configureBlocking(true)方法进行阻塞模式
Buffer
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- ByteBuffer.allocate(1024) # 创建buffer
- buffer.flip() # 反转读写模式
- buffer.clear()/buffer.compact() # 清除缓冲区内容
Selector
选择器或叫多路复用器
Selector运行单线程处理多个Channel
select()方法进行阻塞等待Channel有事件就绪
SelectableChannel
Channel.register(Selector sel, int ops)
- SelectionKey.OP_READ # 可读
- SelectionKey.OP_WRITE # 可写
- SelectionKey.OP_CONNECT # 连接
- SelectionKey.OP_ACCEPT # 接收
- SelectionKey.OP_READ | SelectionKey.OP_WRITE # 使用”位或“来进行多操作通道
Selector selector = Selector.open();
例子
public static void main(String[] args) {
String path = streamDemo.class.getResource("").getPath();
try(RandomAccessFile aFile = new RandomAccessFile(path+"input.txt", "rw")) {
FileChannel channel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buf);
while(bytesRead != -1) {
System.out.println("read:" + bytesRead);
buf.flip();
while(buf.hasRemaining()) {
System.out.print(buf.get());
}
buf.clear();
bytesRead = channel.read(buf);
}
System.out.println("\n--- 结束 ---");
} catch (Exception e) {
e.printStackTrace();
}
}
21、redis中一个字符串类型的值能存储最大容量是多少?
解答:
项目中使用redis存储,key-value方式,在Redis中字符串类型的Value最多可以容纳的数据长度是512M
22、悲观锁和乐观锁的区别?
解答:
悲观锁和乐观锁是并发控制中常用的两种策略,它们在处理并发访问时的思想和实现方式上有所不同。
悲观锁:
1. 思想:悲观锁的思想是认为在并发环境下,数据很可能会发生冲突,因此在访问数据之前先假设会发生冲突,所以会对数据进行加锁,其他事务需要等待锁的释放才能访问数据。
2. 实现方式:悲观锁的实现方式包括数据库中的行锁、表锁、读锁、写锁等,通过锁机制来保证数据的一致性和并发性。
乐观锁:
1. 思想:乐观锁的思想是认为在并发环境下,数据冲突的概率较低,因此不对数据进行加锁,而是在更新数据时进行版本控制,通过比较版本号来判断是否发生冲突。
2. 实现方式:乐观锁的实现方式包括使用版本号、时间戳等机制来标识数据的版本,当更新数据时,先比较版本号,如果版本号一致,则更新数据并增加版本号,如果版本号不一致,则表示数据已被其他事务修改,需要进行相应的处理。
区别:
1. 加锁方式:悲观锁在访问数据之前会加锁,而乐观锁不会加锁。
2. 冲突处理:悲观锁通过加锁来避免数据冲突,其他事务需要等待锁的释放才能访问数据;乐观锁通过版本控制来判断是否发生冲突,如果发生冲突,则需要进行相应的处理。
3. 性能开销:悲观锁在并发高的情况下,由于需要频繁地加锁和释放锁,可能会导致性能开销较大;乐观锁在并发低的情况下,由于不需要加锁,性能开销较小。
4. 适用场景:悲观锁适用于并发写多的场景,乐观锁适用于并发读多的场景。
选择使用悲观锁还是乐观锁,需要根据具体的业务需求和并发访问情况来决定。如果并发写多,且数据冲突的概率较高,可以选择悲观锁;如果并发读多,且数据冲突的概率较低,可以选择乐观锁。
23、DK动态代理与cglib实现的区别?
JDK动态代理和CGLIB是Java中常见的实现动态代理的两种方式,它们在实现机制和适用场景上有一些区别。
JDK动态代理:
1. 基于接口:JDK动态代理是基于接口的代理,只能代理实现了接口的类。
2. 使用Java的反射机制:JDK动态代理在运行时生成代理类,通过反射机制调用被代理类的方法。
3. Proxy类:JDK动态代理通过Proxy类和InvocationHandler接口实现代理,需要实现InvocationHandler接口的invoke方法。
CGLIB:
1. 基于继承:CGLIB基于继承生成代理类,可以代理没有实现接口的类。
2. 使用字节码技术:CGLIB利用字节码技术生成代理类,通过继承被代理类的方式实现代理。
3. Enhancer类:CGLIB使用Enhancer类来生成代理类,可以通过Callback过滤不需要代理的方法。
区别:
1. JDK动态代理可以代理接口,而CGLIB可以代理没有实现接口的类。
2. JDK动态代理是基于接口的,对接口方法的调用会进入InvocationHandler的invoke方法,CGLIB是基于继承的,对方法的调用会进入代理类的重写方法。
3. JDK动态代理的生成代理类的过程相对较慢,启动速度较快。CGLIB的生成代理类的过程相对较快,启动速度较慢。
4. JDK动态代理不依赖于第三方库,CGLIB依赖于cglib库。
5. JDK动态代理适用于代理接口的场景,CGLIB适用于代理类的场景。
选择使用哪种动态代理方式,可以根据具体的需求和场景来决定。如果要代理的类实现了接口,且性能要求较高,可以选择JDK动态代理;如果要代理的类没有实现接口或者需要对类的方法进行代理,可以选择CGLIB。
24、SpringMVC的工作流程
解答:
Spring MVC是一种基于Java的Web开发框架,其工作流程如下:
1. 客户端发送HTTP请求到前端控制器DispatcherServlet。
2. DispatcherServlet接收到请求后,根据请求URL找到对应的处理器映射器HandlerMapping。
3. 处理器映射器根据请求URL找到对应的处理器Handler,即Controller。
4. 处理器映射器将请求及处理器信息传递给DispatcherServlet。
5. DispatcherServlet调用处理器适配器HandlerAdapter执行处理器Handler。
6. 处理器Handler执行业务逻辑,处理请求,并返回一个ModelAndView对象,其中包含了数据模型和视图信息。
7. 处理器Handler将ModelAndView对象返回给处理器适配器HandlerAdapter。
8. 处理器适配器将ModelAndView对象返回给DispatcherServlet。
9. DispatcherServlet将ModelAndView对象传递给视图解析器ViewResolver,用于解析视图信息。
10. 视图解析器ViewResolver根据视图名解析出具体的视图View对象。
11. DispatcherServlet将Model数据传递给视图View,并调用视图View进行渲染。
12. 视图View将渲染结果返回给DispatcherServlet。
13. DispatcherServlet将渲染结果返回给客户端,完成请求响应过程。
在整个流程中,前端控制器DispatcherServlet负责统一的请求分发和调度,处理器映射器HandlerMapping负责将请求映射到相应的处理器,处理器适配器HandlerAdapter负责执行处理器的业务逻辑,视图解析器ViewResolver负责解析视图信息,视图View负责生成最终的响应。这样,Spring MVC可以将请求的处理和页面的渲染解耦,实现了MVC设计模式的分层思想。
25、反射机制的优缺点
优点:
1、能够运行时动态获取类的实例,提高灵活性
2、与动态编译结合
缺点:
1、使用反射性能较低,需要解析字节码,将内存中的对象进行解析;
解决方案:
(1)通过setAccessible(true)关闭]DK的安全检查来提升反射速度;
(2)多次创建一个类的实例时,有缓存会快很多;
(3) ReflflectASM工具类,通过字节码生成的方式加快反射速度;
2、相对不安全,破坏了封装性( 因为通过反射可以获得私有方法和属性)
26、什么是java序列化,如何实现java序列化?
解答:
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对
象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接
口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:
FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法
就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
27、volatile 是什么?可以保证有序性吗?
解答:
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语
义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他
线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操作
什么叫保证部分有序性?
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果
已经对后面的操作可见;在其后面的操作肯定还没有进行;
class MyThread extends Thread {
volatile boolean stop = false;
public void run() {
while (!stop) {
System.out.println(getName() + " is running");
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println("week up from blcok...");
stop = true; // 在异常处理代码中修改共享变量的状态
}
}
System.out.println(getName() + " is exiting...");
}
}
class InterruptThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
MyThread m1 = new MyThread();
System.out.println("Starting thread...");
m1.start();
Thread.sleep(3000);
System.out.println("Interrupt thread...: " + m1.getName());
m1.stop = true; // 设置共享变量为true
m1.interrupt(); // 阻塞时退出阻塞状态
Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况
System.out.println("Stopping application...");
}
}
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前
面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是
不作任何保证的。
使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁
28、简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别
解答:
MyISAM:
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;
存储表的总行数;
一个 MYISAM 表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引
基本一致,但是辅索引不用保证唯一性。
InnoDb:
支持 ACID 的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数:
一个 InnoDb 引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置
为独立表空,表大小受操作系统文件大小限制,一般为 2G),受操作系统文件大小的限制;
主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找
到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持 B+树结构,文件的大调整。
29、Spring中bean的作用域:
(1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
(2)prototype:为每一个bean请求创建一个实例。
(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
30、Spring如何解决循环依赖问题:
循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。
31、Object类的常见方法总结
Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法:
public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了
final关键字修饰,故不允许子类重写。
public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的
HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户
比较字符串的值是否相等。
protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回
当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass()
== x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生
CloneNotSupportedException异常。
public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方
法。
public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视
器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒
在此对象监视器上等待的所有线程,而不是一个线程。
public final native void wait(long timeout) throws InterruptedException//native方法,并且不能
重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,
这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等
待,没有超时时间这个概念
protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
32、 接口和抽象类的区别是什么
解答:
1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以
有非抽象的方法
2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
3. 一个类可以实现多个接口,但最多只能实现一个抽象类
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽
象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现
两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。
33、最左前缀原则
MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是
(name,city)o而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以
被用到。如下:
~~~sql
select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引
select * from user where city=xx; // 无法命中索引
~~~
这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 city= xx and name =xx ,那么现在
的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的.
由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。
ORDERBY子句也遵循此规则。
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两
个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而
不是创建新索引。
MySQLS.7 版本后,可以通过查询 sys 库的 schemal_r dundant_indexes 表来查看冗余索引
34、redis 提供 6种数据淘汰策略
redis 提供 6种数据淘汰策略:
1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).
5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
35、缓存雪崩和缓存穿透问题解决方案
缓存雪崩
简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩
掉。
解决办法(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到):
事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
事后:利用 redis 持久化机制保存的数据尽快恢复缓存
缓存穿透
简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量
请求而崩掉。
解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈
希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压
力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存
在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
36、如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺
序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问
题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的
与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有
序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁
无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
37、sleep()和wait() 有什么区别?
解答:
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中
的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当
指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用
notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
38、Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而
submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了
Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方
法。
39、 Spring中@Autowired和@Resource关键字的区别?
解答:
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包
是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
1、共同点
两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
2、不同点
(1)@Autowired
@Autowired为Spring提供的注解,需要导入包
org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允
许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结
合@Qualifier注解一起使用。
(2)@Resource
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。
@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的
名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策
略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通
过反射机制使用byName自动注入策略。
40、讲一下什么是Spring
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用
于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的
配置、基于注解的配置、基于Java的配置。
公众号:Java专栏
主要由以下几个模块组成:
Spring Core:核心类库,提供IOC服务;
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring AOP:AOP服务;
Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
Spring ORM:对现有的ORM框架的支持;
Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
Spring MVC:提供面向Web应用的Model-View-Controller实现。
41、SpringMVC常用的注解有哪些?
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所
有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
42、解释一下spring bean的生命周期
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
Spring上下文中的Bean生命周期也类似,如下:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入
另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当
容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通
过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方
法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的
是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用
setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用
postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object
obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
公众号:Java专栏
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的
destroy()方法;
(8)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
43、Spring框架中都用到了哪些设计模式?
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它
的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
44、mybatis中的#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
45、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
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()方法的调用。这就是延迟加载的基本原理。
当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
46、Mybatis的一级、二级缓存?
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Sessionflush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
47、什么是SpringBoot?为什么要用SpringBoot
用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置(properties或yml文件)
创建独立的spring引用程序 main方法运行
嵌入的Tomcat 无需部署war文件
简化maven配置
自动配置spring添加对应功能starter自动化配置
spring boot来简化spring应用开发,约定大于配置,去繁从简,just run就能创建一个独立的,产品
级别的应用
Spring Boot 优点非常多,如:
一、独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,
Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
三、自动配置
Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启
动器就能拥有web的功能,无需其他配置。
四、无代码生成和XML配置
Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于
条件注解完成的,这也是Spring4.x的核心功能之一。
五、应用监控
Spring Boot提供一系列端点可以监控服务及应用,做健康检测。
48、Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源
自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
评论区