最近看到红薯的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;
|
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类的地方:
目光所及,点进去看,居然没有看到什么特别的地方,因此问题依然是:这个类有存在的必要么?
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() ;
}
|
另外看到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();
}
|
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[
1
], cmds[
2
], cmds[
3
]);
}
else
if
(
"evict"
.equalsIgnoreCase(cmds[
0
])){
cache.evict(cmds[
1
], cmds[
2
]);
System.out.printf(
"[%s,%s]=>null\n"
,cmds[
1
], cmds[
2
]);
}
else
if
(
"clear"
.equalsIgnoreCase(cmds[
0
])){
cache.clear(cmds[
1
]);
System.out.printf(
"Cache [%s] clear.\n"
, cmds[
1
]);
}
else
if
(
"help"
.equalsIgnoreCase(cmds[
0
])){
printHelp();
}
else
{
System.out.println(
"Unknown command."
);
printHelp();
}
}
catch
(ArrayIndexOutOfBoundsException e) {
System.out.println(
"Wrong arguments."
);
printHelp();
}
catch
(Exception e) {
e.printStackTrace();
}
}
while
(
true
);
cache.close();
System.exit(
0
);
}
private
static
void
printHelp() {
System.out.println(
"Usage: [cmd] region key [value]"
);
System.out.println(
"cmd: get/set/evict/quit/exit/help"
);
}
}
|
同样的还有Command类,
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
public
class
Command {
private
final
static
Logger log = LoggerFactory.getLogger(Command.
class
);
private
final
static
int
SRC_ID = genRandomSrc();
//命令源标识,随机生成
public
final
static
byte
OPT_DELETE_KEY =
0x01
;
//删除缓存
public
final
static
byte
OPT_CLEAR_KEY =
0x02
;
//清除缓存
private
int
src;
private
byte
operator;
private
String region;
private
Object key;
private
static
int
genRandomSrc() {
long
ct = System.currentTimeMillis();
Random rnd_seed =
new
Random(ct);
return
(
int
)(rnd_seed.nextInt(
10000
) *
1000
+ ct %
1000
);
}
public
static
void
main(String[] args) {
for
(
int
i=
0
;i<
5
;i++){
Command cmd =
new
Command(OPT_DELETE_KEY,
"users"
,
"ld"
+i);
byte
[] bufs = cmd.toBuffers();
System.out.print(cmd.getSrc() +
":"
);
for
(
byte
b : bufs){
System.out.printf(
"[%s]"
,Integer.toHexString(b));
}
System.out.println();
Command cmd2 = Command.parse(bufs);
System.out.printf(
"%d -> %d:%s:%s(%s)\n"
, cmd2.getSrc(), cmd2.getOperator(), cmd2.getRegion(), cmd2.getKey(), cmd2.isLocalCommand());
}
}
public
Command(
byte
o, String r, Object k){
this
.operator = o;
this
.region = r;
this
.key = k;
this
.src = SRC_ID;
}
public
byte
[] toBuffers(){
byte
[] keyBuffers =
null
;
try
{
keyBuffers = SerializationUtils.serialize(key);
}
catch
(IOException e) {
e.printStackTrace();
return
null
;
}
int
r_len = region.getBytes().length;
int
k_len = keyBuffers.length;
byte
[] buffers =
new
byte
[
9
+ r_len + k_len];
System.arraycopy(int2bytes(
this
.src),
0
, buffers,
0
,
4
);
int
idx =
4
;
buffers[idx] = operator;
buffers[++idx] = (
byte
)(r_len >>
8
);
buffers[++idx] = (
byte
)(r_len &
0xFF
);
System.arraycopy(region.getBytes(),
0
, buffers, ++idx, r_len);
idx += r_len;
buffers[idx++] = (
byte
)(k_len >>
8
);
buffers[idx++] = (
byte
)(k_len &
0xFF
);
System.arraycopy(keyBuffers,
0
, buffers, idx, k_len);
return
buffers;
}
public
static
Command parse(
byte
[] buffers) {
Command cmd =
null
;
try
{
int
idx =
4
;
byte
opt = buffers[idx];
int
r_len = buffers[++idx] <<
8
;
r_len += buffers[++idx];
if
(r_len >
0
){
String region =
new
String(buffers, ++idx, r_len);
idx += r_len;
int
k_len = buffers[idx++] <<
8
;
k_len += buffers[idx++];
if
(k_len >
0
){
//String key = new String(buffers, idx, k_len);
byte
[] keyBuffers =
new
byte
[k_len];
System.arraycopy(buffers, idx, keyBuffers,
0
, k_len);
Object key = SerializationUtils.deserialize(keyBuffers);
cmd =
new
Command(opt, region, key);
cmd.src = bytes2int(buffers);
}
}
}
catch
(Exception e){
log.error(
"Unabled to parse received command."
, e);
}
return
cmd;
}
private
static
byte
[] int2bytes(
int
i) {
byte
[] b =
new
byte
[
4
];
b[
0
] = (
byte
) (
0xff
&i);
b[
1
] = (
byte
) ((
0xff00
&i) >>
8
);
b[
2
] = (
byte
) ((
0xff0000
&i) >>
16
);
b[
3
] = (
byte
) ((
0xff000000
&i) >>
24
);
return
b;
}
private
static
int
bytes2int(
byte
[] bytes) {
int
num = bytes[
0
] &
0xFF
;
num |= ((bytes[
1
] <<
8
) &
0xFF00
);
num |= ((bytes[
2
] <<
16
) &
0xFF0000
);
num |= ((bytes[
3
] <<
24
) &
0xFF000000
);
return
num;
}
public
boolean
isLocalCommand() {
return
this
.src == SRC_ID;
}
public
byte
getOperator() {
return
operator;
}
public
void
setOperator(
byte
operator) {
this
.operator = operator;
}
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
int
getSrc() {
return
src;
}
}
|
1
2
3
4
5
|
private
static
int
genRandomSrc() {
long
ct = System.currentTimeMillis();
Random rnd_seed =
new
Random(ct);
return
(
int
)(rnd_seed.nextInt(
10000
) *
1000
+ ct %
1000
);
}
|
再比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
static
byte
[] int2bytes(
int
i) {
byte
[] b =
new
byte
[
4
];
b[
0
] = (
byte
) (
0xff
&i);
b[
1
] = (
byte
) ((
0xff00
&i) >>
8
);
b[
2
] = (
byte
) ((
0xff0000
&i) >>
16
);
b[
3
] = (
byte
) ((
0xff000000
&i) >>
24
);
return
b;
}
private
static
int
bytes2int(
byte
[] bytes) {
int
num = bytes[
0
] &
0xFF
;
num |= ((bytes[
1
] <<
8
) &
0xFF00
);
num |= ((bytes[
2
] <<
16
) &
0xFF0000
);
num |= ((bytes[
3
] <<
24
) &
0xFF000000
);
return
num;
}
|
这个类的许多算法,也比较奇葩,个人觉得不用这么复杂,可以用更简单的方式来实现。
J2Cache
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
|
public
class
J2Cache {
private
final
static
Logger log = LoggerFactory.getLogger(J2Cache.
class
);
private
final
static
String CONFIG_FILE =
"/j2cache.properties"
;
private
final
static
CacheChannel channel;
private
final
static
Properties config;
static
{
try
{
config = loadConfig();
String cache_broadcast = config.getProperty(
"cache.broadcast"
);
if
(
"redis"
.equalsIgnoreCase(cache_broadcast))
channel = RedisCacheChannel.getInstance();
else
if
(
"jgroups"
.equalsIgnoreCase(cache_broadcast))
channel = JGroupsCacheChannel.getInstance();
else
throw
new
CacheException(
"Cache Channel not defined. name = "
+ cache_broadcast);
}
catch
(IOException e) {
throw
new
CacheException(
"Unabled to load j2cache configuration "
+ CONFIG_FILE, e);
}
}
public
static
CacheChannel getChannel(){
return
channel;
}
public
static
Properties getConfig(){
return
config;
}
/**
* 加载配置
* @return
* @throws IOException
*/
private
static
Properties loadConfig()
throws
IOException {
log.info(
"Load J2Cache Config File : [{}]."
, CONFIG_FILE);
InputStream configStream = J2Cache.
class
.getClassLoader().getParent().getResourceAsStream(CONFIG_FILE);
if
(configStream ==
null
)
configStream = CacheManager.
class
.getResourceAsStream(CONFIG_FILE);
if
(configStream ==
null
)
throw
new
CacheException(
"Cannot find "
+ CONFIG_FILE +
" !!!"
);
Properties props =
new
Properties();
try
{
props.load(configStream);
}
finally
{
configStream.close();
}
return
props;
}
}
|
第一、它是个单实例的类,也就是说无法启动多个实例。但然也不能说这样就不行,但是如果我的应用场景中需要在不同场景用不同的实例呢?就无法满足了。
第二、它和具体的实现相关:
1
2
3
4
5
6
7
|
String cache_broadcast = config.getProperty(
"cache.broadcast"
);
if
(
"redis"
.equalsIgnoreCase(cache_broadcast))
channel = RedisCacheChannel.getInstance();
else
if
(
"jgroups"
.equalsIgnoreCase(cache_broadcast))
channel = JGroupsCacheChannel.getInstance();
else
throw
new
CacheException(
"Cache Channel not defined. name = "
+ cache_broadcast);
|
第三、命名的不合理性
本来从名字和定位来看,我觉得它应该是Cache或CacheChannel的一个实现类,结果发现它不是,它居然只是起个工厂类作用的类。
第四、config的处理
Config居然是由J2Cache读进来,然后让别的类来获取的,居然是这样实现的???这会导致非常严重的问题,依赖关系势必出现混乱,实际上也会出现循环依赖的问题,当然@红薯 的解决方案就是都放在一个工程里面,循环依赖的问题就没有了,但是就会出现其它的问题,后续再来说明。
好的,上面就把J2Cache应用根目录中的内容都走马观花看了一遍,当然只还只算扒了一下外套,现在开始慢慢扒内裤。
产品的说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
J2Cache —— 基于 Ehcache 和 Redis 实现的两级 Java 缓存框架
J2Cache 是 OSChina 目前正在使用的两级缓存框架。第一级缓存使用 Ehcache,第二级缓存使用 Redis 。由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的 Ehcache 缓存数据丢失。
J2Cache 从 1.3.0 版本开始支持 JGroups 和 Redis Subscribe 两种方式进行缓存时间的通知。在某些云平台上可能无法使用 JGroups 组播方式,可以采用 Redis 发布订阅的方式。详情请看 j2cache.properties 配置文件的说明。
视频介绍:http://v.youku.com/v_show/id_XNzAzMTY5MjUy.html
该项目提供付费咨询服务,详情请看:https://zb.oschina.net/market/opus/12_277
J2Cache 的两级缓存结构
L1: 进程内缓存(ehcache)
L2: 集中式缓存,支持多种集中式缓存服务器,如 Redis、Memcached 等
由于大量的缓存读取会导致 L2 的网络带宽成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数
|
我来看看看它的依赖树:
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] j2cache
[INFO] j2cache-core
[INFO] j2cache-hibernate3
[INFO] j2cache-hibernate4
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache ---
[INFO] net.oschina.j2cache:j2cache:pom:1.3.0
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] | +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] | \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] | \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] | \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache-core 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-core ---
[INFO] net.oschina.j2cache:j2cache-core:jar:1.3.0
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] | +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] | \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] | \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] | \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] Building j2cache-hibernate3 1.3.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-hibernate3 ---
[INFO] net.oschina.j2cache:j2cache-hibernate3:jar:1.3.0
[INFO] +- net.oschina.j2cache:j2cache-core:jar:1.3.0:compile
[INFO] +- org.hibernate:hibernate-core:jar:3.3.2.GA:compile
[INFO] | +- antlr:antlr:jar:2.7.6:compile
[INFO] | +- commons-collections:commons-collections:jar:3.1:compile
[INFO] | +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | | \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] | \- javax.transaction:jta:jar:1.1:compile
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] | +- org.javassist:javassist:jar:3.19.0-GA:compile
[INFO] | \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] | \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] | \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building j2cache-hibernate4 1.3.0
[INFO] ------------------------------------------------------------------------
[WARNING] The POM for avalon-framework:avalon-framework-api:jar:4.1.5-dev is missing, no dependency information available
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ j2cache-hibernate4 ---
[INFO] net.oschina.j2cache:j2cache-hibernate4:jar:1.3.0
[INFO] +- net.oschina.j2cache:j2cache-core:jar:1.3.0:compile
[INFO] +- org.hibernate:hibernate-core:jar:4.3.5.Final:compile
[INFO] | +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
[INFO] | +- org.jboss.logging:jboss-logging-annotations:jar:1.2.0.Beta1:compile
[INFO] | +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:jar:1.0.0.Final:compile
[INFO] | +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | | \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] | +- org.hibernate.common:hibernate-commons-annotations:jar:4.0.4.Final:compile
[INFO] | +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] | +- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] | +- antlr:antlr:jar:2.7.7:compile
[INFO] | \- org.jboss:jandex:jar:1.1.0.Final:compile
[INFO] +- org.hibernate:hibernate-ehcache:jar:4.3.5.Final:compile
[INFO] +- com.cloudhopper.proxool:proxool:jar:0.9.1:test
[INFO] | \- avalon-framework:avalon-framework-api:jar:4.3:test (version selected from constraint [4.1.5,))
[INFO] | \- avalon-logkit:avalon-logkit:jar:2.1:test
[INFO] | +- javax.servlet:servlet-api:jar:2.3:test
[INFO] | +- geronimo-spec:geronimo-spec-javamail:jar:1.3.1-rc3:test
[INFO] | \- geronimo-spec:geronimo-spec-jms:jar:1.1-rc4:test
[INFO] +- com.cloudhopper.proxool:proxool-cglib:jar:0.9.1:test
[INFO] +- org.springframework:spring-context:jar:4.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:4.1.0.RELEASE:compile
[INFO] | | \- aopalliance:aopalliance:jar:1.0:compile
[INFO] | +- org.springframework:spring-core:jar:4.1.0.RELEASE:compile
[INFO] | \- org.springframework:spring-expression:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-web:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-context-support:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-orm:jar:4.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-jdbc:jar:4.1.0.RELEASE:compile
[INFO] | \- org.springframework:spring-tx:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-beans:jar:4.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-test:jar:4.1.0.RELEASE:test
[INFO] +- net.sf.ehcache:ehcache-core:jar:2.6.11:compile
[INFO] +- org.jgroups:jgroups:jar:3.6.6.Final:compile
[INFO] +- org.slf4j:slf4j-api:jar:1.6.6:compile
[INFO] +- redis.clients:jedis:jar:2.8.0:compile
[INFO] +- org.apache.commons:commons-pool2:jar:2.4.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- de.ruedigermoeller:fst:jar:2.42:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.5.3:compile
[INFO] | \- org.objenesis:objenesis:jar:2.1:compile
[INFO] +- com.esotericsoftware:kryo-shaded:jar:3.0.0:compile
[INFO] | \- com.esotericsoftware:minlog:jar:1.3.0:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:test
[INFO] | \- log4j:log4j:jar:1.2.16:test
[INFO] +- org.aspectj:aspectjrt:jar:1.8.2:test
[INFO] +- org.aspectj:aspectjweaver:jar:1.8.2:test
[INFO] +- mysql:mysql-connector-java:jar:5.1.37:test
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] ------------------------------------------------------------------------
|
可以看到,上面的工程里面依赖了许许多多的包,这个时候就可能会引入大量的依赖冲突问题。另外,我只想用你这个东东,为什么还把hibernate引入了?
为什么必须用ehcache,虽然ehcache非常不错,但是如果把它变成可选件是可以接受的,变成必选件就要了命了。
同样的,这里面引入的许多内容从道理上都讲不通,比如:
1
|
redis.clients:jedis:jar:2.8.0:compile
|
当然有的同学又说了,“你觉得不好就别用,觉得好就用”,当然这是另外一个话题,和今天的话题不太相关,今天的任务是扒@红薯 内裤。
上一次我写吐槽J2Cache的时候,其实就有人说了,你吐槽了半天,然并卵,You can you up, no can no BB。话虽不太好听,但是还是有点道理的。
今天我就说说如果是我来设计J2Cache,那么它是怎么个样子的。
其实所有的问题都是可以解决的,而这些问题的根源就在于设计和实现的不合理导致的,那么下面就来讲讲悠然版J2Cache怎么设计。
悠然建议的J2Cache设计
呵呵,继续说一句悠然名言:你做得不够好,是因为你分得不够细。
工程一:Cache接口设计
这个工程啥实现也没有,就是一个Cache的接口,用来规范Cache访问规范,具体怎么写,不同人有不同的写法。工程二:
各种不同种类的Cache实现,比如:ehcache,jedis,memcache,JCS,等等,如果精力过剩,各种版本的都来一个也可以。当然,实际上这里每一个具体实现都是一个独立的工程了。
工程三:
J2Cache,它也是Cache接口的一个实现类,在它里面注入两个Cache实例,然后在处理逻辑上做两级缓冲的处理。
当然,如果你想要弄个CacheManager也是可以的,这个不是关键因素,随便都可以。
最后就贴一下Tiny自己的二级缓冲实现类:
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
public
class
MultiCache
implements
Cache{
/**
* 1级缓存
*/
Cache cacheFirst;
/**
* 2级缓存
*/
Cache cacheSecond;
public
MultiCache() {
}
public
MultiCache(Cache cacheFirst, Cache cacheSecond) {
this
.cacheFirst = cacheFirst;
this
.cacheSecond = cacheSecond;
}
public
Object get(String key) {
Object obj = cacheFirst.get(key);
if
(obj ==
null
) {
obj = cacheSecond.get(key);
cacheFirst.put(key, obj);
}
return
obj;
}
public
Object get(String group, String key) {
Object obj = cacheFirst.get(group ,key);
if
(obj ==
null
) {
obj = cacheSecond.get(group ,key);
cacheFirst.put(group,key, obj);
}
return
obj;
}
public
Object[] get(String[] keys) {
List<Object> objs =
new
ArrayList<Object>();
if
(keys !=
null
&& keys.length >
0
) {
for
(
int
i =
0
; i < keys.length; i++) {
objs.add(get(keys[i]));
}
}
return
objs.toArray();
}
public
Object[] get(String group, String[] keys) {
List<Object> objs =
new
ArrayList<Object>();
if
(keys !=
null
&& keys.length >
0
) {
for
(
int
i =
0
; i < keys.length; i++) {
objs.add(get(group ,keys[i]));
}
}
return
objs.toArray();
}
public
void
put(String key, Object object) {
cacheFirst.put(key ,object);
cacheSecond.put(key ,object);
}
public
void
putSafe(String key, Object object) {
cacheFirst.putSafe(key ,object);
cacheSecond.putSafe(key ,object);
}
public
void
put(String groupName, String key, Object object) {
cacheFirst.put(groupName ,key ,object);
cacheSecond.put(groupName ,key ,object);
}
public
void
remove(String key) {
cacheFirst.remove(key);
cacheSecond.remove(key);
}
public
void
remove(String group, String key) {
cacheFirst.remove(group ,key);
cacheSecond.remove(group ,key);
}
public
void
remove(String[] keys) {
cacheFirst.remove(keys);
cacheSecond.remove(keys);
}
public
void
remove(String group, String[] keys) {
cacheFirst.remove(group ,keys);
cacheSecond.remove(group ,keys);
}
}
|
同时,由于每种不同的缓冲,基于不同的缓冲框架或版本,也可以有多个实例存在,用户在使用的时候,也可以直接引用具体的缓冲实现就好,当然也可以自己根据接口实现一个就OK了,这样避免了大量不需要的Jar包的引入,对于工程化处理是非常有好处的。