Skip to content

Commit 8adea79

Browse files
committed
DATAREDIS-698 - Polishing.
Eagerly initialize known commands. Preallocate Jedis response builder. Replace list concatenation with array copy. Reject null arguments in execute(). Remove inversion through collections with direct byte array creation. Reorder arguments in signatures, visibility modifiers, Javadoc. Original pull request: #283.
1 parent f6a17fb commit 8adea79

File tree

9 files changed

+135
-87
lines changed

9 files changed

+135
-87
lines changed

src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3047,7 +3047,7 @@ public void openPipeline() {
30473047
*/
30483048
@Override
30493049
public Object execute(String command) {
3050-
return execute(command, (byte[][]) null);
3050+
return execute(command, EMPTY_2D_BYTE_ARRAY);
30513051
}
30523052

30533053
/*

src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.data.redis.connection;
1717

18-
import java.util.ArrayList;
1918
import java.util.Collection;
2019
import java.util.List;
2120
import java.util.Properties;
@@ -142,16 +141,22 @@ default List<RedisClientInfo> getClientList(RedisClusterNode node) {
142141
*/
143142
@Nullable
144143
@Override
144+
@SuppressWarnings("unchecked")
145145
default <T> T execute(String command, byte[] key, Collection<byte[]> args) {
146146

147147
Assert.notNull(command, "Command must not be null!");
148148
Assert.notNull(key, "Key must not be null!");
149149
Assert.notNull(args, "Args must not be null!");
150150

151-
ArrayList<byte[]> allArgs = new ArrayList();
152-
allArgs.add(key);
153-
allArgs.addAll(args);
151+
byte[][] commandArgs = new byte[args.size() + 1][];
154152

155-
return (T) execute(command, allArgs.toArray(new byte[allArgs.size()][]));
153+
commandArgs[0] = key;
154+
int targetIndex = 1;
155+
156+
for (byte[] binaryArgument : args) {
157+
commandArgs[targetIndex++] = binaryArgument;
158+
}
159+
160+
return (T) execute(command, commandArgs);
156161
}
157162
}

src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -623,17 +623,16 @@ public HStrLenCommand from(ByteBuffer key) {
623623
}
624624

625625
/**
626-
* @return {@literal null} if not already set.
626+
* @return the field.
627627
*/
628-
@Nullable
629628
public ByteBuffer getField() {
630629
return field;
631630
}
632631
}
633632

634633
/**
635-
* Get the length of the value associated with {@code hashKey}. If either the {@code key} or the {@code hashKey} do
636-
* not exist, {@code 0} is emitted.
634+
* Get the length of the value associated with {@code field}. If either the {@code key} or the {@code field} do not
635+
* exist, {@code 0} is emitted.
637636
*
638637
* @param key must not be {@literal null}.
639638
* @param field must not be {@literal null}.
@@ -649,8 +648,8 @@ default Mono<Long> hStrLen(ByteBuffer key, ByteBuffer field) {
649648
}
650649

651650
/**
652-
* Get the length of the value associated with {@code hashKey}. If either the {@code key} or the {@code hashKey} do
653-
* not exist, {@code 0} is emitted.
651+
* Get the length of the value associated with {@code field}. If either the {@code key} or the {@code field} do not
652+
* exist, {@code 0} is emitted.
654653
*
655654
* @param commands must not be {@literal null}.
656655
* @return never {@literal null}.

src/main/java/org/springframework/data/redis/connection/RedisClusterConnection.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public interface RedisClusterConnection extends RedisConnection, RedisClusterCom
6565
* <pre>
6666
* <code>
6767
* // SET foo bar EX 10 NX
68-
* execute("SET", "foo".getBytes(), asBinaryList("bar", "EX", 10, "NX")
68+
* execute("SET", "foo".getBytes(), asBinaryList("bar", "EX", 10, "NX"))
6969
* </code>
7070
* </pre>
7171
*

src/main/java/org/springframework/data/redis/connection/jedis/JedisClientUtils.java

+84-42
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,31 @@
2929

3030
import java.lang.reflect.Field;
3131
import java.lang.reflect.Method;
32-
import java.util.ArrayList;
33-
import java.util.Collection;
34-
import java.util.List;
32+
import java.util.Arrays;
33+
import java.util.Set;
3534
import java.util.function.Supplier;
35+
import java.util.stream.Collectors;
3636

3737
import org.springframework.beans.DirectFieldAccessor;
38-
import org.springframework.lang.Nullable;
3938
import org.springframework.util.ClassUtils;
4039
import org.springframework.util.ReflectionUtils;
4140

4241
/**
42+
* Utility class to dispatch arbitrary Redis commands using Jedis commands.
43+
*
4344
* @author Christoph Strobl
45+
* @author Mark Paluch
4446
* @since 2.1
4547
*/
48+
@SuppressWarnings({ "unchecked", "ConstantConditions" })
4649
class JedisClientUtils {
4750

4851
private static final Field CLIENT_FIELD;
4952
private static final Method SEND_COMMAND;
5053
private static final Method GET_RESPONSE;
5154
private static final Method PROTOCOL_SEND_COMMAND;
55+
private static final Set<String> KNOWN_COMMANDS;
56+
private static final Builder<Object> OBJECT_BUILDER;
5257

5358
static {
5459

@@ -60,12 +65,12 @@ class JedisClientUtils {
6065
ReflectionUtils.makeAccessible(PROTOCOL_SEND_COMMAND);
6166

6267
try {
68+
6369
Class<?> commandType = ClassUtils.isPresent("redis.clients.jedis.ProtocolCommand", null)
6470
? ClassUtils.forName("redis.clients.jedis.ProtocolCommand", null)
6571
: ClassUtils.forName("redis.clients.jedis.Protocol$Command", null);
6672

67-
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand",
68-
new Class[] { commandType, byte[][].class });
73+
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand", commandType, byte[][].class);
6974
} catch (Exception e) {
7075
throw new NoClassDefFoundError(
7176
"Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'.");
@@ -75,38 +80,56 @@ class JedisClientUtils {
7580

7681
GET_RESPONSE = ReflectionUtils.findMethod(Queable.class, "getResponse", Builder.class);
7782
ReflectionUtils.makeAccessible(GET_RESPONSE);
83+
84+
KNOWN_COMMANDS = Arrays.stream(Command.values()).map(Enum::name).collect(Collectors.toSet());
85+
86+
OBJECT_BUILDER = new Builder<Object>() {
87+
public Object build(Object data) {
88+
return data;
89+
}
90+
91+
public String toString() {
92+
return "Object";
93+
}
94+
};
7895
}
7996

80-
@Nullable
81-
static <T> T execute(String command, Collection<byte[]> keys, Collection<byte[]> args, Supplier<Jedis> jedis) {
97+
/**
98+
* Execute an arbitrary on the supplied {@link Jedis} instance.
99+
*
100+
* @param command the command.
101+
* @param keys must not be {@literal null}, may be empty.
102+
* @param args must not be {@literal null}, may be empty.
103+
* @param jedis must not be {@literal null}.
104+
* @return the response, can be be {@literal null}.
105+
*/
106+
static <T> T execute(String command, byte[][] keys, byte[][] args, Supplier<Jedis> jedis) {
82107

83-
List<byte[]> mArgs = new ArrayList<>(keys);
84-
mArgs.addAll(args);
108+
byte[][] commandArgs = getCommandArguments(keys, args);
85109

86-
Client client = retrieveClient(jedis.get());
87-
sendCommand(client, command, mArgs.toArray(new byte[mArgs.size()][]));
110+
Client client = sendCommand(command, commandArgs, jedis.get());
88111

89112
return (T) client.getOne();
90113
}
91114

92-
static Client retrieveClient(Jedis jedis) {
93-
return (Client) ReflectionUtils.getField(CLIENT_FIELD, jedis);
94-
}
95-
96-
static Client sendCommand(Jedis jedis, String command, byte[][] args) {
115+
/**
116+
* Send a Redis command and retrieve the {@link Client} for response retrieval.
117+
*
118+
* @param command the command.
119+
* @param args must not be {@literal null}, may be empty.
120+
* @param jedis must not be {@literal null}.
121+
* @return the {@link Client} instance used to send the command.
122+
*/
123+
static Client sendCommand(String command, byte[][] args, Jedis jedis) {
97124

98125
Client client = retrieveClient(jedis);
99126

100-
if (isKnownCommand(command)) {
101-
ReflectionUtils.invokeMethod(SEND_COMMAND, client, Command.valueOf(command.trim().toUpperCase()), args);
102-
} else {
103-
sendProtocolCommand(client, command, args);
104-
}
127+
sendCommand(client, command, args);
105128

106129
return client;
107130
}
108131

109-
static void sendCommand(Client client, String command, byte[][] args) {
132+
private static void sendCommand(Client client, String command, byte[][] args) {
110133

111134
if (isKnownCommand(command)) {
112135
ReflectionUtils.invokeMethod(SEND_COMMAND, client, Command.valueOf(command.trim().toUpperCase()), args);
@@ -115,8 +138,9 @@ static void sendCommand(Client client, String command, byte[][] args) {
115138
}
116139
}
117140

118-
static void sendProtocolCommand(Client client, String command, byte[][] args) {
141+
private static void sendProtocolCommand(Client client, String command, byte[][] args) {
119142

143+
// quite expensive to construct for each command invocation
120144
DirectFieldAccessor dfa = new DirectFieldAccessor(client);
121145

122146
client.connect();
@@ -125,34 +149,52 @@ static void sendProtocolCommand(Client client, String command, byte[][] args) {
125149
ReflectionUtils.invokeMethod(PROTOCOL_SEND_COMMAND, null, os, SafeEncoder.encode(command), args);
126150

127151
Integer pipelinedCommands = (Integer) dfa.getPropertyValue("pipelinedCommands");
128-
dfa.setPropertyValue("pipelinedCommands", pipelinedCommands.intValue() + 1);
152+
dfa.setPropertyValue("pipelinedCommands", pipelinedCommands + 1);
129153
}
130154

131-
static boolean isKnownCommand(String command) {
155+
private static boolean isKnownCommand(String command) {
156+
return KNOWN_COMMANDS.contains(command);
157+
}
132158

133-
try {
134-
Command.valueOf(command);
135-
return true;
136-
} catch (IllegalArgumentException e) {
137-
return false;
159+
private static byte[][] getCommandArguments(byte[][] keys, byte[][] args) {
160+
161+
if (keys.length == 0) {
162+
return args;
163+
}
164+
165+
if (args.length == 0) {
166+
return keys;
138167
}
168+
169+
byte[][] commandArgs = new byte[keys.length + args.length][];
170+
171+
System.arraycopy(keys, 0, commandArgs, 0, keys.length);
172+
System.arraycopy(args, 0, commandArgs, keys.length, args.length);
173+
174+
return commandArgs;
139175
}
140176

177+
/**
178+
* @param jedis the client instance.
179+
* @return {@literal true} if the connection has entered {@literal MULTI} state.
180+
*/
141181
static boolean isInMulti(Jedis jedis) {
142182
return retrieveClient(jedis).isInMulti();
143183
}
144184

145-
static Response<Object> getGetResponse(Object target) {
146-
147-
return (Response<Object>) ReflectionUtils.invokeMethod(GET_RESPONSE, target, new Builder<Object>() {
148-
public Object build(Object data) {
149-
return data;
150-
}
151-
152-
public String toString() {
153-
return "Object";
154-
}
155-
});
185+
/**
186+
* Retrieve the {@link Response} object from a {@link redis.clients.jedis.Transaction} or a
187+
* {@link redis.clients.jedis.Pipeline} for response synchronization.
188+
*
189+
* @param target a {@link redis.clients.jedis.Transaction} or {@link redis.clients.jedis.Pipeline}, must not be
190+
* {@literal null}.
191+
* @return the {@link Response} wrapper object.
192+
*/
193+
static Response<Object> getResponse(Object target) {
194+
return (Response<Object>) ReflectionUtils.invokeMethod(GET_RESPONSE, target, OBJECT_BUILDER);
156195
}
157196

197+
private static Client retrieveClient(Jedis jedis) {
198+
return (Client) ReflectionUtils.getField(CLIENT_FIELD, jedis);
199+
}
158200
}

src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java

+23-9
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
import redis.clients.jedis.JedisClusterConnectionHandler;
2424
import redis.clients.jedis.JedisPool;
2525

26-
import java.util.ArrayList;
27-
import java.util.Arrays;
2826
import java.util.Collection;
29-
import java.util.Collections;
3027
import java.util.LinkedHashMap;
3128
import java.util.LinkedHashSet;
3229
import java.util.List;
@@ -70,6 +67,8 @@ public class JedisClusterConnection implements DefaultedRedisClusterConnection {
7067
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
7168
JedisConverters.exceptionConverter());
7269

70+
private static final byte[][] EMPTY_2D_BYTE_ARRAY = new byte[0][];
71+
7372
private final Log log = LogFactory.getLog(getClass());
7473

7574
private final JedisCluster cluster;
@@ -130,14 +129,16 @@ public JedisClusterConnection(JedisCluster cluster, ClusterCommandExecutor execu
130129
* (non-Javadoc)
131130
* @see org.springframework.data.redis.connection.RedisCommands#execute(java.lang.String, byte[][])
132131
*/
132+
@Nullable
133133
@Override
134134
public Object execute(String command, byte[]... args) {
135135

136136
Assert.notNull(command, "Command must not be null!");
137+
Assert.notNull(args, "Args must not be null!");
137138

138139
return clusterCommandExecutor
139140
.executeCommandOnArbitraryNode((JedisClusterCommandCallback<Object>) client -> JedisClientUtils.execute(command,
140-
Collections.emptyList(), Arrays.asList(args), () -> client))
141+
EMPTY_2D_BYTE_ARRAY, args, () -> client))
141142
.getValue();
142143
}
143144

@@ -153,14 +154,26 @@ public <T> T execute(String command, byte[] key, Collection<byte[]> args) {
153154
Assert.notNull(key, "Key must not be null!");
154155
Assert.notNull(args, "Args must not be null!");
155156

156-
Collection<byte[]> commandArgs = new ArrayList<>();
157-
commandArgs.add(key);
158-
commandArgs.addAll(args);
157+
byte[][] commandArgs = getCommandArguments(key, args);
159158

160159
RedisClusterNode keyMaster = topologyProvider.getTopology().getKeyServingMasterNode(key);
161160

162161
return clusterCommandExecutor.executeCommandOnSingleNode((JedisClusterCommandCallback<T>) client -> JedisClientUtils
163-
.execute(command, Collections.emptyList(), commandArgs, () -> client), keyMaster).getValue();
162+
.execute(command, EMPTY_2D_BYTE_ARRAY, commandArgs, () -> client), keyMaster).getValue();
163+
}
164+
165+
private static byte[][] getCommandArguments(byte[] key, Collection<byte[]> args) {
166+
167+
byte[][] commandArgs = new byte[args.size() + 1][];
168+
169+
commandArgs[0] = key;
170+
int targetIndex = 1;
171+
172+
for (byte[] binaryArgument : args) {
173+
commandArgs[targetIndex++] = binaryArgument;
174+
}
175+
176+
return commandArgs;
164177
}
165178

166179
/*
@@ -816,7 +829,8 @@ static class JedisClusterNodeResourceProvider implements ClusterNodeResourceProv
816829

817830
PropertyAccessor accessor = new DirectFieldAccessFallbackBeanWrapper(cluster);
818831
this.connectionHandler = accessor.isReadableProperty("connectionHandler")
819-
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler") : null;
832+
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler")
833+
: null;
820834
} else {
821835
this.connectionHandler = null;
822836
}

0 commit comments

Comments
 (0)