最近需要封装一个redis连接工具,需要根据配置来初始化,但是可能是集群模式也可能是单机模式,如果用普通的方法写的话可能是这样的
public class RedisSingle {
public JedisPool jedisPool;
// 初始化
public void setValue(String key, int second, String value) {
Jedis resource = jedisPool.getResource();
resource.setex(key, second, value);
resource.close();
}
// 其他一些常用操作
}
public class RedisCluster {
public JedisCluster jedisCluster
// 初始化
public void setValue(String key, String value) {
jedisCluster.set(key, value);
}
// 其他一些常用操作
}
public class RedisClient {
private RedisSingle redisSingle;
private RedisCluster redisCluster;
public RedisClient getInstance() {
// 初始化
}
public void setValue(String key, String value) {
if(null == redisSingle) {
//...调用集群客户端
} else {
//...调用单机客户端
}
}
}
这样做的缺点显而易见,就是代码量比较大,一个方法需要实现三次,这个时候就可以使用代理模式来简化代码,Java中提供了动态代理的类,代理可以理解为是一个拦截器,在调用代理的对象的方法时进行拦截,进入invoke方法来调用相应的方法,这样代理的步骤其实就不那么复杂了,核心部分就下面三步:
// 1.构造方法里进行初始化对象
public RedisProxy(String config) {
//初始化为相应的子类
if(StringUtil.isNotBlank(config)) {
redisInterface = new RedisSingle(XXX);
} else {
redisInterface = new RedisCluster(XXX);
}
}
// 2.对对象进行代理
(RedisInterface) Proxy
.newProxyInstance(RedisInterface.class.getClassLoader(), new Class[]{RedisInterface.class},
new RedisProxy(config))
// 3.重写拦截的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(redisInterface, args);
}
具体的原理限于水平没有去仔细研究,回到我们的需求上,如果按照一般的动态代理好像还是要分别实现一下单机版和集群版的代码,然后再使用代理,比如下面这段代码
// 接口
public interface RedisInterface {
void setValue(String key, String value);
String getValue(String key) throws Exception;
}
// 单机版
public class RedisSingle implements RedisInterface {
private JedisPool jedisPool;
public RedisSingle(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
@Override
public void setValue(String key, String value){
Jedis resource = jedisPool.getResource();
resource.set(key, value);
resource.close();
}
@Override
public String getValue(String key){
Jedis resource = jedisPool.getResource();
String s = resource.get(key);
resource.close();
return s;
}
}
// 集群版
public class RedisCluster implements RedisInterface {
private RedisCluster redisCluster;
// 省略构造
@Override
public void setValue(String key, String value){
// 业务逻辑
}
@Override
public String getValue(String key) {
// 业务逻辑
}
}
// 代理
public class RedisProxy implements InvocationHandler {
private RedisInterface redisInterface;
// 省略构造
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(redisInterface, args);
}
/**
* 初始化一些配置
* @param config
*/
public RedisProxy(String config) {
//初始化为相应的子类
if(StringUtil.isNotBlank(config)) {
redisInterface = new RedisSingle(XXX);
} else {
redisInterface = new RedisCluster(XXX);
}
}
public static RedisInterface getInstance(String config) {
return (RedisInterface) Proxy
.newProxyInstance(RedisInterface.class.getClassLoader(), new Class[]{RedisInterface.class},
new RedisProxy(config));
}
}
// 调用一下试试
public class RedisTest {
public static void main(String[] args) throws Exception {
RedisInterface asd = RedisProxy.getInstance(config);
asd.setValue("asdadf", "Asdfasdf");
System.out.println(asd.getValue("asdadf"));
}
}
// 输出
Asdfasdf
Process finished with exit code 0
还有一种更简单的方法,但是通用性比较弱,因为他需要两个接口的方法名字段都相同,但是在一些特定的情况下还是可以用的。这里我为了方便用了lombok的注解和spring注解,也可以删掉注解,手动实现这些过程
// 定义一个接口分别集成JedisCommands, BinaryJedisCommands,用于代理
public interface RedisClient extends JedisCommands, BinaryJedisCommands {
// 这里不需要实现方法
}
// 配置类,也可以不用定义
@Data
@Configuration
public class RedisConfig {
// 注解的方式读取参数,会自动查找application.yml配置文件的redis.host的值
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private String port;
@Value("${redis.timeout}")
private String timeout;
@Value("${redis.password}")
private String password;
@Value("${redis.clusterNodes}")
private String clusterNodes;
}
// 代理类,核心逻辑
@Configuration
public class RedisFactory implements InvocationHandler {
// 定义两个真正调用方法的对象,jedisPool还需要getResource一下再执行
private JedisPool jedisPool;
private JedisCluster jedisCluster;
// 上面定义的redis配置类
private RedisConfig redisConfig;
// 将方法缓存起来,第二次调用的时候查找方便一些
private Map<RedisMethodKey, Method> cacheMap;
// 构造,私有的即可,因为只在下面的getRedisClient里使用
private RedisFactory(RedisConfig redisConfig) {
cacheMap = new LinkedHashMap<>();
this.redisConfig = redisConfig;
int maxAttempts = 3;
int timeout = 300;
int connectionTimeout = 300;
if (StringUtil.isNotBlank(redisConfig.getTimeout())) {
timeout = Integer.parseInt(redisConfig.getTimeout());
}
// 集群版
if (StringUtil.isNotBlank(redisConfig.getClusterNodes())) {
Set<HostAndPort> hostAndPorts = Arrays.stream(redisConfig.getClusterNodes().split(","))
.map(line -> {
String[] split = line.split(":");
return new HostAndPort(split[0], Integer.parseInt(split[1]));
}).collect(Collectors.toSet());
// 如果有密码
if (StringUtil.isNotBlank(redisConfig.getPassword())) {
jedisCluster = new JedisCluster(hostAndPorts, connectionTimeout, timeout, maxAttempts,
redisConfig.getPassword(), new GenericObjectPoolConfig());
} else {
// 如果没有密码
jedisCluster = new JedisCluster(hostAndPorts, connectionTimeout, timeout, maxAttempts,
new GenericObjectPoolConfig());
}
} else {
// 单机版
jedisPool = new JedisPool(redisConfig.getHost(), Integer.parseInt(redisConfig.getPort()));
}
}
// 这个是代理类的调用,在这里实际调用的是上面两个对象的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method myMethod = null;
RedisMethodKey redisMethodKey = new RedisMethodKey(method.getName(),
method.getParameterTypes());
if (cacheMap.containsKey(redisMethodKey)) {
myMethod = cacheMap.get(redisMethodKey);
}
if (StringUtil.isNotBlank(redisConfig.getClusterNodes())) {
if (myMethod == null) {
myMethod = MethodUtils
.getMatchingMethod(jedisCluster.getClass(), method.getName(), method.getParameterTypes());
cacheMap.put(redisMethodKey, myMethod);
}
return myMethod.invoke(jedisCluster, args);
} else {
Jedis jedis = jedisPool.getResource();
if (myMethod == null) {
// 因为没有直接实现方法,而且方法来自两个不同的接口,所以需要根据名称,类型等来找到method之后再invoke,不能直接invoke
myMethod = MethodUtils
.getMatchingMethod(jedis.getClass(), method.getName(), method.getParameterTypes());
cacheMap.put(redisMethodKey, myMethod);
}
try {
Object invoke = myMethod.invoke(jedis, args);
return invoke;
} finally {
jedis.close();
}
}
}
// 返回代理后的redisClient对象
@Bean(name = "redisClient")
public RedisClient getRedisClient(RedisConfig redisConfig) {
return (RedisClient) Proxy
.newProxyInstance(RedisClient.class.getClassLoader(), new Class[]{RedisClient.class},
new RedisFactory(redisConfig));
}
// 内部类,用于缓存方法
@Data
@AllArgsConstructor
class RedisMethodKey {
private String methodName;
private Class<?>[] methodType;
}
}
// 调用这里我用了Spring注解将redisClient注入到Spring里面了,可以直接用@Autowired声明调用就行了
到这就是我能想到的最简单的封装了,这里的写法跟其他的动态代理写法不太一样,主要有两点原因,第一是这个代理虽然同时只能代理一个类,但是他可以代理多个类,第二是将代理的过程放在代理类中进行了,对外只提供了一个getRedisClient方法,这样开发用起来的时候也会方便一些。