JDK、JRE、JVM的区别
- JDK,是Java开发工具包,用于开发Java程序,提供了编译和运行Java程序的各种工具和资源、类库等
- JRE,Java程序的运行环境,用于解释执行Java的字节码文件,想要运行Java程序必须要安装JRE
- JVM,Java虚拟机,Java程序跨平台的核心,负责解析和执行字节码文件
- JDK包含JRE,JRE包含JVM
重写和重载的区别
- 重载,发生在同一个类中,方法名相同,参数列表不一致,与返回值无关
- 重写,发生在父子类中,方法名和参数列表必须一致,返回值和抛出的异常要小于等于父类,访问修饰符要大于等于父类,父类中的
private
方法不能被子类重写
Java中的==和equals的区别
- ==
- 基本数据类型,比较值是否相等
- 引用数据类型,比较的地址是否相等
- equals
- 未重写前,比较的是地址值
- 重写后,按照重写的逻辑进行比较
String、StringBuffer、StringBuilder的区别
String
代表字符串,是一个final修饰的不可变类,一旦创建就不能修改。StringBuilder
是内容可变的字符串容器,可以进行字符串动态增删,它可以提高字符串操作的效率。如果是多线程开发,会有线程安全问题。StringBuffer
是内容可变的字符串容器,可以进行字符串动态增删,可以提高字符串操作的效率。它是线程安全的类,实现线程安全的方式是所有方法都加上synchronized,效率低,不推荐使用。
什么是单例模式,有实现几种?
程序运行中,同一个类的的实例只有一个,就是单例模式。
- 懒汉式
- 饿汉式
- 饿汉式 + 锁
- 饿汉式 + 双重锁
- 静态内部类实现单例
- 枚举实现单例
- 静态Map工厂实现单例
接口和抽象类的区别?
- 抽象类,需要被类继承。接口需要被类实现
- 接口中的变量只能是公共的静态常量,而抽象类中的变量则可以是普通变量
- 接口可以继承接口,可以多继承,而抽象类只能单继承
List和Map、Set的区别?
- List和Set都是单列集合,Map是双列集合
- List存储的元素是有序的,并且允许重复
- Set存储的元素是无序的,并且不允许重复,HashSet依靠对象的hashCode和equals方法来确定元素位置,而TreeSet依靠元素实现Comparable接口或Compartor比较器确定位置
- Map存储的元素是无序的,键是唯一的,不允许重复,而值是允许重复的
创建线程的方式有?
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法。这种方式没有返回值,不允许抛出编译时异常
- 实现Callable接口,重写call方法。这种方式有返回值,允许抛出编译时异常
- 使用线程池创建线程
Ruanable和Callable的区别?
- Runnable接口的run方法没有返回值,Callable接口的call方法有返回值,并且支持泛型
- Runnable接口的run方法不能抛出编译时异常,必须捕获处理。而Callable接口的call方法允许抛出编译时异常
如何启动一个线程?调用start()和run()有什么区别?
- 直接调用run()方法,只是在当前线程调用了对象的普通方法,并不是开启线程,run()方法运行在调用方的线程中
- 调用start()方法,则是让jvm开启一个线程,然后在新开启的线程中调用run()方法,run()方法运行在新线程中
线程有哪几种状态?状态之间是怎么流转的?
NEW:新建
RUNABLE:可运行(就绪)
TERMINATED:结束
BLOCK:阻塞
WATING:无限等待
TIME_WATING:定时等待
线程Thread类对象被new后,就是
新建状态
线程对象调用start()后就流转为
可运行状态
,等待CPU调度线程的run()方法执行完毕,线程就会流转为
结束状态
,然后线程被销毁-
线程执行到同步代码块时
- 如果没有抢到锁,则进入
阻塞状态
- 如果抢到锁,则可以继续执行
- 如果没有抢到锁,则进入
在阻塞状态时,如果重新抢到锁,则回归到
可运行状态
可运行状态时,如果调用了wait()方法,则进入
无限等待状态
,如果调用了sleep(timeount)或wait(timeout)则进入定时等待状态
当另外一个线程调用了notify()或notifyAll(),并且重新抢到锁后,则回归到
可运行状态
如果是
定时等待状态
,重新抢到了锁,则自动回归到可运行状态
wait()和sleep()的区别?
-
来自不同的类
- wait()来自Object,sleep()来自Thread
-
关于锁的释放
- wait()在等待过程中会释放锁
- sleep()在等待过程中不会释放锁
-
关于使用范围
- wait()必须在同步代码块中调用
- sleep()可以在任何地方调用
-
是否需要捕获异常
- wait()不需要捕获异常
- sleep()需要捕获异常
常用线程池种类
- newCacheThreadPool,创建一个可进行缓存,可重用的线程池
- newFixThreadTool,创建一个固定线程的,可重用的线程池
- newSingleThreadExecutor,创建一个单工作线程的Executor,它使用无界队列,该线程池最多执行一个线程
- newSingleThreadScheduledExcutor,创建一个单工作线程的Executor,它可以延时执行任务或定期执行任务
- newScheduledThreadPool,创建一个线程池,它可延时执行任务或定期执行任务
- newWorkStealingPool,创建一个并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如果不传并行级别参数,那么默认为当前系统的CPU核心数 * 2
线程池创建时的参数作用?以及执行流程
- corePoolSize:核心线程数
- maxPoolSize:最大线程数
- keepAliveTime:空闲线程的空闲时间
- unit:空闲时间的时间单位
- workQueue:工作队列,保存任务的阻塞队列
- threadFactory:线程工厂
- handler:饱和策略(也叫拒绝策略)
ArrayList和LinkedList的区别
- ArrayList和LinkedList都实现了List接口,都用于存储元素,并提供对元素增删查改的方法。
- ArrayList底层使用数组实现,它的查找速度很快,并且提供索引进行访问元素的方法,当在尾部插入或删除元素时,耗时时间是一致的。但如果在中间添加或删除元素时,ArrayList需要移动元素,就会比较耗时。
- LinkedList的底层使用链表实现,它的增删元素只需要改变前后元素的前指针和后指针,所以效率比较高,而查找时需要移动指针,相对效率会比较低。
- ArrayList的空间浪费体现在它需要在尾部预留一定容量空间。而LinkedList的空间浪费体现在每个元素都要有一个前指针和一个后指针。
数据库的四大特性
- 原子性:多条指令要么同时成功,要么同时失败
- 一致性:事务前和事务后,数据是一致的
- 隔离性:事务提交前,不会影响其他事务
- 持久性:事务提交后,就会永久存入磁盘
事务的隔离级别
- 读未提交
- 读已提交
- 可重复读
- 可串行化
MyBatis的#{}
和${}
有什么区别?
-
#
是一个占位符,$
是拼接符 -
#{}
,会将内容替换为?号,使用预编译,使用PreparedStatement来执行SQL,可以防止SQL注入,提高安全性 -
${}
,是字符串拼接,会有SQL注入的风险,不能避免注入攻击
MyBatis的resultType和resultMap的区别?
- 如果数据库表的字段名和实体类的成员变量名一致,就可以使用resultType,MyBatis会自动映射查询结果到实体类中
- 如果不一致,那么则要使用resultMap,单独配置2者的映射关系
MyBatis常用动态SQL标签有哪些?有什么作用?
-
<if>
标签,动态SQL的多条件动态拼接 -
<where>
标签,相当于where条件,当有条件时拼接上where关键字,没有条件时则不拼接(相当于不需要使用 where 1 = 1 来保证SQL语法正确),可以去掉多余的and
或or
-
<foreach>
标签,遍历传入的集合或数组,将遍历的每一项拼接为一个字符串(例如在 IN 和 NOT IN 中使用) -
<sql>
和<include>
标签,sql标签可以把SQL的公共片段抽取,例如查询的字段列信息。include标签用于在SQL中引入sql标签的SQL片段,复用SQL片段,减少重复代码 -
<set>
标签,相当于set关键字,用于update语句中,能够去掉多余的逗号(例如最后一个条件的逗号)
Get请求和Post请求的区别
- Get请求不安全,它的请求参数在请求行中,会显示在浏览器的地址栏,用户是可见的,而Post的请求参数在请求体中,不会显示在地址栏中,用户不可见,相对安全
- Get请求传输数据量小,由于不同的浏览器,限制URL的长度不同,因为限制大小也不同,但一般在18kb以内。而Post请求默认不限制大小,所以可以传输更多的数据
- Get请求限制数据必须为ASCII字符,而POST请求则支持整个ISO10646字符集
- Get请求没有请求体,而Post请求有请求体,所以Get请求的效率更高,form表单默认使用Get请求
- 总结:传输非敏感数据,数据量小,使用Get请求。传输敏感数据,数据量大,使用Post请求
Servlet生命周期
- Servlet生命周期,就是Web服务器创建Servlet对象到销毁的过程
- 生命周期方法有:init()初始化方法,service()处理请求方法,destroy销毁方法
- init()初始化方法,创建Servlet对象时调用,只会调用一次
- service()处理请求方法,每次请求该Servlet资源都会调用一次
- destroy()销毁方法,Web服务器关闭或重启时调用,只会调用一次
- Servlet对象创建的时机,在第一次访问该Servlet资源时创建,它使用单例模式,所以只会创建一次,节省内存
- 通过配置load-on-startup参数,就可以让Web服务器启动时,就自动创建该Servlet对象,提升用户的访问速度
请求转发和重定向的区别
- 请求转发只有1次请求,而重定向会有2次请求
- 请求转发不会改变浏览器地址栏地址,而重定向会改变地址栏地址
- 请求转发是服务器内部跳转,而重定向是浏览器跳转
- 请求转发只能跳转当前项目内的资源,重定向可以跳转任何资源,包括外部资源
- 请求转发可以共享Request请求域内的数据,而重定向不可以
什么是HTTP协议,有什么特点和优缺点?
- HTTP协议,就是HyperText Transfer Protocol 超文本传输协议,规定了浏览器和服务器之间数据传输的规则
- 特点:
- 基于TCP协议,面向连接,安全
- 基于请求-响应模型,一次请求只有一次响应
- 请求数据包括,请求行、请求头、请求体
- 响应数据报错,响应行、响应头、响应体
- HTTP是无状态的协议,对于事务处理没有记忆能力,每次请求、响应都是独立的
- 优点:速度快
- 缺点:多次请求间不能共享数据
Cookie和Session的区别?
- 存储问题不同
- Cookie存储在浏览器,Session存储在服务器
- 存储容量不同
- 单个Cookie保存的数据只能<=4kb,一个网站一般能保存20~50个Cookie,不同的浏览器也有区别
- Session没有上限,受限于服务器的内存
- 存储方式的数据类型不同
- Cookie只能存储字符串数据
- Session可以存储任何类型的数据
- 隐私策略不同
- Cookie对客户端是可见的,可能会出现篡改Cookie数据进行欺骗,所以它是不安全的
- Session因为是存储在服务器,不存在敏感信息泄露的风险
什么是Session的钝化和活化?
- 钝化是指服务器正常关闭后,Tomcat会自动将Session数据写入到磁盘,钝化要求Session保存的数据必须实现Serializable序列化接口
- 活化是再次启动服务器后,从磁盘中读取文件,加载数据到Session中
什么是Ajax,有什么优势?
- Ajax,就是Asynchronous JavaScript And XML,异步的JavaScript和XML
- 由前端实现异步请求服务端接口,进行通信交互
- 优势:通过异步非阻塞方式进行请求,用户不需要等待,提升用户体验
- 优化了浏览器和服务器之间的传输,减少了不必要的数据往返,减少了带宽占用,性能好(后端只返回数据,而不需要返回整个网页)
JavaWeb的三大组件及其作用
- Servlet:用于处理资源的请求和响应
- Filter:过滤器,用于拦截请求,以及统一操作每个请求的一些通用处理,例如权限控制、统一编码等
- Listener:监听器,用于监听ServletContext、Request、Session的创建和销毁,以及这3个域对象中数据变化,数据变化时进行额外的业务处理
JSP和Servlet的区别
相同点,JSP编译后,本质就是一个Servlet,由于JVM只能识别Java类,所以需要Web服务器将JSP编译为Servlet,当请求到来时,调用生命周期方法进行处理
不同点,JSP侧重于视图和展现数据,Servlet侧重于逻辑控制和获取数据
Spring的IOC、DI、AOP分别是什么?IOC和DI有什么关系?
- IOC是控制反转,将对象的创建,交给Spring容器,不再亲自new对象,而是Spring根据我们的配置文件生成对象,当我们需要对象时,再通过IOC容器进行获取
- DI是依赖注入,是运行过程中,对IOC中的对象的成员属性进行赋值
- AOP是面向切面编程,是将项目中非业务代码的抽取,进行最大程度的解耦,Spring的AOP使用JDK动态代理,或使用CGLIB进行动态代理
- IOC和DI的区别,IOC侧重于对象的创建上的解耦,主要是将对象创建交给Spring容器。而DI侧重于对象使用上的解耦,对象需要依赖哪些对象,向IOC容器进获取
Spring的Bean作用域有哪些?每种作用域是怎样的?
- Singleton,单例,是默认的作用域,IOC容器启动时就会创建该作用域的bean,每个容器只有一个对象
- prototype,多例,每次向IOC容器获取对象时,都会创建一个新的bean对象
- request,在web工程中使用,IOC容器会在每次Request请求时,创建bean对象,并设置到request域中,同一个request对象中共享一个bean对象,request结束中,bean就会销毁
- session,在web工程中使用,IOC容器在每次会话开启时,创建bean对象,并设置到session域中,同一个session会话中共享一个bean对象,会话结束后,bean对象就会销毁
- global-session,在web工程中使用,在多台web服务器中,所有的session会话共享一个bean实例
Spring的对象默认是单例还是多例?单例bean存在线程安全问题吗?
- Spring的bean的默认作用域是单例的,可以设置bean对象的scope为prototype则为多例
- 在多线程的情况下,操作单例bean的成员属性,会有线程安全问题
- 解决方案是避免在单例bean中定义成员变量,如果无法避免,则需要将成员属性设置到ThreadLocal中
MyBatis编程步骤是怎样的?
- 导入MyBatis的依赖
- 编写Mapper接口
- 编写Mapper映射XML文件
- 执行MyBatis的操作
- 创建SqlSessionFactory
- 通过该工厂类创建SqlSession
- 通过SqlSession的getMapper,创建Mapper接口的代理类,并执行数据库操作
- SqlSession提交事务
- 关闭SqlSession,释放资源
谈谈你对MyBatis的缓存机制的理解
- MyBatis有2级缓存
- 一级缓存是SqlSession级别的,默认开启
- 二级缓存是Mapper级别的,默认不开启,需要手动开启
- 一级缓存,是SqlSession级别的,一个SqlSession范围内共享该缓存,第一次查询时会将查询结果缓存,第二个查询直接返回缓存。当进行增、删、改、提交事务时,就会清空一级缓存
- 二级缓存,是Mapper级别的,多个SqlSession都可以共享该缓存,要使用二级缓存,需要做2个步骤
- 实体类实现Serializable序列化接口
- Mapper.xml中添加
<cache>
标签,才能开启二级缓存
- 当sqlSession执行提交或关闭时,写入缓存
- 执行增、删、改操作时,清空二级缓存
Spring中@Autowired和@Resource的区别
-
@Autowired
是Spring提供的,而@Resource
是javax包下的 -
@Autowired
默认按类型匹配,而@Resource
默认按名称匹配 -
@Qualifier
需要和@Autowired
一起使用,而@Resource
可以单独使用 - Java9及其以上版本,
@Resource
已被删除,所以推荐使用@Qualifier
和@Autowired
Spring的事务传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,Spring如何处理这些事务的行为。备注(方便记忆): propagation传播
require必须的/support支持/mandatory 强制托管/requires-new 需要新建/ not -supported不支持/never从不/nested嵌套的
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
Spring的常用注解
-
IOC注解
- @Component(任何层) @Controller @Service @Repository(dao): 用于实例化对象
- @Scope : 设置Spring对象的作用域
- @PostConstruct、@PreDestroy : 用于设置Spring创建对象在对象创建之后和销毁之前要执行的方法
- @Bean: 表在方法上,用于将方法的返回值对象放入容器
-
DI注解
- @Value: 简单属性的依赖注入
- @Autowired: 对象属性的依赖注入
- @Qualifier: 要和@Autowired联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
- @Resource 按照类型和属性名称依赖注入 @Resource =@Autowired+@Qualifier
- @ComponentScan: 组件扫描
-
AOP注解
- @Before 前置通知,会在运行原有方法前面执行
- @AfterReturning 后置通知,会在运行原有方法后面执行,前提原有方法不发生异常。
- @AfterThrowing 异常通知,会在运行原有方法发生异常的时候运行
- @After 最终通知,会在运行原有方法后运行, 无论原有方法是否发生异常都会运行
- @Around 环绕通知,一个环绕就可以实现上面4个位置的增强
- @Aspect 标识当前类为切面类
- @Pointcut切入点表达式
-
事务注解
- @Transactional 此注解可以标在类上,也可以标在方法上,表示当前类中的方法具有事务管理功能。
-
其他配置
- @PropertySource: 用于引入其它的properties配置文件
- @Import: 在一个配置类中导入其它配置类的内容
- @Configuration: 被此注解标注的类,会被Spring认为是配置类。Spring在启动的时候会自动扫描并加载所有配置类,然后将配置类中bean放入容器
Spring事务的实现方式和实现原理
- Spring事务的本质其实就是
数据库对事务
的支持,没有数据库的事务支持,Spring是无法提供事务功能的。 - Spring事务会调用数据库设置
手动控制
事务set autocommit = 0
,之后通过commit
提交和ro11back
回滚,数据库底层是通过binlog
或者redolog
实现的。 - Spring事务实现主要有两种方法
-
编程式
(编码控制事务),使用Spring框架提供的事务管理器模板TransactionTemplate
相关的方法实现事务控制,会造成代码重复几余。 -
声明式
,利用注解@Transactiona
或者aop
配置
-
Spring中@Autowired和@Resource的区别
-
@Autowired
是Spring框架的,默认按照byType
自动装配,@Resource
是javax包下的和jdk8及以下版本存在,默认byName
自动装配 -
@Autowired
和@Qualifier
一起用可以自定义别名注入,@Resource
可以单独使用
Spring的Bean生命周期
-
简单版
- Spring的Bean生命周期,就是Bean的创建到销毁的过程
- 初始化容器阶段
- 创建Bean对象(内存分配),执行构造方法
- 执行属性注入(set方法、依赖注入)
- 执行Bean的初始化方法
- 使用Bean
- 执行业务操作
- 关闭容器
- 执行Bean销毁方法
-
复杂版
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的依赖和值注入到Bean的属性中
- 如果Bean实现了
BeanNameAware
接口的话,Spring将Bean的id传递给setBeanName()
方法 - 如里Bean实现了
BeanFactoryAware
接口的话,Spring将调用setBeanFactroy()
方法,将BeanFactroy容器实例传入 - 如果Bean实现了
ApplicationContextAware
接口的话,Spring将调用Bean的setApplicationContext()
方法,将Bean所在应用上下文引用传入进来 - 如果Bean实现了
BeanPostProcessor
接口,Spring就将调用他们的postProcessBeforelnitialization()
方法 - 如果Bean实现了
InitializingBean
接口,Spring将会调用他们的afterPropertiesSet
方法。类似的,如果Bean使用init-method
声明了初始化方法,该方法也会被调用 - 如果Bean实现了
BeanPostProcessor
接口,Spring就将调用他们的postProcessAfterlnitialization()
万法 - 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁
- 如果Bean实现了
DisposableBean
接口,Spring将调用它的destory()
接口方法,同样,如果bean使用了destoy-method
声明销毁方法,该方法也会被调用
SpringMVC中拦截器的使用步骤?
- 拦截器,可以拦截器的方法,可以做一些通用增强的功能
- 定义拦截器类
- SpringMVC为我们提供了拦截器规范的接口,创建一个类实现
HandTerInterceptor
,重写接口中的抽象方法 -
preHandle
方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,控制器也不会调用 -
afterCompletion
方法:在前端控制器渲染页面,完成之后调用此方法
SpringMVC的有哪些主要组件
- 前端控制器
Dispatcherservlet
:接收请求、响应结果,相当于转发器,有了Dispatcherservlet
就减少了其它组件之间的耦合度 - 处理器映射器
HandlerMapping
:根据请求的URL来查找Handler
- 处理器适配器
HandlerAdapter
:负责执行Handler
- 处理器
Handler
:处理业务逻辑的Java类(Contro1ler
类) - 视图解析器
viewResolver
:进行视图的解析,根据视图逻辑名将ModeTAndview
解析成真正的视图 (view) 并跳转到视图页面
SpringMVC和SpringBoot的关系
- SpringMVC,提供了一种轻度耦合的方式来进行Web开发,它是Spring的一个??椋且桓鯳eb层的框架
- SpringBoot,实现了自动配置,降低了Spring项目搭建的复杂度
- SpringBoot只是辅助简化Spring项目的搭建过程的,如果搭建的是Web项目,Web层采用SpringMVC,那么SpringMVC的工作原理还是跟原来一样,并没有因为使用SpringBoot而改变
SpringMVC各个组件的执行流程
- 用户发送请求到前端控制器
DispatchServlet
- 前端控制器
DispatchServlet
收到请求后,调用处理器映射器HandlerMapping
,进行查找处理器Handler
- 处理器映射器
HandlerMapping
,根据URL找到具体的处理器Handler
,以及对应的拦截器HandlerIntercepter
,将它们一起返回给前端控制器DispatchServlet
- 前端控制器
DispatchServlet
,调用处理器适配器HandlerAdapter
,进行处理 - 处理器适配器
HandlerAdapter
则调用处理器Handler
(也就是Controller)进行处理,首先将请求参数映射到处理器的方法参数上,然后调用处理方法进行处理,以及返回结果(ModelAndView
模型视图对象),交回给前端控制器DispatchServlet
- 前端控制器
DispatchServlet
则将ModelAndView
模型视图对象,交给视图解析器ViewReslover
,视图解析器则会解析视图地址
,进行请求转发
跳转到视图页面,使用视图数据进行渲染视图,最后再渲染结果交回给前端控制器DispatchServlet
- 前端控制器
DispatchServlet
,将渲染结果返回给浏览器 - 如果是
异步请求
,返回JSON数据
,那么则不需要进行视图解析,直接将Json字符串
数据返回给浏览器
SpringMVC的常用注解
-
@RequestMapping
,用于处理所有请求类型的Url映射的注解??捎糜诶嗌匣蚍椒ㄉ?- 用于类上时,表示该类的所有方法都是以该地址作为父路径
- 用于方法上时,表示该方法能处理的资源路径
-
@RequestMapping
还衍生出4个常用的注解-
@GetMapping
,该注解表示处理Get
请求 -
@PutMapping
,该注解表示处理Put
请求 -
@DeleeteMapping
,该注解表示处理Delete
请求 -
@PostMapping
,该注解表示处理Post
请求
-
-
@RequestBody
,该注解表示接收Http请求传递的json格式的请求体,并将json格式请求体转换为Java对象 -
@ResponseBody
,该注解表示该controller方法返回的对象,会转换为json字符串返回给客户端 -
@PathVariable
,该注解表示方法绑定的url中的占位符参数到指定的方法参数,通过@Pathvariable("要获取的参数名")
来指定 -
@RequestParam
,该注解表示方法参数接收Http请求的参数之前,做一些限制-
value
属性,指定该方法参数接收哪一个请求参数 -
required
属性,指定该方法参数对应的请求参数是否必传 -
defaultValue
属性,当该方法参数对应的请求参数未传时,参数的默认值
-
-
@ControllerAdvice
,用于在类上,表示该类是一个全局异常处理器类 -
@ExceptionHandler(Exception.class)
,用于异常处理器的方法上,表示该方法能处理的异常类型
SpringBoot的常用注解
- @SpringBootApplication注解,SpringBoot项目的核心注解,每个SpringBoot启动类上都有,用于引导SpringBoot项目启动和加载
- @ComponentScan注解,用于扫描Spring的组件,并将其加入IOC容器
- @Configuration注解,声明该类为配置类
- @ConditionOnClass注解,一般与@Configuration注解同时使用,项目导入@ConditionOnClass注解声明的类时,@Configuration配置类中,使用@Bean的方法才会被调用,才会构建方法返回的对象
- @ControllerAdvice和@RestControllerAdvice,声明该类为全局异常处理类
Spring框架中都用到了哪些设计模式
- 工厂模式,BeanFactory就是简单工厂的体现,用来创建对象的实例
- 单例模式,Bean默认为单例模式
- 代理模式,Spring的AOP功能用到了JDK动态代理与CGLIB动态代理
- 模板模式,用于来解决代码重复的问题,比如RestTemplate、JmsTemplate、JpaTemplate、TransactionTemplate
- 观察模式,包含被观察者与观察者两类对象,一个被观察者可以有若干个观察者,一旦被观察者的状态变化,所有观察者都会得到通知。Spring的事件驱动模型就是观察者模式的应用。Spring中的事件监听器的开发,当事件(被观察者)发生时,会自动触发监听器(观察者)的运行。例如Spring中的一种Listener,ApplicationListener
SpringBoot的优势
-
版本锁定
,解决的是maven依赖容易冲突的问题,SpringBoot提供的父工程中集成了常用、且测试过的依赖库版本 -
起步依赖
(简化配置依赖),提供了众多的starter启动器,starter的依赖中集成了需要的依赖以及版本,进行了统一的控制,解决了某一个功能需要整合大量的jar包与依赖的问题 -
自动配置
(简化配置),解决了整合众多框架与技术的配置文件、配置类过多的问题,使用约定大于配置的思想,提供了大量的默认配置 -
内置Tomcat
(简化部署),通过内置Tomcat部署与运行,无需使用外置Tomcat即可直接运行Web应用 - 总结:SpringBoot被称为搭建Web应用的脚手架,主要作用就是帮助开发者快速构建一个庞大的Spring项目,并尽可能减少xml配置与配置类,做到开箱即用,快速上手,让开发者关注业务逻辑而非配置
说一下SpringMVC的统一异常处理的思想和实现方式
- 使用SpringMVC后,我们的代码将有SpringMVC来负责调用,所以我们的代码导致的异常,最终都会抛出到框架中,然后由框架指定异常类来进行统一处理
- 实现统一异常处理的方式:
- 方式一:创建一个类,作为自定义异常处理器,需要实现
HandlerExpcetionResolver
接口,并实现接口里面的异常处理方法,然后将这个类加入到IOC容器中 - 方式二:创建一个类,在类上使用
@ControllerAdvice
注解或RestControllerAdvice
,在类中定义异常处理方法,并在处理方法上添加@ExceptionHandler
注解,在该注解上有一个value属性,用于指定这个异常处理方法能处理哪个类型的异常
- 方式一:创建一个类,作为自定义异常处理器,需要实现
在SpringMVC中,如果想通过请求转发将数据传递到前台页面,有几种写法?
- 方式一:直接使用reuqest域对象进行数据传递
request.setAttirbuate("name", value);
- 方式二:使用Model类进行传值,它的底层会将数据放入request域进行数据的传递
model.addAttribuate("name", value);
- 方式三:使用ModelMap进行传值,底层会将数据放入request域进行数据的传递
modelmap.put("name", value);
- 方式四:借用ModelAndView在其中设置数据和视图
mv.addobject("name", value);
mv.setView("success");
return mv;
SpringBoot的启动器starter是什么?它的执行原理?
- 什么是starter?
- starter启动器,可以通过启动器集成其他的技术,比如说:
web
,mybatis
,redis
等,可以提供对应技术的开发和运行环境 - 比如: pom中引入
spring-boot-starter-web
,就可以进行web开发
- starter启动器,可以通过启动器集成其他的技术,比如说:
- starter执行原理?
- SpringBoot在启动时,会扫描jar包中名为的
spring.factories
配置文件,根据配置文件,加载自动配置类,配置文件的格式为key=value
,value配置了很多需要Spring加载的类 - Spring会去加载这些自动配置类,Spring读取后,就会创建这些类的对象,然后放到Spring容器中,后期就会Spring的容器中获取这些对象
Redis的数据类型有哪些?
Redis一共有5种数据类型:
- String,字符串类型,最基础的类型,最常用
- Set,可以存储多个值,无序,存储的数据不可重复
- List,可以存储多个值,有序,存储数据可以重复,存储顺序就是排序的顺序,可以实现队列和栈
- Hash Value,键值对,可以对里面的value灵活地修改
- SortSet(ZSet),可以存储多个值,有序,存储元素不可重复,可以实现实时排序
SpringCloud常用组件
- Eureka:服务发现与注册,由Netfilx开源
- Nacos:服务发现与注册,以及配置中心的管理功能,由阿里巴巴开源
- SpringCloudGateway:微服务网关,微服务统一路由,统一鉴权、跨域、限流等功能
- Feign:微服务之间远程过程调用,由Netflix开源
- Ribbon:负载均衡组件,在网关路由和Feign远程过程调用中,底层都会使用到Ribbon实现负载均衡
Eureka与Nacos的区别?
- 共同点
- 都支持服务注册与服务拉取
- 都提供服务提供者心跳方式进行健康检测(Eureka30秒一次,Nacos15秒一次)
- 区别
- Nacos支持服务端主动检测提供者的状态,临时实例采用心跳机制,非临时实例(常驻实例)采用主动检测机制
- 临时实例心跳不正?;岜惶蕹?,而非临时实例(常驻实例)则不会被剔除
- Nacos支持服务列表变更后,进行消息推送,服务列表更新更及时(Eureka是每次发起远程调用时拉取,Nacos则是定时更新)
- Eureka是短连接操作(每次拉取完,就断开连接),Nacos是长连接操作(netty实现,一直连接,每次拉取完,不会断开)
- Nacos还可以作为微服务的配置中心,Eureka则不能
Elasticsearch比MySQL的优势在哪里?(为什么要使用Elasticsearch?)
- MySQL的海量数据时,搜索效率比较低,使用Like关键词,如果%放左边,执行全表扫描,导致性能差,而Elasticsearch采用倒排索引法检测数据,从而效率更高
- MySQL的搜索功能比较弱,只有like这种模糊搜索,而Elasticsearch拥有大量复杂场景搜索的API(高亮显示,拼音搜索,地理位置检索),更加适合数据搜索场景
请说说Elasticsearch倒排索引原理?
首先,Elasticsearch将文档数据进行索引构建。将文档数据需要分词的字段内容使用分词器进行分词,并记录每个词条和原文档的出现位置和出现频率等信息,构建出文档的索引库
然后,用户搜索时,可以对关键词进行分词,使用分词后词条来匹配索引库,在索引库匹配到记录后,通过文档位置和频率信息,反查具体的文档数据
请说说什么是分词器?ES有哪些常用的分词器?
- 分词器是Elasticsearch用于对内容进行分词的工具(程序),Elasticsearch内置许多分词器,默认使用Standard标准分词器,而标准分词器对中文支持并不好友(因为它对中文进行单字分词)
- 所以在开发中进行中文分词时使用第三方的ik分词器,ik分词器内置有ik_smart和ik_max_word算法,ik_smart是最小分词器法,ik_max_word是最细分词法。
MySQL、Redis、MongoDB、Elasticsearch各自的优势?
- MySQL:是关系型数据库,磁盘。有复杂表关系(一对一,一对多,多对多),并且有完善事务机制(ACID)。例如:用户、订单、商品
- Redis: 非关系数据库,内存。Redis建议只存储热点(用户查询频率极高)的数据,且数据量相对小的数据。例如:秒杀的库存量、手机验证码、用户token
- MongoDB: 非关系数据库,磁盘。MongoDB适合相对高频擦写(增删改)的海量数据。例如:评论
- Elasticsearch: 非关系数据库,磁盘。Elasticsearch适合海量数据的复杂检索。例如:商品搜索
- 效率: Redis > MongoDB /Elasticsearch:> MySQL
请问Elasticserach中的match和term检索有什么区别?
- match:全文检索,分词,用在text类型中
- 先对搜索内容进行分词,得到词条
- 使用词条匹配倒排索引,得到文档ID
- 再使用文档ID,查询具体的文档记录,进行聚合(并集或交集)
- term:精确匹配,不分词,用在keyword、boolean、日期、数值类型中
- 使用搜索内容,使用搜索全文匹配索引库,得到文档ID
- 使用文档ID,查询具体的文档记录,进行聚合(并集或交集)
请问Elasticsearch如何实现搜索附近的景点?
- 在景点表中,保存景点的经度和纬度
- 前端将用户的经纬度坐标,上传到后端服务中
- 后端根据用户的经纬度坐标,使用Elasticserach的geo_distance,做升序排序,参数是地理坐标、升序或降序排序
"sort": [
{
"_geo_distance": "22.57.113.88",
"order": "asc",
"unit": "km"
}
]
请问在Elasticserach中,如何实现提升指定搜索结果的权重?(类似百度搜索结果中的广告竞价排名)
首先,Elasticsearch默认情况下,使用BM25算法(5.1版本之前,使用TF-IDF算法),计算_score得到算分,按照计算排序。
-
TF-IDF算法:TF(词频) * IDF(逆文档率)
- TF:词频,词条在文档中出现的频率
- IDF:逆文档率,计算词条在所有文档中的权重(出现越多文档,权重越低,反之则越高)
BM25:是TF-IDF算法的升级版,单个词条的算分有一个上限,不至于过高,让曲线更加平滑
如果希望改变指定结果的权重,可以使用
function_score
函数,进行提分function_score函数大体有3部分:
1)原始查询条件
2)过滤条件和权重分值,过滤条件就是哪些部分的文档需要提分
3)加权模式,对权重分值进行sum、avg、相乘等规则
在Elasticsearch中,如何实现景点拼音搜索
- 在Elasticsearch安装拼音分词插件
- 因为拼音分词器只是把词的每个字进行转拼音,无法实现对一个词进行拼音转换,不太满足项目需求
- 这时就需要结合ik分词器,进行自定义分词器
- 自定义分词器,需要配置为先使用ik分词器进行中文分词,然后再将这些词进行拼音分词器,进行转换为拼音
- 但是在搜索中,为了避免中文转为拼音进行搜索,所以搜索时还是使用ik分词器进行分词,使用
search_analayzer
来指定,如:
"analyzer": "my_analyzer",
"search_analyzer": "ik_max_word"
在Elasticsearch中,如何实现搜索景点时,关键字高亮
- 在景点索引库中,添加一个
completion
字段,该字段的内容是数组,在里面填充需要补充的数据 - 在用户搜索时,每次输入关键词时,使用
suggest
搜索,进行suggestion
搜索,把结果列表返回给前端
GET /test/_search
{
"suggest":{
"title_suggest":{
"text":"s",
"completion":{
"field":"title",
"skip_duplicates":true,
"size":10
}
}
}
在Elasticserach中,如何与MySQL进行数据同步?
- 同步方式(不推荐),在对MySQL的数据进行增删改时,同步调用ES更新数据,这种方式会导致整个增删改功能的耗时增加,以及级联失败
- 异步方式,使用MQ发送异步消息,实现增量同步
- 在生产者(如后台管理系统),的增删改方法中,调用MQ发送消息,带上主键ID给MQ
- 在消费者(如前台服务),编写MQ消息监听类,针对增加、更新、删除消息,对ES进行增删改数据(这种方式,如果直接在数据库中,增删改数据,会导致ES和MySQL数据不一致)
- 使用阿里巴巴的Canal,监听MySQL的 bin log 日志,同步增删改到ES中,这种方式,对业务速度影响最小,能更快的响应用户的请求,用户体验更好,但编程方式稍微有些复杂,而且Cannal只支持MySQL,如果数据换成Oracle数据库,则不能支持
请问Elasticserach的脑裂问题,是如何产生?要如何解决?
- 什么是脑裂?
- 一个Elasticserach集群中,出现了多个master主节点的情况,就被称之为脑裂
- 脑裂是如何产生的?
- master主节点和slave从节点,因网络延迟、网络阻塞、网络故障等原因,导致slave从节点无法和master主节点通信
- master节点,需要处理数据,也需要处理任务派发,复杂过重,导致出现假死
- Elasticserach的JVM内存不足,进程出现无响应
- 以上情况,都可能会导致master节点临时丢失,导致slave从节点重新选举master节点(重新选主)
- 如何解决?
- 修改Elasticserach的节点通信的默认超时时长,默认为3秒,改长一些
- master主节点职责分离,master主节点不作为数据存储节点,
nodes.data=false
,减轻主节点的工作量 - 加大Elasticserach的JVM内存,默认1个G,改大一点,最好为物理机器的内存的一半
- 修改Elasticserach的选举票数,默认为3票就开始重新选主,改为
(备选节点数 / 2)+ 1
,ES7默认已经改好,ES6或ES5需要手动修改一下这个值
SpringCloud和SpringCloudAlibaba有什么关系?
-
SpringCloud属于Spring家族的成员
- 组件有:Eureka(注册中心)、Gateway(网关)、Feign(远程过程调用)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、SpringCloudConfig(配置中心)、Sleuth(链路跟踪)
- 依赖特点:spring-cloud-starter-netflix 开头
-
SpringCloudAlibaba属于SpringCloud的一套组件
- 组件有:Nacos(注册中心和配置中心)、Sentinel(熔断器)、Seata(分布式事务)
- 依赖特点:spring-cloud-starter-alibaba 开头
Sentinel和Hystrix的区别?
- 隔离方式不同,Sentinel采用信号量(计数器),而Hystrix支持线程池和信号量,默认采用线程池,信号量性能比线程池好
- 熔断策略不同,Sentinel可以支持超时比例和异常比例,而Hystrix只支持异常比例
- 限流功能不同,Sentinel有丰富的限流功能(QPS、热点参数、关联模式、链路模式等),Hystrix限流功能非常弱
- 第三方框架整合方面,Sentinel可以整合SpringCloud和Dubbo,Hystrix只能整合SpringCloud
介绍一下,Sentinel的熔断机制以及使用?
- Sentinel的熔断机制,主要有线程隔离和熔断降级,启用步骤:
- yml配置文件中,开启Sentinel的线程隔离和熔断降级功能,然后在Sentinel控制台页面中,加上隔离最大并发线程数或熔断参数配置(统计时间,响应超时时间,请求数,熔断时间等)
- 接着,给Feign接口定制一个服务降级实现类,在隔离和熔断发生后,给用户返回友好提示信息
# application.yaml配置文件
feign:
sentinel:
enabled: true # 开启sentinel支持
//Feign接口的服务降级实现类
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
User user = new User();
user.setUsername("查无此人");
user.setAddress("查无地址");
return user;
}
};
}
}
- 线程隔离:在服务消费方加入服务调用占用线程数统计,一旦超过线程数上限,则做服务降级(在服务消费方定制一个降级处理方法,定制失败消息)
- 熔断降级:在服务消费方加入超时或异常比例统计程序,该程序一旦统计超过比例,一旦比例超过阈值,则做服务降级(在服务消费方定制一个降级处理方法,定制失败消息),熔断有时长,时间到达会尝试请求1次,如果成功,则正常调用,如果失败,继续熔断
请解释一下SpringCloud中Feign接口调用的过程/原理?
- Feign是声明式Http客户端,Feign的接口写在服务消费者中,当我们依赖注入Feign的接口时,会使用JDK动态代理,生成接口的实现类。当我们调用Feign接口中的方法时,会拦截我们调用的方法,接着就会解析方法上的注解
- 例如会解析接口上的
@FeignClient注解
,获取我们配置的服务名,Feign通过Ribbon从注册中心nacos中,获取到该服务的服务列表,通过负载均衡算法,挑选出1个,就可以拿到服务的ip和端口,拼接成url - 接着,会解析方法上的例如
@GetMapping
、@RequestParam
注解获取到请求方法、资源地址和请求参数 - 最终通过
RestTemplate
,发起http请求给服务提供者,服务提供者收到请求后,处理请求,返回响应数据,Feign就会使用Jackson,将响应的json数据转换为java对象返回给我们
大概解释一下熔断器的执行流程?
- 熔断器是一个统计超时比例、异常比例的程序,它是放在服务消费者中的,熔断器有3个状态:关闭、开启、半开
- 当服务消费方的请求的超时比例或异常比例,没有达到阈值时,熔断器处于一个关闭状态,请求可以正常通过
- 而当超过了阈值后,熔断器状态处于开启状态,请求会被降级,降级后,会执行我们配置的Fallback接口,我们返回一个对用户友好的提示信息
- 然后,熔断器会等待一段时间,例如5秒,就会进入半开状态,会尝试放行1个请求,如果请求成功,那么熔断器切换到关闭状态,放行后续的请求,如果请求失败,那么切换回开启状态,继续对请求进行降级
说一下CAP定理和BASE理论
CAP定理,C是值一致性,A指可用性,P指容错性
CAP定理,在说P必然存在的,C和A只能选择一个特性。在CAP定理,只存在CP或AP
-
BASE理论,是对CAP一种补充:
- BA指基本可用,S代表软状态,E代表最终一致性CAP定理说如果选择了一致性,就放弃了可用性,BASE理论说选择了一致性,只是损失了部分可用,意味处于基本可用CAP定理说- 如果选择了可用性,就放弃了一致性,BASE理论说选择了可用行,只是存在临时的不一致状态,这种临时的不一致性称为软状态,这种软状态过后达成最终一致性
请问Seata的AT模式是AP还是CP?大概解释Seata的AT模式的原理
- AT模式是一种AP模式(强可用,弱一致性)
- Seata的AT模式的执行济程大概就这样:Seata架构中存在二大组件,TC (TC是事务协调者),TM (事务管理器)RM(资源管理器)
- 首先,由TM向TC发出开始全局事务的请求,TC在全局事务表中记录数据。接着,由TM通知各人的RM调度各自的分支务,这时分支事务开始执行啦,分支事务先向TC进行注册分支事务,开始执行SOL语句并提交,在SQL执行的前后,AT模式会把更新记录的前后数据保存到undo log日志表中作为数据快照,再上报事务执行结果给TC。最后,TC收集到所有分支事务的执行状态,进行分析,决定是否提交还是回滚,如果提交,则TC向所有RM发出删除undo log日志记录的请求.如果回滚,则TC向所有RM发出读取undo log数据快照做数据恢复的请求
- 在AT的执行过程中,会有脏读的情况存在,Seata考虑到了,利用全局事务锁表,在每个分支务提交之前,判断是否能获取全局事务锁决定是否提交,这样就控制脏写
什么是乐观锁、悲观锁,他们有什么差别?
-
悲观锁:
- 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
- 悲观锁的应用场景:关系数据库的行级锁和表级锁等
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候,会判断在此期间别人有没有去更新这个数据。
乐观锁的实现方式:
可以使用版本号机制和CAS算法实现,在数据表中加入一个数版本号version字段,表示数据被修改的次数,当数据被修改时,version的值会加一
当线程A要重新更新数据值时,在读取数据的时候也会读取version值,在提交更新时,若刚才读取到的vesion值与当前数据库中的version值相等才更新,否则重新更新操作,直到更新成功
-
悲观锁与乐观锁的应用差别:
- 乐观锁适用于写少读多的场景。这样可以省去了锁的开销,加大了系统的整个吞吐量
- 悲观锁更适合读少写多的场景。因为如果在写多的场景下使用乐观锁,会导致应用会不断的进行重试,这样反倒是降低了性能,所以一般写多的场景下更适合使用悲观锁
MySQL的引擎有几种?
- InnoDB: MySQL默认存储引擎,支持事务。支持行级锁和表级锁。索引采用聚族索引(索引和数据存储在一个文件,提升查询性能)
- MyISAM: 不支持事务。仅仅支持表级锁。索引采用非聚簇索(索引和数据分开存储,查询性能差一些)
RabbitMQ和Kafka,各有什么优缺点?
- RabbitMQ:
- 优势:
- 支持语言非常广
- 稳定性很好,采用Erlang语言开发
- 吞吐量不算低,万级
- RabbitMQ官方提供7种消息发送模式,开发者轻松选择合适的模式进行开发即可
- 缺点:
- 采用Erlang,太小众,研究源码很难
- 优势:
- Kafka:
- 优势:
- 高吞吐量,百万级
- 稳定性好,采用zookeeper进行注册 (Zookeep采用CP模式,高一致模式)
- 可以应用在大数据数据处理领域 (KafkaStream)
- 缺点:
- 支持的开发语言比较少
- 耦合zk,依赖zookeeper进行注册
- 优势:
什么是事务?
事务是一组原子操作单元,从数据库角度来讲,就是一组SQL语句,要么全部执行,要么全部失败,就是有其中一个指令执行有错误,那么撤销前面执行过的所有SQL指令
-
事务的特性
-
原子性
:即不可分割性,事务要么全部被执行,要么就全部不被执行 -
一致性
:事务的执行使得数据库从一种正确状态转换成另一种正确状态 -
隔离性
:在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务 -
持久性
:事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。
-
事务的四大特性和隔离级别
- 读未提交(read Uncommited)
- 在该隔离级别中,所有事务都可以读取到别的事务未提交的数据,会产生脏读的问题,在项目中基本不用,安全性太差
- 读已提交(read commited)
- 这是大多数的数据库的默认隔离级别,但不是MySQL的默认隔离级别,这个隔离级别满足了简单的隔离,一个事务只能看到已经提交的事务所有的改变,所以避免了脏读的问题,但由于一个事务可以看到别的事务已经提交的数据,随之而来产生了不可重复读和虚读的问题
- 可重复读(Repeatable read)
- 这是MySQL的默认隔离级别,它确保了一个事务并发读取数据时,能读取到一样的数据。不过理论上,也导致了幻读(Phantom Read)。简单的来讲,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再次读取该范围的数据行时,会产生幻读
- 可串行化(serializable)
- 事务的最高级别,它通过强制事务排序,使之不可能互相冲突,从而解决幻读问题。简单来讲,它是在每个读的数据行上加上共享锁,在这个级别,可能会导致大量的超时和锁竞争,一般为了提升程序的吞吐量,不会采用该级别
Redis的数据类型和持久化方式
Redis有5种数据类型
- String
- Set
- List
- Hash
- SortedSet(ZSet)
Redis的持久化方式
- 有2种方式,分别是RDB和AOF,RDB的原理是对整个内存数据进行快照备份,文件体积小,而AOF,原理是每条操作指令都会持久化到文件,导致文件体积比较大
- RDB的2次备份时间间隔最小是1分钟,时间长,容易导入数据丢失。而AOF的默认间隔时间为1秒1次,时间短,数据完整性高
- 但从数据恢复速度来讲,RDB比AOF快,因为体积小