[TOC]
常见八股:
多线程部分:
Spring
Spring和SpringBoot的区别
Spring
是一个生态体系,包括了许多框架,如springboot、springframework等。其中Spring Framework是整个spring生态的基石。
**Spring Framework
**是一个轻量级的Java web开发框架,以IOC和AOP为核心,然后在此两者的基础上实现了其他延伸产品的高级功能。包括了许多框架,如SpringMVC、SpringJDBC。用到的设计模式有:
-
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
-
单例模式:Bean默认为单例模式。
-
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
-
模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
-
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
**Spring Boot
**是基于Spring Framework 4.0派生的,它的诞生是为了简化 Spring 框架初始搭建以及开发的过程,使用它可以不再依赖 Spring 应用程序中的 XML 配置,为更快、更高效的开发 Spring 提供更加有力的支持。并且它还具有以下特点:
- 内置了web容器,如Tomcat。
- 通过starter可以简化构建配置
- xml可以通过注解形式实现自动配置
- 打成jar包后可直接部署运行
对于应用程序启动引导配置,Spring支持传统的web.xml引导方式以及最新的Servlet3+方法,SpringBoot仅使用 Servlet3功能来引导应用程序。
**Spring Cloud
**是一整套基于Spring Boot的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等。
Spring Boot 的启动流程
1.创建并启动计时监控类
此计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。
2.声明应用上下文对象和异常报告集合
此过程声明了应用上下文对象和一个异常报告的 ArrayList 集合。
3.设置系统属性 headless 的值
设置 Java.awt.headless = true,其中 awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true 表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。
4.创建所有 Spring 运行监听器并发布应用启动事件
此过程用于获取配置的监听器名称并实例化所有的类。
5.初始化默认应用的参数类
也就是说声明并创建一个应用参数对象。
6.准备环境
创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。
7.创建 Banner 的打印类
Spring Boot 启动时会打印 Banner 图片。此 banner 信息是在 SpringBootBanner 类中定义的,我们可以通过实现 Banner 接口来自定义 banner 信息,然后通过代码 setBanner() 方法设置 Spring Boot 项目使用自己自定义 Banner 信息,或者是在 resources 下添加一个 banner.txt,把 banner 信息添加到此文件中,就可以实现自定义 banner 的功能了。
8.创建应用上下文
根据不同的应用类型来创建不同的 ApplicationContext 上下文对象。
9.实例化异常报告器
它调用的是 getSpringFactoriesInstances() 方法来获取配置异常类的名称,并实例化所有的异常处理类。
10.准备应用上下文
此方法的主要作用是把上面已经创建好的对象,传递给 prepareContext 来准备上下文,例如将环境变量 environment 对象绑定到上下文中、配置 bean 生成器以及资源加载器、记录启动日志等操作。
11.刷新应用上下文
此方法用于解析配置文件,加载 bean 对象,并且启动内置的 web 容器等操作。
12.应用上下文刷新之后的事件处理
这个方法的源码是空的,可以做一些自定义的后置处理操作。
13.停止计时监控类
停止此过程第一步中的程序计时器,并统计任务的执行信息。
14.输出日志信息
把相关的记录信息,如类名、时间等信息进行控制台输出。
15.发布应用上下文启动完成事件
触发所有 SpringApplicationRunListener 监听器的 started 事件方法。
16.执行所有 Runner 运行器
执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。
17.发布应用上下文就绪事件
触发所有的 SpringApplicationRunListener 监听器的 running 事件。
18.返回应用上下文对象
到此为止 Spring Boot 的启动程序就结束了,我们就可以正常来使用 Spring Boot 框架了。
SpringMVC 五大核心组件
-
DispatcherServlet 请求的入口
-
HandlerMapping 请求的派发,负责让请求和控制器建立一一对应的关联
-
Controller 真正的处理器
-
ModelAndView 封装模型信息和视图信息
-
ViewResolver 视图处理器,定位页面
SpringMVC执行流程
- 客户端发送请求到前端控制器DispatcherServlet
- 前端控制器收到请求后调用处理器映射器hanlderMapping
- 处理器映射器并找到具体的处理器,生成处理器对象和拦截器对象,再一起返回给前端控制器
- 前端控制器调用处理器适配器HandlerAdapter
- 处理器适配器经过适配调用具体的处理器Handler/Controller
- 处理器执行完成返回视图对象modelAndView
- 处理器适配器将视图返回给前端控制器
- 前端控制器将视图传给视图解析器ViewReslover
- 视图解析器返回具体的视图View
- 前端控制器根据视图进行渲染
- 前端控制器响应用户
bean的生命周期
1、实例化Bean对象
2、设置Bean的属性
3、注入Aware的依赖
4、执行前置处理函数,postProcessorBeforeInitialization()
5、执行afterPropertiesSet()方法,
6、执行Bean自定义的初始化方法,可用@PostConstruct注解标注
7、执行后置处理函数,POSTProcessorAfterInitialization()
8、对象创建完毕
9、执行对象销毁方法destory()
10、执行自定义的销毁方法,可用@PreDestory注解标注
11、对象销毁完毕
springboot的核心技术
依赖注入、事件、资源、i18n、验证、数据绑定、类型转换、SpEL、AOP
修改自带的tomcat
在pom.xml去除tomcat相关的配置,再引入其他的servlet容器
SpringMVC常用注解
- @Controller:使用该注解时不需要实现Controller接口,标注在类上面就是一个控制器
- @Repository:用于注解dao层,一般用于DAO的实现类上
- @Service:用于对业务逻辑层进行注解
- @Component:注解在类上,会被spring容器识别,并转为bean。相当于通用的注解
- @RequestMapping:处理请求地址映射,可以作用于类和方法上,表示访问请求该方法时的url
- @Resource:自动注入bean,默认按照ByName自动注入,也可以按byType注入
- @Autowired:自动注入bean,按照byType注入
- @ModelAttribute:用于把参数保存到model中,可注解方法或参数
- @PathVariable:用于将请求URL中的模板变量映射到方法的参数上,即取出url模板中的变量作为参数
- @requestParam:用于获取传入参数的值
- @ResponseBody:作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式
@Component和@Bean的区别
- @Component 注解作用于类,而@Bean注解作用于方法。
- @Component的类可以自动装配到Spring容器中,而@Bean 注解通常是在标有该注解的方法中定义产生这个 bean。(return这个bean)
Spring事务
Spring 支持事务管理的方式有两种:
- 编程式事务管理:通过
TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用 - 声明式事务管理:通过 AOP 实现(基于
@Transactional
的全注解方式使用最多)。
@Transactional的作用范围:
- 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
- 类 :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
- 接口 :不推荐在接口上使用。
Spring的自动装配
构造注入和setter注入有时在做配置时比较麻烦。“自动装配”指的是spring容器依据某种规则,自动建立对象之间的依赖关系。而spring框架式默认不支持自动装配的,要想使用自动装配,则需要修改spring配置文件中标签的autowire属性。
自动装配的规则:
-
no:不支持自动装配功能,spring默认。
-
default:表示默认采用上一级标签的自动装配的取值。如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。
-
byName:
当一个bean节点带有 autowire="byName" 的属性时:
①将查找其类中所有属性。
②去配置文件中查找是否存在id名称与属性名称相同的bean。
③如果有,就取得该bean对象,并调用属性所在类中的setter方法,将找到的bean注入;找不到注入null。
注意:不可能找到多个符合条件的bean(id唯一)
-
byType:
当一个bean节点带有 autowire="byType" 的属性时:
①将查找其类中所有属性。
②去配置文件中查找是否存在bean类型与属性类型相同的bean。
③如果有,就取得该bean对象,并调用属性所在类中的setter方法,将找到的bean注入;找不到注入null。
注意:找到多个符合条件的bean时会报错。
-
constructor:
使用构造方法完成对象注入,其实也是根据构造方法的参数类型进行对象查找,相当于采用byType的方式。即:根据构造方法的参数的数据类型,进行 byType 模式的自动装配。
Spring注入方式
常用的注入方式主要有三种:
- 构造方法注入:将对象传参到构造方法中。
- setter注入
- 基于注解的注入:@Autowired自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入。
Spring循环依赖
zhuanlan.zhihu.com/p/163031798
产生循环依赖问题的前提条件:Spring管理的Bean默认都是单例模式。
解决循环依赖的思路:三级缓存+标记缓存。
Spring的三级缓存:
- 一级缓存:保存所有已经创建完成的bean
- 二级缓存:保存正常创建中的bean(完成实例化,但还未完成属性注入)
- 三级缓存:保存的是ObjectFactory类型的对象的工厂,通过工厂的方法可以获取到目标对象
- 标记缓存:保存正在创建中的对象名称。
注意:第三级缓存经过代理包装或替换后,进入到第二级缓存。
无法解决循环依赖的场景:Spring可以正常解决通过属性进行依赖注入的循环依赖场景,但是无法解决通过构造方法进行注入的循环依赖场景。
IOC、DI、AOP
- IOC: Inversion of Control,控制反转。 创建和管理Bean的控制权从应用程序转移到框架。IOC主要解决了代码的高度耦合问题。主要有3种实现方式:set方法注入、构造方法注入、注解注入。
- DI:Dependency Injection,依赖注入。是IOC的具体实现,将相互依赖的对象分离,在Spring的配置(注解)中描述它们之间的依赖关系,这些依赖关系也只在使用时才被建立。
- AOP:Aspect Oriented Programming,面向切面编程。就是将程序功能中的频繁出现或者与主业务逻辑代码相关度不高的代码抽离出来,通过切面编程的方式在想要调用的时候引入调用的思想。这种思想的实现机制在Spring中便是应用了java的动态代理和java的反射。
Spring AOP 实现原理
通过动态代理实现,分别是JDK的动态代理和CGLib的动态代理。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。
Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。JDK的动态代理是基于反射实现。
JDK的动态代理存在限制,那就是被代理的类必须是一个实现了接口的类,代理类需要实现相同的接口,并实现代理接口中声明的方法。若需要代理的类没有实现接口,此时JDK的动态代理将没有办法使用,于是Spring会使用CGLib的动态代理来生成代理对象。CGLib直接操作字节码,生成类的子类,重写类的方法完成代理。
Java
面向对象和面向过程的区别
面向过程的开发方法中最重要的是对事件的处理过程。处理的对象多以变量的形式存在,且与处理过程之间不存在约束关系。开发简单。由于使用面向过程方法设计的程序把处理的主体与处理的方法分开,因此各种成分错综复杂地放在一起,难以理解,易出错,并且难于调试。
面向对象的开发方式中,以数据为中心,将数据及对数据的操作放在一起,形成对象。使用类来刻画有类似属性的不同对象,使用继承来简化开发过程,使用接口来规范对数据的操作,使用多态达到操作的灵活性。
面向对象易维护、易复用、易扩展。并且由于面向对象有封装、继承、多态性特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
nio的实现方式
select、poll、epoll
零拷贝
零拷贝的“零”是指用户态和内核态间copy数据的次数为零。零拷贝完全依赖于操作系统,不依赖Java本身。
传统的数据copy(文件到文件、client到server等)涉及到四次用户态和内核态切换、四次copy。四次copy中,两次在用户态和内核态间copy需要CPU参与、两次在内核态与IO设备间copy为DMA方式不需要CPU参与。零拷贝避免了用户态和内核态间的copy(共2次)、减少了两次用户态内核态间的切换,因此数据传输效率高(4、4变2、2)。
零拷贝可以提高数据传输效率,但对于需要在用户传输过程中对数据进行加工的场景(如加密)就不适合使用零拷贝。
Java NIO中的FileChannel拥有transferTo和transferFrom两个方法,可直接把FileChannel中的数据拷贝到另外一个Channel
分布式和微服务的区别
-
分布式:将一个项目拆分成了多个模块,并将这些模块分开部署。
-
水平拆分:根据“分层”的思想进行拆分。将一个项目根据“架构”拆分成多个层,再分开部署。
-
垂直拆分:根据业务进行拆分。拆分后的项目,仍然可以作为独立的项目使用。
-
-
微服务:一种非常细粒度的垂直拆分。
网络
HTTP 1.0/1.1
HTTP是客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。是一个应用层协议,基于TCP/IP通信协议。
① HTTP是无连接的:无连接的含义是限制每次连接只能处理一个请求,服务器处理完客户端的请求并收到回复之后立刻断开。(节省传输时间) ② HTTP是媒体独立的:只要客户端和服务端知道如何处理数据内容,任何类型的数据都可以通过HTTP发送; ③ HTTP是无状态协议:无状态协议指协议对事务处理没有记忆能力。
HTTP1.0 | HTTP1.1 | |
---|---|---|
请求方法 | GET、POST、HEAD | GET、POST、HEAD、PUT、DELETE、OPTIONS、CONNECT、TRACE |
TCP连接 | 短连接 | 长连接 |
请求流水线 | 不支持 | 支持 |
Host字段 | 不支持 | 支持 |
100响应码 | 不支持 | 支持 |
HTTP2.0:
- 在应用层和传输层之间增加了一个二进制分帧层,改进了传输性能。
- 每一个request都使用了连接共享机制,接收方通过request的id划分到不同服务端请求。
- 通过encoder表,减少了每次传输的header中的信息。
HTTP长连接
一次TCP连接可以传输多次request,而不需要每个request建立一次TCP连接。
HTTP1.0默认关闭,需要在头信息中加入Connection: Keep-Alive
来开启。
HTTP1.1默认是开启状态,可以使用Connection: close
来关闭。
长连接可以避免连接建立和释放的开销。但长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。
长连接有一个保活时间,一个http产生的tcp连接在传送完最后一个响应后,还需要hold住keepalive_timeout秒后,才开始关闭这个连接。
TCP的Keep-Alive:用于每隔一段时间发送一个侦测包,以判断对方是否在线。与HTTP的Keep-Alive不一样。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
HTTP长连接实现原理
HTTP协议本质是OSI七层参考模型中的应用层协议,而网络进行通信的时候都是通过上层协议封装头部后作为下层协议的数据部分进行封装的,而实际中我们经常接触的是TCP/IP协议簇,也就是传输层利用TCP协议和网络层利用IP协议。因此HTTP协议的长连接本质上就是TCP的长连接。
http、websocket、socket
http:
http是单向的非持久连接的协议。服务端只有在客户端发起请求时才能发送数据。
WebSocket:
WebSocket是基于TCP的应用层的双向通信的持久化协议。协议标识符是ws和wss。在建立握手时,通过 HTTP/1.1 协议的101协议切换状态码进行握手。但是建立之后,在真正传输时候是不需要HTTP协议的,而是使用TCP协议。可以主动向客户端发送信息。
Websocket使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。
Socket:
Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输层之间的一组接口。
在程序内部提供了与外界通信的端口,也就是端口通信。它通过建立socket连接,可以为通信双方的数据传输提供一个通道。
Socket有udp的scoket和tcp的socket,一般采用的是tcp的socket。
浏览器输入URL后
1、域名解析:浏览器自身DNS缓存 -> PC自身的DNS缓存 -> host文件 -> 本地域名服务器 -> 根域名服务器。
2、TCP3次握手建立连接。
3、发起HTTP请求。
4、服务端响应HTTP请求,浏览器得到请求的内容。
5、浏览器解析HTML代码,并请求需要的资源。
6、浏览器对页面进行渲染,展现给用户。
TCP、UDP
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。UDP是一种无连接的、不可靠的、基于报文的传输层通信协议。
TCP的可靠通信是通过3次握手4次挥手、流量控制、拥塞控制、确认应答标志、错误重传等机制保证的,因此也会造成实时性较差,开销比较大,适用于可靠性要求高的场合,如文件传输。而UDP不保证可靠通信,因此开销小,适用于实时性要求不高的场合,如视频通话、网络游戏。
另外,TCP只能点对点通信,而UDP支持多对多通信。
沾包与拆包
UDP 是基于报文发送的,UDP首部采用了 16bit 来指示 UDP 数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。
而 TCP 是基于字节流的,在 TCP 的首部没有表示数据长度的字段。当发送数据超过TCP最大长度时,就会发生拆包;当发送数据不足 时,会先存在缓冲区内,就会发生沾包。
对于UDP协议来说,整个包的最大长度为65535-IP头20-UDP头8;
对于TCP协议来说,整个包的最大长度是由最大传输大小(MSS)决定,MSS就是TCP数据包每次能够传输的最大数据分段。往往MSS为1460(1500-IP头20-TCP头20)。
RPC和HTTP和Feign
RPC (Remote Procedure Call)即远程过程调用,是分布式系统常见的一种通信方法。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
RPC 和 HTTP 调用是没有经过中间件的,它们是端到端系统的直接数据交互。
主流的RPC框架有:dubbo、dubbox、motan
Feign是微服务中最常见的PRC框架,Feign封装了负载均衡Ribbon和服务熔断保护Hystrix,底层用的是RestTemplate,也就是HTTP的形式进行的远程调用,所以也称“伪HTTP客户端”。它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
HTTP | RPC | |
---|---|---|
传输协议 | HTTP | TCP或HTTP2 |
传输效率 | 1.1版本体积大,2版本较小 | 处理后报文体积小 |
性能消耗 | 大部分通过json,消耗大 | 基于thrift实现高效二进制传输 |
负载均衡 | 需搭配Nginx | 自带 |
使用场景 | 对外的异构环境 | 公司内部的服务调⽤ |
MySQL
表连接方式
- 交叉连接cross join,不能跟where和on
- 内连接inner join,可以跟where和on
- 左连接left join
- 右连接right join
- 全连接full join
- 联合查询union/union all,条件是两个表的字段个数都是相同的
索引失效
- 数学运算、函数、类型转换等,会导致索引失效而转向全表扫描
- 在使用不等号(!=或<>)时候,无法使用索引导致全表扫描
- is null,is not null也无法使用索引
- 以 % 开头的 like 查询,会导致全表扫描的操作
- 条件中有 or 时,即使其中有部分条件是索引字段,也不会使用索引
索引优化
- 较频繁作为查询条件的字段才去创建索引
- 更新频繁字段不适合创建索引
- 使用短索引
- 尽量的扩展索引,不要新建索引
- 对于定义为text、image和bit的数据类型的列不要建立索引
- 不要在列上进行运算
- 索引不会包含有NULL值的列
Redis
数据结构
String:字符串类型,可用于缓存、共享session
List:列表类型,可用于消息队列、文章、任务列表
Set:无序集合类型,可用于好友、标签
SortedSet:有序集合类型,可用于排行榜
Hash:哈希表类型,可用于映射管理操作
缓存一致性
jishuin.proginn.com/p/763bfbd59…
缓存雪崩、缓存穿透、缓存击穿
-
缓存穿透
- 现象描述:缓存和数据库中都没有的数据,而用户不断发起请求,每次都从数据库中去取,导致数据库压力过大。
- 解决方法:从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
-
缓存击穿
- 现象描述:并发查同一条数据,缓存中没有但数据库中有(一般是缓存时间到期),引起数据库压力瞬间增大,造成过大压力。
- 解决方法:设置热点数据永远不过期。加互斥锁。
-
缓存雪崩
- 现象描述:缓存中不同数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。
- 解决方法:设置热点数据永远不过期。缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
数据结构
红黑树
红黑树是一种自平衡的二叉搜索树。
“二叉搜索树” 的特点是任何一个结点的值都大于其左子树的所有结点的值,任何一个结点的值都小于其右子树的所有结点的值。
“平衡” 就是当结点数量固定时,左右子树的高度越接近,这棵二叉树越平衡(高度越低)。而最理想的平衡就是完全二叉树/满二叉树,高度最小的二叉树。
二叉树在特殊情况下会退化为链表。
B树是一种平衡的多路搜索树。
其他
如何看待测试工作
测试的主要目的是保证质量,同时现在的软件测试也讲究质量和速度。测试在需求分析阶段就应该介入,把设计上的缺陷,或产品经理、开发、测试间理解不一致的问题都找出来,并达成一致,预防由于理解不一致导致的不必要的缺陷的发生。测试人员必须要深入理解业务。
怎么写测试用例
① 首先是根据产品的需求提炼出测试点,然后根据测试点扩展写成测试用例,像等价类、边界值这种方法在设计测试用例的过程中我就会潜移默化的用到了。 ② 另外写测试用例的时候,会考虑到正例和反例,事实上在写测试点的阶段我就会把正例和反例标记出来了,以及他们的优先级。(理论上来说反例的优先级都是三级,在回归测试的是理论上是可以不做反例的回归测试的→看情况说)我觉得这样其实对后续的工作是一个很大的减负。 ③ 我觉得测试用例设计工作应该有个准出标准,避免陷入测试用例感觉写不完的漩涡。(比如一个注册页面,10几个空,排列组合,都写不完)。所以在整个测试用例设计过程中, 我们要考虑出所有的用户场景(也就是说,当用户的场景都被想全了,就准出了)
测试流程
->需求确定(出一份确定的需求文档) ->开发设计文档(开发人员在开始写代码前就能输出设计文档) ->想好测试策略,写出测试用例 ->发给开发人员和测试经理看看(非正式的评审用例) ->接到测试版本 ->执行测试用例(中间可能会补充用例) ->提交bug(有些bug需要开发人员的确定(严重级别的,或突然发现的在测试用例范围之外的,难以重现的),有些可以直接录制进TD) ->开发人员修改(可以在测试过程中快速的修改) ->回归测试(可能又会发现新问题,再按流程开始跑)。
\