玩转 Spring 全家桶(上)

一. 初识 Spring

1.1 初始 Spring 成员

  • Spring Framework 的历史

玩转Spring全家桶(上) - 掘金 - 图1

  • Spring 不仅仅是框架,还有很多的技术,它代表着整个的家族

玩转Spring全家桶(上) - 掘金 - 图2

  • Spring Framework

玩转Spring全家桶(上) - 掘金 - 图3

Spring 的核心代码比如 IOC、AOP 基本没有改变,代码质量很高,适合大家去学习

  • SpringBoot

玩转Spring全家桶(上) - 掘金 - 图4

  • SpringCloud

玩转Spring全家桶(上) - 掘金 - 图5

它能更快更好的帮助大家开发一个云的应用程序

1.2 跟着 Spring 了解技术趋势

  • 看看 Spring 5.x 的改变暗示了什么

玩转Spring全家桶(上) - 掘金 - 图6

JasperReport 用于做报表

  • SpringBoot 和 SpringCloud 的出现时必然的

玩转Spring全家桶(上) - 掘金 - 图7

  • 为什么呢?

玩转Spring全家桶(上) - 掘金 - 图8

它集合了业内的最佳实践,提供了对众多其他组建的支持,能让你轻松实现一个高可用的程序;不是在跟 Spring 打交道,是与 Spring 家族打交道;

  • Hello Spring [生成你的第一个 Spring 应用]
    • start.spring.io 这个里面有骨架可以生成项目

玩转Spring全家桶(上) - 掘金 - 图9

使用骨架生成项目,然后撰写 Hello,world 接口,启动启动类后点击 url 地址然后访问此端口;

  • 如果 Parent 不继承 spring-boot-parent 怎么办呢?

玩转Spring全家桶(上) - 掘金 - 图10

使用此依赖,即可不用继承,也可以实现 Spring-boot-parent 的功能;

玩转Spring全家桶(上) - 掘金 - 图11

  • 打包出来的,original 是原始包,它只有 3.1k,但是另外一个却有 17M,是因为它包含了相关的依赖,一起打包进去了,是可以直接执行的 jar 包。

二. JDBC 必知必会

2.1 如何配置单数据源

  • 加载了 actuator 的依赖后,我们可以通过访问 localhost:8080/actuator/beans 来查看所有 Spring 的 Bean

玩转Spring全家桶(上) - 掘金 - 图12

  • 直接配置所选的 Bean

玩转Spring全家桶(上) - 掘金 - 图13

  • SpringBoot 做了哪些配置

玩转Spring全家桶(上) - 掘金 - 图14

  • 数据源相关配置属性

玩转Spring全家桶(上) - 掘金 - 图15

  • 如果我们一定要继承自己的 parent 依赖,不能直接继承 SpringBoot 的 parent,如何能让 Spring 来继续管理版本呢?解决步骤如下:

    1. 引入 dependencies: 玩转Spring全家桶(上) - 掘金 - 图16
    2. Maven 打包时设置 repackage: 玩转Spring全家桶(上) - 掘金 - 图17

      使用此方式,可以不继承 parent 也可以达到 Spring 管理依赖版本的功能。

  • 如果我们引入了健康监控相关的依赖,我们可以查看我们引入了哪些 bean:

    • 玩转Spring全家桶(上) - 掘金 - 图18
    • 使用场景:当我们引入 Spring 管理时,可以通过此处来查看是否加入 Bean,对解决问题可以多提供一种方式和思路。

2.2 如何配置多数据源

  • 配置多数据源的注意事项
    • 不同数据源的配置要分开
    • 关注每次使用的数据源
      • 有多个 DataSource 时系统如何判断
      • 对应的设施 (事务、ORM 等) 如何选择 DataSource
    • 使用某些框架的时候,比如 Mybatis-plus,Jpa 等的时候,如何支持多数据源。
  • 手工配置两组 DataSource 及相关内容
    • 与 SpringBoot 协同工作 (二选一)
    • 配置 @Primary 类型的 Bean
    • 排除 SpringBoot 的自动配置
      • DataSourceAutoConfiguration
      • DataSourceTransactionManagerAutoConfiguration
      • JdbcTemplateAutoConfiguration

这几个排除掉,我们在代码中自己进行配置

  • 代码图示: 玩转Spring全家桶(上) - 掘金 - 图19
  • 一般情况下,第三方的框架会有支持多数据源的方式,我们可以参考它们的官方文档来进行配置。

2.3 那些好用的连接池 HikariCP

  • 它是一个高性能的 JDBC 连接池,它比别的数据库连接池更快。
  • HikariCP 为什么快?
    • 字节码级别优化(很多方法通过 JavaAssist 生成)
    • 大量小改进:
      • 用 FastStatementList 代替 ArrayList(列表取放节省时间)
      • 无锁集合 ConcurrentBag(并发操作带来性能提升)
      • 代理类的优化(比如,用 invokestatic 代替了 invokevirtual)
  • HikariCP 在 Spring Boot 中的配置
    • Spring Boot 2.x
      • 默认使用 HikariCP
      • 配置 spring.datasource.hikari.* 配置
    • SpringBoot 1.x
      • 默认使用 Tomcat 连接池,需要移除 tomcat-jdbc 依赖
      • spring.datasource.type=com.zaxxer.hikari.HikariDataSource
  • 常用 HikariCP 配置参数
    • 常用配置:
      • spring.datasource.hikari.maximumPoolSize=10
      • spring.datasource.hikari.minimumldle=10
      • spring.datasource.hikari.idleTimeout=600000
      • spring.datasource.hikari.connectionTimeout=30000
      • spring.datasource.hikari.maxLifetime=1800000
    • 其他配置详见 HikariCP 官网

2.4 那些好用的连接池 Alibaba Druid

  • Alibaba Druid 官方介绍:
    • Druid 连接池是阿里巴巴开源的数据库连接池项目。Druid 连接池是为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防 SQL 注入,内置 Logging 能诊断 Hack 应用行为。
  • Druid 的介绍:

    • 经过阿里巴巴各大系统的考验,值得信赖。
    • 实用的功能:
      1. 详细的监控(真的是全面)
      2. ExceptionSorter,针对主流数据库的返回码都有支持
      3. SQL 防注入
      4. 内置加密配置
      5. 众多扩展点,方便进行定制
    • 其他功能点可以去官网

      官方中的 wiki 中有一些功能介绍及使用方式、问题解决方案等。

  • Alibaba Druid 配置:

    • Spring Boot 方案: 玩转Spring全家桶(上) - 掘金 - 图20
    • 进阶可选配置: 玩转Spring全家桶(上) - 掘金 - 图21
    • Druid Filter 扩展点:
      1. 用于定制连接池操作的各种环节
      2. 可以继承 FilterEventAdapter 以方便地实现 Filter
      3. 修改 META-INF/druid-filter.properties 增加 Filter 配置
      4. 代码演示: 玩转Spring全家桶(上) - 掘金 - 图22
  • 需要注意的地方:
    • 如果引入 Alibaba Druid 时,可能需要将 HikariCP 排除掉,如图代码所示: 玩转Spring全家桶(上) - 掘金 - 图23
    • 连接池选择时的考量点: 玩转Spring全家桶(上) - 掘金 - 图24

      根据实际业务场景哪些点更为重要,然后选择合适的连接池。

2.5 如何通过 Spring JDBC 访问数据库

  • Spring 的 JDBC 操作类
    • Spring-jdbc
      • core,JdbcTemplate 等相关核心接口和类
      • datasource, 数据源相关的辅助类
      • object,将基本的 JDBC 操作封装成对象
      • support, 错误码等其他辅助工具
    • 通过注解定义 Bean
      • @Component: 通用定义 Bean 的注解
      • @Repository
      • @Service: 业务逻辑
      • Controller
        • RestController
    • JdbcTemplate
      • query
      • queryForObject
      • queryForList
      • update
      • execute
      • 部分操作代码示例: 玩转Spring全家桶(上) - 掘金 - 图25
        玩转Spring全家桶(上) - 掘金 - 图26
  • SQL 批处理
    • JDBC Template
      • batchUpdate
        • BatchPreparedStatementSetter
    • NamedParameterJdbcTemplate
      • batchUpdate
        • SqlParameterSourceUtils.createBatch
    • 批处理部分代码示例: 玩转Spring全家桶(上) - 掘金 - 图27

2.6 什么是 Spring 的事务抽象

  • 在 Spring 中为我们提供了很多的抽象,我们因此可以在不同的框架中使用一样的方式来进行数据操作。最重要的抽象有事务抽象,异常的抽象。
  • Spring 的事务抽象:
    • 一致的事务模型
    • DataSource/JTA
    • 事务抽象的核心接口: 玩转Spring全家桶(上) - 掘金 - 图28
  • 事务的传播特性(七种): 玩转Spring全家桶(上) - 掘金 - 图29
  • 事务的隔离特性: 玩转Spring全家桶(上) - 掘金 - 图30

    直接使用默认的事务模式或者根据实际的业务需求去设定事务的隔离级别等。

  • 编程式事务:

    • TransactionTemplate

      • TransactionCallBack
      • TransactionCallbackWithoutResult

        TransactionCallback 用于有返回值的,而 TransactionCallbackWithoutResult 用于没有返回值。

    • PlatformTransactionManager

      • 可以传入 TransactionDefinition 进行定义
    • 代码示例: 玩转Spring全家桶(上) - 掘金 - 图31
  • 声明式事务
    • 图示: 玩转Spring全家桶(上) - 掘金 - 图32
    • 基于注解的配置方式: 玩转Spring全家桶(上) - 掘金 - 图33
    • 示例如图: 玩转Spring全家桶(上) - 掘金 - 图34

2.7 了解 Spring 的 JDBC 异常抽象

  • Spring 会将数据操作异常转换为 DataAccessException
  • 无论使用何种数据访问方式,都能使用一样的异常,图示: 玩转Spring全家桶(上) - 掘金 - 图35
  • Spring 是怎么认识那些错误码的
    • 通过 SQLErrorCodeSQLExceptionTranslator 解析错误码
    • ErrorCode 定义:
      • org/springframework/jdbc/support/sql-error-codes.xml
      • Classpatch 下的 sql-error-codes.xml
  • 定制错误码解析逻辑: 玩转Spring全家桶(上) - 掘金 - 图36

2.8 一些其他的知识点

  • 一些常用注解:

  • Actuator 提供的一些好用的 Endpoint: 玩转Spring全家桶(上) - 掘金 - 图37

  • 如何解禁 Endpoint:

    • 默认:
      • /actuator/health 和 /actuator/info 可 web 访问
    • 解禁所有 Endpoint:
      • application.properties /application.yml
      • management.endpoints.web.exposure.include=*

        生产环境需谨慎

  • 多数据源、分库分表、读写分离的关系

    • 几种常见的情况:
      1. 系统需要访问几个完全不同的数据库
      2. 系统需要访问同一个库的主库与备库
      3. 系统需要访问一组做了分库分表的数据库
      4. 如图所示: 玩转Spring全家桶(上) - 掘金 - 图38
    • 使用数据库中间件的情况: 玩转Spring全家桶(上) - 掘金 - 图39
  • 事务的本质:

    1. Spring 的声明式事务本质上是通过 AOP 来增强了类的功能
    2. Spring 的 AOP 本质上就是为类做了一个代理

      看似在调用自己写的类,实际用的是增强后的代理类 访问增强后的代理类的方法,而非直接访问自身的方法

  • Requires_new 与 Nested 事务传播特性的说明

    • Requires_new, 始终启动一个新事务
      • 两个事务没有关联
    • Nested, 在原事务内启动一个内嵌事务
      • 两个事务有关联
      • 外部事务回滚,内嵌事务也会回滚
  • Alibaba Druid 的一些展开说明

    • 慢 SQL 日志: 玩转Spring全家桶(上) - 掘金 - 图40

      在执行 jar 时可以配置慢日志

    • 一些注意事项:

      1. 没特殊情况,不要在生产环境下打开监控的 Servelt
      2. 没有连接泄露可能的情况下,不要开启 removeAbandoned
      3. testXxx 的使用需要注意
      4. 务必配置合理的超时时间

三. O/R Mapping 实践

3.1 认识 Spring Data JPA

  • 对象与关系的范式不匹配 玩转Spring全家桶(上) - 掘金 - 图41
  • Hibernate
    • 一款开源的对象映射(Object/Relational Mapping)框架
    • 将开发者从 95% 的常见数据持久化工作中解放出来
    • 屏蔽了底层数据库的各种细节
  • Hibernate 发展历程
    • 2001 年,Gavin King 发布第一个版本
    • 2003 年,Hibernate 开发团队加入 JBoss
    • 2006 年,Hibernate 3.2 成为 JPA 实现
  • JPA 为对象映射提供了一种基于 POJO 的持久化模型

    • 简化数据持久化代码的开发工作
    • 为 Java 社区屏蔽不同持久化 API 的差异

      2006 年,JPA1.0 作为 JSR 220 的一部分正式发布

  • Spring Data

    • 在保留底层存储特性的同时,提供相对一致的、基于 Spring 的编程模型
    • 主要模块:
      1. Spring Data Commons
      2. Spring Data JDBC
      3. Spring Data JPA
      4. Spring Data Redis

很多…

  • 图示引入 Jpa: 玩转Spring全家桶(上) - 掘金 - 图42

3.2 定义 JPA 的实体对象

3.3 Repository 是怎么从接口变成 Bean 的?

  • Repository Bean 是如何创建的? 玩转Spring全家桶(上) - 掘金 - 图44
  • 接口中的方法是如何被解释的? 玩转Spring全家桶(上) - 掘金 - 图45

3.4 使用 Mybatis 操作数据

3.5 让 Mybatis 更好用的那些工具 - Mybatis Generator

  • 它是官方提供的生成器: Mybatis Generator(www.mybatis.org/generator)
  • Mybatis 代码生成器
  • 根据数据库表生成相关代码
    • POJO
    • Mapper 接口
    • SQL Map XML
  • 运行 Mybatis Generator 玩转Spring全家桶(上) - 掘金 - 图48

    第一个是通过命令行运行,第二个是安装 Maven 的一个插件,第三种是 Eclipse Plugin(我们现在一般都是用 IDEA 了),第四种是 Java 程序,第五种不常用了。

  • 配置 Mybatis Generator 玩转Spring全家桶(上) - 掘金 - 图49

  • 生成时可以使用的插件 玩转Spring全家桶(上) - 掘金 - 图50
  • 使用生成的对象
    1. 简单操作,直接使用生成的 xxxMapper 的方法
    2. 复杂查询,使用生成的 xxxExample 对象

3.6 让 Mybatis 更好用的那些工具 Mybatis PageHelper

四. NoSQL 实践

4.1 通过 Docker 辅助开发

  • 什么是容器:
    • 容器映像是一个软件的轻量级可执行软件包,包含运行它所需的一切:代码,运行时,系统工具,系统库,设置。不管环境如何,集装箱化软件都可以运行相同的 Linux 和 Windows 应用程序。容器将软件与其周围环境隔离开来,例如开发环境和登台环境之间的差异,并有助于减少在同一基础架构上运行不同软件的团队之间的冲突。
    • 图示:

玩转Spring全家桶(上) - 掘金 - 图52玩转Spring全家桶(上) - 掘金 - 图53

  • 认识 docker:
    • 介绍:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。
    • 图示: 玩转Spring全家桶(上) - 掘金 - 图54
  • 不同人眼中的 Docker:

    • 开发眼中的 Docekr

      • 简化了重复搭建开发环境的工作

        使用镜像可以直接使用,免去了一些重复的配置

    • 运维眼中的 Docker:

      • 交付系统更为顺畅
      • 伸缩性更好
  • Docker 常用命令
    • 镜像相关:
      1. docker pull <image>
      2. docker search <image>
    • 容器相关:
      1. docker run
      2. docker start/stop <容器名>
      3. docker ps <容器名>
      4. docker logs <容器名>
  • docker run 的常用选项
    • docker run [OPTIONS] IMAGE [COMMAND] [ARG…]
    • 选项说明:
      1. -d, 后台运行容器
      2. -e,设置环境变量
      3. —expose/ -p 宿主端口:容器端口
      4. —name, 指定容器名称
      5. —link, 链接不同容器
      6. -v 宿主目录:容器目录,挂载磁盘卷
  • 国内 Docker 镜像配置

  • 通过 Docker 启动 MongoDB 玩转Spring全家桶(上) - 掘金 - 图56
  • 通过 Docker 启动 MongoDB
    • 登录到 MongoDB 容器中:```java
      docker exec -it mongo bash
    • 通过 Shell 链接 MongoDB```java
      mongo -u admin -p admin

4.2 在 Spring 中访问 MongoDB

  • MongoDB 是一款开源的文档型数据库,官网地址, 它是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
  • 介绍:
    1. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
    2. 它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较复杂的数据类型。
    3. Mongo 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
  • Spring 对 MongoDB 的支持
    • Spring Data MongoDB
    • MongoTemplate
    • Repository 支持
  • Spring Data MongoDB 的基本用法
    • 注解:
      • @Document (表示对应的是哪个文档,类似表名的对应)
      • @Id
    • MongoTemplate:
      • save/remove
      • Criteria/Query/Update
  • 使用示例:
    1. 引入依赖: 玩转Spring全家桶(上) - 掘金 - 图57
    2. 数据源配置:
      1. 如果 MongoDB 端口是默认端口,并且没有设置密码,可不配置,SpringBoot 会开启默认的 ```java
        spring.data.mongodb.uri=mongodb://localhost:27017/springboot-db
      2. MongoDB 设置了密码,这样配置:```java
        spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/dbname
    3. 定义一个简单的实体:java<br />package com.forezp.entity;<br />import org.springframework.data.annotation.Id;<br />public class Customer {<br />@Id<br />public String id;<br />public String firstName;<br />public String lastName; <br />} public Customer() {} public Customer(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }

@Override public String toString() { return String.format(“Customer[id=%s, firstName=’%s’, lastName=’%s’]”,id, firstName, lastName); }

  1. 4. 数据操作 dao 层:```java<br />public interface CustomerRepository extends MongoRepository<Customer, String> {<br />public Customer findByFirstName(String firstName);<br />public List findByLastName(String lastName);<br />}
  2. 5. 创建测试类:```java<br />import org.springframework.beans.factory.annotation.Autowired;<br />import org.springframework.boot.CommandLineRunner;<br />import org.springframework.boot.SpringApplication;<br />import org.springframework.boot.autoconfigure.SpringBootApplication;<br />@SpringBootApplication<br />public class SpringbootMongodbApplication implements CommandLineRunner {<br />[@Autowired ](/Autowired ) <br />private CustomerRepository repository; <br />// save a couple of customers<br />repository.save(new Customer("Alice", "Smith"));<br />repository.save(new Customer("Bob", "Smith"));<br />for (Customer customer : repository.findAll()) {<br />System.out.println(customer);<br />}<br />System.out.println(repository.findByFirstName("Alice"));<br />for (Customer customer : repository.findByLastName("Smith")) {<br />System.out.println(customer);<br />}<br />}<br />}

public static void main(String[] args) { SpringApplication.run(SpringbootMongodbApplication.class, args); }

@Override public void run(String… args) throws Exception { repository.deleteAll();

  1. <a name="171ba7b6"></a>
  2. ### 4.3 在 Spring 中访问 Redis
  3. - Spring 对 Redis 的支持
  4. 1. Redis 是一款开源的内存 KV 存储,支持多种数据结构,[官网地址](https://link.juejin.cn?target=https%3A%2F%2Fredis.io)
  5. 1. Spring Data Redis
  6. 1. 支持的客户端 Jedis / Lettuce
  7. 1. RedisTemplate
  8. 1. Repository 支持
  9. - Jedis 客户端的简单使用
  10. 1. Jedis 不是线程安全的
  11. 1. 通过 JedisPool 获得 Jedis 实例
  12. 1. 直接使用 Jedis 中的方法
  13. 1. 图示: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9907b08874374055bbf7e32ecf795c04~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=XCK9t&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  14. - 通过 Docker 启动 Redis
  15. 1. [官方指引](https://link.juejin.cn?target=https%3A%2F%2Fhub.docker.com%2F_%2Fredis)
  16. 1. 获取镜像: `docker pull redis`
  17. 1. 启动 Redis:```java<br />docker run --name redis -d -p 6379:6379 redis
  18. <a name="03a50574"></a>
  19. ### 4.4 Redis 的哨兵模式:
  20. - Redis Sentinel 是 Redis 的一种高可用方案
  21. - 监控、通知、自动故障转移、服务发现
  22. - JedisSentinelPool
  23. - Jedis 是 Redis 官网推荐的 Java 客户端,Redis-Sentinel 作为官方推荐的 HA 解决方案,Jedis 也在客户端角度实现了对 Sentinel 的支持,主要实现在 JedisSentinelPool.java 这个类中,Jedis 的 JedisSentinelPool 的实现仅仅适用于单个 master-slave。
  24. - 内部有如下属性:```java<br />// 基于 apache 的 commom-pool2 的对象池配置<br />protected GenericObjectPoolConfig poolConfig;<br />// 超时时间,默认是 2000<br />protected int timeout = Protocol.DEFAULT_TIMEOUT;<br />//sentinel 的密码<br />protected String password;<br />//redis 数据库的数目<br />protected int database = Protocol.DEFAULT_DATABASE;<br />//master 监听器,当 master 的地址发生改变时,会触发这些监听者<br />protected Set masterListeners = new HashSet();<br />protected Logger log = Logger.getLogger(getClass().getName());<br />//Jedis 实例创建工厂<br />private volatile JedisFactory factory;<br />// 当前的 master,HostAndPort 是一个简单的包装了 ip 和 port 的模型类<br />private volatile HostAndPort currentHostMaster;
  25. - 哨兵模式是 redis 高可用的实现方式之一使用一个或者多个哨兵 (Sentinel) 实例组成的系统,对 redis 节点进行监控,在主节点出现故障的情况下,能将从节点中的一个升级为主节点,进行故障转义,保证系统的可用性。
  26. - 图示: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/466e7b503e5e4ba0b9c5f5cb0a3a6d40~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=piTVK&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  27. - 哨兵们是怎么感知整个系统中的所有节点 (主节点 / 从节点 / 哨兵节点) 的?
  28. 1. 首先主节点的信息是配置在哨兵 (Sentinel) 的配置文件中
  29. 1. 哨兵节点会和配置的主节点建立起两条连接命令连接和订阅连接
  30. 1. 哨兵会通过命令连接每 10s 发送一次 INFO 命令,通过 INFO 命令,主节点会返回自己的 run_id 和自己的从节点信息
  31. 1. 哨兵会对这些从节点也建立两条连接命令连接和订阅连接
  32. 1. 哨兵通过命令连接向从节点发送 INFO 命令,获取到他的一些信息
  33. 1. 因为哨兵对与集群中的其他节点 (主从节点) 当前都有两条连接,命令连接和订阅连接
  34. - 哨兵模式下的故障迁移?
  35. - 主观下线:哨兵 (Sentinel) 节点会每秒一次的频率向建立了命令连接的实例发送 PING 命令,如果在 down-after-milliseconds 毫秒内没有做出有效响应包括 (PONG/LOADING/MASTERDOWN) 以外的响应,哨兵就会将该实例在本结构体中的状态标记为 SRI_S_DOWN 主观下线
  36. - 客观下线:当一个哨兵节点发现主节点处于主观下线状态是,会向其他的哨兵节点发出询问,该节点是不是已经主观下线了。如果超过配置参数 quorum 个节点认为是主观下线时,该哨兵节点就会将自己维护的结构体中该主节点标记为 SRI_O_DOWN 客观下线
  37. 询问命令 SENTINEL is-master-down-by-addr <current_epoch> <run_id>
  38. - leader 选举:
  39. - 在认为主节点客观下线的情况下, 哨兵节点节点间会发起一次选举,命令还是上面的命令 SENTINEL is-master-down-by-addr <current_epoch> <run_id>, 只是 run_id 这次会将自己的 run_id 带进去,希望接受者将自己设置为主节点。如果超过半数以上的节点返回将该节点标记为 leader 的情况下,会有该 leader 对故障进行迁移
  40. - 优缺点:
  41. - 优点:高可用,在主节点故障时能实现故障的转移
  42. - 缺点:好像没办法做到水平拓展,如果内容很大的情况下
  43. <a name="18334ad3"></a>
  44. ### 4.5 - Redis 的集群模式:
  45. - 官方提供的分布式方案 (槽指派 / 重新分片 / 故障转移),集群内的节点,都会有个数据结构存储整个集群内的节点信息。
  46. 1. Redis Cluster
  47. - 数据自动分片(分成 16384 个 Hash Slot)
  48. - 在部分节点失效时有一定可用性
  49. 2. Jedis Cluster
  50. - Jedis 只从 Master 读数据,如果想要读写分离,可以定制
  51. - 槽指派
  52. - redis 集群可以被分为 16384 个槽,只有这些槽全被指派了处理的节点的情况下,集群的状态才能是上线状态 (ok)
  53. - 操作 redis 集群的时候,将 key 作为参数,就可以计算出对应的处理槽上,所以存储等操作都应该在该槽对应的节点上。通过这种方式,可以完美的实现集群存储的水平拓展。
  54. - 发现故障节点
  55. 1. 集群内的节点会向其他节点发送 PING 命令,检查是否在线
  56. 1. 如果未能在规定时间内做出 PONG 响应,则会把对应的节点标记为疑似下线
  57. 1. 集群中一半以上负责处理槽的主节点都将主节点 X 标记为疑似下线的话,那么这个主节点 X 就会被认为是已下线
  58. 1. 向集群广播主节点 X 已下线, 大家收到消息后都会把自己维护的结构体里的主节点 X 标记为已下线
  59. - 从节点选举
  60. 1. 当从节点发现自己复制的主节点已下线了,会向集群里面广播一条消息,要求所有有投票权的节点给自己投票 (所有负责处理槽的主节点都有投票权)
  61. 1. 主节点会向第一个给他发选举消息的从节点回复支持
  62. 1. 当支持数量超过 N/2+1 的情况下,该从节点当选新的主节点
  63. - 故障的迁移
  64. 1. 新当选的从节点执行 SLAVEOF no one, 修改成主节点
  65. 1. 新的主节点会撤销所有已下线的老的主节点的槽指派,指派给自己
  66. 1. 新的主节点向集群发送命令,通知其他节点自己已经变成主节点了,负责哪些槽指派
  67. 1. 新的主节点开始处理自己负责的槽的命令
  68. - 集群模式和哨兵模式的区别
  69. 1. 哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控
  70. 1. 哨兵模式发起选举是选举一个 leader 哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移
  71. <a name="b605c1d0"></a>
  72. ### 4.6 了解 Spring 的缓存抽象
  73. - Spring 给我们提供了一个更好用的缓存抽象,它可以在 Java 方法上加一个注解,达到缓存的效果。如果缓存过的,则返回结果,否则就执行。
  74. - 为不同的缓存提供一层抽象:
  75. 1. 为 Java 方法增加缓存,缓存执行结果
  76. 1. 支持 ConcurrentMap、EhCache、Caffeine、JCache(JSR-107)
  77. - 接口:
  78. - org.springframework.cache.Cache
  79. - org.springframework.cache.CacheManager
  80. - 基于注解的缓存
  81. - [@EnableCaching ](/EnableCaching )
  82. - [@Cacheable ](/Cacheable )
  83. - [@CacheEvict ](/CacheEvict )
  84. - [@CachePut ](/CachePut )
  85. - [@Caching ](/Caching )
  86. - [@CacheConfig ](/CacheConfig )
  87. - 通过 SpringBoot 配置 Redis 缓存
  88. - ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb15f3e5619c4e74a4b6132f0c3e3173~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=VIMLJ&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  89. - ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cfc9a6bf3fc2464191acaf056f382a1c~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=KoEDB&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  90. > 这里是引入了 Redis 作为 Spring 的缓存抽象
  91. <a name="b42e5d08"></a>
  92. ### 4.7 Redis 在 Spring 中的其他用法
  93. - 与 Redis 建立连接
  94. - 配置连接工厂:
  95. - 新版本中已经用 LettuceConnectionFactory 代替 JedisConnectionFactory 了。
  96. - 单节点:RedisStandaloneConfiguration
  97. - 哨兵:RedisSentinelConfiguration
  98. - 集群:RedisClusterConfiguration
  99. - 读写分离:
  100. - Lettuce 内置支持读写分离
  101. - 只读主、只读从
  102. - 优先读主、优先读从
  103. - LettuceClientConfiguration
  104. - LettucePoolingClientConfiguration
  105. - LettuceClientConfigurationBuilderCustomizer
  106. > 第二个带池,第三个有回调
  107. - RedisTemplate
  108. - RedisTemplate<K,V>
  109. - opsForXxx()
  110. - StringRedisTemplate
  111. > 只操作 String 类型的可以使用这个, 在使用 redis 的过程中一定要对 key 设置过期时间。
  112. - RedisRepository
  113. - spring-boot-starter-data-redis 2.1 以上支持以 repository 的方式存取对象了。
  114. - 实现步骤:
  115. 1. 我们引入 Springboot Web 的依赖,以启动 REST 服务。还需要引入 Spring Data Redis 相关的依赖。最后,还需要 commons-pool2,不然会因为缺少类而无法启动。```java
  116. org.springframework.boot<br />spring-boot-starter-web
  117. org.springframework.boot<br />spring-boot-starter-data-redis
  118. org.apache.commons<br />commons-pool2<br />
  119. 2. 配置连接信息:
  120. - 配置 Redis 的连接信息,这个信息跟你安装时的配置有关,同时配置了连接池,各项的配置及相关解释如下:```java<br /># Redis数据库索引,默认为0<br />spring.redis.database=0<br /># Redis端口<br />spring.redis.port=6379<br /># Redis服务器主机<br />spring.redis.host=localhost<br /># 连接池最大连接数<br />spring.redis.lettuce.pool.max-active=8<br /># 连接池最大空闲<br />spring.redis.lettuce.pool.max-idle=8<br /># 连接池最小空闲<br />spring.redis.lettuce.pool.min-idle=2<br /># 连接池最大阻塞等待时间<br />spring.redis.lettuce.pool.max-wait=1ms<br /># 超时时间<br />spring.redis.lettuce.shutdown-timeout=100ms
  121. 3. 创建实体类
  122. - 存入 Redis 中的数据类型,可以是自定义的一个类,注意需要加上注解 [@RedisHash ](/RedisHash ) 和 @Id。存入 Redis 的数据为 Set 类型。```java <br />package com.pkslow.redis.model;<br />import org.springframework.data.annotation.Id;<br />import org.springframework.data.redis.core.RedisHash;<br />import java.util.Date;<br />@RedisHash("User")<br />public class User {<br />@Id<br />private String userId;<br />private String name;<br />private Integer age;<br />private Date createTime = new Date(); <br />}

public String getUserId() { return userId; }

public void setUserId(String userId) { this.userId = userId; }

public String getName() { return name; }

public void setName(String name) { this.name = name; }

public Integer getAge() { return age; }

public void setAge(Integer age) { this.age = age; }

public Date getCreateTime() { return createTime; }

public void setCreateTime(Date createTime) { this.createTime = createTime; }

  1. 4. 数据库访问层 UserRepository 接口
  2. - 直接继承 CrudRepository 接口就行了,不用自己来实现,需要注意 CrudRepository<User, String> 的泛型类型:```java<br />import com.pkslow.redis.model.User;<br />import org.springframework.data.repository.CrudRepository;<br />public interface UserRepository extends CrudRepository<User, String> {<br />}
  3. 5. Controller 实现了 RESTful 风格的增删改查功能,只要把 UserRepository 注入便可以使用它来操作:```java<br />import com.pkslow.redis.dal.UserRepository;<br />import com.pkslow.redis.model.User;<br />import org.springframework.beans.factory.annotation.Autowired;<br />import org.springframework.web.bind.annotation.*;<br />[@RestController ](/RestController ) <br />@RequestMapping("/user")<br />public class UserController {<br />[@Autowired ](/Autowired ) <br />private final UserRepository userRepository; <br />}

public UserController(UserRepository userRepository) { this.userRepository = userRepository; }

@GetMapping(“”) public Iterable getAllUsers() { return userRepository.findAll(); }

@GetMapping(“/{userId}”) public User getByUserId(@PathVariable String userId) { return userRepository.findById(userId).orElse(new User()); }

@PostMapping(“”) public User addNewUser(@RequestBody User user) { return userRepository.save(user); }

@DeleteMapping(“/{userId}”) public String delete(@PathVariable String userId) { User user = new User(); user.setUserId(userId); userRepository.deleteById(userId); return “deleted: “ + userId; }

@PutMapping(“”) public User update(@RequestBody User user) { return userRepository.save(user); }

  1. <a name="09291340"></a>
  2. ## 五. 数据访问进阶
  3. <a name="e5a5ba89"></a>
  4. ### 5.1 Project Reactor 介绍
  5. - Project Reactor 简介
  6. 1. Project Reactor 是一个运行在 JVM 上的反应式编程基础库,以 “背压” 的形式管理数据处理,提供了可组合的异步序列 APIFlux 和 Mono
  7. 1. Project Reactor 主要是由 Pivotal 公司开发和维护的,Spring 框架也是该公司在维护,而且 Spring Framework 5 中默认使用 Reactor 作为反应式编程的实现,由此虽然 Reactor 不是 Spring 的子项目,也有人称 Reactor 为 Spring Reactor。
  8. - Project Reactor 特点:
  9. - I/O 阻塞浪费了系统性能,只有纯异步处理才能发挥系统的全部性能,不作丝毫浪费;而 JDK 的异步 API 比较难用,成为异步编程的瓶颈,这就是 Reactor 等其它反应式框架诞生的原因。
  10. - Reactor 大大降低了异步编码难度 (尽管相比同步编码,复杂度仍然是上升的),变得简单的根本原因,是编码思想的转变。
  11. - JDK 的异步 API 使用的是传统的命令式编程,命令式编程是以控制流为核心,通过顺序、分支和循环三种控制结构来完成不同的行为。而 Reactor 使用反应式编程,应用程序从以逻辑为中心转换为了以数据为中心,这也是命令式到声明式的转换。
  12. - Project Reactor 相比于命令式编程还具备哪些特点?
  13. 1. 可组合性和可读性,完美规避了 Callback Hell
  14. 1. 以流的形式进行数据处理时,为流中每个节点提供了丰富的操作符
  15. 1. 在 Subscribe 之前,不会有任何事情发生
  16. 1. 支持背压,消费者可以向生产者发出信号表明排放率过高
  17. 1. 支持两种反应序列:hot 和 cold
  18. - 一些核心的概念:
  19. - Operators-Publisher/Subscriber
  20. - Nothing Happens Until You subscribe()
  21. - Flux[0..N]-onNext()、onComplete()、onError()
  22. - Mono[0..1]-onNext()、onComplete()、onError()
  23. > Flux 是 N 个元素的序列,onNext() 在遇到下一个元素时执行什么,onComplete() 在整个完成时执行什么, onError() 在异常时应该执行什么的一些方法,每个里面可以传入 Lambda 表达式做处理。Mono 的同理,他的[0..1]代表 0 或 1 个元素。
  24. - Backpressure
  25. - Subscription
  26. - onRequest()、onCancel()、onDispose()
  27. > Backpressure 是一种反压力,在上游的生产速度快于下游时,可以设置每次请求多少个元素以及取消中止这个过程等行为。
  28. - 线程调度 Schedulers
  29. - immediate()/single()/newSingle()
  30. > immediate() 是在当前线程上去做某些任务,single() 是服用线程,newSingle() 是新建线程。
  31. - elastic()/parallel()/newParallel()
  32. > elastics() 是线程池,它空闲 60 秒后会被回收。parallel() 是一个固定线程池,它会创建出跟 CPU 核数对应的线程,它是不会被回收的。newParallel() 新建。
  33. - 错误处理
  34. - onError/onErrorReturn/onErrorResume
  35. > onError 相当于 try...catch,当抛出异常后的代码执行则在此方法中定义。onErrorReturn 是在异常时返回一个默认值。onErrorResume 用一个特定的 lambda 处理
  36. - doOnError/doFinally
  37. > doFinally 表示总会被执行,无论抛出异常后还是未抛出异常。
  38. - 实战代码示例:
  39. 1. pom.xml: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/03df780e5f6a4a7aabce1a65b41a2254~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=DKZdG&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  40. 1. 代码示例: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e3a8d5410b947dfa18bec70186a1cb0~tplv-k3u1fbpfcp-watermark.awebp#crop=0&crop=0&crop=1&crop=1&id=xz6XX&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  41. > 这是一个简单的 Project Reactor 的示例,里面用到了一些上面使用到的一些方法,不明白的地方可以配合百度理解。
  42. <a name="d02e3835"></a>
  43. ### 5.2 通过 Reactive 的方式访问 Redis
  44. - Spring Data Redis
  45. 1. Lettuce 能够支持 Reactive 方式
  46. 1. Spring Data Redis 中主要的支持:
  47. - ReactiveRedisConnection
  48. - ReactiveRedisConnectionFactory
  49. - ReactiveRedisTemplate
  50. - opsForXxx()
  51. > Redis 支持 Jedis 和 Lettuce,但是如果要使用 Reactive 则只能使用 Lettuce,因为它支持 Reactive 方式
  52. - 代码示例:
  53. 1. 引入 pom.xml 的依赖:
  54. ```java
  55. <dependency>
  56. <groupId>org.springframework.boot</groupId>
  57. <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
  58. </dependency>
  1. 定义 Bean:

    1. @Bean
    2. ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory){
    3. return new ReactiveStringRedisTemplate(factory);
    4. }

    注册一个 Bean,可以单独建一个类,也可以直接写在 SpringBoot 的启动类中;

  2. 引入 Bean

    1. @Autowired
    2. private ReactiveStringRedisTemplate redisTemplate;
  3. 代码示例: 玩转Spring全家桶(上) - 掘金 - 图58
    玩转Spring全家桶(上) - 掘金 - 图59

5.3 通过 Reactive 的方式访问 MongoDB

  • mongodb 官方还提供了支持 Reactive 的驱动

    • mongodb-driver-reactivestreams

      在 Java 中,提供了两种方式来支持通过 Reactive 的方式访问 MongoDB。一种是 MongoDB 官方提供了支持 Reactive 的驱动另一种是 Spring Data MongoDB 中主要的支持我们主要看 Spring Data MongoDB 中主要的支持

  • Spring Data MongoDB 中主要的支持

    • ReactiveMongoClientFactoryBean
    • ReactiveMongoDatabaseFactory
    • ReactiveMongoTemplate
  • 先传入一个只打印日志的动作:java<br />[@Slf4j ](/Slf4j ) <br />@SpringBootApplication<br />public class MongodbDemoApplicationTest implements ApplicationRunner {<br />[@Autowired ](/Autowired ) <br />private ReactiveMongoTemplate mongoTemplate;<br />private CountDownLatch cdl=new CountDownLatch(1);<br />[@Bean ](/Bean ) <br />public MongoCustomConversions mongoCustomConversions(){<br />return new MongoCustomConversions(<br />Arrays.asList(<br />new MoneyReadConverter(),<br />new MoneyWriteConverter())); //将读与写注册到bean里面<br />}<br />public static void main(String[] args) {<br />SpringApplication.run(MongodbDemoApplicationTest.class,args);<br />}<br />[@Override ](/Override ) <br />public void run(ApplicationArguments args) throws Exception {<br />startFromInsertion(() -> log.info("Runnable"));<br />//startFromInsertion(() -> {<br />// log.info("Runnable");<br />// decreaseHighPrice();<br />//});

    1. log.info("after starting");
    2. //decreaseHighPrice();
    3. cdl.await(); //做一个等待

    }

    private void decreaseHighPrice() {

    1. mongoTemplate.updateMulti(query(where("price").gte(3000L)), //查询出所有大于30块的咖啡
    2. new Update().inc("price", -500L) //对于key,做一个减少500的操作
    3. .currentDate("updateTime"), Coffee.class) //更新updatetime
    4. .doFinally(s -> {
    5. cdl.countDown();
    6. log.info("Finnally 2, {}", s);
    7. })
    8. .subscribe(r -> log.info("Result is {}", r));

    }

    public void startFromInsertion(Runnable runnable){

    1. mongoTemplate.insertAll(initCoffee())
    2. .publishOn(Schedulers.elastic()) //将插入的结果publishOn到elastic线程池上
    3. .doOnNext(c -> log.info("Next: {}", c)) //针对每一个next动作,都会打印我们取到的coffee
    4. .doOnComplete(runnable) //整体,即插入完成之后,我们去执行一个runnable对象,即() -> log.info("Runnable")
    5. .doFinally(s -> {
    6. cdl.countDown(); //计数器减一
    7. log.info("Finnally 1, {}", s);
    8. }) //上面的操作最后返回的是一个flux流 对insert的结果做一个count
    9. .count()
    10. .subscribe(c -> log.info("Insert {} records", c));

    }

    private List initCoffee() {

    1. Coffee espresso = Coffee.builder()
    2. .name("espresso")
    3. .price(Money.of(CurrencyUnit.of("CNY"), 30))
    4. .createTime(new Date())
    5. .updateTime(new Date())
    6. .build();
    7. Coffee latte = Coffee.builder()
    8. .name("latte")
    9. .price(Money.of(CurrencyUnit.of("CNY"), 20))
    10. .createTime(new Date())
    11. .updateTime(new Date())
    12. .build();
    13. return Arrays.asList(espresso, latte);

    }

} ```

5.4 通过 Reactive 的方式访问 RDBMS

  • 图示:
    • 玩转Spring全家桶(上) - 掘金 - 图60
    • 玩转Spring全家桶(上) - 掘金 - 图61
  • 代码示例:
    • pom.xml(核心依赖): 玩转Spring全家桶(上) - 掘金 - 图62
      玩转Spring全家桶(上) - 掘金 - 图63
    • 实体类: 玩转Spring全家桶(上) - 掘金 - 图64
    • 测试代码: 玩转Spring全家桶(上) - 掘金 - 图65
      玩转Spring全家桶(上) - 掘金 - 图66
      玩转Spring全家桶(上) - 掘金 - 图67
  • R2DBC Repository 支持: 玩转Spring全家桶(上) - 掘金 - 图68
  • 总结: 它支持的数据库比较少,还不具备在生产环境中使用的条件,但是它的思想可以作为我们学习的地方,以后可能会兴起,作为我们的知识储备。

5.5 AOP

  • Spring AOP 的一些核心概念 | 概念 | 含义 | | —- | —- | | Aspect | 切面 | | Advice | 连接点,Spring AOP 里总是代表一次方法执行 | | Pointcut | 切入点,说明如何匹配连接点 | | Introduction | 引入,为现有类型声明额外的方法和属性 | | Target object | 目标对象(动态代理的代理对象) | | AOP proxy | AOP 代理对象,可以使 JDK 动态代理,也可以是 CGLIB 代理 | | Weaving | 织入,连接切面与目标对象或类型创建代理的过程 |

六. Spring MVC 实践

6.1 编写第一个 Spring MVC Controller

  • 认识 Spring MVC 玩转Spring全家桶(上) - 掘金 - 图71
  • SpringMVC 中的常用注解 玩转Spring全家桶(上) - 掘金 - 图72
  • Controller 代码示例: 玩转Spring全家桶(上) - 掘金 - 图73

6.2 理解 Spring 的应用上下文

  • 图示: 玩转Spring全家桶(上) - 掘金 - 图74
    玩转Spring全家桶(上) - 掘金 - 图75
  • 关于上下文常用的接口及其实现: 玩转Spring全家桶(上) - 掘金 - 图76
  • Web 上下文层次: 玩转Spring全家桶(上) - 掘金 - 图77
    玩转Spring全家桶(上) - 掘金 - 图78

6.3 理解请求的处理机制

  • SpringMVC 的请求处理流程: 玩转Spring全家桶(上) - 掘金 - 图79

6.4 一个请求的大致处理流程

  • 玩转Spring全家桶(上) - 掘金 - 图80
  • 一个请求的大致处理:
    1. 绑定一些 Attribute
      • WebApplicationContext/LocaleResolver/ThemeResolver
    2. 处理 Multipart
      • 如果是,则将请求转为 MultipartHttpServletRequest
    3. Handler 处理
      • 如果找到对应 Handler,执行 Controller 及前后置处理器逻辑
    4. 处理返回的 Model, 呈现视图

6.5 如何定义处理方法

  1. 定义映射关系

玩转Spring全家桶(上) - 掘金 - 图81
2. 定义处理方法 玩转Spring全家桶(上) - 掘金 - 图82

  1. 玩转Spring全家桶(上) - 掘金 - 图84
  2. 定义类型转换(自己实现 WebMvcConfigurer)
    1. Spring Boot 在 WebMvcAutoConfiguration 中实现了一个
    2. 添加自定义的 Converter
    3. 添加自定义的 Formatter
  3. 定义校验
    1. 通过 Validator 对绑定结果进行校验
      1. Hibernate Validator
    2. @Valid 注解
    3. BindingResult
      • 示例: 玩转Spring全家桶(上) - 掘金 - 图85
  4. Multipart 上传

    1. 配置 MultipartResolver

      Spring Boot 自动配置 MultipartAutoConfiguration

    2. 支持类型 multipart/form-data

    3. MultipartFile 类型

6.6 Spring MVC 中的视图解析机制

  1. 视图解析的基础(viewResolver 与 View 接口)
    • AbstractCachingViewResolver
    • UrlBasedViewResolver
    • FreeMarkerViewResolver
    • ContentNegotiatingViewResolver
    • InternalResourceViewResolver
  2. DispatcherServelt 中的视图解析逻辑
    • initStrategies()
      • initViewResolvers() 初始化了对应 ViewResolver
    • doDispatch()
      • processDispatchResult()
        • 没有返回视图的话,尝试 RequestToViewNameTranslator
        • resolveViewName() 解析 View 对象
  3. 使用 @ResponseBody 的情况
    • 在 HandlerAdapter.handle() 中完成了 Response 输出
      • RequestMappingHandlerAdapter.invokeHandlerMethod()
        • HandlerMethodReturnValueHandlerComposite.handleReturnValue()
          • RequestResponseBodyMethodProcessor.handleReturnValue()

6.7 Spring MVC 中的常用视图

  • SpringMVC 支持的视图: 玩转Spring全家桶(上) - 掘金 - 图86
  • 配置 MessageConverter 玩转Spring全家桶(上) - 掘金 - 图87
  • Spring Boot 对 Jackson 的支持 玩转Spring全家桶(上) - 掘金 - 图88
  • Thymeleaf
    • 引入方式: 玩转Spring全家桶(上) - 掘金 - 图89
    • 一些默认配置: 玩转Spring全家桶(上) - 掘金 - 图90

6.8 静态资源与缓存

  • Spring Boot 中的静态资源配置 玩转Spring全家桶(上) - 掘金 - 图91
  • SpringBoot 中的缓存配置 玩转Spring全家桶(上) - 掘金 - 图92
  • Controller 中手工设置缓存 玩转Spring全家桶(上) - 掘金 - 图93
  • 建议的资源访问方式: 玩转Spring全家桶(上) - 掘金 - 图94

6.9 Spring MVC 中的异常处理机制

  • SpringMVC 的异常解析 玩转Spring全家桶(上) - 掘金 - 图95
  • 异常处理方法 玩转Spring全家桶(上) - 掘金 - 图96

    @ControllerAdvice 内的异常处理方法的优先级要低于 Controller 内的方法。所以在 Controller 内单独对异常处理的优先级最高。

6.10 了解 Spring MVC 的切入点

  • Spring MVC 的拦截器: 玩转Spring全家桶(上) - 掘金 - 图97
    玩转Spring全家桶(上) - 掘金 - 图98
  • 拦截器的配置方式: 玩转Spring全家桶(上) - 掘金 - 图99

七. 访问 Web 资源

7.1 通过 RestTemplate 访问 Web 资源

  • 简单说明:
    • Spring Boot 中没有自动配置 RestTemplate
    • Spring Boot 提供了 RestTemplateBuilder
      • RestTemplateBuilder.build()
  • 常用方法: 玩转Spring全家桶(上) - 掘金 - 图100
  • 构造 URI
    • 构造 URI:
      • UriComponentsBuilder
    • 构造相当于当前请求的 URI
      • ServletUriComponentsBuilder
    • 构造指向 Controller 的 URI
      • MvcUriCOmponentsBuilder
    • 代码示例: 玩转Spring全家桶(上) - 掘金 - 图101

7.2 RestTemplate 的高阶段用法

  • 传递 HTTP Header
    • RestTemplate.exchange()
    • RequestEntity / ResponseEntity
  • 类型转换:
  • 解析泛型对象
    • RestTemplate.exchange()
    • ParameterizedTypeReference

7.3 简单定制 RestTemplate

  • RestTemplate 支持的 HTTP 库
    • 通用接口:
      • ClientHttpRequestFactory
    • 默认实现:
      • SimpleClientHttpRequestFactory
    • 支持的 HTTP 库: 玩转Spring全家桶(上) - 掘金 - 图102
  • 优化底层请求库: 玩转Spring全家桶(上) - 掘金 - 图103
  • 连接复用: 玩转Spring全家桶(上) - 掘金 - 图104

7.4 通过 WebClient 访问 Web 资源

  • 了解 WebClient:
    • 它是一个以 Reactive 方式处理 HTTP 请求的非阻塞式的客户端
    • 支持的底层 HTTP 库
      • Reactor Netty - ReactorClientHttpConnector
      • Jetty ReactiveStream HttpClient - JettyClientHttpConnector
    • 创建 WebClient:
      • WebClient.create()
      • WebClient.builder()
  • 发起请求:
    • get() / post() / put() / delete() / patch()
  • WebClient 的基本用法:
    • 获得结果:
      • retrieve() / exchange()
    • 处理 HTTP Status
      • onStatus()
    • 应答正文:
      • bodyToMono() / bodyToFlux()
    • 代码示例: 玩转Spring全家桶(上) - 掘金 - 图105
      玩转Spring全家桶(上) - 掘金 - 图106

八. Web 开发进阶

8.1 设计好的 Restful Web Service

  • 什么是 REST?
    • REST 提供了一组架构约束,当作为一个整体来应用时,强调组件交互的可伸缩性、接口的通用性、组件的独立部署、以及用来减少交互延迟、增强安全性、封装遗留系统的中间组件。
  • Richardson 成熟度模型: 玩转Spring全家桶(上) - 掘金 - 图107
  • 如何实现 Restful Web Service:
    • 识别资源
    • 选择合适的资源粒度
    • 设计 URI
    • 选择合适的 HTTP 方法和返回码
    • 设计资源的表述
  • 识别资源
    • 找到领域名词
      • 能用 CRUD 操作的名词
    • 将资源组织为集合(即集合资源)
    • 将资源合并为复合资源
    • 计算或处理函数
  • 资源的粒度:
    • 站在服务端的角度:
      1. 网络效率
      2. 表述的多少
      3. 客户端的易用程度
    • 站在客户端的角度,要考虑:
      1. 可缓存性
      2. 修改频率
      3. 可变性
  • 构建更好的 URI
    1. 使用域及子域对资源进行合理的分组或划分
    2. 在 URI 的路径部分使用斜杠分隔符来表示资源之间的层次关系
    3. 在 URL 的路径部分使用逗号 (,) 和分号(;)来表示非层次元素
    4. 使用连字符(-)和下划线(_)来改善长路径中名称的可读性
    5. 在 URI 的查询部分使用 “与” 符号(&)来分隔参数
    6. 在 URI 中避免出现文件扩展名(例如. php,.aspx 和 .jsp)
  • 认识 HTTP 方法
    • 玩转Spring全家桶(上) - 掘金 - 图108
  • URI 与 HTTP 方法的组合 玩转Spring全家桶(上) - 掘金 - 图109
  • 认识 HTTP 状态码 玩转Spring全家桶(上) - 掘金 - 图110

    一般我们经常遇到的是 200/404/500 等

  • 选择合适的表述 玩转Spring全家桶(上) - 掘金 - 图111

8.2 什么是 HATEOAS

  • Richardson 成熟度模型
    • Level 3 - Hypermedia Controls
  • HATEOAS:
    • Hybermedia As The Engine Of Application State
    • REST 统一接口的必要组成部分
  • HATEOAS vs WSDL
    • HATEOAS:
      1. 表述中的超链接会提供服务所需的各种 REST 接口信息
      2. 无需事先约定如何访问服务
    • 传统的服务契约:
      1. 必须实现约定服务的地址与格式
    • 代码示例: 玩转Spring全家桶(上) - 掘金 - 图112
  • 常见的超链接类型: 玩转Spring全家桶(上) - 掘金 - 图113

8.3 使用 Spring Data REST 实现简单的媒体服务

  • 认识 HAL
    • 全称: Hypertext Application Language
    • HAL 是一种简单的格式,为 API 中的资源提供简单一致的链接
    • HAL 模型: 链接 | 内嵌资源 | 状态
  • Spring Data REST
    • Spring Boot 依赖
      • spring-boot-starter-data-rest
    • 常用注解与类
      • @RepositoryREstResource
      • Resource
      • PagedResource
    • 代码示例: 玩转Spring全家桶(上) - 掘金 - 图114
      玩转Spring全家桶(上) - 掘金 - 图115
  • 如何访问 HATEOAS 服务
    • 配置 Jackson Json
      • 注册 HAL 支持
    • 操作超链接
      • 找到需要的 Link
      • 访问超链接

8.4 分布式环境中如何解决 Session 的问题

  • 常见的会话解决方案

    1. 粘性会话:Sticky Session

      让会话尽可能分配到同一台机器上,让分布式变成单机

    2. 会话复制: Session Replication

      复制有成本、可能存在不一样、每台机器保存所有会话信息数据量大灯

    3. 集中会话: Centralized Session

      使用 JDBC、Redis 等来集中存储这些信息

  • 认识 Spring Session

    • Spring Session
      1. 简化集群中的用户会话管理
      2. 无需绑定容器特定解决方案
    • 支持的存储
      1. Redis
      2. MongoDB
      3. JDBC
      4. Hazelcast
  • 实现原理(它是通过定制 HttpSesion):
    • 通过定制的 HttpServeltRequest 返回定制的 HttpSession
      • SessionRepositoryRequestWrapper
      • SessionRepositoryFilter
      • DelegatingFilterProxy
  • 基于 Redis 的 HttpSession
    • 引入依赖:spring-session-data-redis
    • 基本配置:
      1. @EnableRedisHttpSession
      2. 提供 RedisConnectionFactory
      3. 实现 AbstractHttpSessionApplicationInitializer
        1. 配置 DelegatingFilterProxy
  • SpringBoot 对 Spring Session 的支持: 玩转Spring全家桶(上) - 掘金 - 图116

8.5 使用 WebFlux 代替 Spring MVC

  • 认识 WebFlux

    • 什么是 WebFlux

      • 用于构建基于 Reactive 技术栈之上的 Web 应用程序
      • 基于 Reactive Streams API 运行在非阻塞服务器上

        WebFlux 可以运行在 Netty 等服务器上,同时能够提供极大的并发量。

    • 为什么会有 WebFLux

      • 对于非阻塞 Web 应用的需要
      • 函数式编程
    • 关于 WebFlux 的性能
      • 请求的耗时并不会有很大的改善
      • 仅需少量固定数量的线程和较少的内存即可实现扩展

        使用 WebFlux 能够以极少的线程和开销就可以实现同等的并发,所以它的性能更好。

  • Web MVC vs WebFlux

    1. 已有 Spring MVC 应用,运行正常,就别改了
    2. 依赖了大量阻塞式持久化 API 和网络 API,建议使用 Spring MVC
    3. 已经使用了非阻塞技术栈,可以考虑使用 WebFlux
    4. 想要使用 Java 8 Lambda 结合轻量级函数式框架,可以考虑 WebFlux
  • WebFlux 中的编程模型
    1. 基于注解的控制器
    2. 函数式 Endpoints
  • 基于注解的控制器: