整合 Redis
# 550.整合 Redis
接下来我们试试整合 NoSQL,例如 Redis
由于本系列教程是 SpringBoot,如果不熟悉 Redis 的话,可以参考下 Redis 教程 (opens new window)
# 引入依赖
SpringBoot 已经提供了相关的 starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2
3
4
# 自动配置分析
可以分析下其依赖:
引入了 keyvalue 等操作数据的,以及 lettuce 这个客户端用来操作 Redis(基于 Netty)
在 SpringBoot 的自动配置类中,有一个叫做 RedisAutoConfiguration
,就是配置了和 Redis 相关的:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
配置属性的类 RedisProperties
,就是会将 spring.redis
开头的属性绑定:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
//..................
2
3
4
5
6
7
8
9
10
11
12
13
14
15
还引入了 LettuceConnectionConfiguration
,这个就是客户端连接的配置;该配置会放入一个客户端资源 DefaultClientResources
,以及 Lettuce 的连接工厂
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(ClientResources.class)
DefaultClientResources lettuceClientResources() {
return DefaultClientResources.create();
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory() {
//.....
}
2
3
4
5
6
7
8
9
10
11
除此之外,还引入了 JedisConnectionConfiguration
,这也是一个客户端工具,至于到底是用 Jedis
还是 Lettuce
,就看引入了哪个依赖了,或者指定配置。
在自动配置类中,还注入了 RedisTemplate
,StringRedisTemplate
。Redis 是通过 key-value 方式存储数据的,第一个 template
中,可以用 Object 来表示 key 和 value;而第二个 template
,则是用于 key 和 value 都是 String 的情况(因为 String 的情况比较多)。
# 准备 Redis
读者可以使用本地的 Redis,或者使用云服务器厂商的 Redis(按量访问)。
然后新增配置:
spring:
redis:
url: redis://localhost:6379/0
2
3
在测试类中中新增方法:
class LearnSpringBootWebAdminApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Test
void testRedis(){
ValueOperations<String, String> stringStringValueOperations = redisTemplate.opsForValue();
stringStringValueOperations.set("hello", "world");
String hello = stringStringValueOperations.get("hello");
log.info("hello:{}", hello);
}
//............
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后可以运行该测试方法,并且可以用其他 Redis 客户端来查看数据
# 切换至 Jedis
默认使用的是 lettuce,如何使用 Jedis 呢?
- 引入 Jedis 的依赖(版本号已经默认配置了)
- 去除 lettuce 的依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后我们可以打印下工厂的类型:
class LearnSpringBootWebAdminApplicationTests {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis(){
log.info("redis类型:{}", redisConnectionFactory.getClass());
}
//............
2
3
4
5
6
7
8
9
10
11
12
运行结果:
redis类型:class org.springframework.data.redis.connection.jedis.JedisConnectionFactory
除此之外,还可以对连接池信息进行配置,例如:
spring:
redis:
url: redis://localhost:6379/0
jedis:
pool:
max-active: 8
max-wait: -1ms
2
3
4
5
6
7
# 统计功能
我们可以做个统计,例如访问了多少次/sql 请求,然后展示到页面上:
我们新增一个拦截器:
package com.peterjxl.learnspringbootwebadmin.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component // 将拦截器交给Spring管理
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI(); // 获取请求的url
// 访问量加1
redisTemplate.opsForValue().increment(uri);
return true; // 放行
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
然后讲拦截器加入到 SpringBoot 中:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求,包括静态资源
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**", "/sql");
registry.addInterceptor(redisUrlCountInterceptor)
.addPathPatterns("/**") // 拦截所有请求,包括静态资源
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意,不能用 registry.addInterceptor(new RedisUrlCountInterceptor)
,因为用 new 的话,我们拦截器中的 StringRedisTemplate
就不会注入了
接下来我们可以重启下项目,然后多访问一些页面,可以看到 Redis 中确实有这些数据:
接下来我们展示在首页中,首先在首页的 controller 中放入次数:
@GetMapping("/main.html")
public String mainPage(HttpSession session, Model model) {
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";
}
2
3
4
5
6
7
8
9
10
11
然后在首页中修改:
效果:
# 扩展
Filter 和 Interceptor 几乎都有同样的功能,用哪个好?
- Filter 是 Servlet 原生的 API ,没有 Spring 也能使用
- Interceptor 是 Spring 定义的接口,只能在 Spring 中使用,好处是可以使用 Spring 的功能(例如注入)
# 源码
已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo16,读者可以通过切换分支来查看本文的示例代码