Initial commit

This commit is contained in:
Eric
2026-01-16 18:51:16 +08:00
commit 98c057de11
280 changed files with 16665 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
</parent>
<artifactId>lingniu-framework-plugin-jetcache</artifactId>
<name>lingniu-framework-plugin-jetcache</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-anno</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,95 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.AbstractCacheBuilder;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.anno.KeyConvertor;
import com.alicp.jetcache.anno.support.ParserFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Created on 2016/11/29.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public abstract class AbstractCacheAutoInit implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(AbstractCacheAutoInit.class);
@Autowired
protected ConfigurableEnvironment environment;
@Autowired
protected AutoConfigureBeans autoConfigureBeans;
protected String[] typeNames;
private volatile boolean inited = false;
public AbstractCacheAutoInit(String... cacheTypes) {
Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");
Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
this.typeNames = cacheTypes;
}
@Override
public void afterPropertiesSet() {
if (!inited) {
synchronized (this) {
if (!inited) {
process("framework.lingniu.jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
process("framework.lingniu.jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
inited = true;
}
}
}
}
private void process(String prefix, Map cacheBuilders, boolean local) {
ConfigTree resolver = new ConfigTree(environment, prefix);
Map<String, Object> m = resolver.getProperties();
Set<String> cacheAreaNames = resolver.directChildrenKeys();
for (String cacheArea : cacheAreaNames) {
final Object configType = m.get(cacheArea + ".type");
boolean match = Arrays.stream(typeNames).anyMatch((tn) -> tn.equals(configType));
if (!match) {
continue;
}
ConfigTree ct = resolver.subTree(cacheArea + ".");
logger.info("init cache area {} , type= {}", cacheArea, typeNames[0]);
CacheBuilder c = initCache(ct, local ? "local." + cacheArea : "remote." + cacheArea);
cacheBuilders.put(cacheArea, c);
}
}
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
AbstractCacheBuilder acb = (AbstractCacheBuilder) builder;
acb.keyConvertor(new ParserFunction(ct.getProperty("keyConvertor", KeyConvertor.FASTJSON2)));
String expireAfterWriteInMillis = ct.getProperty("expireAfterWriteInMillis");
if (expireAfterWriteInMillis == null) {
// compatible with 2.1
expireAfterWriteInMillis = ct.getProperty("defaultExpireInMillis");
}
if (expireAfterWriteInMillis != null) {
acb.setExpireAfterWriteInMillis(Long.parseLong(expireAfterWriteInMillis));
}
String expireAfterAccessInMillis = ct.getProperty("expireAfterAccessInMillis");
if (expireAfterAccessInMillis != null) {
acb.setExpireAfterAccessInMillis(Long.parseLong(expireAfterAccessInMillis));
}
}
protected abstract CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix);
}

View File

@@ -0,0 +1,45 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Created on 2016/12/28.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public class AutoConfigureBeans {
private Map<String, CacheBuilder> localCacheBuilders = new HashMap<>();
private Map<String, CacheBuilder> remoteCacheBuilders = new HashMap<>();
private Map<String, Object> customContainer = Collections.synchronizedMap(new HashMap<>());
public Map<String, CacheBuilder> getLocalCacheBuilders() {
return localCacheBuilders;
}
public void setLocalCacheBuilders(Map<String, CacheBuilder> localCacheBuilders) {
this.localCacheBuilders = localCacheBuilders;
}
public Map<String, CacheBuilder> getRemoteCacheBuilders() {
return remoteCacheBuilders;
}
public void setRemoteCacheBuilders(Map<String, CacheBuilder> remoteCacheBuilders) {
this.remoteCacheBuilders = remoteCacheBuilders;
}
public Map<String, Object> getCustomContainer() {
return customContainer;
}
public void setCustomContainer(Map<String, Object> customContainer) {
this.customContainer = customContainer;
}
}

View File

@@ -0,0 +1,33 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import java.util.Arrays;
/**
* Created on 2017/5/5.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public class BeanDependencyManager implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] autoInitBeanNames = beanFactory.getBeanNamesForType(AbstractCacheAutoInit.class, false, false);
if (autoInitBeanNames != null) {
BeanDefinition bd = beanFactory.getBeanDefinition(JetCacheAutoConfiguration.GLOBAL_CACHE_CONFIG_NAME);
String[] dependsOn = bd.getDependsOn();
if (dependsOn == null) {
dependsOn = new String[0];
}
int oldLen = dependsOn.length;
dependsOn = Arrays.copyOf(dependsOn, dependsOn.length + autoInitBeanNames.length);
System.arraycopy(autoInitBeanNames, 0, dependsOn, oldLen, autoInitBeanNames.length);
bd.setDependsOn(dependsOn);
}
}
}

View File

@@ -0,0 +1,32 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.embedded.CaffeineCacheBuilder;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
/**
* Created on 2016/12/2.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
@Component
@Conditional(CaffeineAutoConfiguration.CaffeineCondition.class)
public class CaffeineAutoConfiguration extends EmbeddedCacheAutoInit {
public CaffeineAutoConfiguration() {
super("caffeine");
}
@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
CaffeineCacheBuilder builder = CaffeineCacheBuilder.createCaffeineCacheBuilder();
parseGeneralConfig(builder, ct);
return builder;
}
public static class CaffeineCondition extends JetCacheCondition {
public CaffeineCondition() {
super("caffeine");
}
}
}

View File

@@ -0,0 +1,106 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Created on 2017/11/20.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public class ConfigTree {
private ConfigurableEnvironment environment;
private String prefix;
public ConfigTree(ConfigurableEnvironment environment, String prefix) {
Assert.notNull(environment, "environment is required");
Assert.notNull(prefix, "prefix is required");
this.environment = environment;
this.prefix = prefix;
}
public ConfigTree subTree(String prefix) {
return new ConfigTree(environment, fullPrefixOrKey(prefix));
}
private String fullPrefixOrKey(String prefixOrKey) {
return this.prefix + prefixOrKey;
}
public Map<String, Object> getProperties() {
Map<String, Object> m = new HashMap<>();
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
for (String name : ((EnumerablePropertySource<?>) source)
.getPropertyNames()) {
if (name != null && name.startsWith(prefix)) {
String subKey = name.substring(prefix.length());
m.put(subKey, environment.getProperty(name));
}
}
}
}
return m;
}
public boolean containsProperty(String key) {
key = fullPrefixOrKey(key);
return environment.containsProperty(key);
}
public String getProperty(String key) {
key = fullPrefixOrKey(key);
return environment.getProperty(key);
}
public String getProperty(String key, String defaultValue) {
if (containsProperty(key)) {
return getProperty(key);
} else {
return defaultValue;
}
}
public boolean getProperty(String key, boolean defaultValue) {
if (containsProperty(key)) {
return Boolean.parseBoolean(getProperty(key));
} else {
return defaultValue;
}
}
public int getProperty(String key, int defaultValue) {
if (containsProperty(key)) {
return Integer.parseInt(getProperty(key));
} else {
return defaultValue;
}
}
public long getProperty(String key, long defaultValue) {
if (containsProperty(key)) {
return Long.parseLong(getProperty(key));
} else {
return defaultValue;
}
}
public String getPrefix() {
return prefix;
}
public Set<String> directChildrenKeys() {
Map<String, Object> m = getProperties();
return m.keySet().stream().map(
s -> s.indexOf('.') >= 0 ? s.substring(0, s.indexOf('.')) : null)
.filter(s -> s != null)
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,25 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;
/**
* Created on 2016/12/2.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {
public EmbeddedCacheAutoInit(String... cacheTypes) {
super(cacheTypes);
}
@Override
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
super.parseGeneralConfig(builder, ct);
EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;
ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
}
}

View File

@@ -0,0 +1,36 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.anno.support.ParserFunction;
import com.alicp.jetcache.external.ExternalCacheBuilder;
/**
* Created on 2016/11/29.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public abstract class ExternalCacheAutoInit extends AbstractCacheAutoInit {
public ExternalCacheAutoInit(String... cacheTypes) {
super(cacheTypes);
}
@Override
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
super.parseGeneralConfig(builder, ct);
ExternalCacheBuilder ecb = (ExternalCacheBuilder) builder;
ecb.setKeyPrefix(ct.getProperty("keyPrefix"));
ecb.setBroadcastChannel(parseBroadcastChannel(ct));
ecb.setValueEncoder(new ParserFunction(ct.getProperty("valueEncoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
ecb.setValueDecoder(new ParserFunction(ct.getProperty("valueDecoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
}
protected String parseBroadcastChannel(ConfigTree ct) {
String broadcastChannel = ct.getProperty("broadcastChannel");
if (broadcastChannel != null && !"".equals(broadcastChannel.trim())) {
return broadcastChannel.trim();
} else {
return null;
}
}
}

View File

@@ -0,0 +1,83 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.SimpleCacheManager;
import com.alicp.jetcache.anno.support.EncoderParser;
import com.alicp.jetcache.anno.support.GlobalCacheConfig;
import com.alicp.jetcache.anno.support.JetCacheBaseBeans;
import com.alicp.jetcache.anno.support.KeyConvertorParser;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.support.StatInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.util.function.Consumer;
/**
* Created on 2016/11/17.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
@Configuration
@ConditionalOnClass(GlobalCacheConfig.class)
@ConditionalOnMissingBean(GlobalCacheConfig.class)
@EnableConfigurationProperties(JetCacheProperties.class)
@Import({CaffeineAutoConfiguration.class,
MockRemoteCacheAutoConfiguration.class,
LinkedHashMapAutoConfiguration.class,
RedissonAutoConfiguration.class})
public class JetCacheAutoConfiguration {
public static final String GLOBAL_CACHE_CONFIG_NAME = "globalCacheConfig";
@Bean
@ConditionalOnMissingBean
public SpringConfigProvider springConfigProvider(
@Autowired ApplicationContext applicationContext,
@Autowired GlobalCacheConfig globalCacheConfig,
@Autowired(required = false) EncoderParser encoderParser,
@Autowired(required = false) KeyConvertorParser keyConvertorParser,
@Autowired(required = false) Consumer<StatInfo> metricsCallback) {
return new JetCacheBaseBeans().springConfigProvider(applicationContext, globalCacheConfig,
encoderParser, keyConvertorParser, metricsCallback);
}
@Bean(name = "jcCacheManager")
@ConditionalOnMissingBean
public CacheManager cacheManager(@Autowired SpringConfigProvider springConfigProvider) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCacheBuilderTemplate(springConfigProvider.getCacheBuilderTemplate());
return cacheManager;
}
@Bean
public AutoConfigureBeans autoConfigureBeans() {
return new AutoConfigureBeans();
}
@Bean
public static BeanDependencyManager beanDependencyManager() {
return new BeanDependencyManager();
}
@Bean(name = GLOBAL_CACHE_CONFIG_NAME)
public GlobalCacheConfig globalCacheConfig(AutoConfigureBeans autoConfigureBeans, JetCacheProperties props) {
GlobalCacheConfig _globalCacheConfig = new GlobalCacheConfig();
_globalCacheConfig = new GlobalCacheConfig();
_globalCacheConfig.setHiddenPackages(props.getHiddenPackages());
_globalCacheConfig.setStatIntervalMinutes(props.getStatIntervalMinutes());
_globalCacheConfig.setAreaInCacheName(props.isAreaInCacheName());
_globalCacheConfig.setPenetrationProtect(props.isPenetrationProtect());
_globalCacheConfig.setEnableMethodCache(props.isEnableMethodCache());
_globalCacheConfig.setLocalCacheBuilders(autoConfigureBeans.getLocalCacheBuilders());
_globalCacheConfig.setRemoteCacheBuilders(autoConfigureBeans.getRemoteCacheBuilders());
return _globalCacheConfig;
}
}

View File

@@ -0,0 +1,48 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Created on 2016/11/28.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
public abstract class JetCacheCondition extends SpringBootCondition {
private String[] cacheTypes;
protected JetCacheCondition(String... cacheTypes) {
Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");
Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
this.cacheTypes = cacheTypes;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
ConfigTree ct = new ConfigTree((ConfigurableEnvironment) conditionContext.getEnvironment(), "framework.lingniu.jetcache.");
if (match(ct, "local.") || match(ct, "remote.")) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("no match for " + cacheTypes[0]);
}
}
private boolean match(ConfigTree ct, String prefix) {
Map<String, Object> m = ct.subTree(prefix).getProperties();
Set<String> cacheAreaNames = m.keySet().stream().map((s) -> s.substring(0, s.indexOf('.'))).collect(Collectors.toSet());
final List<String> cacheTypesList = Arrays.asList(cacheTypes);
return cacheAreaNames.stream().anyMatch((s) -> cacheTypesList.contains(m.get(s + ".type")));
}
}

View File

@@ -0,0 +1,68 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Created on 2016/11/23.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
@ConfigurationProperties(prefix = "jetcache")
public class JetCacheProperties {
private String[] hiddenPackages;
private int statIntervalMinutes;
private boolean areaInCacheName = true;
private boolean penetrationProtect = false;
private boolean enableMethodCache = true;
public JetCacheProperties() {
}
public String[] getHiddenPackages() {
// keep same with GlobalCacheConfig
return hiddenPackages;
}
public void setHiddenPackages(String[] hiddenPackages) {
// keep same with GlobalCacheConfig
this.hiddenPackages = hiddenPackages;
}
public void setHidePackages(String[] hidePackages) {
// keep same with GlobalCacheConfig
this.hiddenPackages = hidePackages;
}
public int getStatIntervalMinutes() {
return statIntervalMinutes;
}
public void setStatIntervalMinutes(int statIntervalMinutes) {
this.statIntervalMinutes = statIntervalMinutes;
}
public boolean isAreaInCacheName() {
return areaInCacheName;
}
public void setAreaInCacheName(boolean areaInCacheName) {
this.areaInCacheName = areaInCacheName;
}
public boolean isPenetrationProtect() {
return penetrationProtect;
}
public void setPenetrationProtect(boolean penetrationProtect) {
this.penetrationProtect = penetrationProtect;
}
public boolean isEnableMethodCache() {
return enableMethodCache;
}
public void setEnableMethodCache(boolean enableMethodCache) {
this.enableMethodCache = enableMethodCache;
}
}

View File

@@ -0,0 +1,32 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
/**
* Created on 2016/12/2.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
@Component
@Conditional(LinkedHashMapAutoConfiguration.LinkedHashMapCondition.class)
public class LinkedHashMapAutoConfiguration extends EmbeddedCacheAutoInit {
public LinkedHashMapAutoConfiguration() {
super("linkedhashmap");
}
@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();
parseGeneralConfig(builder, ct);
return builder;
}
public static class LinkedHashMapCondition extends JetCacheCondition {
public LinkedHashMapCondition() {
super("linkedhashmap");
}
}
}

View File

@@ -0,0 +1,40 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.external.MockRemoteCacheBuilder;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
/**
* Created on 2016/12/2.
*
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
*/
@Component
@Conditional(MockRemoteCacheAutoConfiguration.MockRemoteCacheCondition.class)
public class MockRemoteCacheAutoConfiguration extends ExternalCacheAutoInit {
public MockRemoteCacheAutoConfiguration() {
super("mock");
}
@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
MockRemoteCacheBuilder builder = MockRemoteCacheBuilder.createMockRemoteCacheBuilder();
parseGeneralConfig(builder, ct);
return builder;
}
@Override
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
super.parseGeneralConfig(builder, ct);
MockRemoteCacheBuilder b = (MockRemoteCacheBuilder) builder;
b.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
}
public static class MockRemoteCacheCondition extends JetCacheCondition {
public MockRemoteCacheCondition() {
super("mock");
}
}
}

View File

@@ -0,0 +1,73 @@
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
import cn.lingniu.framework.plugin.jetcache.redisson.RedissonCacheBuilder;
import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.CacheConfigException;
import com.alicp.jetcache.external.ExternalCacheBuilder;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.Objects;
/**
* Created on 2022/7/12.
*
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
*/
@Configuration
@Conditional(RedissonAutoConfiguration.RedissonCondition.class)
public class RedissonAutoConfiguration {
private static final String CACHE_TYPE = "redisson";
public static class RedissonCondition extends JetCacheCondition {
public RedissonCondition() {
super(CACHE_TYPE);
}
}
@Bean
public RedissonAutoInit redissonAutoInit() {
return new RedissonAutoInit();
}
public static class RedissonAutoInit extends ExternalCacheAutoInit implements ApplicationContextAware {
private ApplicationContext context;
public RedissonAutoInit() {
super(CACHE_TYPE);
}
@Override
protected CacheBuilder initCache(final ConfigTree ct, final String cacheAreaWithPrefix) {
final Map<String, RedissonClient> beans = this.context.getBeansOfType(RedissonClient.class);
if (beans.isEmpty()) {
throw new CacheConfigException("no RedissonClient in spring context");
}
RedissonClient client = beans.values().iterator().next();
if (beans.size() > 1) {
final String redissonClientName = ct.getProperty("redissonClient");
if (Objects.isNull(redissonClientName) || redissonClientName.isEmpty()) {
throw new CacheConfigException("redissonClient is required, because there is multiple RedissonClient in Spring context");
}
if (!beans.containsKey(redissonClientName)) {
throw new CacheConfigException("there is no RedissonClient named " + redissonClientName + " in Spring context");
}
client = beans.get(redissonClientName);
}
final ExternalCacheBuilder<?> builder = RedissonCacheBuilder.createBuilder().redissonClient(client);
parseGeneralConfig(builder, ct);
return builder;
}
@Override
public void setApplicationContext(final ApplicationContext context) throws BeansException {
this.context = context;
}
}
}

View File

@@ -0,0 +1,70 @@
package cn.lingniu.framework.plugin.jetcache.redisson;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.CacheResult;
import com.alicp.jetcache.support.BroadcastManager;
import com.alicp.jetcache.support.CacheMessage;
import com.alicp.jetcache.support.SquashedLogger;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
/**
* Created on 2022/7/12.
*
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
*/
public class RedissonBroadcastManager extends BroadcastManager {
private static final Logger logger = LoggerFactory.getLogger(RedissonBroadcastManager.class);
private final RedissonCacheConfig<?, ?> config;
private final String channel;
private final RedissonClient client;
private volatile int subscribeId;
public RedissonBroadcastManager(final CacheManager cacheManager, final RedissonCacheConfig<?, ?> config) {
super(cacheManager);
checkConfig(config);
this.config = config;
this.channel = config.getBroadcastChannel();
this.client = config.getRedissonClient();
}
@Override
public synchronized void startSubscribe() {
if (this.subscribeId == 0 && Objects.nonNull(this.channel) && !this.channel.isEmpty()) {
this.subscribeId = this.client.getTopic(this.channel)
.addListener(byte[].class, (channel, msg) -> processNotification(msg, this.config.getValueDecoder()));
}
}
@Override
public synchronized void close() {
final int id;
if ((id = this.subscribeId) > 0 && Objects.nonNull(this.channel)) {
this.subscribeId = 0;
try {
this.client.getTopic(this.channel).removeListener(id);
} catch (Throwable e) {
logger.warn("unsubscribe {} fail", this.channel, e);
}
}
}
@Override
public CacheResult publish(final CacheMessage cacheMessage) {
try {
if (Objects.nonNull(this.channel) && Objects.nonNull(cacheMessage)) {
final byte[] msg = this.config.getValueEncoder().apply(cacheMessage);
this.client.getTopic(this.channel).publish(msg);
return CacheResult.SUCCESS_WITHOUT_MSG;
}
return CacheResult.FAIL_WITHOUT_MSG;
} catch (Throwable e) {
SquashedLogger.getLogger(logger).error("jetcache publish error", e);
return new CacheResult(e);
}
}
}

View File

@@ -0,0 +1,180 @@
package cn.lingniu.framework.plugin.jetcache.redisson;
import com.alicp.jetcache.CacheConfig;
import com.alicp.jetcache.CacheGetResult;
import com.alicp.jetcache.CacheResult;
import com.alicp.jetcache.CacheResultCode;
import com.alicp.jetcache.CacheValueHolder;
import com.alicp.jetcache.MultiGetResult;
import com.alicp.jetcache.external.AbstractExternalCache;
import org.redisson.api.RBatch;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created on 2022/7/12.
*
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
*/
public class RedissonCache<K, V> extends AbstractExternalCache<K, V> {
private final RedissonClient client;
private final RedissonCacheConfig<K, V> config;
public RedissonCache(final RedissonCacheConfig<K, V> config) {
super(config);
this.config = config;
this.client = config.getRedissonClient();
}
protected String getCacheKey(final K key) {
final byte[] newKey = buildKey(key);
return new String(newKey, StandardCharsets.UTF_8);
}
@Override
public CacheConfig<K, V> config() {
return this.config;
}
@Override
public <T> T unwrap(final Class<T> clazz) {
throw new UnsupportedOperationException("RedissonCache does not support unwrap");
}
@Override
@SuppressWarnings({"unchecked"})
protected CacheGetResult<V> do_GET(final K key) {
try {
final RBucket<CacheValueHolder<V>> rb = this.client.getBucket(getCacheKey(key));
final CacheValueHolder<V> holder = rb.get();
if (Objects.nonNull(holder)) {
final long now = System.currentTimeMillis(), expire = holder.getExpireTime();
if (expire > 0 && now >= expire) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
}
return new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
}
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
} catch (Throwable e) {
logError("GET", key, e);
return new CacheGetResult<>(e);
}
}
@Override
@SuppressWarnings({"unchecked"})
protected MultiGetResult<K, V> do_GET_ALL(final Set<? extends K> keys) {
try {
final Map<K, CacheGetResult<V>> retMap = new HashMap<>(1 << 4);
if (Objects.nonNull(keys) && !keys.isEmpty()) {
final Map<K, String> keyMap = new HashMap<>(keys.size());
for (K k : keys) {
if (Objects.nonNull(k)) {
final String key = getCacheKey(k);
if (Objects.nonNull(key)) {
keyMap.put(k, key);
}
}
}
if (!keyMap.isEmpty()) {
final Map<String, Object> kvMap = this.client.getBuckets().get(keyMap.values().toArray(new String[0]));
final long now = System.currentTimeMillis();
for (K k : keys) {
final String key = keyMap.get(k);
if (Objects.nonNull(key) && Objects.nonNull(kvMap)) {
final CacheValueHolder<V> holder = (CacheValueHolder<V>) kvMap.get(key);
if (Objects.nonNull(holder)) {
final long expire = holder.getExpireTime();
final CacheGetResult<V> ret = (expire > 0 && now >= expire) ? CacheGetResult.EXPIRED_WITHOUT_MSG :
new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
retMap.put(k, ret);
continue;
}
}
retMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
}
}
}
return new MultiGetResult<>(CacheResultCode.SUCCESS, null, retMap);
} catch (Throwable e) {
logError("GET_ALL", "keys(" + (Objects.nonNull(keys) ? keys.size() : 0) + ")", e);
return new MultiGetResult<>(e);
}
}
@Override
protected CacheResult do_PUT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));
this.client.getBucket(getCacheKey(key)).set(holder, expireAfterWrite, timeUnit);
return CacheGetResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("PUT", key, e);
return new CacheResult(e);
}
}
@Override
protected CacheResult do_PUT_ALL(final Map<? extends K, ? extends V> map, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
if (Objects.nonNull(map) && !map.isEmpty()) {
final long expire = timeUnit.toMillis(expireAfterWrite);
final RBatch batch = this.client.createBatch();
map.forEach((k, v) -> {
final CacheValueHolder<V> holder = new CacheValueHolder<>(v, expire);
batch.getBucket(getCacheKey(k)).setAsync(holder, expireAfterWrite, timeUnit);
});
batch.execute();
}
return CacheResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("PUT_ALL", "map(" + map.size() + ")", e);
return new CacheResult(e);
}
}
@Override
protected CacheResult do_REMOVE(final K key) {
try {
final boolean ret = this.client.getBucket(getCacheKey(key)).delete();
return ret ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.FAIL_WITHOUT_MSG;
} catch (Throwable e) {
logError("REMOVE", key, e);
return new CacheResult(e);
}
}
@Override
protected CacheResult do_REMOVE_ALL(final Set<? extends K> keys) {
try {
if (Objects.nonNull(keys) && !keys.isEmpty()) {
final RBatch batch = this.client.createBatch();
keys.forEach(key -> batch.getBucket(getCacheKey(key)).deleteAsync());
batch.execute();
}
return CacheResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("REMOVE_ALL", "keys(" + keys.size() + ")", e);
return new CacheResult(e);
}
}
@Override
protected CacheResult do_PUT_IF_ABSENT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));
final boolean success = this.client.getBucket(getCacheKey(key)).trySet(holder, expireAfterWrite, timeUnit);
return success ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.EXISTS_WITHOUT_MSG;
} catch (Throwable e) {
logError("PUT_IF_ABSENT", key, e);
return new CacheResult(e);
}
}
}

View File

@@ -0,0 +1,52 @@
package cn.lingniu.framework.plugin.jetcache.redisson;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.external.ExternalCacheBuilder;
import com.alicp.jetcache.support.BroadcastManager;
import org.redisson.api.RedissonClient;
/**
* Created on 2022/7/12.
*
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
*/
public class RedissonCacheBuilder<T extends ExternalCacheBuilder<T>> extends ExternalCacheBuilder<T> {
public static class RedissonDataCacheBuilderImpl extends RedissonCacheBuilder<RedissonDataCacheBuilderImpl> {
}
public static RedissonDataCacheBuilderImpl createBuilder() {
return new RedissonDataCacheBuilderImpl();
}
@SuppressWarnings({"all"})
protected RedissonCacheBuilder() {
buildFunc(config -> new RedissonCache((RedissonCacheConfig) config));
}
@Override
@SuppressWarnings({"all"})
public RedissonCacheConfig getConfig() {
if (this.config == null) {
this.config = new RedissonCacheConfig();
}
return (RedissonCacheConfig) this.config;
}
public T redissonClient(final RedissonClient client) {
this.getConfig().setRedissonClient(client);
return self();
}
@Override
public boolean supportBroadcast() {
return true;
}
@Override
public BroadcastManager createBroadcastManager(final CacheManager cacheManager) {
final RedissonCacheConfig<?, ?> c = (RedissonCacheConfig<?, ?>) this.getConfig().clone();
return new RedissonBroadcastManager(cacheManager, c);
}
}

View File

@@ -0,0 +1,21 @@
package cn.lingniu.framework.plugin.jetcache.redisson;
import com.alicp.jetcache.external.ExternalCacheConfig;
import org.redisson.api.RedissonClient;
/**
* Created on 2022/7/12.
*
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
*/
public class RedissonCacheConfig<K, V> extends ExternalCacheConfig<K, V> {
private RedissonClient redissonClient;
public RedissonClient getRedissonClient() {
return redissonClient;
}
public void setRedissonClient(final RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lingniu.framework.plugin.jetcache.autoconfigure.JetCacheAutoConfiguration

View File

@@ -0,0 +1,122 @@
# 【重要】jetcache详细资料---参考官网
## 概述 (Overview)
1. 基于 JetCache 封装的企业级二级缓存解决方案,提供本地 JVM 本地缓存和远程 Redis 缓存的组合缓存能力
2. 核心能力
* 二级缓存机制:
- 一级缓存:本地 JVM 缓存(基于 Caffeine等
- 二级缓存:远程 Redis 缓存redisson 模式)
* 多样化缓存配置:
- 本地缓存:支持 Caffeine 类型,可配置缓存数量限制和过期时间
- 远程缓存:支持 redisson 类型
- 多实例支持:可配置多个缓存分组(如 default、employee 等)
* 注解式缓存操作:
- @Cached:标记方法结果需要缓存,支持缓存名称、过期时间等配置
- @CacheRefresh:支持缓存自动刷新机制
- @EnableMethodCache:启用 JetCache Bean 扫描
* 灵活的缓存策略:
- 支持不同的 Key 生成策略
- 可配置缓存过期时间和时间单位
- 支持不同的缓存类型(本地、远程、双向)
3. 适用场景:该组件特别适用于需要高性能缓存访问的企业级应用,通过本地+远程的二级缓存架构,在保证缓存访问速度的同时,确保缓存数据的共享和一致性。通过注解方式简化了缓存的使用。
* 高并发读场景:需要减少数据库访问压力,提升系统响应速度的业务场景
* 高可用访问:需要自动感知 Redis 集群变化以及故障自动切换能力的应用场景
* 复杂查询结果缓存:对于计算复杂或关联查询较多的数据结果进行缓存
* 数据一致性要求较高:需要通过二级缓存机制保证数据访问性能和一致性的场景
* 需要缓存自动刷新:对于有一定时效性要求但不需要实时更新的数据
## 如何配置--更多参数参考更多配置请参考官网jetcache
* 此处为了统一中间件配置前缀framework.lingniu.jetcache 和更好的个性化扩展, 其他jetcache所有配置方式未改变
* 请先依赖lingniu-framework-plugin-redisson
redissonClient 就是 RedissonConfig配置的key名称
```yaml
framework:
lingniu:
jetcache:
# 需要隐藏的包路径(数组),默认为空
hiddenPackages: [ ]
# 统计信息输出间隔(分钟),默认为 0不输出
statIntervalMinutes: 10
# 是否在缓存名称中包含区域area信息默认为 true
areaInCacheName: true
# 是否开启缓存穿透保护,默认为 false
penetrationProtect: false
# 本地缓存配置
local:
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
ca2:
# 本地缓存实现类类型支持linkedhashmap/caffeine
type: linkedhashmap
# 缓存key数量限制, 默认100
limit: 100
default:
# 本地缓存实现类类型支持linkedhashmap/caffeine
type: caffeine
# 缓存key数量限制, 默认100
limit: 100
# 过期时间0为不失效
expireAfterAccessInMillis: 0
# key 转换器用于序列化目前支持NONE、FASTJSON、JACKSON、FASTJSON2
keyConvertor: FASTJSON2
remote:
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
ca2:
# 远程缓存实现类类型目前支持redisson
type: redisson
# 远程缓存客户端名称默认r1 -- todo 需要在application-redisson.yml中配置
redissonClient: r1
# key 前缀
keyPrefix: "cacheKeyPrefix:"
# 连接超时时间ms
timeout: 2000
# 重试次数
maxAttempt: 5
# 连接池配置
poolConfig:
maxTotal: 8
maxIdle: 8
minIdle: 0
# key 转换器用于序列化目前支持NONE、FASTJSON、JACKSON、FASTJSON2
keyConvertor: FASTJSON2
# JAVA KRYOKRYO5自定义spring beanName
valueEncoder: JAVA
# JAVA KRYOKRYO5自定义spring beanName
valueDecoder: JAVA
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
default:
# 远程缓存实现类类型目前支持redisson
type: redisson
# 远程缓存客户端名称默认r1 -- todo 需要在application-redisson.yml中配置
redissonClient: r1
```
## 如何使用-
```java
//启动 扫描类添加注解
@EnableMethodCache(basePackages = "cn.lingniu.*")
@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
// 多种使用方式参考
// 1. 注解方式
@CachePenetrationProtect
@Cached(name = "orderCache:", key = "#orderId", expire = 3600, cacheType = CacheType.BOTH)
@CacheRefresh(refresh = 10, timeUnit = TimeUnit.MINUTES)
public DataResult<KeyValueEntity> getOrderById(String orderId) {
...
}
// 2. SimpleCacheManager方式
Cache<Object, Object> localCache = simpleCacheManager.getOrCreateCache(QuickConfig.newBuilder(localArea, "user:").cacheType(CacheType.LOCAL).build());
```

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
</parent>
<artifactId>lingniu-framework-plugin-redisson</artifactId>
<name>lingniu-framework-plugin-redisson</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,28 @@
package cn.lingniu.framework.plugin.redisson;
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
import org.redisson.api.RedissonClient;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RedissonClientFactory {
private static volatile Map<String, RedissonClient> redisMap = new ConcurrentHashMap<>();
public static void putRedissonClient(String beanName, RedissonClient redissonClient) {
redisMap.put(beanName, redissonClient);
}
public static RedissonClient getRedissonClient(String beanName) {
if (ObjectEmptyUtils.isEmpty(beanName) || !redisMap.containsKey(beanName)) {
throw new IllegalStateException("(redisson)bean-对应的RedissonClient未注册:" + beanName);
}
return redisMap.get(beanName);
}
public static Map<String, RedissonClient> getRedisMap() {
return redisMap;
}
}

View File

@@ -0,0 +1,30 @@
package cn.lingniu.framework.plugin.redisson;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* 锁
**/
@SuppressWarnings("all")
public interface RedissonClusterLockerService {
default public <T, R> R wrapLock(T source, Function<T, String> fun,
Function<T, String> keyGetter, Function<T, R> wrapFun) {
return wrapLock(source, fun, keyGetter, wrapFun, 15L, TimeUnit.SECONDS);
}
<T, R> R wrapLock(T source, Function<T, String> fun,
Function<T, String> keyGetter, Function<T, R> wrapFun,
Long leaseTime, TimeUnit leaseTimeUnit);
default public <T, R> R wrapLock(T source, Function<T, String> keyGetter, Function<T, R> wrapFun) {
return wrapLock(source, (t) -> {
String key = keyGetter.apply(source);
return delivery(key);
}, keyGetter, wrapFun);
}
String delivery(String key);
}

View File

@@ -0,0 +1,51 @@
package cn.lingniu.framework.plugin.redisson;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 支持注解获取锁
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RedissonLockAction {
/**
* RedissonConfig 中 remote 的 key 名字
*/
String redissonName();
/**
* 锁的资源key。支持spring El表达式
*/
@AliasFor("key")
String value() default "'default'";
@AliasFor("value")
String key() default "'default'";
/**
* 锁类型
*/
RedissonLockType lockType() default RedissonLockType.REENTRANT_LOCK;
/**
* 获取锁等待时间默认3秒
*/
long waitTime() default 3000L;
/**
* 锁自动释放时间默认10秒
*/
long leaseTime() default 10000L;
/**
* 时间单位
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
/**
* 锁失败是否Throw异常
*/
boolean throwException() default false;
}

View File

@@ -0,0 +1,23 @@
package cn.lingniu.framework.plugin.redisson;
/**
* 锁类型
*/
public enum RedissonLockType {
/**
* 读锁
*/
READ_LOCK,
/**
* 写锁
*/
WRITE_LOCK,
/**
* 可重入锁
*/
REENTRANT_LOCK,
/**
* 公平锁
*/
FAIR_LOCK;
}

View File

@@ -0,0 +1,58 @@
package cn.lingniu.framework.plugin.redisson.builder;
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.config.Config;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public abstract class RedissonAbstractClientBuilder {
@Getter(AccessLevel.PUBLIC)
private RedissonProperties redissonProperties;
protected RedissonAbstractClientBuilder(RedissonProperties redissonProperties) {
this.redissonProperties = redissonProperties;
}
protected abstract void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts);
public Config build() {
Config redissionConfig = new Config();
Set<RedisHostAndPort> redisHostAndPorts = parseRedisHostAndPort(redissonProperties.getRedisAddresses());
config(redissionConfig, redisHostAndPorts);
return redissionConfig;
}
public Set<RedisHostAndPort> parseRedisHostAndPort(String redisAddress) {
Set<RedisHostAndPort> redisHostAndPorts = new HashSet<>();
// 分离地址部分和密码部分
String[] parts = redisAddress.split(";");
String addressPart = parts[0];
String password = parts.length > 1 ? parts[1] : null;
// 解析host:port格式的地址
String[] hosts = addressPart.split(",");
for (String hostPort : hosts) {
hostPort = hostPort.trim();
if (hostPort.contains(":")) {
int colonIndex = hostPort.indexOf(":");
String host = hostPort.substring(0, colonIndex);
int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
RedisHostAndPort redisHostAndPort = new RedisHostAndPort();
redisHostAndPort.setHost(host);
redisHostAndPort.setPort(port);
if (password != null) {
redisHostAndPort.setPkey(password);
}
redisHostAndPorts.add(redisHostAndPort);
}
}
return redisHostAndPorts;
}
}

View File

@@ -0,0 +1,47 @@
package cn.lingniu.framework.plugin.redisson.builder;
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
import cn.lingniu.framework.plugin.util.ip.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import java.util.Set;
/**
* @description: 集群类型
**/
public class RedissonClusterClientBuilder extends RedissonAbstractClientBuilder {
public RedissonClusterClientBuilder(RedissonProperties properties) {
super(properties);
}
@Override
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
ClusterServersConfig clusterServersConfig = redssionConfig.useClusterServers();
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
clusterServersConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
}
clusterServersConfig.setMasterConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
clusterServersConfig.setSlaveConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
clusterServersConfig.setMasterConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
clusterServersConfig.setSlaveConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
clusterServersConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
clusterServersConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
clusterServersConfig.setTimeout(getRedissonProperties().getTimeout());
clusterServersConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
clusterServersConfig.setPassword(redisHostAndPort.getPkey());
clusterServersConfig.setCheckSlotsCoverage(getRedissonProperties().getCheckSlotsCoverage());
clusterServersConfig.setReadMode(ReadMode.valueOf(getRedissonProperties().getReadMode()));
redisHostAndPorts.stream().forEach(h -> {
clusterServersConfig.addNodeAddress(String.format("redis://%s/", h.getHost() + ":" + h.getPort()));
});
}
}

View File

@@ -0,0 +1,41 @@
package cn.lingniu.framework.plugin.redisson.builder;
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
import cn.lingniu.framework.plugin.util.ip.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import java.util.Set;
/**
* @description: 单节点类型
**/
public class RedissonStandaloneClientBuilder extends RedissonAbstractClientBuilder {
public RedissonStandaloneClientBuilder(RedissonProperties properties) {
super(properties);
}
@Override
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
SingleServerConfig singleServerConfig = redssionConfig.useSingleServer();
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
singleServerConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
}
singleServerConfig.setDatabase(getRedissonProperties().getDatabase());
singleServerConfig.setConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
singleServerConfig.setConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
singleServerConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
singleServerConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
singleServerConfig.setTimeout(getRedissonProperties().getTimeout());
singleServerConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
singleServerConfig.setPassword(redisHostAndPort.getPkey());
singleServerConfig.setAddress(String.format("redis://%s/", redisHostAndPort.getHost() + ":" + redisHostAndPort.getPort()));
}
}

View File

@@ -0,0 +1,13 @@
package cn.lingniu.framework.plugin.redisson.config;
import lombok.Data;
@Data
public class RedisHostAndPort {
private String host;
private int port;
private String pkey;
}

View File

@@ -0,0 +1,23 @@
package cn.lingniu.framework.plugin.redisson.config;
import lombok.Getter;
/**
* 集群类型
**/
public enum RedisType {
STANDALONE("standalone", "单节点类型"),
CLUSTER("cluster", "集群类型"),
SENTINEL("sentinel", "哨兵类型");
@Getter
private String type;
@Getter
private String memo;
RedisType(String type, String memo) {
this.type = type;
this.memo = memo;
}
}

View File

@@ -0,0 +1,21 @@
package cn.lingniu.framework.plugin.redisson.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.Map;
/**
* redisson 配置
*/
@ConfigurationProperties(prefix = RedissonConfig.PREFIX)
@Data
public class RedissonConfig {
public static final String PREFIX = "framework.lingniu.redisson";
private Map<String, RedissonProperties> remote = new HashMap<>();
}

View File

@@ -0,0 +1,46 @@
package cn.lingniu.framework.plugin.redisson.config;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* 详细配置
*/
@Data
public class RedissonProperties {
/**
* redis连接串信息 host:port,host:port, 多个用,分割,最后一个是;是密码,可以不配置
*/
private String redisAddresses;
@Pattern(regexp = "^(SLAVE|MASTER|MASTER_SLAVE)$", message = "readMode must be one of SLAVE, MASTER, MASTER_SLAVE")
private String readMode = "MASTER";
/**
* 类型
*/
private RedisType storageType = RedisType.CLUSTER;
private String clientName = "";
@Min(value = 0, message = "database must be greater than or equal to 0")
@Max(value = 15, message = "database must be less than or equal to 15")
private int database = 0;
private int connectionMinimumIdleSize = 12;
private int connectionPoolSize = 32;
private int idleConnectionTimeout = 10000;
private int connectTimeout = 2000;
private int timeout = 2000;
private int retryAttempts = 4;
//cluster: Slots检查
private Boolean checkSlotsCoverage = true;
}

View File

@@ -0,0 +1,53 @@
package cn.lingniu.framework.plugin.redisson.init;
import cn.lingniu.framework.plugin.redisson.RedissonClusterLockerService;
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* 初始化-RedissonClusterLockerService
*/
@Slf4j
@EnableConfigurationProperties({RedissonConfig.class})
public class RedissonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedissonClusterLockerService redissonClusterLockerService() {
return new RedissonClusterLockerService() {
@Override
public <T, R> R wrapLock(T source, Function<T, String> fun, Function<T, String> keyGetter, Function<T, R> wrapFun, Long leaseTime, TimeUnit leaseTimeUnit) {
RedissonClient client = Optional.ofNullable(RedissonClientFactory.getRedissonClient(fun.apply(source))).orElseThrow(() -> new IllegalStateException("Redisson client not found for: " + fun.apply(source)));
RLock lock = client.getLock(keyGetter.apply(source));
try {
lock.lock(leaseTime, leaseTimeUnit);
return wrapFun.apply(source);
} finally {
lock.unlock();
}
}
@Override
public String delivery(String key) {
return new ArrayList<>(RedissonClientFactory.getRedisMap().keySet()).toArray(new String[]{})[Math.abs(key.hashCode() % RedissonClientFactory.getRedisMap().size())];
}
};
}
@Bean
RedissonBeanRegisterPostProcessor redissonBeanRegister() {
return new RedissonBeanRegisterPostProcessor();
}
}

View File

@@ -0,0 +1,75 @@
package cn.lingniu.framework.plugin.redisson.init;
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
import cn.lingniu.framework.plugin.redisson.builder.RedissonClusterClientBuilder;
import cn.lingniu.framework.plugin.redisson.builder.RedissonStandaloneClientBuilder;
import cn.lingniu.framework.plugin.redisson.config.RedisType;
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Map;
import java.util.Optional;
/**
* 初始化
*/
@Slf4j
public class RedissonBeanRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
private ConfigurableApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RedissonConfig config = Binder.get(applicationContext.getEnvironment()).bind(RedissonConfig.PREFIX, RedissonConfig.class).orElse(new RedissonConfig());
Map<String, RedissonProperties> remote = config.getRemote();
Optional.ofNullable(remote).ifPresent(b -> b.forEach((k, v) -> {
if (applicationContext.containsBean(k)) {
throw new IllegalStateException("redisson的实例对象已注册请查看配置:" + k);
}
registerContainer(registry, k, v);
}));
}
private void registerContainer(BeanDefinitionRegistry registry, String beanName, RedissonProperties redissonProperties) {
Config redissonConfig = null;
if (RedisType.STANDALONE.equals(redissonProperties.getStorageType())) {
redissonConfig = new RedissonStandaloneClientBuilder(redissonProperties).build();
} else if (RedisType.CLUSTER.equals(redissonProperties.getStorageType())) {
redissonConfig = new RedissonClusterClientBuilder(redissonProperties).build();
} else {
throw new IllegalArgumentException("不支持的Redisson存储类型:" + redissonProperties.getStorageType());
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Redisson.class);
builder.addConstructorArgValue(redissonConfig);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
// 从ApplicationContext中获取已注册的Bean实例
RedissonClient jedisCluster = applicationContext.getBean(beanName, RedissonClient.class);
RedissonClientFactory.putRedissonClient(beanName, jedisCluster);
if (!registry.containsBeanDefinition("redissonDistributedLockAspectConfiguration")) {
registry.registerBeanDefinition("redissonDistributedLockAspectConfiguration",
BeanDefinitionBuilder.rootBeanDefinition(RedissonDistributedLockAspectConfiguration.class).getBeanDefinition());
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}

View File

@@ -0,0 +1,92 @@
package cn.lingniu.framework.plugin.redisson.init;
import cn.lingniu.framework.plugin.redisson.RedissonLockAction;
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
/**
* Aspect 实现
*/
@Slf4j
@Aspect
public class RedissonDistributedLockAspectConfiguration {
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Pointcut("@annotation(cn.lingniu.framework.plugin.redisson.RedissonLockAction)")
public void lockPoint() {
}
@Around("lockPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
RedissonLockAction redissonLockAction = method.getAnnotation(RedissonLockAction.class);
Object[] args = pjp.getArgs();
String key = redissonLockAction.value();
key = parse(key, method, args);
RLock lock = getLock(key, redissonLockAction);
if (!lock.tryLock(redissonLockAction.waitTime(), redissonLockAction.leaseTime(), redissonLockAction.unit())) {
log.warn("RedissonLockAction-获取锁失败: [{}]", key);
if (redissonLockAction.throwException())
throw new RuntimeException(String.format("RedissonLockAction-获取锁失败:[%s]", key));
return null;
}
log.info("RedissonLockAction-获取锁成功: [{}]", key);
try {
return pjp.proceed();
} catch (Exception e) {
log.error(String.format("RedissonLockAction-方法锁执行异常:[%s]", key), e);
throw e;
} finally {
lock.unlock();
log.info("RedissonLockAction-释放锁成功: [{}]", key);
}
}
/**
* 解析spring EL表达式
*/
private String parse(String key, Method method, Object[] args) {
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
return parser.parseExpression(key).getValue(context, String.class);
}
private RLock getLock(String key, RedissonLockAction redissonLockAction) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient(redissonLockAction.redissonName());
switch (redissonLockAction.lockType()) {
case REENTRANT_LOCK:
return redissonClient.getLock(key);
case FAIR_LOCK:
return redissonClient.getFairLock(key);
case READ_LOCK:
return redissonClient.getReadWriteLock(key).readLock();
case WRITE_LOCK:
return redissonClient.getReadWriteLock(key).writeLock();
default:
throw new RuntimeException("RedissonLockAction-不支持的锁类型:" + redissonLockAction.lockType().name());
}
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lingniu.framework.plugin.redisson.init.RedissonAutoConfiguration

View File

@@ -0,0 +1,121 @@
# 【重要】redisson详细资料-redis搭建---参考官网
## 概述 (Overview)
1. 基于 Redisson 封装的 Redis 集群操作和分布式锁组件,专门用于处理 Redis 集群环境下的分布式锁场景
2. 核心能力
* 高性能键值存储基于RocksDB的高效读写能力支持快速的数据存取操作。
* 集群自适应感知:客户端能够感知集群变化并自动刷新配置
* Redisson 操作:基于 Redisson 提供 Redis 集群操作能力
* 分布式锁支持:支持多种类型的分布式锁:
■ 可重入锁(REENTRANT_LOCK)
■ 公平锁(FAIR_LOCK)
■ 读锁(READ_LOCK)
■ 写锁(WRITE_LOCK)
* 注解式锁操作:通过 @LockAction 注解简化分布式锁的使用
* 灵活配置:支持锁等待时间、自动释放时间、时间单位等参数配置
* 异常处理:支持锁获取失败时的异常抛出或返回 null 两种处理方式
* 支持多集群操作redis,也可扩展队列消费
3. 适用场景:该组件特别适用于需要在 Redis 集群环境下使用分布式锁的企业级应用,通过注解方式简化了分布式锁的使用,提供了多种锁类型以满足不同业务场景的需求
* 分布式系统:需要在分布式环境下保证数据一致性的场景
* 高并发业务:需要防止并发操作导致数据不一致的业务场景
* 集群环境:基于 Redis 集群部署的应用系统
* 资源竞争控制:需要对共享资源进行并发访问控制的场景
* 定时任务:分布式环境下需要确保任务单实例执行的定时任务
* 库存扣减:电商系统中需要防止超卖的库存扣减场景
* 幂等性保证:需要保证接口幂等性的业务操作。
## 如何配置--更多参数参考RedissonConfig/RedissonProperties
```yaml
framework:
lingniu:
redisson:
remote:
r1:
# 应用id需在cachecloud申请
redisAddresses: xxxx:6387
# 客户端名称,默认为应用名 + IP
clientName: ""
# 单节点 Redis 默认数据库,默认为 0
database: 0
# 连接池最小空闲连接数,默认为 12
connectionMinimumIdleSize: 12
# 连接池最大连接数,默认为 32
connectionPoolSize: 32
# 空闲连接超时时间(毫秒),默认为 10000
idleConnectionTimeout: 10000
# 连接超时时间(毫秒),默认为 2000
connectTimeout: 2000
# 操作超时时间(毫秒),默认为 2000
timeout: 2000
# 操作重试次数,默认为 4
retryAttempts: 4
# 集群模式下是否检查 Slots 覆盖,默认为 true
checkSlotsCoverage: true
# 读取模式,默认为 "SLAVE"
# 可选值:
# - SLAVE: 从节点读取
# - MASTER: 主节点读取
# - MASTER_SLAVE: 主从节点读取
readMode: "SLAVE"
# 存储类型,默认为 "CLUSTER"
# 可选值:
# - CLUSTER: 集群模式
# - SINGLE: 单节点模式
# - SENTINEL: 哨兵模式
# - REPLICATED: 复制模式
storageType: "CLUSTER"
# 实例名称
r2:
# 应用id需在cachecloud申请
redisAddresses: xxx:6387
# 单节点 Redis 默认数据库,默认为 0
database: 0
```
## 如何使用--正常操作redis
```java
@Autowired
private RedissonClient test;
// 或者
RedissonFactory.getRedissonClient("test").api
```
## 如何使用--分布式锁
```java
/**
* 使用 #orderId 参数作为锁的key
*/
@PostMapping("/process/{orderId}")
@LockAction(
redissonName = "r1",
key = "#orderId",
lockType = LockType.REENTRANT_LOCK,
waitTime = 3000L, leaseTime = 30000L, unit = TimeUnit.MILLISECONDS,
throwEx = true
)
public ResponseEntity<String> processOrder(@PathVariable String orderId) {
// 业务逻辑
return ResponseEntity.ok("Order processed: " + orderId);
}
/**
* 使用请求参数
*/
@GetMapping("/query")
@LockAction(
redissonName = "defaultRedisson",
key = "'query:' + #userId + ':' + #status",
throwEx = true
)
public ResponseEntity<List<Order>> queryOrders(
@RequestParam String userId,
@RequestParam String status) {
// 业务逻辑
return ResponseEntity.ok(new ArrayList<>());
}
```