8.1 Spring MVC
8.1.1 谈谈你对Spring MVC的理解
Spring MVC 是 Spring 框架中的一个模块,用于构建基于 Web 的应用程序。它遵循 Model-View-Controller(MVC)设计模式,将业务逻辑、用户界面和数据分离,以促进代码的可维护性和可扩展性。
- 模型(Model)
模型代表应用程序的数据和业务逻辑。它通常包含数据对象(如 POJO)和服务层(如 Spring 服务)来处理业务逻辑。模型负责从数据库或其他数据源获取数据,并将数据传递给视图以显示给用户。 - 视图(View)
视图负责展示数据,通常是 HTML 页面或其他类型的用户界面。Spring MVC 支持多种视图技术,包括 JSP、Thymeleaf、FreeMarker 等。视图从模型获取数据并将其呈现给用户。 - 控制器(Controller)
控制器处理用户请求并决定将数据传递给哪个视图。它接收用户输入,调用模型进行处理,并选择合适的视图来显示结果。控制器通常使用 @Controller 注解来标识,并使用 @RequestMapping 注解来映射 URL 请求。
Spring MVC 的工作流程 - 用户请求:用户通过浏览器发送 HTTP 请求到服务器。
- 前端控制器(DispatcherServlet):Spring MVC 的前端控制器 DispatcherServlet 拦截所有请求并进行分发。
- 处理器映射(Handler Mapping):根据请求 URL,DispatcherServlet 查找相应的控制器。
- 控制器处理:控制器处理请求,调用服务层或数据访问层以获取数据,并将数据封装到模型中。
- 视图解析器(View Resolver):控制器返回视图名称,DispatcherServlet 使用视图解析器将视图名称解析为实际的视图对象。
- 视图渲染:视图对象负责将模型数据渲染为用户界面,通常是 HTML 页面。
- 响应返回:渲染后的视图返回给 DispatcherServlet,DispatcherServlet 将最终的响应发送回用户浏览器。
核心组件 - DispatcherServlet:前端控制器,负责接收并分发请求。
- Controller:处理用户请求,包含业务逻辑。
- ModelAndView:包含模型数据和视图名称的对象。
- View Resolver:将视图名称解析为实际的视图对象。
- Handler Mapping:根据请求 URL 查找相应的控制器。
8.1.4 Spring中@Autowired 和@Resoure的区别
@Resource 和@Autowired 这两个注解的作用都是在 Spring 生态里面去实现 Bean 的依赖注入。
下面我分别说一下@Autowired 和@Resource 这两个注解。
@Autowired 是 Spring 里面提供的一个注解,默认是根据类型来实现 Bean 的依赖注入。@Autowired 注解里面有一个 required 属性默认值是 true,表示强制要求 bean 实例的注入,在应用启动的时候,如果 IOC 容器里面不存在对应类型的 Bean,就会报错。
当然,如果不希望自动注入,可以把这个属性设置成 false。
其次呢, 如果在 Spring IOC 容器里面存在多个相同类型的 Bean 实例。由于@Autowired注解是根据类型来注入 Bean 实例的 所以 Spring 启动的时候,会提示一个错误,大概意思原本只能注入一个单实例 Bean,但是在 IOC 容器里面却发现有多个,导致注入失败
当然,针对这个问题,我们可以使用 @Primary 或者@Qualifier 这两个注解来解决。 @Primary 表示主要的 bean,当存在多个相同类型的 Bean 的时候,优先使用声明了@Primary 的Bean。
@Qualifier 的作用类似于条件筛选,它可以根据 Bean 的名字找到需要装配的目标 Bean。
@Resource 是 JDK 提供的注解,只是 Spring 在实现上提供了这个注解的功能支持。
它的使用方式和@Autowired 完全相同,(如图)最大的差异于@Resource 可以支持 ByName 和
ByType 两种注入方式。
如果使用 name,Spring 就根据 bean 的名字进行依赖注入,如果使用 type,Spring 就根据类型实现依赖注入。
如果两个属性都没配置,就先根据定义的属性名字去匹配,如果没匹配成功,再根据类型匹配。两个都没匹配到,就报错。
总结:
@Autowired 是根据 type 来匹配,@Resource 可以根据 name 和 type 来匹配,默认是 name
匹配。
@Autowired 是 Spring 定义的注解,@Resource 是 JSR 250 规范里面定义的注解,而 Spring
对 JSR 250 规范提供了支持。
@Autowired 如果需要支持 name 匹配,就需要配合@Primary 或者@Qualifier 来实现。
8.2 Spring Boot
8.2.1 SpringBoot的优势
更快地构建独立的、生产级的 Spring 应用程序,而无需进行繁琐的配置。
Spring Boot 的核心特性
- 自动配置: Spring Boot 提供了自动配置功能,能够根据项目中的依赖和配置自动设置 Spring 应用的默认配置。这减少了开发者手动配置的工作量。
- 独立运行的 Spring 应用: Spring Boot 可以将应用打包成一个独立的 JAR 文件,内嵌一个 Web容器(如 Tomcat、Jetty),使得应用可以通过java -jar命令直接运行。
- 生产级功能: Spring Boot 提供了一系列生产级功能,如监控、健康检查、外部化配置、指标收集等,帮助开发者更好地管理和监控应用。
- 简化的依赖管理: Spring Boot 提供了一系列的 “starter” 依赖,帮助开发者快速引入常用的库和框架。这些 starter 依赖已经过精心选择和配置,确保它们能够很好地协同工作。
- Spring Boot CLI: Spring Boot 提供了一个命令行工具(CLI),可以用来快速创建、运行和测试 Spring 应用。这对于快速原型开发非常有用。
8.2.2 如何理解Spring Boot的约定优于配置
我从 4 个方面来回答。
- 首先, 约定优于配置是一种软件设计的范式,它的核心思想是减少软件开发人员对于配置项的维护,从而让开发人员更加聚焦在业务逻辑上。
- Spring Boot 就是约定优于配置这一理念下的产物,它类似于 Spring 框架下的一个脚手架,通过
Spring Boot,我们可以快速开发基于 Spring 生态下的应用程序。 - 基于传统的Spring框架开发web应用,我们需要做很多和业务开发无关并且只需要做一次的配置,
比如
a. 管理 jar 包依赖
b. web.xml 维护
c. Dispatch-Servlet.xml 配置项维护
d. 应用部署到 Web 容器
e. 第三方组件集成到 Spring IOC 容器中的配置项维护
而在 Spring Boot 中,我们不需要再去做这些繁琐的配置,Spring Boot 已经自动帮我们完成了,这
就是约定于配置思想的体现。 - Spring Boot 约定优于配置的体现有很多,比如
a. Spring Boot Starter 启动依赖,它能帮我们管理所有 jar 包版本
b. 如果当前应用依赖了 spring mvc 相关的 jar,那么 Spring Boot 会自动内置 Tomcat 容器来运行 web 应用,我们不需要再去单独做应用部署。
c. Spring Boot 的自动装配机制的实现中,通过扫描约定路径下的 spring.factories 文件来识别配置类,实现 Bean 的自动装配。
d. 默认加载的配置文件 application.properties 等等。
总的来说,约定优于配置是一个比较常见的软件设计思想,它的核心本质都是为了更高效以及更便捷地实现软件系统的开发和维护。
8.2.3 SpringBoot的自动装配机制的实现原理
自动装配,简单来说就是自动把第三方组件的 Bean 装载到 Spring IOC 器里面,不需要开发人员再去写 Bean 的装配配置。
在 Spring Boot 应用里面,只需要在启动类加上@SpringBootApplication 注解就可以实现自动装配。 @SpringBootApplication 是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。
(如图)自动装配的实现主要依靠三个核心关键技术。
- 引入 Starter 启动依赖组件的时候,这个组件里面必须包含@Configuration 配置类,在这个配置
类里面通过@Bean 注解声明需要装配到 IOC 容器的 Bean 对象。 - 这个配置类是放在第三方的 jar 包里面,然后通过 SpringBoot 中的约定优于配置思想,把这个配
置类的全路径放在 classpath:META-INF/spring.factories 文件中。这样 SpringBoot 就可以知道
第三方 jar 包里面的配置类的位置,这个步骤主要是用到了 Spring 里面的 SpringFactoriesLoader
来完成的。 - SpringBoot 拿到第三方 jar 包里面声明的配置类以后,再通过 Spring 提供的 ImportSelector 接
口,实现对这些配置类的动态加载。
在我看来,SpringBoot 是约定优于配置这一理念下的产物,所以在很多的地方,都会看到这类的思想。
它的出现,让开发人员更加聚焦在了业务代码的编写上,而不需要去关心和业务无关的配置。
其实,自动装配的思想,在 SpringFramework3.x 版本里面的@Enable 注解,就有了实现的雏形。 @Enable 注解是模块驱动的意思,我们只需要增加某个@Enable 注解,就自动打开某个功能,而不需要针对这个功能去做 Bean 的配置,@Enable 底层也是帮我们去自动完成这个模块相关 Bean 的注入。
8.2.4 如何理解Spring Boot 中的Starter
Starters 可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成
Spring 及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,
只要 加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。Starters 包含了许多项目中需要用到的 依赖,它们能快速持续地运行,都是一系列得到支持的管理传递性依赖。
Spring Boot 、 中的监视器是什么?
Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助访问生产 环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。
即使一些 外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组
可直接作 为 HTTP URL 访问的 REST 端点来检查状态。
如何使用 Spring Boot 实现异常处理?
Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。
9 SpringCloud
9.1 谈谈你对Spring Cloud的理解
Spring Cloud 是一套分布式微服务的技术解决方案,它提供了快速构建分布式系统的常用的一些组件
比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等
不过 Spring Cloud 只是 Spring 官方提供的一套微服务标准定义,而真正地实现目前有两套体系用得比较多。
一个是 Spring Cloud Netflix
一个是 Spring Cloud Alibaba
Spring Cloud Netflix 是基于 Netflix 这个公司的开源组件集成的一套微服务解决方案,其中的组件有
- Ribbon——负载均衡
- Hystrix——服务熔断
- Zuul——网关
- Eureka——服务注册与发现
- Feign——服务调用
Spring Cloud Alibaba 是基于阿里巴巴开源组件集成的一套微服务解决方案,其中包括 - Dubbo——消息通讯
- Nacos——服务注册与发现
- Seata——事务隔离
- Sentinel——熔断降级
有了 Spring Cloud 这样的技术生态,使得我们在落地微服务架构时。不用去考虑第三方技术集成带来额外成本,只要通过配置组件来完成架构下的技术问题,从而可以让我们更加侧重性能方面
9.2 谈谈 Eureka Server数据同步原理
Eureka 是一个服务注册中心,在 Eureka 的设计里面,为了保证 Eureka 的高可用性,提供了集群的部署方式。
Eureka 的集群部署采用的是两两相互注册的方式来实现,也就是说每个 Eureka Server 节点都需要发现集群中的其他节点并建立连接,然后通过心跳的方式来维持这个连接的状态。
(如图),Eureka Server 集群节点之间的数据同步方式非常简单粗暴,使用的是对等复制的方式来实现数据同步。
也就是说,在 Eureka Server 集群中,不存在所谓主从节点,任何一个节点都可以接收或者写入数据。
一旦集群中的任意一个节点接收到了数据的变更,就直接同步到其他节点上。
这种无中心化节点的数据同步,需要考虑到一个数据同步死循环的问题,也就是需要区分 Eureka
Server 收到的数据 是属于客户端传递来的数据还是集群中其他节点发过来的同步数据。
Eureka 使用了一个时间戳的标记来实现类似于数据的版本号来解决这个问题。
另外,从 Eureka 的数据同步方案来看,Eureka 集群采用的是 AP 模型,也就是只提供高可用保障,而不提供数据强一致性保障。
之所以采用 AP,我认为注册中心它只是维护服务之间的通信地址,数据是否一致对于服务之间的通信影响并不大。
而注册中心对 Eureka 的高可用性要求会比较高,不能出现因为 Eureka 的故障导致服务之间无法通信的问题。
9.3 简述Nacos配置更新的工作流程
首先,Nacos 是采用长轮训的方式向 Nacos Server 端发起配置更新查询的功能。
所谓长轮训,(如图)就是客户端发起一次轮训请求到服务端,当服务端配置没有任何变更的时候,这个连接一直打开。
直到服务端有配置或者连接超时后返回。
Nacos Client 端需要获取服务端变更的配置,前提是要有一个比较,也就是拿客户端本地的配置信息和服务端的配置信息进行比较。
一旦发现和服务端的配置有差异,就表示服务端配置有更新,于是把更新的配置拉到本地。
在这个过程中,有可能因为客户端配置比较多,导致比较的时间较长,使得配置同步较慢的问题。
于是 Nacos 针对这个场景,做了两个方面的优化。
- 减少网络通信的数据量,客户端把需要进行比较的配置进行分片,每一个分片大小是 3000,也就是说,每次最多拿 3000 个配置去 Nacos Server 端进行比较。
- 分阶段进行比较和更新,
第一阶段,客户端把这 3000 个配置的 key 以及对应的 value 值的 md5 拼接成一个字符串,然后发送到 Nacos Server 端进行判断,服务端会逐个比较这些配置中 md5 不同的 key,把存在更新的key 返回给客户端。
第二阶段,客户端拿到这些变更的 key,循环逐个去调用服务器获取这些 key 的 value 值。
这两个优化,核心目的是减少网络通信数据包的大小,把一次大的数据包通信拆分成了多次小的数据包通信。
虽然会增加网络通信次数,但是对整体的性能有较大的提升。
最后,再采用长连接这种方式,既减少了 pull 轮询次数,又利用了长连接的优势,很好地实现了配置的动态更新同步功能。