Skip to content

Commit fe940ad

Browse files
Add missing hints for key bound operations
Closes: #2397
1 parent 55a27db commit fe940ad

File tree

4 files changed

+213
-58
lines changed

4 files changed

+213
-58
lines changed

Jenkinsfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ pipeline {
6767
}
6868
}
6969

70+
stage("test: native-hints") {
71+
when {
72+
beforeAgent(true)
73+
anyOf {
74+
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
75+
not { triggeredBy 'UpstreamCause' }
76+
}
77+
}
78+
agent {
79+
label 'data'
80+
}
81+
options { timeout(time: 30, unit: 'MINUTES') }
82+
environment {
83+
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
84+
}
85+
steps {
86+
script {
87+
docker.image("harbor-repo.vmware.com/dockerhub-proxy-cache/springci/spring-data-with-redis-6.2:${p['java.main.tag']}").inside('-v $HOME:/tmp/jenkins-home') {
88+
sh 'PROFILE=runtimehints LONG_TESTS=false ci/test.sh'
89+
}
90+
}
91+
}
92+
}
93+
7094
stage('Release to artifactory') {
7195
when {
7296
beforeAgent(true)

pom.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@
234234
<scope>test</scope>
235235
</dependency>
236236

237+
<dependency>
238+
<groupId>org.springframework</groupId>
239+
<artifactId>spring-core-test</artifactId>
240+
<scope>test</scope>
241+
</dependency>
242+
237243
<dependency>
238244
<groupId>org.awaitility</groupId>
239245
<artifactId>awaitility</artifactId>
@@ -317,5 +323,32 @@
317323
<!-- placeholder for no profile -->
318324
<id>none</id>
319325
</profile>
326+
<profile>
327+
<id>runtimehints</id>
328+
<build>
329+
<plugins>
330+
<plugin>
331+
<groupId>org.apache.maven.plugins</groupId>
332+
<artifactId>maven-dependency-plugin</artifactId>
333+
<version>3.3.0</version>
334+
<executions>
335+
<execution>
336+
<goals>
337+
<goal>properties</goal>
338+
</goals>
339+
</execution>
340+
</executions>
341+
</plugin>
342+
<plugin>
343+
<artifactId>maven-surefire-plugin</artifactId>
344+
<version>2.22.2</version>
345+
<configuration>
346+
<groups>RuntimeHintsTests</groups>
347+
<argLine>-javaagent:${org.springframework:spring-core-test:jar}</argLine>
348+
</configuration>
349+
</plugin>
350+
</plugins>
351+
</build>
352+
</profile>
320353
</profiles>
321354
</project>

src/main/java/org/springframework/data/redis/aot/RedisRuntimeHints.java

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.redis.aot;
1717

1818
import java.util.Arrays;
19+
import java.util.function.Consumer;
1920

2021
import org.springframework.aot.hint.MemberCategory;
2122
import org.springframework.aot.hint.RuntimeHints;
@@ -48,14 +49,29 @@
4849
import org.springframework.data.redis.repository.query.RedisQueryCreator;
4950
import org.springframework.data.redis.repository.support.RedisRepositoryFactoryBean;
5051
import org.springframework.lang.Nullable;
52+
import org.springframework.util.ClassUtils;
5153

5254
/**
5355
* {@link RuntimeHintsRegistrar} for Redis operations and repository support.
5456
*
5557
* @author Christoph Strobl
5658
* @since 3.0
5759
*/
58-
class RedisRuntimeHints implements RuntimeHintsRegistrar {
60+
public class RedisRuntimeHints implements RuntimeHintsRegistrar {
61+
62+
/**
63+
* Get a {@link RuntimeHints} instance containing the ones for Redis.
64+
*
65+
* @param config callback to provide additional custom hints.
66+
* @return new instance of {@link RuntimeHints}.
67+
*/
68+
public static RuntimeHints redisHints(Consumer<RuntimeHints> config) {
69+
70+
RuntimeHints hints = new RuntimeHints();
71+
new RedisRuntimeHints().registerHints(hints, null);
72+
config.accept(hints);
73+
return hints;
74+
}
5975

6076
@Override
6177
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
@@ -65,49 +81,62 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
6581
hint -> hint.onReachableType(TypeReference.of(RedisCacheManager.class)));
6682

6783
// REFLECTION
68-
hints.reflection().registerTypes(Arrays.asList(TypeReference.of(RedisConnection.class),
69-
TypeReference.of(StringRedisConnection.class), TypeReference.of(DefaultedRedisConnection.class),
70-
TypeReference.of(DefaultedRedisClusterConnection.class), TypeReference.of(RedisKeyCommands.class),
71-
TypeReference.of(RedisStringCommands.class), TypeReference.of(RedisListCommands.class),
72-
TypeReference.of(RedisSetCommands.class), TypeReference.of(RedisZSetCommands.class),
73-
TypeReference.of(RedisHashCommands.class), TypeReference.of(RedisTxCommands.class),
74-
TypeReference.of(RedisPubSubCommands.class), TypeReference.of(RedisConnectionCommands.class),
75-
TypeReference.of(RedisServerCommands.class), TypeReference.of(RedisStreamCommands.class),
76-
TypeReference.of(RedisScriptingCommands.class), TypeReference.of(RedisGeoCommands.class),
77-
TypeReference.of(RedisHyperLogLogCommands.class), TypeReference.of(RedisClusterCommands.class),
78-
TypeReference.of(ReactiveRedisConnection.class), TypeReference.of(ReactiveKeyCommands.class),
79-
TypeReference.of(ReactiveStringCommands.class), TypeReference.of(ReactiveListCommands.class),
80-
TypeReference.of(ReactiveSetCommands.class), TypeReference.of(ReactiveZSetCommands.class),
81-
TypeReference.of(ReactiveHashCommands.class), TypeReference.of(ReactivePubSubCommands.class),
82-
TypeReference.of(ReactiveServerCommands.class), TypeReference.of(ReactiveStreamCommands.class),
83-
TypeReference.of(ReactiveScriptingCommands.class), TypeReference.of(ReactiveGeoCommands.class),
84-
TypeReference.of(ReactiveHyperLogLogCommands.class), TypeReference.of(ReactiveClusterKeyCommands.class),
85-
TypeReference.of(ReactiveClusterStringCommands.class), TypeReference.of(ReactiveClusterListCommands.class),
86-
TypeReference.of(ReactiveClusterSetCommands.class), TypeReference.of(ReactiveClusterZSetCommands.class),
87-
TypeReference.of(ReactiveClusterHashCommands.class), TypeReference.of(ReactiveClusterServerCommands.class),
88-
TypeReference.of(ReactiveClusterStreamCommands.class), TypeReference.of(ReactiveClusterScriptingCommands.class),
89-
TypeReference.of(ReactiveClusterGeoCommands.class), TypeReference.of(ReactiveClusterHyperLogLogCommands.class),
90-
TypeReference.of(ReactiveRedisOperations.class), TypeReference.of(ReactiveRedisTemplate.class),
91-
TypeReference.of(RedisOperations.class), TypeReference.of(RedisTemplate.class),
92-
TypeReference.of(StringRedisTemplate.class), TypeReference.of(KeyspaceConfiguration.class),
93-
TypeReference.of(MappingConfiguration.class), TypeReference.of(MappingRedisConverter.class),
94-
TypeReference.of(RedisConverter.class), TypeReference.of(RedisCustomConversions.class),
95-
TypeReference.of(ReferenceResolver.class), TypeReference.of(ReferenceResolverImpl.class),
96-
TypeReference.of(IndexConfiguration.class), TypeReference.of(ConfigurableIndexDefinitionProvider.class),
97-
TypeReference.of(RedisMappingContext.class), TypeReference.of(RedisRepositoryFactoryBean.class),
98-
TypeReference.of(RedisQueryCreator.class), TypeReference.of(MessageListener.class),
99-
TypeReference.of(RedisMessageListenerContainer.class),
100-
101-
TypeReference.of(RedisKeyValueAdapter.class), TypeReference.of(RedisKeyValueTemplate.class),
102-
103-
// Key-Value
104-
TypeReference.of(KeySpace.class), TypeReference.of(AbstractKeyValueAdapter.class),
105-
TypeReference.of(KeyValueAdapter.class), TypeReference.of(KeyValueOperations.class),
106-
TypeReference.of(KeyValueTemplate.class), TypeReference.of(KeyValueMappingContext.class),
107-
TypeReference.of(KeyValueRepository.class), TypeReference.of(KeyValueRepositoryFactoryBean.class),
108-
TypeReference.of(QueryCreatorType.class), TypeReference.of(KeyValuePartTreeQuery.class)),
109-
110-
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
84+
hints.reflection().registerTypes(
85+
Arrays.asList(TypeReference.of(RedisConnection.class), TypeReference.of(StringRedisConnection.class),
86+
TypeReference.of(DefaultedRedisConnection.class), TypeReference.of(DefaultedRedisClusterConnection.class),
87+
TypeReference.of(RedisKeyCommands.class), TypeReference.of(RedisStringCommands.class),
88+
TypeReference.of(RedisListCommands.class), TypeReference.of(RedisSetCommands.class),
89+
TypeReference.of(RedisZSetCommands.class), TypeReference.of(RedisHashCommands.class),
90+
TypeReference.of(RedisTxCommands.class), TypeReference.of(RedisPubSubCommands.class),
91+
TypeReference.of(RedisConnectionCommands.class), TypeReference.of(RedisServerCommands.class),
92+
TypeReference.of(RedisStreamCommands.class), TypeReference.of(RedisScriptingCommands.class),
93+
TypeReference.of(RedisGeoCommands.class), TypeReference.of(RedisHyperLogLogCommands.class),
94+
TypeReference.of(RedisClusterCommands.class), TypeReference.of(ReactiveRedisConnection.class),
95+
TypeReference.of(ReactiveKeyCommands.class), TypeReference.of(ReactiveStringCommands.class),
96+
TypeReference.of(ReactiveListCommands.class), TypeReference.of(ReactiveSetCommands.class),
97+
TypeReference.of(ReactiveZSetCommands.class), TypeReference.of(ReactiveHashCommands.class),
98+
TypeReference.of(ReactivePubSubCommands.class), TypeReference.of(ReactiveServerCommands.class),
99+
TypeReference.of(ReactiveStreamCommands.class), TypeReference.of(ReactiveScriptingCommands.class),
100+
TypeReference.of(ReactiveGeoCommands.class), TypeReference.of(ReactiveHyperLogLogCommands.class),
101+
TypeReference.of(ReactiveClusterKeyCommands.class), TypeReference.of(ReactiveClusterStringCommands.class),
102+
TypeReference.of(ReactiveClusterListCommands.class), TypeReference.of(ReactiveClusterSetCommands.class),
103+
TypeReference.of(ReactiveClusterZSetCommands.class), TypeReference.of(ReactiveClusterHashCommands.class),
104+
TypeReference.of(ReactiveClusterServerCommands.class),
105+
TypeReference.of(ReactiveClusterStreamCommands.class),
106+
TypeReference.of(ReactiveClusterScriptingCommands.class),
107+
TypeReference.of(ReactiveClusterGeoCommands.class),
108+
TypeReference.of(ReactiveClusterHyperLogLogCommands.class), TypeReference.of(ReactiveRedisOperations.class),
109+
TypeReference.of(ReactiveRedisTemplate.class), TypeReference.of(RedisOperations.class),
110+
TypeReference.of(RedisTemplate.class), TypeReference.of(StringRedisTemplate.class),
111+
TypeReference.of(KeyspaceConfiguration.class), TypeReference.of(MappingConfiguration.class),
112+
TypeReference.of(MappingRedisConverter.class), TypeReference.of(RedisConverter.class),
113+
TypeReference.of(RedisCustomConversions.class), TypeReference.of(ReferenceResolver.class),
114+
TypeReference.of(ReferenceResolverImpl.class), TypeReference.of(IndexConfiguration.class),
115+
TypeReference.of(ConfigurableIndexDefinitionProvider.class), TypeReference.of(RedisMappingContext.class),
116+
TypeReference.of(RedisRepositoryFactoryBean.class), TypeReference.of(RedisQueryCreator.class),
117+
TypeReference.of(MessageListener.class), TypeReference.of(RedisMessageListenerContainer.class),
118+
119+
TypeReference
120+
.of("org.springframework.data.redis.core.BoundOperationsProxyFactory$DefaultBoundKeyOperations"),
121+
TypeReference.of("org.springframework.data.redis.core.DefaultGeoOperations"),
122+
TypeReference.of("org.springframework.data.redis.core.DefaultHashOperations"),
123+
TypeReference.of("org.springframework.data.redis.core.DefaultKeyOperations"),
124+
TypeReference.of("org.springframework.data.redis.core.DefaultListOperations"),
125+
TypeReference.of("org.springframework.data.redis.core.DefaultSetOperations"),
126+
TypeReference.of("org.springframework.data.redis.core.DefaultStreamOperations"),
127+
TypeReference.of("org.springframework.data.redis.core.DefaultValueOperations"),
128+
TypeReference.of("org.springframework.data.redis.core.DefaultZSetOperations"),
129+
130+
TypeReference.of(RedisKeyValueAdapter.class), TypeReference.of(RedisKeyValueTemplate.class),
131+
132+
// Key-Value
133+
TypeReference.of(KeySpace.class), TypeReference.of(AbstractKeyValueAdapter.class),
134+
TypeReference.of(KeyValueAdapter.class), TypeReference.of(KeyValueOperations.class),
135+
TypeReference.of(KeyValueTemplate.class), TypeReference.of(KeyValueMappingContext.class),
136+
TypeReference.of(KeyValueRepository.class), TypeReference.of(KeyValueRepositoryFactoryBean.class),
137+
TypeReference.of(QueryCreatorType.class), TypeReference.of(KeyValuePartTreeQuery.class)),
138+
139+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
111140

112141
// PROXIES
113142
hints.proxies().registerJdkProxy(TypeReference.of(RedisConnection.class));
@@ -117,24 +146,31 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
117146
TypeReference.of(DecoratedRedisConnection.class));
118147

119148
// keys are bound by a proxy
120-
boundOperationsProxy(BoundGeoOperations.class, hints);
121-
boundOperationsProxy(BoundHashOperations.class, hints);
122-
boundOperationsProxy(BoundKeyOperations.class, hints);
123-
boundOperationsProxy(BoundListOperations.class, hints);
124-
boundOperationsProxy(BoundSetOperations.class, hints);
125-
boundOperationsProxy(BoundStreamOperations.class, hints);
126-
boundOperationsProxy(BoundValueOperations.class, hints);
127-
boundOperationsProxy(BoundZSetOperations.class, hints);
128-
boundOperationsProxy(
129-
TypeReference.of("org.springframework.data.redis.core.BoundOperationsProxyFactory$DefaultBoundKeyOperations"),
130-
hints);
149+
boundOperationsProxy(BoundGeoOperations.class, classLoader, hints);
150+
boundOperationsProxy(BoundHashOperations.class, classLoader, hints);
151+
boundOperationsProxy(BoundKeyOperations.class, classLoader, hints);
152+
boundOperationsProxy(BoundListOperations.class, classLoader, hints);
153+
boundOperationsProxy(BoundSetOperations.class, classLoader, hints);
154+
boundOperationsProxy(BoundStreamOperations.class, classLoader, hints);
155+
boundOperationsProxy(BoundValueOperations.class, classLoader, hints);
156+
boundOperationsProxy(BoundZSetOperations.class, classLoader, hints);
131157
}
132158

133-
private void boundOperationsProxy(Class<?> type, RuntimeHints hints) {
134-
boundOperationsProxy(TypeReference.of(type), hints);
159+
static void boundOperationsProxy(Class<?> type, ClassLoader classLoader, RuntimeHints hints) {
160+
boundOperationsProxy(TypeReference.of(type), classLoader, hints);
135161
}
136162

137-
private void boundOperationsProxy(TypeReference typeReference, RuntimeHints hints) {
163+
static void boundOperationsProxy(TypeReference typeReference, ClassLoader classLoader, RuntimeHints hints) {
164+
165+
String boundTargetClass = typeReference.getPackageName() + "." + typeReference.getSimpleName().replace("Bound", "");
166+
if (ClassUtils.isPresent(boundTargetClass, classLoader)) {
167+
hints.reflection().registerType(TypeReference.of(boundTargetClass), hint -> hint
168+
.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
169+
}
170+
171+
hints.reflection().registerType(typeReference, hint -> hint
172+
.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
173+
138174
hints.proxies().registerJdkProxy(typeReference, //
139175
TypeReference.of("org.springframework.aop.SpringProxy"), //
140176
TypeReference.of("org.springframework.aop.framework.Advised"), //
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.core;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.aop.scope.ScopedObject;
20+
import org.springframework.aot.hint.MemberCategory;
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
23+
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
24+
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
25+
import org.springframework.data.redis.aot.RedisRuntimeHints;
26+
import org.springframework.data.redis.connection.DataType;
27+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
28+
import org.springframework.data.redis.connection.lettuce.extension.LettuceConnectionFactoryExtension;
29+
import org.springframework.data.redis.test.extension.RedisStanalone;
30+
31+
/**
32+
* @author Christoph Strobl
33+
*/
34+
@EnabledIfRuntimeHintsAgent
35+
class BoundOperationsProxyFactoryRuntimeHintTests {
36+
37+
@Test // GH-2395
38+
void boundOpsRuntimeHints() {
39+
40+
LettuceConnectionFactory connectionFactory = LettuceConnectionFactoryExtension
41+
.getConnectionFactory(RedisStanalone.class);
42+
RedisTemplate template = new RedisTemplate<>();
43+
template.setConnectionFactory(connectionFactory);
44+
template.afterPropertiesSet();
45+
46+
BoundOperationsProxyFactory factory = new BoundOperationsProxyFactory();
47+
BoundListOperations listOp = factory.createProxy(BoundListOperations.class, "key", DataType.LIST, template,
48+
RedisOperations::opsForList);
49+
50+
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
51+
listOp.trim(0, 10);
52+
});
53+
54+
RuntimeHints hints = RedisRuntimeHints.redisHints(it -> {
55+
// hints that should come from another module
56+
it.reflection().registerType(ScopedObject.class,
57+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
58+
});
59+
60+
invocations.assertThat().match(hints);
61+
}
62+
}

0 commit comments

Comments
 (0)