【Spring Boot】Spring Boot2 整合第三方技术

本文将介绍Spring Boot整合第三方技术的配置、原理与示例。

  • DataSourceAutoConfiguration:数据源自动配置类
  • MybatisAutoConfiguration:MyBatis 自动配置类(第三方)
  • RedisAutoConfiguration:Redis 自动配置类
  • RabbitAutoConfiguration:RabbitMQ 自动配置类

整合 Druid 数据源

导入 JDBC 场景

  1. 在Maven中导入JDBC场景spring-boot-starter-data-jdbc
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

导入该场景后,将出现数据源Hikari、JDBC和事务等依赖:

image-20210804152401123

  1. 导入数据库 MySQL 驱动的依赖:
1
2
3
4
5
6
<!-- 导入 mysql 驱动,官方推荐 8.0 版本 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>

MySQL 官方推荐使用的驱动版本是 8.0,其向下兼容 5.6, 5.7, 8.0 版本的 MySQL。


Spring Boot2 提供的 MySQL 驱动的默认版本:<mysql.version>8.0.22</mysql.version>。若想要修改版本,可以:

  1. 直接依赖引入具体版本(maven的就近依赖原则)
  2. 重新声明版本(maven的属性的就近优先原则)
1
2
3
4
5
6
7
8
9
10
11
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>

<!-- 或者:-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>

  1. 修改数据源的配置项:
1
2
3
4
5
6
7
8
spring:
datasource:
url: jdbc:mysql://localhost:3306/tableName?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: zhaoyuyun
driver-class-name: com.mysql.cj.jdbc.Driver
# 注意:MySQL5.0版本的驱动类名和8.0版本的不同
# 5.0版本的驱动类名:com.mysql.jdbc.Driver

数据源自动配置原理

DataSourceAutoConfiguration: 数据源的自动配置类

  • 修改数据源相关的配置前缀:"spring.datasource"
  • 数据库连接池的配置,是容器中没有自定义的DataSource时才自动配置的
  • 底层自动配置的数据源是:HikariDataSource

image-20210804153524354

其他数据库相关的自动配置类:

  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud。容器中有JdbcTemplate这个组件,可以修改配置前缀 "spring.jdbc" 来修改JdbcTemplate的配置。
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的

Druid 数据源

Druid官方github地址:https://github.com/alibaba/druid

  1. 引入Druid官方提供的starter场景依赖:
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>

其向容器中添加了一个 Druid 数据源自动配置类 DruidDataSourceAutoConfigure

image-20210804202513107

  • 该配置器在Spring Boot自带的数据源自动配置器DataSourceAutoConfiguration之前配置,因此不再注册Spring Boot默认的数据源HikariDataSource
  • 该配置器绑定了DataSourcePropertiesDruidStatProperties资源配置类,分别对应资源路径"spring.datasource""spring.datasource.druid"
  • 该配置器导入了其他相关的配置类,用于开启配置页、防火墙、Web监控等功能

导入的其他相关配置类如下:

  • DruidSpringAopConfiguration.classspring.datasource.druid.aop-patterns):监控Spring Bean
  • DruidStatViewServletConfiguration.classspring.datasource.druid.stat-view-servlet):配置监控页:
  • DruidWebStatFilterConfiguration.classspring.datasource.druid.web-stat-filter):Web监控配置
  • DruidFilterConfiguration.class:配置Druid的所有Filters:
1
2
3
4
5
6
7
8
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";

配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
spring:
datasource:
url: jdbc:mysql://localhost:3306/tableName?useUnicode=true&characterEncoding=utf8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 5.0版本的驱动要使用:com.mysql.jdbc.Driver

druid:
aop-patterns: com.zhao.admin.* # 监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)

stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false

web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false

SpringBoot配置示例:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置项列表:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

整合 MyBatis

MyBatis官方链接:https://github.com/mybatis

原理

  1. 导入 MyBatis 的starter场景依赖:
1
2
3
4
5
6
7
8
<dependencies>
<!-- 导入 MyBatis 场景启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>

其导入了如下包:

image-20210805142616006

其中,MyBatis的自动配置器MybatisAutoConfiguration会在Spring Boot启动时注册到容器中:

image-20210805143359047

该类绑定了MybatisProperties,对应Spring Boot的配置文件中以"mybatis"为前缀的属性:

image-20210805144216362

  1. MybatisAutoConfiguration向容器中注册了sqlSessionFactory,其使用容器中存在的数据源,并且从配置资源类MybatisProperties中获取MyBatis的配置属性值:

image-20210805143832167

  1. MybatisAutoConfiguration向容器中注册了SqlSessionTemplate,其可以执行批量的SqlSession

image-20210805144629709

image-20210805144721508

  1. MybatisAutoConfiguration向容器中注册了AutoConfiguredMapperScannerRegistrar,其用于扫描容器中带有 @Mapper 注解的组件:

image-20210805151107546


使用 MyBatis

开启 MyBatis 流程:

  • 导入MyBatis官方starter场景: mybatis-spring-boot-starter
  • 编写xxxMapper接口,并在其上使用 @Mapper 注解(也可以使用 @MapperScan() 简化)
  • 编写sql映射文件xxxMapper.xml(放置在classpath:mapper/*.xml下)并绑定xxxMapper接口
  • application.yaml中指定mapper配置文件的位置mapper-locations,以及指定全局配置文件的信息

具体步骤如下:

  1. 导入 MyBatis 的 starter 场景: mybatis-spring-boot-starter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
<!-- 导入 mysql 驱动,官方推荐 8.0 版本 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- 导入 MyBatis 场景启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
  1. 编写UserMapper接口,并在其上使用 @Mapper 注解(也可以使用 @MapperScan(“com.zhao.mapper”) 简化)
1
2
3
4
5
6
7
8
9
@Mapper
public interface UserMapper {

// 可以使用注解代替xml里的sql语句
@Select("select * from user where id = #{id}")
User selectUser(Long id);

void deleteUser(Long id);
}
  1. 编写sql映射文件userMapper.xml(放置在classpath:mapper/*.xml下)
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhao.admin.mapper.UserMapper">
<select id="selectUser" resultType="com.zhao.admin.bean.User">
select * from user where id = #{id}
</select>

<delete id="deleteUser" parameterType="long">
delete from user where id = #{id}
</delete>
</mapper>
  1. application.yaml中配置MyBatis:
1
2
3
4
5
6
7
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mapper/*.xml

# 可以不写mybatis-config.xml,所有全局配置文件的配置都放在configuration配置项中即可
configuration:
map-underscore-to-camel-case: true

项目结构:

image-20210805212017625

整合 MyBatis Plus

  1. 导入MyBatis-Plus的starter场景:mybatis-plus-boot-starter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<!-- 导入 mysql 驱动,官方推荐 8.0 版本 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- 导入 MyBatis-plus 的场景启动器 -->
<!-- 无需额外导入 MyBatis、Mybatis-Spring 等依赖,其由 MyBatis-plus 自动维护 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>

其会向容器中导入MybatisPlusAutoConfiguration

image-20210805194210051

其对应的配置前缀为"mybatis-plus",其会默认扫描"classpath*:/mapper/**/*.xml",即当前模块类路径(包括引用其他jar包的类路径,classpath:/mapper...则只会包含自己的类路径)下mapper目录下的所有.xml文件都会被作为MyBatis的.xml进行扫描(开发人员将sql映射文件放置在该目录下即可):

image-20210805193856503

注意:"classpath*:/mapper/**/*.xml"所指的类路径不仅包含当前模块的类路径,而且还包含了当前模块引用的其他包的类路径。若想只引用自己模块的类路径,则可以写:"classpath:/mapper/**/*.xml"


  1. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
application:
name: yunmall-product
datasource:
username: root
password: xxx
url: jdbc:mysql://yuyunzhao.cn:3306/yunmall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
# 若使用 MySQl 5.0 版本的驱动,则类名为:com.mysql.jdbc.Driver
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
mapperLocations: classpath:mapper/**/*.xml
global-config:
db-config:
# 设置主键自增
id-type: auto
# 设置逻辑删除
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
configuration:
# 开启日志显示详细Sql语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. 使用时,自定义的Mapper接口继承 BaseMapper<User> 接口即可自动实现简单功能的CRUD:
1
2
3
4
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

BaseMapper<User> 接口中默认实现了简单CRUD的方法:

image-20210805213009214

使用 MyBatis Plus 提供的IServiceServiceImpl,减轻Service层开发工作。

1
2
3
4
5
6
7
8
9
10
11
import com.zhao.hellomybatisplus.model.User;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

/**
* Service 的CRUD也不用写了
*/
public interface UserService extends IService<User> {
//此处故意为空
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import com.zhao.hellomybatisplus.model.User;
import com.zhao.hellomybatisplus.mapper.UserMapper;
import com.zhao.hellomybatisplus.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
//此处故意为空
}
  1. 配置 MyBatis-Plus 分页插件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@EnableTransactionManagement
@MapperScan("com.zhao.yunmall.product.dao")
public class MyBatisConfig {

/**
* 注入MyBatis-Plus的分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}

整合 Redis

  1. 导入 Redis 的starter场景依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<!-- 导入 redis 场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X 集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 导入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>

Redis 相关的组件都由 RedisAutoConfiguration 完成配置和注入:

image-20210914141335348

其绑定了 spring.redis 属性,并且向容器中注入了:

  • RedisTemplate<Object, Object>:可以指定任意泛型
  • StringRedisTemplate:常用。其 key-value 都是 String 类型

提供了两种 Redis 客户端:

  • Lettuce:默认,基于 Netty 框架的客户端,线程安全,性能较好(旧版本存在 BUG:可能产生堆外内存溢出问题 OutOfDirectMemoryError,这是因为其一些计数逻辑没有处理好,导致 Netty 一直在计数没有减数,从而超出堆外内存)。
  • Jedis:原生 API

二者都是底层的客户端,RedisTemplate是 Spring 在二者基础上进行的二次封装,更方便操作 Redis。


  1. 在配置文件中设置 redis 信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
redis:
host: 192.168.1.203
port: 6379
password:
database: 0 # Redis 数据库索引(默认为0)
client-type: lettuce # Redis 客户端类型
timeout: 1800000 # 连接超时时间(毫秒)
lettuce:
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 最大阻塞等待时间(负数表示没限制)
min-idle: 5 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接

# 也可以配置客户端为 jedis
# jedis:
# pool:
# max-active: 10
  1. 向容器中注入 Redis 配置类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// key序列化方式
template.setKeySerializer(redisSerializer);
// value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题), 过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@GetMapping
public String testRedis01() {
// 设置值到redis
redisTemplate.opsForValue().set("name","lucy");

// 从redis获取值
String name = (String) redisTemplate.opsForValue().get("name");
return name;
}

@GetMapping
void testRedis02(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");

System.out.println(hello);
System.out.println(redisConnectionFactory.getClass());
}
}

应用:统计 URL 访问次数

  1. 自定义拦截器统计 URL 的访问次数
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
// 默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
return true;
}
}
  1. 注册 URL 统计拦截器:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(redisUrlCountInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**","/aa/**");
}
}
  1. 查看 Redis 内的统计数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Slf4j
@Controller
public class IndexController {
@Autowired
StringRedisTemplate redisTemplate;

@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");

ValueOperations<String, String> opsForValue =
redisTemplate.opsForValue();

String s = opsForValue.get("/main.html");
String s1 = opsForValue.get("/sql");

model.addAttribute("mainCount",s);
model.addAttribute("sqlCount",s1);

return "main";
}
}

整合 Redisson

  1. 导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 原生 redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>

<!-- 与 spring boot 整合 -->
<!-- redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.0</version>
</dependency>
  1. 程序化配置:Redisson程序化的配置方法是通过构建Config对象实例来实现的,使用Config对象创建出RedissonClient对象,后续所有对Redisson的使用都借助于RedissonClient对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public static class MyRedissonConfig {
// 集群模式
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useClusterServers()
.setPassword("zhaoyuyun")
.addNodeAddress("redis://127.0.0.1:7004", "redis://127.0.0.1:7001"); // 可以用"rediss://"来启用SSL连接
return Redisson.create(config);
}

// 单节点模式
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer()
.setPassword("zhaoyuyun")
.setAddress("redis://myredisserver:6379");
return Redisson.create(config);
}
}

后续使用 RedissonClient 创建锁:RLock lock = redissonClient.getLock("catalogJson-lock");

整合 ElasticSearch

Java 中的 ES 客户端有多种选择,具体见官方文档: https://www.elastic.co/guide/en/elasticsearch/client/index.html。本章将介绍 Java REST Client 的配置和使用。与 Spring Data 的整合配置与使用见博客:https://blog.csdn.net/u011863024/article/details/115721328

  1. 导入依赖:
1
2
3
4
5
6
<!-- 必须指定版本,覆盖 spring-boot-dependencies 中的依赖版本 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
  1. 配置类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@NoArgsConstructor
@Data
@Configuration
public class MallElasticSearchConfig {
/**
* 通用的设置项
*/
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
COMMON_OPTIONS = builder.build();
}

/**
* 向容器中注入ES的客户端
*/
@Bean
public RestHighLevelClient esRestClient() {
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
}
  1. 注入 RestHighLevelClient
1
2
@Autowired
public RestHighLevelClient restHighLevelClient;

操作索引

  1. 创建索引
1
2
3
4
5
6
7
8
9
// 测试索引的创建, Request PUT liuyou_index
@Test
public void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("liuyou_index");
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged()); // 查看是否创建成功
System.out.println(response); // 查看返回对象
restHighLevelClient.close();
}
  1. 获取索引:
1
2
3
4
5
6
7
8
// 测试获取索引,并判断其是否存在
@Test
public void testIndexIsExists() throws IOException {
GetIndexRequest request = new GetIndexRequest("index");
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists); // 索引是否存在
restHighLevelClient.close();
}
  1. 删除索引:
1
2
3
4
5
6
7
8
// 测试索引删除
@Test
public void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("liuyou_index");
AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());// 是否删除成功
restHighLevelClient.close();
}

整合 RabbitMQ

  1. 导入 Maven 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

该场景启动器将导入 RabbitMQ 的自动配置类 RabbitAutoConfiguration

image-20220115204414635

image-20220115210448861

其中,RabbitProperties 类绑定了配置文件中的 spring.rabbitmq 前缀:

image-20220115210648640
  1. 在配置文件中配置 RabbitMQ
1
2
3
4
5
6
7
8
9
10
11
rabbitmq:
host: yuyunzhao.cn
port: 5672
virtual-host: / # 虚拟主机
publisher-confirm-type: correlated # 是否启用【发布确认】:发布消息成功到交换器后会触发回调方法
publisher-returns: true # 是否启用【消息回退】:发送端消息抵达 Queue 失败时进行回调
template:
mandatory: true # 开启强制消息投递:发送端消息抵达 Queue 失败时进行回调,二者需要同时开启
listener:
simple:
acknowledge-mode: manual # 消费者应答

注意:rabbitmq 的属性需要配置在 spring.rabbitmq 下,否则会走默认的本地端口

  1. 在主启动类上添加 @EnableRabbit 注解:
1
2
3
4
5
6
7
@EnableRabbit
@SpringBootApplication
public class MallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(MallOrderApplication.class, args);
}
}
  1. 在配置类中注入 RabbitTemplate,定制化其消息转换器为 JSON 格式转换器,并设置确认回调失败返回回调方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Configuration
public class MyRabbitConfig {

private RabbitTemplate rabbitTemplate;

/**
* 消息转换器:使用 JSON 序列化方式将 POJO 以 JSON 形式保存到 RabbitMQ 中
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}

/**
* 定制 RabbitTemplate,为其设置 JSON 消息转换器
*/
@Primary
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
this.rabbitTemplate = rabbitTemplate;
rabbitTemplate.setMessageConverter(messageConverter());
initRabbitTemplate();
return rabbitTemplate;
}

/**
* 定制 RabbitTemplate
* 1. broker 收到消息就会回调
* 1.1 设置 spring.rabbitmq.publisher-confirms: true
* 1.2 设置确认回调
* 2. 消息无法正常抵达队列就会进行回调
* 2.1 设置 spring.rabbitmq.publisher-returns: true
* 设置 spring.rabbitmq.template.mandatory: true
* 2.2 设置确认回调 ReturnCallback
* 3. 消费端确认(保证每个消息都被正确消费,此时才可以从 broker 中删除这个消息)
*/
public void initRabbitTemplate() {
/**
* 1. 只要消息抵达 Broker 就 ack = true。并设置确认回调
* correlationData:当前消息的唯一关联数据(这个是消息的唯一id)
* ack:消息是否成功收到
* cause:失败的原因
*/
rabbitTemplate.setConfirmCallback((correlationData,ack,cause) -> {
System.out.println("confirm...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]");
});

/**
* 2. 只要消息没有投递给指定的队列,就触发这个失败回调
* message:投递失败的消息详细信息
* replyCode:回复的状态码
* replyText:回复的文本内容
* exchange:当时这个消息发给哪个交换机
* routingKey:当时这个消息用哪个路邮键
*/
rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> {
System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]" +
"==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
});
}
}

整合 ZooKeeper

ZooKeeper是一个分布式协调工具,可以实现注册中心功能,Spring Cloud中整合ZooKeeper时创建的服务节点是临时节点。ZooKeeper详细介绍参考文章 【ZooKeeper】ZooKeeper

支付服务

  1. 新建名为cloud-provider-payment8004的支付服务Maven工程,占用端口8004。
  2. 引入 Maven 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- Spring Cloud整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3 防止与3.4.9起冲突-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>

<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>

需要注意,spring-cloud-starter-zookeeper-discovery场景启动器自带一个ZooKeeper依赖,其版本可能与自己的ZooKeeper版本不兼容,从而无法启动Spring Boot项目,因此在pom文件中首先排除掉spring-cloud-starter-zookeeper-discovery中的ZooKeeper依赖,再自己添加符合自己版本的ZooKeeper依赖。(也可能会出现log4j依赖的冲突,解决方案相同)

  1. 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
# 8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004

# 服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 127.0.0.1:2181 # 192.168.111.144:2181 #
  1. 创建主启动类
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient // 该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class, args);
}
}
  1. 创建 Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController
{
@Value("${server.port}")
private String serverPort;

@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: " + serverPort + "\t" + UUID.randomUUID().toString();
}
}
  1. 测试

启动支付服务8004注册进ZooKeeper(要先启动zookeeper的server)

1
2
3
4
5
6
7
8
9
10
11
[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services/cloud-provider-payment
[a4567f50-6ad9-47a3-9fbb-7391f41a9f3d]
[zk: localhost:2181(CONNECTED) 2] get /services/cloud-provider-payment/a4567f50-6ad9-47a3-9fbb-7391f41a9f3d
{"name":"cloud-provider-payment","id":"a4567f50-6ad9-47a3-9fbb-7391f41a9f3d","address":"192.168.199.218","port":8004,"ss
lPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","
name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1612811116918,"serviceType":"DYNAMIC","uriSpec":{"pa
rts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":"
:","variable":false},{"value":"port","variable":true}]}}
[zk: localhost:2181(CONNECTED) 3]

json格式化get /services/cloud-provider-payment/a4567f50-6ad9-47a3-9fbb-7391f41a9f3d的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"name": "cloud-provider-payment",
"id": "a4567f50-6ad9-47a3-9fbb-7391f41a9f3d",
"address": "192.168.199.218",
"port": 8004,
"sslPort": null,
"payload": {
"@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
"id": "application-1",
"name": "cloud-provider-payment",
"metadata": { }
},
"registrationTimeUTC": 1612811116918,
"serviceType": "DYNAMIC",
"uriSpec": {
"parts": [
{
"value": "scheme",
"variable": true
},
{
"value": "://",
"variable": false
},
{
"value": "address",
"variable": true
},
{
"value": ":",
"variable": false
},
{
"value": "port",
"variable": true
}
]
}
}

订单服务

  1. 新建名为cloud-consumerzk-order80的订单服务Maven工程,端口号为80。
  2. 引入 Maven 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Spring Cloud整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3 防止与3.4.9起冲突-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>

需要注意,spring-cloud-starter-zookeeper-discovery场景启动器自带一个ZooKeeper依赖,其版本可能与自己的ZooKeeper版本不兼容,从而无法启动Spring Boot项目,因此在pom文件中首先排除掉spring-cloud-starter-zookeeper-discovery中的ZooKeeper依赖,再自己添加符合自己版本的ZooKeeper依赖。(也可能会出现log4j依赖的冲突,解决方案相同)

  1. 修改配置文件
1
2
3
4
5
6
7
8
9
10
server:
port: 80

# 服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
connect-string: 127.0.0.1:2181 # 192.168.111.144:2181 #
  1. 创建主启动类
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZKMain80.class, args);
}
}
  1. 业务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced // 负载均衡
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderZKController
{
public static final String INVOKE_URL = "http://cloud-provider-payment";

@Resource
private RestTemplate restTemplate;

@GetMapping(value = "/consumer/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);
return result;
}
}
  1. 测试

先后运行ZooKeeper服务端,cloud-consumerzk-order80cloud-provider-payment8004

打开ZooKeeper客户端:

1
2
3
4
5
[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-consumer-order, cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 2]

访问测试地址:http://localhost/consumer/payment/zk

原理

80客户端将从ZooKeeper中订阅8004服务端的URL信息,从而利用RestTemplate对象调用该URL对应的Rest请求,从而实现远程调用的效果