深入剖析J2Cache

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介:

最近看到红薯的J2Cache强大到不行,居然长期占据开源中国开源项目排行榜,偶就气不打一处来。
话说你是开源中国第一帅,这个咱们大家有共识,确实实力在那里,我们都认了。
话说你口才比@永和 好,这个只要永和没有意见,我们也同意。
但是,做个J2Cache居然还悬赏好多次,貌似要打造成开源中国第一开源项目,这就有点过分了。不对,不是过分,是相当过分。
所以今天,偶就狠狠的扒掉@红薯 的内裤,对J2Cache进行一下深入剖析。

前面写过一篇文章,标题是吐槽一下J2Cache,吐槽过后发现J2Cache的热度居然火速上升,貌似有成为开源中国第一开源项目的意思,偶这小心脏就有点受不了了,于是决定再写一篇文章,直接狠一点把@红薯 的内裤扒掉,对J2Cache进行一下深入剖析。

Cache接口

?
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
  * Implementors define a caching algorithm. All implementors
  * <b>must</b> be threadsafe.
  * @author liudong
  */
public interface Cache {
 
     /**
      * Get an item from the cache, nontransactionally
      * @param key cache key
      * @return the cached object or null
      */
     public Object get(Object key) throws CacheException;
     
     /**
      * Add an item to the cache, nontransactionally, with
      * failfast semantics
      * @param key cache key
      * @param value cache value
      */
     public void put(Object key, Object value) throws CacheException;
     
     /**
      * Add an item to the cache
      * @param key cache key
      * @param value cache value
      */
     public void update(Object key, Object value) throws CacheException;
 
     @SuppressWarnings ( "rawtypes" )
     public List keys() throws CacheException ;
     
     /**
      * @param key Cache key
      * Remove an item from the cache
      */
     public void evict(Object key) throws CacheException;
     
     /**
      * Batch remove cache objects
      * @param keys the cache keys to be evicted
      */
     @SuppressWarnings ( "rawtypes" )
     public void evict(List keys) throws CacheException;
     
     /**
      * Clear the cache
      */
     public void clear() throws CacheException;
     
     /**
      * Clean up
      */
     public void destroy() throws CacheException;
     
}
这个没有什么问题,不管谁来做,大致也是这个样子的,所以到这里来说,还是非常不错的。

但是实际上也有改进的余地,比如把Cache换成Cache<KeyType>,这样

?
1
public Object get(Object key) throws CacheException;
就可以变成:
?
1
public <T> T get(KeyType key) throws CacheException;
这样有个好处,就是可以避免大量的强制类型转换,另外对于不支持Object类型的缓冲框架来说,就可以文件的实现类中如下实现:
?
1
2
3
4
5
public Class XxxCache implements Cache<String){
     public <T> T get(String key) throws CacheException{
     ......
     }
}
而不用加大量的类型强力转,当然这里是个细节问题。

CacheObject类

它的内容如下:

?
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
28
29
30
31
32
33
34
35
36
/**
  * 所获取的缓存对象
  * @author winterlau
  */
public class CacheObject {
 
     private String region;
     private Object key;
     private Object value;
     private byte level;
     public String getRegion() {
         return region;
     }
     public void setRegion(String region) {
         this .region = region;
     }
     public Object getKey() {
         return key;
     }
     public void setKey(Object key) {
         this .key = key;
     }
     public Object getValue() {
         return value;
     }
     public void setValue(Object value) {
         this .value = value;
     }
     public byte getLevel() {
         return level;
     }
     public void setLevel( byte level) {
         this .level = level;
     }
     
}

这个类,从作者的本意来说是增加了一个描述所处理缓冲对象的所在级别及区域相关的对象,但是我感觉,这个类是完全不必要的,而且正是由于它的存在导致后续围绕它的处理都是不必要的。

那么我们来看看它是怎么被使用的:

在J2HibernateCache类中


?
1
2
3
4
5
6
7
public Object get(Object key) throws CacheException {
     CacheObject cobj = cache.get(region, key);
     if (log.isDebugEnabled()) {
         log.debug( "get value for j2cache which key:" + key + ",value:" + cobj.getValue());
     }
     return cobj.getValue();
}



看到没,前面折腾了半天,还是把CacheObject中的value属性搞出来的,这不是脱裤子放屁,多了一次手续么?

我们来搜索一下使用CacheObject类的地方:

目光所及,点进去看,居然没有看到什么特别的地方,因此问题依然是:这个类有存在的必要么?

CacheChannel接口

这个时候又看到一个接口,叫CacheChannel,内容如下:

?
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
  * Cache Channel
  * @author winterlau
  */
public interface CacheChannel {
 
     public final static byte LEVEL_1 = 1 ;
     public final static byte LEVEL_2 = 2 ;
     
     /**
      * 获取缓存中的数据
      * @param region: Cache Region name
      * @param key: Cache key
      * @return cache object
      */
     public CacheObject get(String region, Object key);
     
     /**
      * 写入缓存
      * @param region: Cache Region name
      * @param key: Cache key
      * @param value: Cache value
      */
     public void set(String region, Object key, Object value);
 
     /**
      * 删除缓存
      * @param region:  Cache Region name
      * @param key: Cache key
      */
     public void evict(String region, Object key) ;
 
     /**
      * 批量删除缓存
      * @param region: Cache region name
      * @param keys: Cache key
      */
     @SuppressWarnings ({ "rawtypes" })
     public void batchEvict(String region, List keys) ;
 
     /**
      * Clear the cache
      * @param region: Cache region name
      */
     public void clear(String region) throws CacheException ;
     
     /**
      * Get cache region keys
      * @param region: Cache region name
      * @return key list
      */
     @SuppressWarnings ( "rawtypes" )
     public List keys(String region) throws CacheException ;
 
     /**
      * 关闭到通道的连接
      */
     public void close() ;
}
这个接口,就有点疑问,如果说它只是一个通道的话,为什么它还有与缓冲相关的get和set方法?在这里,可以看到它的许多方法都增加了region的参数,表明它是支持区域的一些缓冲处理。

另外看到close方法的时候,感觉这个接口的设置可能定位有改进的余地,因为它把通道层的和缓冲相关的接口混在一个里面了,表面看看问题不大,实际上在实现时会带来一些不爽,同时可能会诱导开发人员做了不合理的实现。或许这个方法应该放在CacheProvider当中去?

CacheProvider

?
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
28
29
30
31
32
33
34
35
36
37
38
/**
  * Support for pluggable caches.
  * @author liudong
  */
public interface CacheProvider {
 
     /**
      * 缓存的标识名称
      * @return return cache provider name
      */
     public String name();
     
     /**
      * Configure the cache
      *
      * @param regionName the name of the cache region
      * @param autoCreate autoCreate settings
      * @param listener listener for expired elements
      * @return return cache instance
      * @throws CacheException cache exception
      */
     public Cache buildCache(String regionName, boolean autoCreate, CacheExpiredListener listener) throws CacheException;
 
     /**
      * Callback to perform any necessary initialization of the underlying cache implementation
      * during SessionFactory construction.
      *
      * @param props current configuration settings.
      */
     public void start(Properties props) throws CacheException;
 
     /**
      * Callback to perform any necessary cleanup of the underlying cache implementation
      * during SessionFactory.close().
      */
     public void stop();
     
}
这个类实际上就是个工厂类,用于创建Cache实例,问题不大。

CacheTester

?
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
  * 缓存测试入口
  * @author Winter Lau
  */
public class CacheTester {
 
     public static void main(String[] args) {
         
         System.setProperty( "java.net.preferIPv4Stack" , "true" ); //Disable IPv6 in JVM
         
         CacheChannel cache = J2Cache.getChannel();
         BufferedReader in= new BufferedReader( new InputStreamReader(System.in));
 
         do {
             try {
                 System.out.print( "> " );
                 System.out.flush();
                 
                 String line=in.readLine().trim();
                 if (line.equalsIgnoreCase( "quit" ) || line.equalsIgnoreCase( "exit" ))
                     break ;
 
                 String[] cmds = line.split( " " );
                 if ( "get" .equalsIgnoreCase(cmds[ 0 ])){
                     CacheObject obj = cache.get(cmds[ 1 ], cmds[ 2 ]);
                     System.out.printf( "[%s,%s,L%d]=>%s\n" , obj.getRegion(), obj.getKey(), obj.getLevel(), obj.getValue());
                 }
                 else
                 if ( "set" .equalsIgnoreCase(cmds[ 0 ])){
                     cache.set(cmds[ 1 ], cmds[ 2 ],cmds[ 3 ]);
                     System.out.printf( "[%s,%s]<=%s\n" ,cmds[