玩转Spring全家桶(上)
一. 初识Spring
1.1 初始Spring成员
- Spring Framework的历史
- Spring 不仅仅是框架,还有很多的技术,它代表着整个的家族
- Spring Framework
Spring的核心代码比如IOC、AOP基本没有改变,代码质量很高,适合大家去学习
- SpringBoot
- SpringCloud
它能更快更好的帮助大家开发一个云的应用程序
1.2 跟着Spring了解技术趋势
- 看看Spring 5.x 的改变暗示了什么
JasperReport用于做报表
- SpringBoot 和SpringCloud的出现时必然的
- 为什么呢?
它集合了业内的最佳实践,提供了对众多其他组建的支持,能让你轻松实现一个高可用的程序;不是在跟Spring打交道,是与Spring家族打交道;
- Hello Spring [生成你的第一个Spring应用]
- start.spring.io 这个里面有骨架可以生成项目
使用骨架生成项目,然后撰写Hello,world接口,启动启动类后点击url地址然后访问此端口;
- 如果Parent不继承spring-boot-parent怎么办呢?
使用此依赖,即可不用继承,也可以实现Spring-boot-parent的功能;
- 打包出来的,original是原始包,它只有3.1k,但是另外一个却有17M,是因为它包含了相关的依赖,一起打包进去了,是可以直接执行的jar包。
二. JDBC必知必会
2.1 如何配置单数据源
-
加载了actuator的依赖后,我们可以通过访问localhost:8080/actuator/beans来查看所有Spring的Bean
-
直接配置所选的Bean
-
SpringBoot做了哪些配置
-
数据源相关配置属性
-
如果我们一定要继承自己的parent依赖,不能直接继承SpringBoot的parent,如何能让Spring来继续管理版本呢?解决步骤如下:
- 引入dependencies:
- Maven打包时设置repackage:
使用此方式,可以不继承parent也可以达到Spring管理依赖版本的功能。
-
如果我们引入了健康监控相关的依赖,我们可以查看我们引入了哪些bean:
- 使用场景:当我们引入Spring管理时,可以通过此处来查看是否加入Bean,对解决问题可以多提供一种方式和思路。
2.2 如何配置多数据源
- 配置多数据源的注意事项
- 不同数据源的配置要分开
- 关注每次使用的数据源
- 有多个DataSource时系统如何判断
- 对应的设施(事务、ORM等) 如何选择DataSource
- 使用某些框架的时候,比如Mybatis-plus,Jpa等的时候,如何支持多数据源。
- 手工配置两组DataSource及相关内容
- 与SpringBoot协同工作(二选一)
- 配置@Primary类型的Bean
- 排除SpringBoot的自动配置
- DataSourceAutoConfiguration
- DataSourceTransactionManagerAutoConfiguration
- JdbcTemplateAutoConfiguration
这几个排除掉,我们在代码中自己进行配置
- 代码图示:
- 一般情况下,第三方的框架会有支持多数据源的方式,我们可以参考它们的官方文档来进行配置。
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
- Spring Boot 2.x
-
常用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的介绍:
- 经过阿里巴巴各大系统的考验,值得信赖。
- 实用的功能:
- 详细的监控(真的是全面)
- ExceptionSorter,针对主流数据库的返回码都有支持
- SQL防注入
- 内置加密配置
- 众多扩展点,方便进行定制
- 其他功能点可以去官网看
官方中的wiki中有一些功能介绍及使用方式、问题解决方案等。
-
Alibaba Druid配置:
-
Spring Boot方案:
-
进阶可选配置:
-
Druid Filter扩展点:
- 用于定制连接池操作的各种环节
- 可以继承FilterEventAdapter以方便地实现Filter
- 修改META-INF/druid-filter.properties增加Filter配置
- 代码演示:
-
-
需要注意的地方:
- 如果引入Alibaba Druid时,可能需要将HikariCP排除掉,如图代码所示:
- 连接池选择时的考量点:
根据实际业务场景哪些点更为重要,然后选择合适的连接池。
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-jdbc
- SQL批处理
- JDBC Template
- batchUpdate
- BatchPreparedStatementSetter
- batchUpdate
- NamedParameterJdbcTemplate
- batchUpdate
- SqlParameterSourceUtils.createBatch
- batchUpdate
- 批处理部分代码示例:
- JDBC Template
2.6 什么是Spring的事务抽象
- 在Spring中为我们提供了很多的抽象,我们因此可以在不同的框架中使用一样的方式来进行数据操作。最重要的抽象有事务抽象,异常的抽象。
- Spring的事务抽象:
- 一致的事务模型
- DataSource/JTA
- 事务抽象的核心接口:
- 事务的传播特性(七种):
- 事务的隔离特性:
直接使用默认的事务模式或者根据实际的业务需求去设定事务的隔离级别等。
- 编程式事务:
- TransactionTemplate
- TransactionCallBack
- TransactionCallbackWithoutResult
TransactionCallback用于有返回值的,而TransactionCallbackWithoutResult用于没有返回值。
- PlatformTransactionManager
- 可以传入TransactionDefinition进行定义
- 代码示例:
- TransactionTemplate
- 声明式事务
-
图示:
-
基于注解的配置方式:
-
示例如图:
-
2.7 了解Spring的JDBC异常抽象
- Spring会将数据操作异常转换为DataAccessException
- 无论使用何种数据访问方式,都能使用一样的异常,图示:
- Spring是怎么认识那些错误码的
- 通过SQLErrorCodeSQLExceptionTranslator解析错误码
- ErrorCode定义:
- org/springframework/jdbc/support/sql-error-codes.xml
- Classpatch下的sql-error-codes.xml
- 定制错误码解析逻辑:
2.8 一些其他的知识点
- 一些常用注解:
- Java Config相关注解
- @Configuration
- @ImportResource
- @ComponentScan
- @Bean
- @ConfigurationProperties
- 定义相关注解:
- @Component/@Repository/@Service
- @Controller/@RestController
- @RequestMapping
- 注入相关注解:
- @Autowried/@Qualifier/@Resource
- @Value
@Value注入一些常量等数据
- Java Config相关注解
- Actuator提供的一些好用的Endpoint:
- 如何解禁Endpoint:
- 默认:
- /actuator/health和 /actuator/info 可web访问
- 解禁所有Endpoint:
- application.properties /application.yml
- management.endpoints.web.exposure.include=*
生产环境需谨慎
- 默认:
- 多数据源、分库分表、读写分离的关系
- 几种常见的情况:
- 系统需要访问几个完全不同的数据库
- 系统需要访问同一个库的主库与备库
- 系统需要访问一组做了分库分表的数据库
- 如图所示:
- 使用数据库中间件的情况:
- 几种常见的情况:
- 事务的本质:
- Spring的声明式事务本质上是通过AOP来增强了类的功能
- Spring的AOP本质上就是为类做了一个代理
看似在调用自己写的类,实际用的是增强后的代理类 访问增强后的代理类的方法,而非直接访问自身的方法
- Requires_new 与Nested事务传播特性的说明
- Requires_new,始终启动一个新事务
- 两个事务没有关联
- Nested,在原事务内启动一个内嵌事务
- 两个事务有关联
- 外部事务回滚,内嵌事务也会回滚
- Requires_new,始终启动一个新事务
- Alibaba Druid的一些展开说明
- 慢SQL日志:
在执行jar时可以配置慢日志
- 一些注意事项:
- 没特殊情况,不要在生产环境下打开监控的Servelt
- 没有连接泄露可能的情况下,不要开启removeAbandoned
- testXxx的使用需要注意
- 务必配置合理的超时时间
- 慢SQL日志:
三. O/R Mapping实践
3.1 认识Spring Data JPA
- 对象与关系的范式不匹配
- 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的编程模型
- 主要模块:
- Spring Data Commons
- Spring Data JDBC
- Spring Data JPA
- Spring Data Redis ...
很多...
- 图示引入Jpa:
3.2 定义JPA的实体对象
- 常用Jpa注解
-
实体:
- @Entity、@MappendSuperclass
- @Table(name)
-
主键:
- @Id
- @GeneratedValue(strategy,generator)
- @SequenceGenerator(name,sequenceName)
- @Id
-
映射:
- @Cloumn(name,nullable,length,insertable,updatable)
- @JoinTable(name)、@JoinColumn(name)
-
关系:
- @OneToOne、@OneToMany、@ManyToOne、@ManyToMany
- OrderBy
-
部分代码示例:
-
- Project Lombok
- 它能够自动嵌入IDE和构建工具,提升开发效率
- 常用功能
- @Getter/@Setter
- @ToString
- @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor
- @Data
- @Builder
- @Slf4j/@CommonsLog/@Log4j2
3.3 Repository 是怎么从接口变成Bean的?
- Repository Bean是如何创建的?
- 接口中的方法是如何被解释的?
3.4 使用Mybatis操作数据
-
认识Mybatis
- 地址:github.com/mybatis/myb…
- 一款优秀的持久层框架
- 支持定制化SQL、存储过程和高级映射
-
在Spring 中使用Mybatis
- Mybatis Spring Adapter(github.com/mybatis/spr…)
- Mybatis Spring-Boot-Starter(github.com/mybatis/spr…)
-
简单配置:
-
Mapper的定义与扫描
- @MapperScan配置扫描位置
- @Mapper定义接口
- 映射的定义--XML与注解
3.5 让Mybatis更好用的那些工具-Mybatis Generator
-
它是官方提供的生成器: Mybatis Generator(www.mybatis.org/generator)
-
Mybatis代码生成器
-
根据数据库表生成相关代码
- POJO
- Mapper接口
- SQL Map XML
-
运行Mybatis Generator
第一个是通过命令行运行,第二个是安装Maven的一个插件,第三种是Eclipse Plugin(我们现在一般都是用IDEA了),第四种是Java程序,第五种不常用了。
-
配置Mybatis Generator
-
生成时可以使用的插件
-
使用生成的对象
- 简单操作,直接使用生成的xxxMapper的方法
- 复杂查询,使用生成的xxxExample对象
3.6 让Mybatis更好用的那些工具 Mybatis PageHelper
- 认识PageHelper
- Github地址:pagehelper.github.io
- 支持多种数据库
- 支持多种分页方式
- SpringBoot 支持(github.com/pagehelper/…
- pagehelper-spring-boot-starter
- Github地址:pagehelper.github.io
- 使用的几种方式:
四. NoSQL实践
4.1 通过Docker辅助开发
-
什么是容器:
- 容器映像是一个软件的轻量级可执行软件包,包含运行它所需的一切:代码,运行时,系统工具,系统库,设置。不管环境如何,集装箱化软件都可以运行相同的Linux和Windows应用程序。容器将软件与其周围环境隔离开来,例如开发环境和登台环境之间的差异,并有助于减少在同一基础架构上运行不同软件的团队之间的冲突。
- 图示:
-
认识docker:
- 介绍:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。
- 图示:
-
不同人眼中的Docker:
- 开发眼中的Docekr
- 简化了重复搭建开发环境的工作
使用镜像可以直接使用,免去了一些重复的配置
- 简化了重复搭建开发环境的工作
- 运维眼中的Docker:
- 交付系统更为顺畅
- 伸缩性更好
- 开发眼中的Docekr
-
Docker常用命令
- 镜像相关:
docker pull <image>
docker search <image>
- 容器相关:
docker run
docker start/stop <容器名>
docker ps <容器名>
docker logs <容器名>
- 镜像相关:
-
docker run 的常用选项
- docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
- 选项说明:
- -d, 后台运行容器
- -e,设置环境变量
- --expose/ -p 宿主端口:容器端口
- --name,指定容器名称
- --link,链接不同容器
- -v 宿主目录:容器目录,挂载磁盘卷
-
国内Docker 镜像配置
- 官方Docker Hub
- hub.docker.com
- 图示:
在官方的Docker Hub里就可以搜索官方的容器了
- 官方镜像:
- 阿里云镜像:
- 官方Docker Hub
-
通过Docker启动MongoDB
-
通过Docker 启动MongoDB
- 登录到MongoDB容器中:
docker exec -it mongo bash
- 通过Shell链接MongoDB
mongo -u admin -p admin
- 登录到MongoDB容器中:
4.2 在Spring中访问MongoDB
- MongoDB是一款开源的文档型数据库,官网地址,它是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
- 介绍:
- MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
- 它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
- Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
- Spring对MongoDB的支持
- Spring Data MongoDB
- MongoTemplate
- Repository支持
- Spring Data MongoDB的基本用法
- 注解:
- @Document (表示对应的是哪个文档,类似表名的对应)
- @Id
- MongoTemplate:
- save/remove
- Criteria/Query/Update
- 注解:
- 使用示例:
- 引入依赖:
- 数据源配置:
- 如果MongoDB端口是默认端口,并且没有设置密码,可不配置,SpringBoot会开启默认的
spring.data.mongodb.uri=mongodb://localhost:27017/springboot-db
- MongoDB设置了密码,这样配置:
spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/dbname
- 如果MongoDB端口是默认端口,并且没有设置密码,可不配置,SpringBoot会开启默认的
- 定义一个简单的实体:
package com.forezp.entity; import org.springframework.data.annotation.Id; public class Customer { @Id public String id; public String firstName; public String lastName; 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); } }
- 数据操作dao层:
public interface CustomerRepository extends MongoRepository<Customer, String> { public Customer findByFirstName(String firstName); public List<Customer> findByLastName(String lastName); }
- 创建测试类:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootMongodbApplication implements CommandLineRunner { @Autowired private CustomerRepository repository; public static void main(String[] args) { SpringApplication.run(SpringbootMongodbApplication.class, args); } @Override public void run(String... args) throws Exception { repository.deleteAll(); // save a couple of customers repository.save(new Customer("Alice", "Smith")); repository.save(new Customer("Bob", "Smith")); for (Customer customer : repository.findAll()) { System.out.println(customer); } System.out.println(repository.findByFirstName("Alice")); for (Customer customer : repository.findByLastName("Smith")) { System.out.println(customer); } } }
4.3 在Spring中访问Redis
- Spring 对Redis的支持
- Redis是一款开源的内存KV存储,支持多种数据结构,官网地址
- Spring Data Redis
- 支持的客户端Jedis / Lettuce
- RedisTemplate
- Repository支持
- Jedis客户端的简单使用
- Jedis不是线程安全的
- 通过JedisPool获得Jedis实例
- 直接使用Jedis中的方法
- 图示:
- 通过Docker启动Redis
- 官方指引
- 获取镜像:
docker pull redis
- 启动Redis:
docker run --name redis -d -p 6379:6379 redis
4.4 Redis的哨兵模式:
-
Redis Sentinel是Redis的一种高可用方案
- 监控、通知、自动故障转移、服务发现
-
JedisSentinelPool
- Jedis是Redis官网推荐的Java客户端,Redis-Sentinel作为官方推荐的HA解决方案,Jedis也在客户端角度实现了对Sentinel的支持,主要实现在JedisSentinelPool.java这个类中,Jedis的JedisSentinelPool的实现仅仅适用于单个master-slave。
- 内部有如下属性:
//基于apache的commom-pool2的对象池配置 protected GenericObjectPoolConfig poolConfig; //超时时间,默认是2000 protected int timeout = Protocol.DEFAULT_TIMEOUT; //sentinel的密码 protected String password; //redis数据库的数目 protected int database = Protocol.DEFAULT_DATABASE; //master监听器,当master的地址发生改变时,会触发这些监听者 protected Set<MasterListener> masterListeners = new HashSet<MasterListener>(); protected Logger log = Logger.getLogger(getClass().getName()); //Jedis实例创建工厂 private volatile JedisFactory factory; //当前的master,HostAndPort是一个简单的包装了ip和port的模型类 private volatile HostAndPort currentHostMaster;
-
哨兵模式是redis高可用的实现方式之一使用一个或者多个哨兵(Sentinel)实例组成的系统,对redis节点进行监控,在主节点出现故障的情况下,能将从节点中的一个升级为主节点,进行故障转义,保证系统的可用性。
- 图示:
-
哨兵们是怎么感知整个系统中的所有节点(主节点/从节点/哨兵节点)的?
- 首先主节点的信息是配置在哨兵(Sentinel)的配置文件中
- 哨兵节点会和配置的主节点建立起两条连接命令连接和订阅连接
- 哨兵会通过命令连接每10s发送一次INFO命令,通过INFO命令,主节点会返回自己的run_id和自己的从节点信息
- 哨兵会对这些从节点也建立两条连接命令连接和订阅连接
- 哨兵通过命令连接向从节点发送INFO命令,获取到他的一些信息
- 因为哨兵对与集群中的其他节点(主从节点)当前都有两条连接,命令连接和订阅连接
-
哨兵模式下的故障迁移?
- 主观下线:哨兵(Sentinel)节点会每秒一次的频率向建立了命令连接的实例发送PING命令,如果在down-after-milliseconds毫秒内没有做出有效响应包括(PONG/LOADING/MASTERDOWN)以外的响应,哨兵就会将该实例在本结构体中的状态标记为SRI_S_DOWN主观下线
- 客观下线:当一个哨兵节点发现主节点处于主观下线状态是,会向其他的哨兵节点发出询问,该节点是不是已经主观下线了。如果超过配置参数quorum个节点认为是主观下线时,该哨兵节点就会将自己维护的结构体中该主节点标记为SRI_O_DOWN客观下线 询问命令SENTINEL is-master-down-by-addr <current_epoch> <run_id>
-
leader选举:
- 在认为主节点客观下线的情况下,哨兵节点节点间会发起一次选举,命令还是上面的命令SENTINEL is-master-down-by-addr <current_epoch> <run_id>,只是run_id这次会将自己的run_id带进去,希望接受者将自己设置为主节点。如果超过半数以上的节点返回将该节点标记为leader的情况下,会有该leader对故障进行迁移
-
优缺点:
- 优点:高可用,在主节点故障时能实现故障的转移
- 缺点:好像没办法做到水平拓展,如果内容很大的情况下
4.5 - Redis的集群模式:
-
官方提供的分布式方案(槽指派/重新分片/故障转移),集群内的节点,都会有个数据结构存储整个集群内的节点信息。
- Redis Cluster
- 数据自动分片(分成16384个Hash Slot)
- 在部分节点失效时有一定可用性
- Jedis Cluster
- Jedis只从Master读数据,如果想要读写分离,可以定制
- Redis Cluster
-
槽指派
- redis集群可以被分为16384个槽,只有这些槽全被指派了处理的节点的情况下,集群的状态才能是上线状态(ok)
- 操作redis集群的时候,将key作为参数,就可以计算出对应的处理槽上,所以存储等操作都应该在该槽对应的节点上。通过这种方式,可以完美的实现集群存储的水平拓展。
-
发现故障节点
- 集群内的节点会向其他节点发送PING命令,检查是否在线
- 如果未能在规定时间内做出PONG响应,则会把对应的节点标记为疑似下线
- 集群中一半以上负责处理槽的主节点都将主节点X标记为疑似下线的话,那么这个主节点X就会被认为是已下线
- 向集群广播主节点X已下线,大家收到消息后都会把自己维护的结构体里的主节点X标记为已下线
-
从节点选举
- 当从节点发现自己复制的主节点已下线了,会向集群里面广播一条消息,要求所有有投票权的节点给自己投票(所有负责处理槽的主节点都有投票权)
- 主节点会向第一个给他发选举消息的从节点回复支持
- 当支持数量超过N/2+1的情况下,该从节点当选新的主节点
-
故障的迁移
- 新当选的从节点执行 SLAVEOF no one,修改成主节点
- 新的主节点会撤销所有已下线的老的主节点的槽指派,指派给自己
- 新的主节点向集群发送命令,通知其他节点自己已经变成主节点了,负责哪些槽指派
- 新的主节点开始处理自己负责的槽的命令
-
集群模式和哨兵模式的区别
- 哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控
- 哨兵模式发起选举是选举一个leader哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移
4.6 了解Spring的缓存抽象
- Spring给我们提供了一个更好用的缓存抽象,它可以在Java方法上加一个注解,达到缓存的效果。如果缓存过的,则返回结果,否则就执行。
- 为不同的缓存提供一层抽象:
- 为Java方法增加缓存,缓存执行结果
- 支持ConcurrentMap、EhCache、Caffeine、JCache(JSR-107)
- 接口:
- org.springframework.cache.Cache
- org.springframework.cache.CacheManager
- 基于注解的缓存
- @EnableCaching
- @Cacheable
- @CacheEvict
- @CachePut
- @Caching
- @CacheConfig
- @EnableCaching
- 通过SpringBoot 配置Redis缓存
这里是引入了Redis作为Spring的缓存抽象
4.7 Redis在Spring中的其他用法
-
与Redis建立连接
- 配置连接工厂:
- 新版本中已经用LettuceConnectionFactory代替JedisConnectionFactory了。
- 单节点:RedisStandaloneConfiguration
- 哨兵:RedisSentinelConfiguration
- 集群:RedisClusterConfiguration
- 配置连接工厂:
-
读写分离:
- Lettuce内置支持读写分离
- 只读主、只读从
- 优先读主、优先读从
- LettuceClientConfiguration
- LettucePoolingClientConfiguration
- LettuceClientConfigurationBuilderCustomizer
第二个带池,第三个有回调
- Lettuce内置支持读写分离
-
RedisTemplate
- RedisTemplate<K,V>
- opsForXxx()
- StringRedisTemplate
只操作String类型的可以使用这个,在使用redis的过程中一定要对key设置过期时间。
- RedisTemplate<K,V>
-
RedisRepository
- spring-boot-starter-data-redis 2.1 以上支持以repository的方式存取对象了。
- 实现步骤:
- 我们引入Springboot Web的依赖,以启动REST服务。还需要引入Spring Data Redis相关的依赖。最后,还需要commons-pool2,不然会因为缺少类而无法启动。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
- 配置连接信息:
- 配置Redis的连接信息,这个信息跟你安装时的配置有关,同时配置了连接池,各项的配置及相关解释如下:
# Redis数据库索引,默认为0 spring.redis.database=0 # Redis端口 spring.redis.port=6379 # Redis服务器主机 spring.redis.host=localhost # 连接池最大连接数 spring.redis.lettuce.pool.max-active=8 # 连接池最大空闲 spring.redis.lettuce.pool.max-idle=8 # 连接池最小空闲 spring.redis.lettuce.pool.min-idle=2 # 连接池最大阻塞等待时间 spring.redis.lettuce.pool.max-wait=1ms # 超时时间 spring.redis.lettuce.shutdown-timeout=100ms
- 配置Redis的连接信息,这个信息跟你安装时的配置有关,同时配置了连接池,各项的配置及相关解释如下:
- 创建实体类
- 存入Redis中的数据类型,可以是自定义的一个类,注意需要加上注解@RedisHash和@Id。存入Redis的数据为Set类型。
package com.pkslow.redis.model; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; import java.util.Date; @RedisHash("User") public class User { @Id private String userId; private String name; private Integer age; private Date createTime = new Date(); 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; } }
- 存入Redis中的数据类型,可以是自定义的一个类,注意需要加上注解@RedisHash和@Id。存入Redis的数据为Set类型。
- 数据库访问层UserRepository接口
- 直接继承CrudRepository接口就行了,不用自己来实现,需要注意CrudRepository<User, String>的泛型类型:
import com.pkslow.redis.model.User; import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, String> { }
- 直接继承CrudRepository接口就行了,不用自己来实现,需要注意CrudRepository<User, String>的泛型类型:
- Controller实现了RESTful风格的增删改查功能,只要把UserRepository注入便可以使用它来操作:
import com.pkslow.redis.dal.UserRepository; import com.pkslow.redis.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/user") public class UserController { @Autowired private final UserRepository userRepository; public UserController(UserRepository userRepository) { this.userRepository = userRepository; } @GetMapping("") public Iterable<User> 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); } }
- 我们引入Springboot Web的依赖,以启动REST服务。还需要引入Spring Data Redis相关的依赖。最后,还需要commons-pool2,不然会因为缺少类而无法启动。
五. 数据访问进阶
5.1 Project Reactor介绍
-
Project Reactor 简介
- Project Reactor是一个运行在JVM上的反应式编程基础库,以“背压”的形式管理数据处理,提供了可组合的异步序列APIFlux和Mono
- Project Reactor主要是由Pivotal公司开发和维护的,Spring框架也是该公司在维护,而且Spring Framework 5中默认使用Reactor作为反应式编程的实现,由此虽然Reactor不是Spring的子项目,也有人称Reactor为Spring Reactor。
-
Project Reactor特点:
- I/O阻塞浪费了系统性能,只有纯异步处理才能发挥系统的全部性能,不作丝毫浪费;而JDK的异步API比较难用,成为异步编程的瓶颈,这就是Reactor等其它反应式框架诞生的原因。
- Reactor大大降低了异步编码难度(尽管相比同步编码,复杂度仍然是上升的),变得简单的根本原因,是编码思想的转变。
- JDK的异步API使用的是传统的命令式编程,命令式编程是以控制流为核心,通过顺序、分支和循环三种控制结构来完成不同的行为。而Reactor使用反应式编程,应用程序从以逻辑为中心转换为了以数据为中心,这也是命令式到声明式的转换。
-
Project Reactor相比于命令式编程还具备哪些特点?
- 可组合性和可读性,完美规避了Callback Hell
- 以流的形式进行数据处理时,为流中每个节点提供了丰富的操作符
- 在Subscribe之前,不会有任何事情发生
- 支持背压,消费者可以向生产者发出信号表明排放率过高
- 支持两种反应序列:hot和cold
-
一些核心的概念:
- Operators-Publisher/Subscriber
- Nothing Happens Until You subscribe()
- Flux[0..N]-onNext()、onComplete()、onError()
- Mono[0..1]-onNext()、onComplete()、onError()
Flux是N个元素的序列,onNext()在遇到下一个元素时执行什么,onComplete()在整个完成时执行什么,onError()在异常时应该执行什么的一些方法,每个里面可以传入Lambda表达式做处理。Mono的同理,他的[0..1]代表0或1个元素。
- Backpressure
- Subscription
- onRequest()、onCancel()、onDispose()
Backpressure是一种反压力,在上游的生产速度快于下游时,可以设置每次请求多少个元素以及取消中止这个过程等行为。
- 线程调度 Schedulers
- immediate()/single()/newSingle()
immediate()是在当前线程上去做某些任务,single()是服用线程,newSingle()是新建线程。
- elastic()/parallel()/newParallel()
elastics()是线程池,它空闲60秒后会被回收。parallel()是一个固定线程池,它会创建出跟CPU核数对应的线程,它是不会被回收的。newParallel()新建。
- immediate()/single()/newSingle()
- 错误处理
- onError/onErrorReturn/onErrorResume
onError相当于try...catch,当抛出异常后的代码执行则在此方法中定义。onErrorReturn是在异常时返回一个默认值。onErrorResume用一个特定的lambda处理
- doOnError/doFinally
doFinally表示总会被执行,无论抛出异常后还是未抛出异常。
- onError/onErrorReturn/onErrorResume
- Operators-Publisher/Subscriber
-
实战代码示例:
- pom.xml:
- 代码示例:
这是一个简单的Project Reactor的示例,里面用到了一些上面使用到的一些方法,不明白的地方可以配合百度理解。
5.2 通过Reactive的方式访问Redis
-
Spring Data Redis
- Lettuce能够支持Reactive方式
- Spring Data Redis中主要的支持:
- ReactiveRedisConnection
- ReactiveRedisConnectionFactory
- ReactiveRedisTemplate
- opsForXxx()
Redis支持Jedis和Lettuce,但是如果要使用Reactive则只能使用Lettuce,因为它支持Reactive方式
-
代码示例:
-
引入pom.xml的依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
-
定义Bean:
@Bean ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory){ return new ReactiveStringRedisTemplate(factory); }
注册一个Bean,可以单独建一个类,也可以直接写在SpringBoot 的启动类中;
-
引入Bean
@Autowired private ReactiveStringRedisTemplate redisTemplate;
-
代码示例:
-
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
- 先传入一个只打印日志的动作:
@Slf4j @SpringBootApplication public class MongodbDemoApplicationTest implements ApplicationRunner { @Autowired private ReactiveMongoTemplate mongoTemplate; private CountDownLatch cdl=new CountDownLatch(1); @Bean public MongoCustomConversions mongoCustomConversions(){ return new MongoCustomConversions( Arrays.asList( new MoneyReadConverter(), new MoneyWriteConverter())); //将读与写注册到bean里面 } public static void main(String[] args) { SpringApplication.run(MongodbDemoApplicationTest.class,args); } @Override public void run(ApplicationArguments args) throws Exception { startFromInsertion(() -> log.info("Runnable")); //startFromInsertion(() -> { // log.info("Runnable"); // decreaseHighPrice(); //}); log.info("after starting"); //decreaseHighPrice(); cdl.await(); //做一个等待 } private void decreaseHighPrice() { mongoTemplate.updateMulti(query(where("price").gte(3000L)), //查询出所有大于30块的咖啡 new Update().inc("price", -500L) //对于key,做一个减少500的操作 .currentDate("updateTime"), Coffee.class) //更新updatetime .doFinally(s -> { cdl.countDown(); log.info("Finnally 2, {}", s); }) .subscribe(r -> log.info("Result is {}", r)); } public void startFromInsertion(Runnable runnable){ mongoTemplate.insertAll(initCoffee()) .publishOn(Schedulers.elastic()) //将插入的结果publishOn到elastic线程池上 .doOnNext(c -> log.info("Next: {}", c)) //针对每一个next动作,都会打印我们取到的coffee .doOnComplete(runnable) //整体,即插入完成之后,我们去执行一个runnable对象,即() -> log.info("Runnable") .doFinally(s -> { cdl.countDown(); //计数器减一 log.info("Finnally 1, {}", s); }) //上面的操作最后返回的是一个flux流 对insert的结果做一个count .count() .subscribe(c -> log.info("Insert {} records", c)); } private List<Coffee> initCoffee() { Coffee espresso = Coffee.builder() .name("espresso") .price(Money.of(CurrencyUnit.of("CNY"), 30)) .createTime(new Date()) .updateTime(new Date()) .build(); Coffee latte = Coffee.builder() .name("latte") .price(Money.of(CurrencyUnit.of("CNY"), 20)) .createTime(new Date()) .updateTime(new Date()) .build(); return Arrays.asList(espresso, latte); } }
5.4 通过Reactive的方式访问RDBMS
-
图示:
-
代码示例:
- pom.xml(核心依赖):
- 实体类:
- 测试代码:
-
R2DBC Repository 支持:
-
总结: 它支持的数据库比较少,还不具备在生产环境中使用的条件,但是它的思想可以作为我们学习的地方,以后可能会兴起,作为我们的知识储备。
5.5 AOP
-
Spring AOP的一些核心概念 概念 | 含义 -------- | ----- Aspect| 切面 Advice| 连接点,Spring AOP里总是代表一次方法执行 Pointcut |切入点,说明如何匹配连接点 Introduction| 引入,为现有类型声明额外的方法和属性 Target object |目标对象(动态代理的代理对象) AOP proxy | AOP代理对象,可以使JDK动态代理,也可以是CGLIB代理 Weaving | 织入,连接切面与目标对象或类型创建代理的过程
-
常用注解
- @EnableAspectJAutoProxy
- @Aspect
- @Pointcut
- @Before
- @After / @AfterReturning / @AfterThrowing
- @Around
- @Order
-
如何打印SQL:
-
使用AOP写一个SQL性能测试:
启动时,Application类上需添加@EnableAspectJAutoProxy注解。在金融行业、客户敏感的行业,注意日志打印的脱敏。
六. Spring MVC实践
6.1 编写第一个Spring MVC Controller
- 认识Spring MVC
- SpringMVC中的常用注解
- Controller代码示例:
6.2 理解Spring 的应用上下文
- 图示:
- 关于上下文常用的接口及其实现:
- Web上下文层次:
6.3 理解请求的处理机制
- SpringMVC 的请求处理流程:
6.4 一个请求的大致处理流程
- 一个请求的大致处理:
- 绑定一些Attribute
- WebApplicationContext/LocaleResolver/ThemeResolver
- 处理Multipart
- 如果是,则将请求转为MultipartHttpServletRequest
- Handler处理
- 如果找到对应Handler,执行Controller及前后置处理器逻辑
- 处理返回的Model,呈现视图
- 绑定一些Attribute
6.5 如何定义处理方法
-
定义映射关系
-
定义处理方法
-
方法示例:
-
定义类型转换(自己实现WebMvcConfigurer)
- Spring Boot在WebMvcAutoConfiguration中实现了一个
- 添加自定义的Converter
- 添加自定义的Formatter
-
定义校验
- 通过Validator对绑定结果进行校验
- Hibernate Validator
- @Valid注解
- BindingResult
- 示例:
- 通过Validator对绑定结果进行校验
-
Multipart上传
- 配置MultipartResolver
Spring Boot 自动配置MultipartAutoConfiguration
- 支持类型multipart/form-data
- MultipartFile类型
- 配置MultipartResolver
6.6 Spring MVC中的视图解析机制
-
视图解析的基础(viewResolver与View接口)
- AbstractCachingViewResolver
- UrlBasedViewResolver
- FreeMarkerViewResolver
- ContentNegotiatingViewResolver
- InternalResourceViewResolver
-
DispatcherServelt中的视图解析逻辑
- initStrategies()
- initViewResolvers()初始化了对应ViewResolver
- doDispatch()
- processDispatchResult()
- 没有返回视图的话,尝试RequestToViewNameTranslator
- resolveViewName()解析View对象
- processDispatchResult()
- initStrategies()
-
使用@ResponseBody的情况
- 在HandlerAdapter.handle()中完成了Response输出
- RequestMappingHandlerAdapter.invokeHandlerMethod()
- HandlerMethodReturnValueHandlerComposite.handleReturnValue()
- RequestResponseBodyMethodProcessor.handleReturnValue()
- HandlerMethodReturnValueHandlerComposite.handleReturnValue()
- RequestMappingHandlerAdapter.invokeHandlerMethod()
- 在HandlerAdapter.handle()中完成了Response输出
6.7 Spring MVC中的常用视图
-
SpringMVC 支持的视图:
-
配置MessageConverter
-
Spring Boot 对Jackson的支持
-
Thymeleaf
- 引入方式:
- 一些默认配置:
6.8 静态资源与缓存
- Spring Boot 中的静态资源配置
- SpringBoot中的缓存配置
- Controller中手工设置缓存
- 建议的资源访问方式:
6.9 Spring MVC中的异常处理机制
- SpringMVC的异常解析
- 异常处理方法
@ControllerAdvice内的异常处理方法的优先级要低于Controller内的方法。所以在Controller内单独对异常处理的优先级最高。
6.10 了解Spring MVC的切入点
- Spring MVC的拦截器:
- 拦截器的配置方式:
七. 访问Web资源
7.1 通过RestTemplate访问Web资源
- 简单说明:
- Spring Boot中没有自动配置RestTemplate
- Spring Boot提供了RestTemplateBuilder
- RestTemplateBuilder.build()
- 常用方法:
- 构造URI
- 构造URI:
- UriComponentsBuilder
- 构造相当于当前请求的URI
- ServletUriComponentsBuilder
- 构造指向Controller的URI
- MvcUriCOmponentsBuilder
- 代码示例:
- 构造URI:
7.2 RestTemplate 的高阶段用法
- 传递HTTP Header
- RestTemplate.exchange()
- RequestEntity / ResponseEntity
- 类型转换:
- JsonSerializer/JsonDeserializer
- @JsonComponent
- 解析泛型对象
- RestTemplate.exchange()
- ParameterizedTypeReference
7.3 简单定制RestTemplate
- RestTemplate支持的HTTP库
- 通用接口:
- ClientHttpRequestFactory
- 默认实现:
- SimpleClientHttpRequestFactory
- 支持的HTTP库:
- 通用接口:
- 优化底层请求库:
- 连接复用:
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()
- 代码示例:
- 获得结果:
八. Web开发进阶
8.1 设计好的Restful Web Service
-
什么是REST?
- REST提供了一组架构约束,当作为一个整体来应用时,强调组件交互的可伸缩性、接口的通用性、组件的独立部署、以及用来减少交互延迟、增强安全性、封装遗留系统的中间组件。
-
Richardson成熟度模型:
-
如何实现Restful Web Service:
- 识别资源
- 选择合适的资源粒度
- 设计URI
- 选择合适的HTTP方法和返回码
- 设计资源的表述
-
识别资源
- 找到领域名词
- 能用CRUD操作的名词
- 将资源组织为集合(即集合资源)
- 将资源合并为复合资源
- 计算或处理函数
- 找到领域名词
-
资源的粒度:
- 站在服务端的角度:
- 网络效率
- 表述的多少
- 客户端的易用程度
- 站在客户端的角度,要考虑:
- 可缓存性
- 修改频率
- 可变性
- 站在服务端的角度:
-
构建更好的URI
- 使用域及子域对资源进行合理的分组或划分
- 在URI的路径部分使用斜杠分隔符来表示资源之间的层次关系
- 在URL的路径部分使用逗号(,)和分号(;)来表示非层次元素
- 使用连字符(-)和下划线(_)来改善长路径中名称的可读性
- 在URI的查询部分使用“与”符号(&)来分隔参数
- 在URI中避免出现文件扩展名(例如.php,.aspx 和 .jsp)
-
认识HTTP方法
-
URI与HTTP方法的组合
-
认识HTTP状态码
一般我们经常遇到的是200/404/500等
-
选择合适的表述
8.2 什么是HATEOAS
- Richardson成熟度模型
- Level 3 - Hypermedia Controls
- HATEOAS:
- Hybermedia As The Engine Of Application State
- REST统一接口的必要组成部分
- HATEOAS vs WSDL
- HATEOAS:
- 表述中的超链接会提供服务所需的各种REST接口信息
- 无需事先约定如何访问服务
- 传统的服务契约:
- 必须实现约定服务的地址与格式
- 代码示例:
- HATEOAS:
- 常见的超链接类型:
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 Boot 依赖
-
如何访问HATEOAS服务
- 配置Jackson Json
- 注册HAL支持
- 操作超链接
- 找到需要的Link
- 访问超链接
- 配置Jackson Json
8.4 分布式环境中如何解决Session的问题
-
常见的会话解决方案
- 粘性会话:Sticky Session
让会话尽可能分配到同一台机器上,让分布式变成单机
- 会话复制: Session Replication
复制有成本、可能存在不一样、每台机器保存所有会话信息数据量大灯
- 集中会话: Centralized Session
使用JDBC、Redis等来集中存储这些信息
- 粘性会话:Sticky Session
-
认识 Spring Session
- Spring Session
- 简化集群中的用户会话管理
- 无需绑定容器特定解决方案
- 支持的存储
- Redis
- MongoDB
- JDBC
- Hazelcast
- Spring Session
-
实现原理(它是通过定制HttpSesion):
- 通过定制的HttpServeltRequest返回定制的HttpSession
- SessionRepositoryRequestWrapper
- SessionRepositoryFilter
- DelegatingFilterProxy
- 通过定制的HttpServeltRequest返回定制的HttpSession
-
基于Redis的HttpSession
- 引入依赖:spring-session-data-redis
- 基本配置:
- @EnableRedisHttpSession
- 提供RedisConnectionFactory
- 实现AbstractHttpSessionApplicationInitializer
- 配置DelegatingFilterProxy
-
SpringBoot 对Spring Session的支持:
8.5 使用WebFlux 代替Spring MVC
-
认识WebFlux
- 什么是WebFlux
- 用于构建基于Reactive技术栈之上的Web应用程序
- 基于Reactive Streams API运行在非阻塞服务器上
WebFlux可以运行在Netty等服务器上,同时能够提供极大的并发量。
- 为什么会有WebFLux
- 对于非阻塞Web应用的需要
- 函数式编程
- 关于WebFlux的性能
- 请求的耗时并不会有很大的改善
- 仅需少量固定数量的线程和较少的内存即可实现扩展
使用WebFlux能够以极少的线程和开销就可以实现同等的并发,所以它的性能更好。
- 什么是WebFlux
-
Web MVC vs WebFlux
- 已有Spring MVC应用,运行正常,就别改了
- 依赖了大量阻塞式持久化API和网络API,建议使用Spring MVC
- 已经使用了非阻塞技术栈,可以考虑使用WebFlux
- 想要使用Java 8 Lambda结合轻量级函数式框架,可以考虑WebFlux
-
WebFlux 中的编程模型
- 基于注解的控制器
- 函数式Endpoints
-
基于注解的控制器:
- 常用注解:
- @Controller
- @RequestMapping及其等价注解
- @RequestBody / @ResponseBody
- 返回:
- Mono /Flux
- 常用注解: