?目的:为了spring上下文添加读写分离的接口,可以在启动后自动注入redis只读接口或可读写接口
?
1. 定义一个相关的functional interface - 在spring boot的启动类里面要用到(@EnableDynamicKeyValueServiceCreation)
@Retention(value = RetentionPolicy.RUNTIME)
@Import(KeyValueServiceRegister.class)
public @interface EnableDynamicKeyValueServiceCreation {
}
2. 定义只读接口和可读写接口
public interface KeyValueReadService { String get(String key); ...}
public interface KeyValueReadWriteService extends KeyValueReadService { void set(String key, String value); ..}
?
3. 实现类
public class RedisConfigInfo {
? ? private String hostName;
? ? private int port;
? ? private String password;
? ? private JedisPoolConfig poolConfig;
}
?
public abstract class AbstractRedisOperation {
? ? private final JedisPool jedisPool;
? ? public AbstractRedisOperation (RedisConfigInfo info) { jedisPool = ...;}
}
?
public class RedisReadServiceImpl extends AbstractRedisOperation implements KeyValueReadService {
?
? ? public RedisReadServiceImpl(RedisConfigInfo temp) {
? ? ? ? super(temp);
? ? }
?
? ? @Override
? ? public String get(String key) {
? ? ? ? return stringValueOps.get(key);
? ? }
}
?
4. 通过spring去获取redis的配置信息
@Component
public class KeyValueServiceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
?
? ? private static Logger logger = LoggerFactory.getLogger(KeyValueServiceRegister.class);
?
? ? private Map<String, BeanDefinition> beanDefinitionMap = null;
?
? ? @Override
? ? public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
? ? ? ? ? ? BeanDefinitionRegistry registry) {
? ? ? ? if (beanDefinitionMap == null || beanDefinitionMap.isEmpty()) {
? ? ? ? ? ? logger.warn("beanDefinitions is empty");
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? String beanName;
? ? ? ? BeanDefinition beanDefinition;
? ? ? ? for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
? ? ? ? ? ? beanName = entry.getKey();
? ? ? ? ? ? beanDefinition = entry.getValue();
? ? ? ? ? ? registry.registerBeanDefinition(beanName, beanDefinition);
? ? ? ? }
?
? ? }
?
? ? @SuppressWarnings("rawtypes")
? ? @Override
? ? public void setEnvironment(Environment environment) {
? ? ? ? String prefix = environment.getProperty("prefix.keyvalue.cache", "kv.");
?
? ? ? ? Properties props = new Properties();
? ? ? ? MutablePropertySources propSrcs = ((AbstractEnvironment) environment).getPropertySources();
? ? ? ? StreamSupport.stream(propSrcs.spliterator(), false)
? ? ? ? ? ? ? ? .filter(ps -> ps instanceof EnumerablePropertySource)
? ? ? ? ? ? ? ? .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames())
? ? ? ? ? ? ? ? .flatMap(Arrays::<String>stream).filter(name -> name.startsWith(prefix)).forEach(
? ? ? ? ? ? ? ? ? ? ? ? propName -> props.setProperty(propName, environment.getProperty(propName)));
? ? ? ??
? ? ? ? beanDefinitionMap = Collections
? ? ? ? ? ? ? ? .unmodifiableMap(KeyValueServiceHelper.buildBeanDefinitionMap(props));
? ? }
?
}
?
5. 根据配置信息构建bean
public class KeyValueServiceHelper {
?
? ? private static final String POSFIX_READ = ".r";
?
? ? private static final String POSFIX_WRITE = ".w";
?
? ? public static final Map<String, Properties> buildDataSourceMap(Properties prop) {
? ? ? ? if (prop == null || prop.isEmpty()) {
? ? ? ? ? ? return Collections.emptyMap();
? ? ? ? }
? ? ? ? Map<String, Properties> result = new HashMap<>();
? ? ? ? Set<String> keys = prop.stringPropertyNames();
? ? ? ? Properties temp;
? ? ? ? String dsName;
? ? ? ? for (String key : keys) {
? ? ? ? ? ? dsName = key;
? ? ? ? ? ? if (!isKeyValueServiceBeanName(key)) {
? ? ? ? ? ? ? ? dsName = removePosfix(key);
? ? ? ? ? ? }
? ? ? ? ? ? temp = result.get(dsName);
? ? ? ? ? ? if (temp == null) {
? ? ? ? ? ? ? ? temp = new Properties();
? ? ? ? ? ? ? ? result.put(dsName, temp);
? ? ? ? ? ? }
? ? ? ? ? ? temp.setProperty(key, prop.getProperty(key));
? ? ? ? }
? ? ? ? return result;
? ? }
?
? ? public static final boolean isKeyValueServiceBeanName(String str) {
? ? ? ? if (StringUtils.isBlank(str)) {
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? return str.endsWith(POSFIX_READ) || str.endsWith(POSFIX_WRITE);
? ? }
?
? ? public static final String removePosfix(String str) {
? ? ? ? if (StringUtils.isBlank(str)) {
? ? ? ? ? ? return str;
? ? ? ? }
? ? ? ? int idx = str.lastIndexOf(".");
? ? ? ? if (idx == -1) {
? ? ? ? ? ? return str;
? ? ? ? }
? ? ? ? return str.substring(0, idx);
? ? }
?
? ? public static final String resolveBeanClassName(final String beanName,
? ? ? ? ? ? final String connectionStr) {
? ? ? ? if (StringUtils.isBlank(beanName)) {
? ? ? ? ? ? throw new IllegalArgumentException("bean name is blank");
? ? ? ? }
?
? ? ? ? if (StringUtils.isBlank(connectionStr)) {
? ? ? ? ? ? throw new IllegalArgumentException("connectionStr is blank");
? ? ? ? }
?
? ? ? ? if (!RedisConnectionHelper.isRedisConnectionString(connectionStr)) {
? ? ? ? ? ? throw new IllegalArgumentException("connectionStr is invalid");
? ? ? ? }
?
? ? ? ? if (beanName.endsWith(POSFIX_READ)) {
? ? ? ? ? ? return RedisReadServiceImpl.class.getName();
? ? ? ? }
? ? ? ? if (beanName.endsWith(POSFIX_WRITE)) {
? ? ? ? ? ? return RedisReadWriteServiceImpl.class.getName();
? ? ? ? }
? ? ? ? throw new IllegalArgumentException("beanName is invalid,should end with '.r' or '.w'");
? ? }
?
? ? public static final Map<String, BeanDefinition> buildBeanDefinitionMap(Properties props) {
? ? ? ? Map<String, Properties> cacheSources = buildDataSourceMap(props);
? ? ? ? if (cacheSources == null || cacheSources.isEmpty()) {
? ? ? ? ? ? return Collections.emptyMap();
? ? ? ? }
? ? ? ? BeanDefinition def;
? ? ? ? String beanName;
? ? ? ? String connectionStr;
? ? ? ? String beanClassName;
? ? ? ? Properties prop;
? ? ? ? RedisConfigInfo info;
? ? ? ? Map<String, BeanDefinition> beanMap =
? ? ? ? ? ? ? ? new HashMap<String, BeanDefinition>(cacheSources.size());
? ? ? ? for (Map.Entry<String, Properties> entry : cacheSources.entrySet()) {
? ? ? ? ? ? beanName = entry.getKey();
? ? ? ? ? ? prop = entry.getValue();
? ? ? ? ? ? connectionStr = prop.getProperty(beanName);
? ? ? ? ? ? if (!RedisConnectionHelper.isRedisConnectionString(connectionStr)) {
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? info = RedisConnectionHelper.parseRedisConfigInfo(prop, beanName);
?
? ? ? ? ? ? def = new GenericBeanDefinition();
? ? ? ? ? ? beanClassName = KeyValueServiceHelper.resolveBeanClassName(beanName, connectionStr);
? ? ? ? ? ? def.setBeanClassName(beanClassName);
?
? ? ? ? ? ? def.getConstructorArgumentValues().addGenericArgumentValue(info);
? ? ? ? ? ? beanMap.put(beanName, def);
? ? ? ? }
? ? ? ? return beanMap;
? ? }
}
?
6. 如何使用
a. 在SpringBoot的启动类里面加上@EnableDynamicKeyValueServiceCreation
b. 配置文件里面加上读写分离的redis的配置信息 - 读的是.r结尾,可写的是.w结尾
c. 在需要只读redis的service里面, 直接注入(@KeyValueReadService (name=**.r)
d. 在需要读写redis的service里面, 直接注入(@KeyValueReadService (name=**.w)
?