在Eclipse的RunJettyRun(jetty 7.6.8.v20121106)使用jetty时,在项目中使用到了spring-data-redis-1.5.0.RELEASE.jar、jedis-2.7.2.jar、jedis-2.7.3.jar,于是如以下代码方式访问Redis时:
private static RedisTemplate<String, Object> redisTemplate = ...;
redisTemplate.opsForHash().put(CACHE_NAME + key, hashKey, value);
就会出现如下错误:
java.lang.ExceptionInInitializerError
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:252)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:58)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:178)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:153)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:86)
at org.springframework.data.redis.core.DefaultHashOperations.put(DefaultHashOperations.java:169)
.........
java.lang.NullPointerException
at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:455)
at org.springframework.data.redis.connection.jedis.JedisConnection.<clinit>(JedisConnection.java:108)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:252)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:58)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:178)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:153)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:86)
at org.springframework.data.redis.core.DefaultHashOperations.put(DefaultHashOperations.java:169)
查源码时发现在进行spring-data-redis-1.5.0.RELEASE.jar包下的JedisConnection类中静态块执行的时候失败报出NullPointerException,源码如下所示:
static {
CLIENT_FIELD = ReflectionUtils.findField(BinaryJedis.class, "client", Client.class);
ReflectionUtils.makeAccessible(CLIENT_FIELD);
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand", new Class[] { Command.class,
byte[][].class });
ReflectionUtils.makeAccessible(SEND_COMMAND);
GET_RESPONSE = ReflectionUtils.findMethod(Queable.class, "getResponse", Builder.class);
ReflectionUtils.makeAccessible(GET_RESPONSE);
}
发现在反射获取Connection的sendCommand方法授权时,出现错误,通过查源码发现问题出现在了jedis-2.7.3.jar、jedis-2.7.2.jar 中的Connection的sendCommand的方法,2.7.2对该方法的Command参数进行了升级,与jedis-2.7.3又将2.7.2的改进还原了,从而不兼容导致初始化错误的出现。
其中jedis-2.7.3.jar的 Connection源码如下所示:
protected Connection sendCommand(final Command cmd, final String... args) {
final byte[][] bargs = new byte[args.length][];
for (int i = 0; i < args.length; i++) {
bargs[i] = SafeEncoder.encode(args[i]);
}
return sendCommand(cmd, bargs);
}
public static enum Command {
PING,.....;
public final byte[] raw;
Command() {
raw = SafeEncoder.encode(this.name());
}
}
其中jedis-2.7.2.jar的 Connection源码如下所示:
protected Connection sendCommand(final ProtocolCommand cmd, final String... args) {
final byte[][] bargs = new byte[args.length][];
for (int i = 0; i < args.length; i++) {
bargs[i] = SafeEncoder.encode(args[i]);
}
return sendCommand(cmd, bargs);
}
public static enum Command implements ProtocolCommand {
PING,..... ERGE;
private final byte[] raw;
Command() {
raw = SafeEncoder.encode(this.name());
}
@Override
public byte[] getRaw() {
return raw;
}
}
先说解决办法将spring-data-redis升级到spring-data-redis-1.6.0.RELEASE.jar,其中JedisConnection的静态块如下所示:
static {
CLIENT_FIELD = ReflectionUtils.findField(BinaryJedis.class, "client", Client.class);
ReflectionUtils.makeAccessible(CLIENT_FIELD);
try {
Class<?> commandType = ClassUtils.isPresent("redis.clients.jedis.ProtocolCommand", null) ? ClassUtils.forName(
"redis.clients.jedis.ProtocolCommand", null) : ClassUtils.forName("redis.clients.jedis.Protocol$Command",
null);
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand", new Class[] { commandType,
byte[][].class });
} catch (Exception e) {
throw new NoClassDefFoundError(
"Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'.");
}
ReflectionUtils.makeAccessible(SEND_COMMAND);
GET_RESPONSE = ReflectionUtils.findMethod(Queable.class, "getResponse", Builder.class);
ReflectionUtils.makeAccessible(GET_RESPONSE);
}
关键一点在于反射调用sendCommand方法时对方法的参数进行了类型判断从而防止了错误的产生。
注:经猜测最终原因可能是jetty在进行类实例化时使用的jedis-2.7.2.jar但是在真正调用时却使用的jedis-2.7.3.jar从而导致错误的产生!