<dependency>
<groupId>net.oschina.j2cachegroupId>
<artifactId>j2cache-coreartifactId>
<version>2.8.4-releaseversion>
dependency>
<dependency>
<groupId>net.oschina.j2cachegroupId>
<artifactId>j2cache-spring-boot2-starterartifactId>
<version>2.8.0-releaseversion>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
application.yml
j2cache:
config-location: j2cache.properties
ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://www.ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="D:\ehcache" />
<defaultCache
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="60"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU" />
<cache
name="smscode"
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="10"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU" />
ehcache>
j2cache.properties
# 1级缓存
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml
# 2级缓存
j2cache.L2.provider_class =net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379
# 1级缓存中的数据如何到达2级缓存
j2cache.broadcast =net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
CacheChannel
@RestController
@RequestMapping("/sms")
public class SmsCodeController {
@Autowired
private CacheChannel cacheChannel;
@GetMapping("/set")
public String getCode(String phone) {
String code = "123456";
cacheChannel.set("sms", phone, code);
return code;
}
@GetMapping("/get")
public String checkCode(String phone) {
String code2 = cacheChannel.get("sms", phone).asString();
return code2;
}
}
在j2cache-spring-boot2-starter
的spring.factories
中加载配置。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.oschina.j2cache.autoconfigure.J2CacheAutoConfiguration,\
net.oschina.j2cache.autoconfigure.J2CacheSpringCacheAutoConfiguration,\
net.oschina.j2cache.autoconfigure.J2CacheSpringRedisAutoConfiguration
J2CacheAutoConfiguration
加载j2cache.config-location
指定路径下的文件。往容器中存放CacheChannel
和J2CacheConfig
两个实体类。
@ConditionalOnClass({J2Cache.class})
@EnableConfigurationProperties({J2CacheConfig.class})
@Configuration
@PropertySource(
value = {"${j2cache.config-location}"},
encoding = "UTF-8",
ignoreResourceNotFound = true
)
public class J2CacheAutoConfiguration {
@Autowired
private StandardEnvironment standardEnvironment;
public J2CacheAutoConfiguration() {
}
@Bean
public net.oschina.j2cache.J2CacheConfig j2CacheConfig() throws IOException {
net.oschina.j2cache.J2CacheConfig cacheConfig = SpringJ2CacheConfigUtil.initFromConfig(this.standardEnvironment);
return cacheConfig;
}
@Bean
@DependsOn({"springUtil", "j2CacheConfig"})
public CacheChannel cacheChannel(net.oschina.j2cache.J2CacheConfig j2CacheConfig) throws IOException {
J2CacheBuilder builder = J2CacheBuilder.init(j2CacheConfig);
return builder.getChannel();
}
@Bean
public SpringUtil springUtil() {
return new SpringUtil();
}
}
CacheProviderHolder#init
,根据配置信息加载一级缓存和二级缓存组件。
public static CacheProviderHolder init(J2CacheConfig config, CacheExpiredListener listener) {
CacheProviderHolder holder = new CacheProviderHolder();
holder.listener = listener;
holder.l1_provider = loadProviderInstance(config.getL1CacheName());
if (!holder.l1_provider.isLevel(CacheObject.LEVEL_1))
throw new CacheException(holder.l1_provider.getClass().getName() + " is not level_1 cache provider");
holder.l1_provider.start(config.getL1CacheProperties());
log.info("Using L1 CacheProvider : {}", holder.l1_provider.getClass().getName());
holder.l2_provider = loadProviderInstance(config.getL2CacheName());
if (!holder.l2_provider.isLevel(CacheObject.LEVEL_2))
throw new CacheException(holder.l2_provider.getClass().getName() + " is not level_2 cache provider");
holder.l2_provider.start(config.getL2CacheProperties());
log.info("Using L2 CacheProvider : {}", holder.l2_provider.getClass().getName());
return holder;
}
CacheProviderHolder#loadProviderInstance
,如果是指定的服务,加载对应的组件。如果找不到,根据类名的绝对路径加载。
private static CacheProvider loadProviderInstance(String cacheIdent) {
switch (cacheIdent.toLowerCase()) {
case "ehcache":
return new EhCacheProvider();
case "ehcache3":
return new EhCacheProvider3();
case "caffeine":
return new CaffeineProvider();
case "redis":
return new RedisCacheProvider();
case "readonly-redis":
return new ReadonlyRedisCacheProvider();
case "memcached":
return new XmemcachedCacheProvider();
case "lettuce":
return new LettuceCacheProvider();
case "none":
return new NullCacheProvider();
}
try {
return (CacheProvider) Class.forName(cacheIdent).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new CacheException("Failed to initialize cache providers", e);
}
}
CacheChannel#set()
。更新一级缓存,并且更新二级缓存。判断是否需要存在空值。
public void set(String region, String key, Object value, boolean cacheNullObject) {
this.assertNotClose();
if (!cacheNullObject && value == null)
return ;
try {
Level1Cache level1 = holder.getLevel1Cache(region);
level1.put(key, (value==null && cacheNullObject)?newNullObject():value);
Level2Cache level2 = holder.getLevel2Cache(region);
if(config.isSyncTtlToRedis())
level2.put(key, (value==null && cacheNullObject)?newNullObject():value, level1.ttl());
else
level2.put(key, (value==null && cacheNullObject)?newNullObject():value);
} finally {
this.sendEvictCmd(region, key);//清除原有的一级缓存的内容
}
}
CacheChannel#get()
。一级缓存中存在,则从一级缓存中获取,否则从二级缓存中获取。
public CacheObject get(String region, String key, boolean...cacheNullObject) {
this.assertNotClose();
CacheObject obj = new CacheObject(region, key, CacheObject.LEVEL_1);
obj.setValue(holder.getLevel1Cache(region).get(key));
if(obj.rawValue() != null)
return obj;
String lock_key = key + '%' + region;
synchronized (_g_keyLocks.computeIfAbsent(lock_key, v -> new Object())) {
obj.setValue(holder.getLevel1Cache(region).get(key));
if(obj.rawValue() != null)
return obj;
try {
obj.setLevel(CacheObject.LEVEL_2);
obj.setValue(holder.getLevel2Cache(region).get(key));
if (obj.rawValue() != null) {
holder.getLevel1Cache(region).put(key, obj.rawValue());
}else {
boolean cacheNull = (cacheNullObject.length > 0) ? cacheNullObject[0] : defaultCacheNullObject;
if (cacheNull)
set(region, key, newNullObject(), true);
}
} finally {
_g_keyLocks.remove(lock_key);
}
}
return obj;
}