从第二章知道了 jedis 本身并没有直接与 redis 通信操作,而通过创建 Client 调用其方法实现。所以这章节就来看看,Client 类的源码。
1、Client 类主要继承了 BinaryClient 和实现了Commands,BinaryClient 继承的 Connection 主要实现对 redis 的连接操作处理。
2、首先看下 Client 构造方法逻辑。
public Client() {
super();
}
public Client(final String host) {
super(host);
}
public Client(final String host, final int port) {
super(host, port);
}
public Client(final String host, final int port, final boolean ssl) {
super(host, port, ssl);
}
public Client(final String host, final int port, final boolean ssl,
final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
final HostnameVerifier hostnameVerifier) {
super(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
}
源码可以看出 Client 并不保存任何信息,都是直接调用父类构造方法,接下来看下 BinaryClient 的构造方法:
private boolean isInMulti;
private String password;
private long db;
private boolean isInWatch;
public BinaryClient() {
super();
}
public BinaryClient(final String host) {
super(host);
}
public BinaryClient(final String host, final int port) {
super(host, port);
}
public BinaryClient(final String host, final int port, final boolean ssl) {
super(host, port, ssl);
}
public BinaryClient(final String host, final int port, final boolean ssl,
final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
final HostnameVerifier hostnameVerifier) {
super(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
}
可以看到 BinaryClient 也没有任何操作,直接调用父类 Connection 类的构造方法:
private static final byte[][] EMPTY_ARGS = new byte[0][];
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private Socket socket;
private RedisOutputStream outputStream;
private RedisInputStream inputStream;
private int pipelinedCommands = 0;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
private boolean broken = false;
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private HostnameVerifier hostnameVerifier;
public Connection() {
}
public Connection(final String host) {
this.host = host;
}
public Connection(final String host, final int port) {
this.host = host;
this.port = port;
}
public Connection(final String host, final int port, final boolean ssl) {
this.host = host;
this.port = port;
this.ssl = ssl;
}
public Connection(final String host, final int port, final boolean ssl,
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier) {
this.host = host;
this.port = port;
this.ssl = ssl;
this.sslSocketFactory = sslSocketFactory;
this.sslParameters = sslParameters;
this.hostnameVerifier = hostnameVerifier;
}
这里就可以知道了,整个连接信息是有 Connection 来保存处理的。先看下 Connection 的 connect 方法:
public void connect() {
if (!isConnected()) {
try {
socket = new Socket();
// ->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
if (ssl) {
if (null == sslSocketFactory) {
sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
socket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, true);
if (null != sslParameters) {
((SSLSocket) socket).setSSLParameters(sslParameters);
}
if ((null != hostnameVerifier) &&
(!hostnameVerifier.verify(host, ((SSLSocket) socket).getSession()))) {
String message = String.format(
"The connection to '%s' failed ssl/tls hostname verification.", host);
throw new JedisConnectionException(message);
}
}
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException("Failed connecting to host "
+ host + ":" + port, ex);
}
}
}
其实没有多复杂,就使用 socket 和 redis 进行连接通信,没什么复杂的。
3、接下来看下 Client 中实现 Commands 的方法。
@Override
public void get(final String key) {
get(SafeEncoder.encode(key));
}
@Override
public void exists(final String key) {
exists(SafeEncoder.encode(key));
}
这里就以这两个方法来说,其实里面都是这样的,Client 都是直接调用继承 BinaryClient 中实现的方法。所以可以直接去 BinaryClient 看下实现的逻辑:
public void ping() {
sendCommand(Command.PING);
}
public void set(final byte[] key, final byte[] value) {
sendCommand(Command.SET, key, value);
}
public void set(final byte[] key, final byte[] value, final byte[] nxxx, final byte[] expx,
final long time) {
sendCommand(Command.SET, key, value, nxxx, expx, toByteArray(time));
}
public void get(final byte[] key) {
sendCommand(Command.GET, key);
}
从上面的方法可以知道 BinaryClient 也没有做什么事情,直接调用了继承 Connection 中的 sendCommand 方法。所以我们可以直接去 Connection 中 sendCommand 方法:
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);
}
protected Connection sendCommand(final Command cmd) {
return sendCommand(cmd, EMPTY_ARGS);
}
protected Connection sendCommand(final Command cmd, final byte[]... args) {
try {
connect();
Protocol.sendCommand(outputStream, cmd, args);
pipelinedCommands++;
return this;
} catch (JedisConnectionException ex) {
/*
* When client send request which formed by invalid protocol, Redis send back error message
* before close connection. We try to read it to provide reason of failure.
*/
try {
String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
if (errorMessage != null && errorMessage.length() > 0) {
ex = new JedisConnectionException(errorMessage, ex.getCause());
}
} catch (Exception e) {
/*
* Catch any IOException or JedisConnectionException occurred from InputStream#read and just
* ignore. This approach is safe because reading error message is optional and connection
* will eventually be closed.
*/
}
// Any other exceptions related to connection?
broken = true;
throw ex;
}
}
这里可以看到这里逻辑处理,将 key、value 等各种信息转换成 byte 放在 byte 数组中,Protocol 工具类来封装执行请求到 redis。
接下来看下 Protocol 类中封装方法逻辑:
public static void sendCommand(final RedisOutputStream os, final Command command,
final byte[]... args) {
sendCommand(os, command.raw, args);
}
private static void sendCommand(final RedisOutputStream os, final byte[] command,
final byte[]... args) {
try {
os.write(ASTERISK_BYTE);
os.writeIntCrLf(args.length + 1);
os.write(DOLLAR_BYTE);
os.writeIntCrLf(command.length);
os.write(command);
os.writeCrLf();
for (final byte[] arg : args) {
os.write(DOLLAR_BYTE);
os.writeIntCrLf(arg.length);
os.write(arg);
os.writeCrLf();
}
} catch (IOException e) {
throw new JedisConnectionException(e);
}
}
可以看到直接使用流输出到 redis,并没有很复杂的逻辑。其中还有很多方法可以到源码中看。