Java面试2
一、说说你对Spring MVC的理解
Spring mvc 是一个基于java的实现了mvc设计模式的轻量级web框架,在这种模式下软件被分为三层,即Model、View、Controller。将软件分层的好处是可以将对象之间的耦合度降低,便于代码的维护。Model(模型)封装了数据和对数据的操作,是实际进行数据处理的地方,View(视图)负责进行模型的展示,一般就是我们见到的用户界面,Controller(控制器)负责视图和模型之间的交互,主要负责两方面的动作,一是把用户的请求分发到相应的模型,二是把模型的改变及时地响应到视图上。
mvc的执行流程如下: 当一个http请求过来之后,前端控制器(Dispatcherservlet)接收和拦截,将请求发给处理器适配器(HandlerMapping),处理器适配器找到能处理该请求的处理器,打包成执行链,返回给前端控制器,前端控制器找到能执行该处理器的处理器适配器,处理器适配器执行对应的Controller,Controller执行业务层代码,获得数据Modal,然后交给渲染器进行渲染视图view,打包成ModalAndView对象,返回处理器适配器,处理器适配器再返回给前端控制器,前端控制器调用视图解析器(ViewResolver),将视图封装成视图对象,前端控制器调用视图对象,让其自己渲染,打包成响应对象,返回给客户端。
二、简单介绍Spring
Spring是一个轻量级的企业应用开发框架,他有众多模块,其中core模块是spring的核心模块,spring的核心功能IOC和Aop 都是都是该模块提供的。
IOC 就是控制反转的意思,他将对象的创建和管理交给IOC容器,来实现程序的解耦。IOC通过依赖注入来实现,依赖注入的概念就是说你不用创建对象,而只需要描述它如何被创建。依赖注入有set注入,构造方法注入、注解注入等方式。
Aop 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。在 Aop 的思想下,我们可以通过不修改原来代码的前提下给系统添加功能。通常用于事务和日志管理。
三、说说你对AOP的理解
AOP是面向切面编程,一种编程思想,是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一添加功能的技术。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。所谓切面,相当于应用对象键的横切点,我们可以将其单独抽象为单独的模块。AOP技术利用一种称为“横切”的技术,剖解开封装对象的内部,将影响多个类的公共行为封装到一个可重用的模块中,并将其命名为切面。Aop支持如下两种实现方式,JDK动态代理:这是java提供的动态代理技术,可以在运行时创建接口的代理实例;CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。常见的aop使用场景,日志统一管理,事务管理器。
四、说说你对Ioc的理解
Ioc全称inversion of control,即控制反转,指将对象的创建、销毁等的控制权交由Spring框架的Ioc容器进行管理,以往的JavaEE程序设计的对象创建时通过new来完成的,需要我们主动去创建。而Ioc就是一个大工厂,现在不需要我们手动去new创建一个对象,需要时可以直接通过反射的机制来注入对象,同时也不需要我们主动去销毁对象,Spring框架帮我们进行了统一的管理。Dl依赖注入有三种实现方式,一种接口注入已经被淘汰了,另外两种一个是属性setter注入方式,Spring通过无参构造或无参静态工厂方法实例化Bean对象后,再通过调用该Bean的setter方法实现注入。第二种是构造注入,Spring直接通过调用有参的构造方法实现依赖注入,每一个参数就是一个依赖。
五、说说Bean的作用域,以及默认的作用域
在默认情况下,Bean在Spring容器中是单例的,但是我们可以通过@Scope注解来修改Bean的作用域。这个注解有五个不同的取值,代表了Bean的五种不同类型作用域。
1)singleton:在Spring容器中仅存在一个实例,即Bean以单例的形式存在。
2)prototype:每次调用getBean()时,都会执行new操作,返回一个新的实例。
3)request:每次HTTP请求都会创建一个新的Bean。
4)session:同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。
5)globalSession:同一个全局的Session共享一个Bean,一般用于Portlet环境。
六、说说BeanFactory和FactoryBean的区别
它们在拼写上非常相似,一个是Factory,也就是IOC容器或者对象工厂。一个是Bean,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分获取FactoryBean产生的对象和获取FactoryBean本身。
七、说说bean的生命周期
Bean的生命周期主要包括:Bean定义,Bean初始化,Bean生存期,Bean销毁。
具体流程如下:
1,spring 启动,查找并加载所需的 Bean,然后初始化。
2,进行 Bean 的属性依赖注入。
3,如果 Bean 实现了 BeanNameAware 接口(@Resource,@Qualifier),spring 会将 Bean 的Id传入 SetBeanNam e方法去执行。
4,如果 Bean 实现了 BeanFactoryAware 接口,spring会调用 Bean 的 setBeanFactory 方法将 BeanFactory 的ioc容器传入。
5,如果 Bean 实现的时 ApplicationContextAware 接口的话,Spring 会调用 Bean 的 setApplicationContext 将 Bean 应用的上下文传入进来。
6,还一些其他的设定例如使用 @PostContruct 来定义 Bean 在初始化时执行的方法,或者使用 @PreDestory 来定义 Ioc 容器被销毁时执行的方法等。
八、说说@Autowired和@Resource注解的区别
@Autowied 是 Spring 提供的注解,@Resource是 JDK 提供的注解。
@Autowied 是只能按类型注入,@Resource 默认按名称注入,也支持按类型注入。
@Autowired 按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它 required 属性为 false,如果我们想使用按名称装配,可以结合 @Qualifier 注解一起使用。
九、说说Spring事务管理
Spring支持两种事务编程模型:
1、编程式事务 Spring 提供了 TransactionTemplate 模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取、复用、释放、事务同步及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确。
2、声明式事务 Spring事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可。
十、介绍一下MyBatis的缓存机制
MyBatis 的缓存机制,一级缓存也称为本地缓存,它默认启用且不能关闭。一级缓存存在于 SqlSession 的生命周期中,即它是 SqlSession 级别的缓存,在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map 对象中,如果同一个 SqlSession 中执行的方法和参数完全一致,则会将缓存的对象返回;
二级缓存则为 SqlSessionFactory,mybaits 的全局配置 setting 有一个参数 cacheEnabled,这个参数是二级缓存的全局开关,默认值是true,初始状态为启用状态,映射语句文件中的所有 SELECT 语句将会被缓存。 - 映射语句文件中的所有时INSERT 、UPDATE 、DELETE 语句会刷新缓存。 - 缓存会使用 Least Recently U sed ( LRU ,最近最少使用的)算法来收回。
十一、在MyBatis中$和#有什么区别
“ $ ” 创建普通的SQL语句,然后在执行SQL语句时将参数拼入SQL,“ # ”设置参数时,MyBatis 会创建预编译 SQL 语句,相当于占位符。
“ # ”执行效率高,并且可以防止注入攻击,效率和安全性大大优于前者。但是“ $ ”不是一无是处,当要传递列名给 SQL 时,只能使用“ $ ”。
十二、说说Spring Boot的启动流程
创建 SpringBoot 项目时,会默认生成一个 application 入口类,该类中含有的 main 方法可以实现项目的启动,main 方法中 spring application 的静态方法,即 run 方法完成对 spring application 的实例化操作,针对实例化对象调用另一个 run 方法来实现整个项目的初始化和启动。run 方法的操作有:获取监听器的参数配置,打印banner信息,创建并初始化容器,监听器发送通知。
十三、说说Soring Boot的起步依赖
SpringBoot 将日常企业应用研发中的各种常见都抽取出来,做成一个个的 starter(启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 Maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 提供了大量的自动配置,让用户摆脱了处理各种依赖和配置的困扰。所有这些 starter 都遵循这约定俗成的默认配置,并允许用户调整这些配置,即遵循“约定大于配置”的原则。
十四、说说你对Spring Boot的理解,以及它和Spring的区别?
pringBoot 是 Spring 提供的一个快速开发工具包。SpringBoot 本身不提供 Spring 的核心功能而是作为 Spring 的脚手框架,达到快速构建项目的目的。 springBoot的核心功能包括:
1,自动配置:针对 Spring 常用的功能 SpringBoot 提供自动配置功能。
2,起步依赖:SpringBoot 通过起步依赖为项目的依赖管理提供帮助。起步依赖把常用的库聚合在一起,组成了几个为特定的功能而定制的依赖。
3,端点监控:SpringBoot 可以对正在运行的项目提供监控。
十五、说说Spring Boot的自动装配
SpringBoot 自动装配:SpringBoot 自动装配时,需要引入相应的 Starters(启动器),启动 SpringBoot 后会自动配置相对应的依赖和配置相对应的初始化参数,以最便捷、最简单的方式完成第三方软件的集成。
具体的配置过程是:SpringBoot 通过 @EnableAutoConfiguration 开启自动配置功能,加载 Spring.factories 中注册的 AutoConfiguration 类,当某个 AutoConfiguration 类满足指定的生效条件时,实例化该 AutoConfiguration 中定义的 Bean,并注入到 Spring 容器中,完成依赖框架的自动配置。
十六、说说Spring Boot常用的注解
@SpringBootApplication:它是SpringBoot的核心注解,用于开启自动配置,准确的说是通过该注解内的 @EnablAutoConfiguration 注解实现的自动配置。
@EnableAutoConfiguration:自动配置注解,在启动 Spring 应用程序上下文时进行自动配置,自动配置通常是基于项目 classpath 中引入的类和已定义的 bean 来实现的。
@Import:@EnableAutoConfiguration 的关键功能是通过 @Import 注解导入的 ImportSelector 来完成的。 @Congiguration:配置类注解,根据一些特定条件来控制bean的实例化的行为。
@ComponentScan:位置在SpringBoot的启动类上,Spring包扫描。
十七、说说你对Redis的了解
Redis是一款基于键值对的NoSQL数据库,与其他键值对数据库不同的是,Redis中拥有string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构,这给Redis带来了满足多种应用场景的能力,而且Redis将所有数据放到内存中的做法让它的读写性能十分好。不仅如此,Redis的持久化机制保证了再发生类似断电、机器故障等情况时,内存中的数据不会丢失。此外Redis还提供了键过期、发布订阅、事务、Lua脚本等多个附加功能。总之,在合适的情况下使用Redis会大大增强系统的性能,减少开发人员工作量。
十八、详细的说说Redis的数据类型
redis中常用的五种数据结构:string、list、set、zset、hash。
string:底层数据结构是一个简单动态字符串,支持扩容,存储字符串。
list:存储线性有序且可重复的元素,底层数据结构可以是双向链表/压缩列表。
set:存储不可重复的元素,一般用于求交集、差集等,底层数据结构可以是hash和整数数组。
zset:存储有序不可重复的元素,zset为每个元素添加了一个score属性作为排序依据,底层数据结构可以是ziplist和跳表。
hash:存储键值对,底层数据结构是ziplist和hash。
redis会在性能以及节省内存间考虑,选择最适合当前状态的底层数据结构实现。
十九、请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?
1、set存放的是无序不重复的数据,Zset存放的是有序不重复的数据,主要通过给每个元素添加分数属性来实现排序。
2、Zset底层使用的是压缩表(ziplist)以及跳跃表(skiplist),当元素数量小于128,所有member的长度都小于64字节时使用压缩表,不满足这两个条件时使用跳跃表。set底层使用的是哈希表(hashtable)和整数集合(inset)。
二十一、Redis如何与数据库保持双写一致性
保证双写一致性共有四种方案:
1,先更新缓存,在更新数据库。优点:能够保证数据访问是都会命中redis,降低数据库压力。缺点:每次修改都会操作缓存,降低了服务器的性能。
2,先更新数据库,再更新缓存。缺点:可能导致数据库和redis中的数据不一致(数据库更新成功,redis更新失败)。
3,先删除redis,再更新数据库。缺点:可能导致数据库和redis中的数据都是旧数据(删除redis后,再更新数据时失败了)
4,先更新数据库,再删除redis。缺点:可能导致数据库和redis的数据不一致(redis删除失败)。出错时,使用重试机制异步重新处理。
二十二、如何利用Redis实现一个分布式锁?
最简单redis分布式锁的实现方式:加锁:setnx key value,解锁:del key。问题:如果客户忘记解锁,将会出现死锁。
第二种分布式锁的实现方式:加锁:setnx key value + expire key seconds,解锁:del key。问题:由于setnx和expire的非原子性,当第二步挂掉,仍然会出现死锁。
第三种分布式锁的实现方式:加锁:将setnx和expire变成原子性操作,set key value nx ex seconds,解锁:del key。同时考虑到线程A还在执行,但是锁已经到期,当线程A执行结束时去释放锁时,可能就会释放别的线程锁,所以在解锁时要先判断一下value值,看是不是该锁,如果是,再进行删除。
二十三、说说缓存穿透、击穿、雪崩的区别
缓存穿透:当用户查询的数据不存在,导致访问直达存储层,负载加大,服务器宕机。
发生的可能原因:(1)业务层误删缓存和数据库的数据;(2)恶意访问不存在数据。
解决方式:缓存空对象和设置布隆过滤器。1、缓存空对象就是:如果访问的数据不存在,就将空对象保存在缓存中。2、布隆过滤器:就是在访问缓存之前用过滤器进行拦截,若请求的数据不存在,则返回空值。
缓存击穿:缓存中,某一热点数据过期,导致大量访问直达存储层,导致服务崩溃。
解决方式:永不过期,加互斥锁。
缓存雪崩:指某一时刻缓存层无法继续提高服务,导致大量的访问直达存储层,造成数据库宕机。
造成的原因:缓存中的大量数据在同一时刻过期,或者redis节点发生故障。
解决方式:1、对数据设置随机过期时间。2、构建高可用的redis服务,例如使用哨兵模式和集群模式,部署多个redis实例。
二十四、说说Redis的持久化策略
Redis有两种持久化策略,一种是RDB(Redis DataBase),一种是AOF(Append Only File)。 RDB是Redis的默认持久化策略,在指定时间内执行指定次数的写操作。Redis会复制一个一摸一样的进程类进行持久化操作,保证了主进程的高效率。RDB将数据备份到dump.rdb文件中。每次Redis启动会加载RDB文件。RDB适合大量恢复数据且完整性要求不高的环境下。 AOF的出现是为了弥补RDB的不足(数据的不一致性),采用日志来保存每个写操作,并追加到appendonly.aof文件中。Redis每次启动会将日志文件的内容从前到后的执行一次。 如果两种持久化策略都开启了,Redis启动会优先使用AOF日志恢复数据。因为这个文件能提供更好的持久性保障。
二十五、说说Redis的单线程架构
redis 的网络 IO 和键值对读写是单线程的,而其他功能如持久化、异步删除等是依赖其他线程来执行的。事实上他底层并不是单线程的。
1.对于服务端程序来说,线程切换和锁通常很影响性能,redis 采用单线程则避免了线程间的阿切换和锁,减小了消耗。 2.redis 的大部分操作实在内存上的完成的,因此它的性能很高。
3.redis 采用 IO 多路复用机制,使其能够处理并发请求。
二十六、如何实现Redis高可用
为了减轻数据库访问的压力,把热点数据存储与内存中而不是从后方数据库中读取,但是在大型网络应用中,单个Redis很难保证数据量的访问于存储,所以需要搭建Redis集群,保证数据的分撒存储与数据的一致性,实现Redis的高可用。
1)主从模式:一个master主机与多个slave从机,主节点负责写操作,从节点负责读操作。优点:读写分离,主节点的数据会自动复制给从节点,分担主节点的压力。缺点:一旦主节点宕机,会导致部分数据来实现同步,主节点宕机与从节点宕机都需要进行重启。
2)哨兵模式:本身也是主从模式,但是添加了哨兵功能,可以对主节点进行监控,一旦主节点挂掉就在从节点中进行投票选举。优点:主机可以自组切换,增加了系统的健壮性和高可用性。缺点:在选举期间无法确定主从,无法工作。
3)集群模式:实现的是数据的分布式存储,数据存储与不同的节点上,所以集群实现的是一种去中心化控制。在每个小集群中实现的也是主从模式,从节点只会拉取主节点的备份数据,不会参与数据的存取操作。当主节点宕机,机会启动从节点。
二十七、说说Redis的主从同步机制
Redis 的主从同步指的是任意个从节点都可以从主节点复制数据,除了多个从节点连接到同一个主节点外,一个从节点还可以接收到另一个从节点的连接,形成一个树的结构,使得 Redis 可以完成一个单层树复制。
连接过程: 1)启动一个从节点,从节点发送一个 PSYNC 命令,主节点收到后开启一个后台线程生成一份 RDB 文件发送给从节点,从节点收到后线程存入磁盘,再加载进内存(全量同步)。 2)RDB 发送完成后,主节点还会发送在生成 RDB 期间缓存中新出现的写命令发送从节点。 3)当从节点断开并重新连接时主节点会复制期间丢失的数据给从节点(增量同步)。
二十八、说说Redis的缓存淘汰策略
惰性删除:客户端访问一个 key 的时候,redis 先检查它的过期时间,如果已经过期了,就立刻删除这个 key。
定期删除:redis 会将设置了过期时间的 key 保存到一个字典里面,然后每过十秒就扫描一次。这个定期删除也不扫描字典中所有的 key,而是采用了一种简单的弹性策略。
定期删除对内存更加友好,而惰性删除对CPU更加友好,所以redis采用的是定期删除+惰性/懒汉式删除。