Spring boot 开发
本节内容的版本声明:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.cloud-version>2020.0.1</spring.cloud-version>
<spring.boot-version>2.4.2</spring.boot-version>
<spring.cloud-alibaba-version>2021.1</spring.cloud-alibaba-version>
<kotlin.version>1.4.31</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
</properties>
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
官网有2个redis,一个是reactive的,一个不是。我这只讲reactive的。
引入的依赖结构:
查看spring-boot-autoconfigure:2.4.2的自动配置中关于redis
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
看源码讲起来就比较复杂了,简单说一下关键项
RedisAutoConfiguration下@EnableConfigurationProperties(RedisProperties.class)可以看见配置类是RedisProperties
RedisProperties上看见@ConfigurationProperties(prefix = “spring.redis”)所以配置怎么写就在了。里面有个静态类Cluster,静态类下面属性就是集群配置。
最简配置如下:
对应redis devops(一)的配置
spring:
redis:
cluster:
nodes:
- ip:端口
- ip:端口
- ip:端口
- ip:端口
- ip:端口
- ip:端口
port: 端口
password: 密码
开发中有用json序列化器,就配一个序列化器。
工具类,我之前有发过一篇。
关键是,引入之后R2dbc的Repository注入会失败。
申明一下spring boot版本2.4.2
错误类型: NoSuchBeanDefinitionException 一个找不到BeanDefinition的错误,很常见!
具体描述是(隐去一些项目信息)
Parameter 0 of constructor in XXService required a bean of type 'XXRepository' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'XXRepository' in your configuration.
提示BeanDefinition,把端口打在 DefaultListableBeanFactory 1343行代码(截图已解决了,machingBeans size = 1),用条件断点
步过1次,machingBeans size = 0,用评估器确认一下
确定了是BeanDefinition没有被ioc容器收集。
r2dbc Repository 从接口到实现类,用的jdk动态代理。去除redis依赖可以验证一下:
依赖注入打印实例:
org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository@d6e361b3
打印java class:
class com.sun.proxy.$Proxy123
依赖查找打印实例:
org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository@d6e361b3
从SimpleR2dbcRepository可以推测R2dbc的实现基本就是jpaRepository类似
从@EnableR2dbcRepositories 的@Import(R2dbcRepositoriesRegistrar.class) 进入
找到RepositoryBeanDefinitionRegistrarSupport#registerBeanDefinitions 断点打在 104 行 new RepositoryConfigurationDelegate这。RepositoryConfigurationDelegate 是实际注册Repository BeanDefinitions的类,这个类主要的注册实现是registerRepositoriesIn方法。
单步调试这个方法,会发现 148 行 返回的configurations长度为0。项目中应有4个,跳转到RepositoryConfigurationExtensionSupport#getRepositoryConfigurations 中。
发现返回result :HashSet没有内容。2个add方法都跳过了!
//98行
if (repositoryInterface == null) {
result.add(configuration);
continue;
}
//108行
if (qualifiedForImplementation && useRepositoryConfiguration(metadata)) {
result.add(configuration);
}
第一个repositoryInterface 明显不为空,任务就落在第二个if,其中qualifiedForImplementation 很关键
//105行
boolean qualifiedForImplementation = !strictMatchesOnly || configSource.usesExplicitFilters()
|| isStrictRepositoryCandidate(metadata);
通过去除依赖,发现 !strictMatchesOnly = true 所以qualifiedForImplementation = true 所以add。
引入redis依赖之后!strictMatchesOnly = false。
strictMatchesOnly 是参数传入,来源RepositoryConfigurationDelegate#inMultiStoreMode,赋值唯一的是构造函数时调用了RepositoryConfigurationDelegate#multipleStoresDetected。函数中对RepositoryFactorySupport.class进行计数,返回是否有多个RepositoryFactorySupport的支持。
通过idea看到引入后的支持
看到这,大家都会以为有冲突,我们排除一下依赖
@SpringBootApplication(exclude = [RedisRepositoriesAutoConfiguration::class])
或者配置一下
spring:
data:
redis:
repositories:
enabled: false
现实很残酷,以上做法都不会改变RepositoryConfigurationDelegate#multipleStoresDetected函数的返回结果。所以,到qualifiedForImplementation 的值依然是false。
回到qualifiedForImplementation 值判断上第二个参数configSource.usesExplicitFilters(),看一下走到RepositoryConfigurationSource接口,实现类是我们是注解启动,源码:
@Override
public boolean usesExplicitFilters() {
return hasExplicitFilters;
}
给AnnotationRepositoryConfigurationSource#hasExplicitFilters 打上检测断点(直接看代码也可以发现),这个属性值依赖
private static boolean hasExplicitFilters(AnnotationAttributes attributes) {
return Stream.of("includeFilters", "excludeFilters") //
.anyMatch(it -> attributes.getAnnotationArray(it).length > 0);
}
也就是我们需要在@EnableR2dbcRepositories 配置includeFilters 或者excludeFilters 可以改变 configSource.usesExplicitFilters()的值从而改变qualifiedForImplementation 从而让BeanDefinition 添加到ioc容器管理。
附:以上顺便解释为什么配置basePackages也不能注入
解决方法
需要配置 @EnableR2dbcRepositories 的 includeFilters 或者 excludeFilters 属性。
Filter[] 数组,是一个注解数组,有很多方式实现,具体不讲解,就讲一下我的实现。
自定义一个注解 R2dbcRepository
//kotlin 版本的注解 用java的自己写
@Target(AnnotationTarget.CLASS)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class R2dbcRepository()
然后在@EnableR2dbcRepositories配置
//kotlin 版本 用java的自己写
@EnableR2dbcRepositories(
basePackages = ["com.xxx.xxx.repository"],
includeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, classes = [R2dbcRepository::class])])
找一个Component打上就可以了。
C++ 开发
留空,暂时还不会,会了再来补。
业务逻辑
无非是一些缓存,token等应用场景,主要是分布式后,相同微服务一些的状态同步,锁,计时器都可以找redis。具体到公司业务,不能说。大家可以去看一些教学视频,想到什么能说,以后发出来,这里也留个坑。