springboot+redis实现自动切换库

多说无益,直接上图,当然此项目不仅包含redis,所以其他不相关可以忽略。

主要代码分先后依次展示:源码会上传git,所以不多展示,只介绍重点
项目目录结构:


项目结构.png

pom文件:其中版本一定要注意,低版本会报些许异常。
只要引入相关redis即可。


        org.springframework.boot
    spring-boot-starter-parent
    2.0.8.RELEASE
         
    

    org.springframework.boot
    spring-boot-starter-data-redis

        
        
            org.springframework.session
            spring-session-data-redis
        
        
        
            org.springframework.boot
            spring-boot-starter-cache
        
        
            redis.clients
            jedis
        

application.properties
本配置文件由于涉及到其他方面,也不删了,看需要删减。

server.port=8080
spring.tomcat.uri-encoding=utf-8
spring.thymeleaf.prefix=classpath:/templates/
logging.level.com.king.mapper=debug
#DB MYSQL Configuration:
spring.datasource.mysql.driverClassName = com.mysql.jdbc.Driver
spring.datasource.mysql.url = jdbc:mysql://localhost:3306/base?useUnicode=true&characterEncoding=utf-8&relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull
spring.datasource.mysql.username = root
spring.datasource.mysql.password = 123456
#spring.datasource.mysql.maximun-pool-size\u9ed8\u8ba4\u4e3a100
#\u9a8c\u8bc1\u8fde\u63a5\u7684\u6709\u6548\u6027
spring.datasource.mysql.test-while-idle=true
#\u83b7\u53d6\u8fde\u63a5\u65f6\u5019\u9a8c\u8bc1\uff0c\u4f1a\u5f71\u54cd\u6027\u80fd
spring.datasource.mysql.test-on-borrow=false
#\u5728\u8fde\u63a5\u5f52\u8fd8\u5230\u8fde\u63a5\u6c60\u65f6\u662f\u5426\u6d4b\u8bd5\u8be5\u8fde\u63a5
spring.datasource.mysql.test-on-return=false
spring.datasource.mysql.validation-query=SELECT 1 FROM DUAL
#\u7a7a\u95f2\u8fde\u63a5\u56de\u6536\u7684\u65f6\u95f4\u95f4\u9694\uff0c\u4e0etest-while-idle\u4e00\u8d77\u4f7f\u7528\uff0c\u8bbe\u7f6e5\u5206\u949f
spring.datasource.mysql.time-between-eviction-runs-millis=300000
#\u8fde\u63a5\u6c60\u7a7a\u95f2\u8fde\u63a5\u7684\u6709\u6548\u65f6\u95f4 \uff0c\u8bbe\u7f6e30\u5206\u949f
spring.datasource.mysql.min-evictable-idle-time-millis=1800000
spring.datasource.mysql.initial-size=5
#\u6307\u5b9a\u8fde\u63a5\u6c60\u4e2d\u6700\u5927\u7684\u6d3b\u8dc3\u8fde\u63a5\u6570.
spring.datasource.mysql.max-active=70
#\u6307\u5b9a\u8fde\u63a5\u6c60\u7b49\u5f85\u8fde\u63a5\u8fd4\u56de\u7684\u6700\u5927\u7b49\u5f85\u65f6\u95f4\uff0c\u6beb\u79d2\u5355\u4f4d.
spring.datasource.mysql.max-wait=60000
#\u6307\u5b9a\u5fc5\u987b\u4fdd\u6301\u8fde\u63a5\u7684\u6700\u5c0f\u503c
spring.datasource.mysql.min-idle=5
#\u6700\u5927\u7b49\u5f85\u8fde\u63a5\u4e2d\u7684\u6570\u91cf
spring.datasource.mysql.max-idle=20
#DB test Configuration:
spring.datasource.test.driverClassName = com.mysql.jdbc.Driver
spring.datasource.test.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull
spring.datasource.test.username = root
spring.datasource.test.password = 123456
#spring.datasource.opaq.maximun-pool-size\u9ed8\u8ba4\u4e3a100
#\u9a8c\u8bc1\u8fde\u63a5\u7684\u6709\u6548\u6027
spring.datasource.test.test-while-idle=true
#\u83b7\u53d6\u8fde\u63a5\u65f6\u5019\u9a8c\u8bc1\uff0c\u4f1a\u5f71\u54cd\u6027\u80fd
spring.datasource.test.test-on-borrow=false
#\u5728\u8fde\u63a5\u5f52\u8fd8\u5230\u8fde\u63a5\u6c60\u65f6\u662f\u5426\u6d4b\u8bd5\u8be5\u8fde\u63a5
spring.datasource.test.test-on-return=false
spring.datasource.test.validation-query=SELECT 1 FROM DUAL
#\u7a7a\u95f2\u8fde\u63a5\u56de\u6536\u7684\u65f6\u95f4\u95f4\u9694\uff0c\u4e0etest-while-idle\u4e00\u8d77\u4f7f\u7528\uff0c\u8bbe\u7f6e5\u5206\u949f
spring.datasource.test.time-between-eviction-runs-millis=300000
#\u8fde\u63a5\u6c60\u7a7a\u95f2\u8fde\u63a5\u7684\u6709\u6548\u65f6\u95f4 \uff0c\u8bbe\u7f6e30\u5206\u949f
spring.datasource.test.min-evictable-idle-time-millis=1800000
spring.datasource.test.initial-size=5
#\u6307\u5b9a\u8fde\u63a5\u6c60\u4e2d\u6700\u5927\u7684\u6d3b\u8dc3\u8fde\u63a5\u6570.
spring.datasource.test.max-active=70
#\u6307\u5b9a\u8fde\u63a5\u6c60\u7b49\u5f85\u8fde\u63a5\u8fd4\u56de\u7684\u6700\u5927\u7b49\u5f85\u65f6\u95f4\uff0c\u6beb\u79d2\u5355\u4f4d.
spring.datasource.test.max-wait=60000
#\u6307\u5b9a\u5fc5\u987b\u4fdd\u6301\u8fde\u63a5\u7684\u6700\u5c0f\u503c
spring.datasource.test.min-idle=5
#\u6700\u5927\u7b49\u5f85\u8fde\u63a5\u4e2d\u7684\u6570\u91cf
spring.datasource.test.max-idle=20
#redis配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
#连接超时时间
spring.redis.timeout=6000
#连接池最大连接数,负值无限制
spring.redis.jedis.pool.max-active=1000
#连接池最大阻塞等待时间,负值表示无限制
spring.redis.jedis.pool.max-wait=-1
#连接池最大空闲连接
spring.redis.jedis.pool.max-idle=10
#连接池最小空闲连接
spring.redis.jedis.pool.min-idle=5
spring.cache.type=none
#是否开启redis缓存
king.redis.open=true

redis配置类:

package com.king.common.redis;

import java.time.Duration;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
//此注解会自动获取application.properties中的redis配置值
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisTemplateConfig {
private RedisProperties redisProperties;
public RedisTemplateConfig(RedisProperties redisProperties) {
    this.redisProperties=redisProperties;
}
@Bean
@Primary
public JedisConnectionFactory jedisConnectionFactory() {
    RedisStandaloneConfiguration configuration=new RedisStandaloneConfiguration();
    configuration.setHostName(redisProperties.getHost());
    configuration.setPort(redisProperties.getPort());
    configuration.setPassword(RedisPassword.of(redisProperties.getPassword()));
    configuration.setDatabase(redisProperties.getDatabase());
    return new JedisConnectionFactory(configuration,getJedisClientConfiguration());
}
/**获取jedis客户端配置*/
private JedisClientConfiguration getJedisClientConfiguration() {
    JedisClientConfiguration.JedisClientConfigurationBuilder builder=JedisClientConfiguration.builder();
    //isssl,网络连接安全,一种安全协议
    if(redisProperties.isSsl()) {
        builder.useSsl();
    }
//Duration,执行时间,超时时间
    if(redisProperties.getTimeout()!=null) {
        Duration timeOut=redisProperties.getTimeout();
        builder.readTimeout(timeOut).connectTimeout(timeOut);
    }
    RedisProperties.Pool pool=redisProperties.getJedis().getPool();
    if(pool!=null) {
        builder.usePooling().poolConfig(jedisPoolConfig(pool));
    }
    return builder.build();
}
/**jedis连接池配置*/
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool redisProperties) {
    JedisPoolConfig config=new JedisPoolConfig();
    config.setMaxIdle(redisProperties.getMaxIdle());
    config.setMaxTotal(redisProperties.getMaxActive());
    config.setMinIdle(redisProperties.getMinIdle());
    if(redisProperties.getMaxWait()!=null) {
        config.setMaxWaitMillis(redisProperties.getMaxWait().toMillis());
    }
    return config;
}
/**redis的选择模板
 * 有很多人问为什么用注解primary,不多说,这里是说明作用:自动装配时,如果出现多个bean的候选者,被注解为primary的bean将作为首选者,优先选择,否则会抛出异常。*/
@Bean(name="redisTemplate")
@Primary
public RedisSelectConfig redisSelectConfig() {
    RedisSelectConfig tem=new RedisSelectConfig();
    tem.setConnectionFactory(jedisConnectionFactory());
    return tem;
}
}

RedisSelectSuport--redis切换库支持类

package com.king.common.redis;
/**redis切换库支持类*/

public class RedisSelectSuport {
private static final ThreadLocal SELECT_CONTEXT=new ThreadLocal<>();
public static void select(int db) {
    SELECT_CONTEXT.set(db);
}
public static Integer getSelect() {
    return SELECT_CONTEXT.get();
}
}

RedisSelect--自定义注解类

package com.king.common.redis;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**自定义注解RedisSelect*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisSelect {
    //redis库,默认为0
int value() default 0;
}

RedisSelectConfig--redis切换支持类,切换执行类。

package com.king.common.redis;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;

public class RedisSelectConfig extends StringRedisTemplate{
     @Override
        protected RedisConnection createRedisConnectionProxy(RedisConnection pm) {
            return super.createRedisConnectionProxy(pm);
        }

        @Override
        protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
            Integer db;
            if((db = RedisSelectSuport.getSelect()) != null){
                connection.select(db);
            }
            return super.preProcessConnection(connection, existingConnection);
        }
}

RedisAspect--切面类,重点,在此利用环绕通知,实现切面执行。

package com.king.common.redis;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RedisAspect {
private static Logger log=LoggerFactory.getLogger(RedisAspect.class);
@Value("${king.redis.open}")
private boolean open;
@Value("${spring.redis.database}")
private int defaultDatabase;
/**这里解释下point.proceed用法。
 * 环绕通知,简单说就是:环绕通知=前绕通知+目标方法执行+后置通知。
 * proceed方法就是用于启动目标方法执行的。
 * 如果在Around中不调用point.proceed(),那@Before注解的方法不会调用,不过after还是会调用*/
@Around("execution(* com.king.util.RedisUtilsone.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
    Object result=null;
    if(open) {
        try {
            //调用执行目标方法(result为目标方法执行结果)
            result=point.proceed();
        } catch (Exception e) {
            // TODO: handle exception
            log.error("redis error"+e);
        }
    }
    return result;
}
@Around("@annotation(com.king.common.redis.RedisSelect)")
@ConditionalOnBean(RedisSelectConfig.class)
public Object configRedis(ProceedingJoinPoint point) throws Throwable {
    int db=defaultDatabase;
    try {
        MethodSignature signature=(MethodSignature) point.getSignature();
        Method method=signature.getMethod();
        RedisSelect config=method.getAnnotation(RedisSelect.class);
        if(config!=null) {
            db=config.value();
        }
        RedisSelectSuport.select(db);
        return point.proceed();
    }finally {
        RedisSelectSuport.select(defaultDatabase);
        log.debug("redis reset {} to {}",db,defaultDatabase);
    }
}
}

RedisUtilsone--redis工具类,暂时少许操作,后期追加完善。

package com.king.util;

import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;

@Component
public class RedisUtilsone {
@Autowired
private StringRedisTemplate redisTemplate;

private   ValueOperations valueOperations=null;
@PostConstruct
public void init() {
    this.valueOperations=redisTemplate.opsForValue();
}
/**定义默认过期时长*/
public final static long DEFAULT_EXPIRE=60*60*24;
/**不设置过期时长*/
public final static long NOT_EXPIRE=-1;
public void set(String key,Object value,long expire) {
    valueOperations.set(key, toJson(value));
    if(expire!=NOT_EXPIRE) {
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }
}
public void set(String key, Object value){
    set(key, value, DEFAULT_EXPIRE);
}

public  T get(String key, Class clazz, long expire) {
    String value = valueOperations.get(key);
    if(expire != NOT_EXPIRE){
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }
    return value == null ? null : fromJson(value, clazz);
}

public  T get(String key, Class clazz) {
    return get(key, clazz, NOT_EXPIRE);
}

public String get(String key, long expire) {
    String value = valueOperations.get(key);
    if(expire != NOT_EXPIRE){
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }
    return value;
}

public String get(String key) {
    return get(key, NOT_EXPIRE);
}

public void delete(String key) {
    redisTemplate.delete(key);
}

/**
 * Object转成JSON数据
 */
private String toJson(Object object){
    if(object instanceof Integer || object instanceof Long || object instanceof Float ||
            object instanceof Double || object instanceof Boolean || object instanceof String){
        return String.valueOf(object);
    }
    return JSON.toJSONString(object);
}

/**
 * JSON数据,转成Object
 */
private  T fromJson(String json, Class clazz){
    return JSON.parseObject(json, clazz);
}
}

RedisSelectController--直接测试,不搞那么多繁杂的。

package com.king.Controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.king.common.redis.RedisSelect;
import com.king.util.RedisUtilsone;

@RestController
@RequestMapping(value="/selectone")
public class RedisSelectController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedisUtilsone utilsOne;
@RequestMapping(value="/setZhi")
@RedisSelect(1)
public String getZhi() {
    //redisTemplate.opsForValue().set("hello", "测试");
    /**这里调用工具类操作,会出现问题,无法进入到类中,是因为RedisAspect切面类中,有判断open(applications配置中),为false会不执行point.proceed,所以自然进不到工具类中。
     * 将配置改为true,可以。*/
    utilsOne.set("hello", "测试");
    String hello=utilsOne.get("hello");
    return hello;
}
@RequestMapping(value="/getZhi")
@RedisSelect(1)
public String getNum() {
    String num=redisTemplate.opsForValue().get("hello");
    return num;
}
}

SpringbootMybatisDemoApplication--启动类:

package com.king;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@EnableCaching
@MapperScan("com.king.mapper")//将项目中对应的mapper类的路径加进来就可以了
public class SpringbootMybatisDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
    }
}

其中图片所展示项目结构,redis另一个pro配置,只是另外一个redis整合缓存和其他操作用到,这里忽略。
源码地址:敬请Fork跟Star。
https://github.com/wangdonghuihuang/HappyKing.git

你可能感兴趣的:(springboot+redis实现自动切换库)