表示好久没写blog了啊,上次写已经是去年的事情了啊!!!
还是自己的项目, 不过这次用到了redis,然后用到了jedis,然后用到了pool,于是得想个简单的方式封装下
为什么要封装 因为如果不封装,那么如果用connection pool的话,那么代码需要这么写
1 2 3 4 5 6 7 8 9 10 11 12 ShardedJedisPool pool = new ShardedJedisPool(new Config(), shards); ShardedJedis jedis = pool.getResource(); jedis.set("a" , "foo" ); .... pool.returnResource(jedis); .... ShardedJedis jedis2 = pool.getResource(); jedis.set("z" , "bar" ); pool.returnResource(jedis); pool.destroy();
也就是说每一句对 redis 的操作我前后都要加上 pool.getResource 和 pool.returnResource
嗯,这么干显然是太2了
关于pool pool 帮我干了这些事情
建立了 n 个没有 close 的 connection
然后还有一些管理 connection 的策略,然后,没了。至少 jedis 的源码就是这么简单, 表示他直接用了apache pool, 没有定义额外功能。
于是,如果光拿 connection 不还,则会导致第超过 MaxCount 个请求想要连接的时候就会 block 住,直到别人还 connection 。。。
正题 => 封装 怎么封装,以及封装者
与使用者
的感受。。。
java的传统封装方式 - 动态代理 - 用的爽,封的蛋疼 表示这算是一种正统的封装方式。原理就是动态调用method,并在前后增加重复逻辑。
但是由于封装出的对象和需要代理的对象必须出自一个接口
但是
1 2 3 4 5 public class Jedis extends BinaryJedis implements JedisCommands , MultiKeyCommands , AdvancedJedisCommands , ScriptingCommands , BasicCommands , ClusterCommands { }
jedis implements 太多的接口,尼玛!
方法1. 残缺方案,(仅仅封装用的到的)比如JedisCommands
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public JedisCommands redis () { return (JedisCommands) Proxy.newProxyInstance(this .getClass().getClassLoader(), new Class[]{JedisCommands.class}, new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Jedis j = RedisInner.pool().getResource(); try { return method.invoke(j, args); } catch (Exception e) { throw e.getCause(); }finally { RedisInner.pool().returnResourceObject(j); } } }); } public static void main (String[] args) { JedisCommands s = new Rredis().redis(); System.out.println(s); System.out.println(s.set("aa" , "13" )); System.out.println(s.get("aa" )); }
不过这样的话,别的指令不能用了。
方法2. 方法1的扩展,既然 jedis 继承了6个接口,那我封6个代理对象不就得了!
(你愿意我也不拦你)
方法3. 究极方案(也是最烦的),自己再封装一个 Jedis class 和 interface,囊括那6个。
(要写好多好多代码,你愿意我也不拦你)
python 的封装方式 with (举例子而已..) 1 2 3 4 5 6 7 8 9 10 11 12 from contextlib import contextmanager@contextmanager def jedis () : j = RedisInner.pool.getResource try : yield j finally : RedisInner.pool.returnResourceObject(j) with jedis() as j: j.set("aa" ,"12" )
这个封装的很爽,只不过每次都要包 with
表示 scala 也可以用 with!!!
scala’s with 参考了这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def withJedis [T ](body: Jedis => T ): T = { val jedis: Jedis = RedisInner .pool.getResource try { body(jedis) } finally { RedisInner .pool.returnResourceObject(jedis) } } withJedis { c => println(c.set("aa" , "1" )) println(c.get("aa" )) }
表示一样好用到爆啊!!! 我自己项目就用的这种
还看到一种 封装者要花点力气的方案 (不是花一点点)
https://github.com/top10/scala-redis-client/blob/master/src/main/scala/com/top10/redis/SingleRedis.scala
表示写了 200+行代码其实就代理了 JedisCommands 和 BasicCommands 2个接口,好吧,你是劳模
最后,我蛋疼的将 java create proxy 的代码硬是翻译成了 scala, 又发现一个坑!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def jedis (): JedisCommands = { java.lang.reflect.Proxy .newProxyInstance( Redis .getClass.getClassLoader, Array (classOf[JedisCommands ], classOf[BasicCommands ]), new InvocationHandler { override def invoke (proxy: Object , method: Method , args: Array [Object ]): Object = { val j = RedisInner .pool.getResource try { return method.invoke(j, args:_*) } catch { case e: Exception => { logger.error(e.getMessage, e) throw e.getCause } } finally { RedisInner .pool.returnResourceObject(j) } null } } ).asInstanceOf[JedisCommands ] } def main (args: Array [String ]): Unit = { println(jedis().set("aa" , "14" )) println(jedis().get("aa" )) }
对比下这个, 表示数组入参又是一神坑!也是醉了
1 2 3 4 method.invoke(j, args); method.invoke(j, args:_*)
The invoke method takes a varargs parameter. In Scala (unlike Java), you can't just pass an array and have it automatically treated as all the arguments
可以参考下这里