关于JAVA的redis客户端的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l00149133/article/details/78782421

最近在研究java的redis使用,尤其是在spring中的使用,总结一下。

首先java的redis客户端比较好用的,也是比较常用的是Jedis。
关于Jedis需要注意的有2点:
1. Jedis不是线程安全的。也就是说当多个线程同时使用Jedis的同一个实例的时候会出现并发问题。所以Jedis提供了一个线程池JedisPool,这个我们在后面的代码中也会使用到。
2. JedisPool需要你去手动释放其中的Jedis对象,通过你去调用returnResource方法或者returnBrokenResource方法

另外在我们的实例中,因为我们不想去每次调用jedis时都写一遍释放jedis对象的方法,所以我们利用了cglib做了一个AOP,去截获所有的jedis方法,并给它加上一个释放对象的后缀。

前提基本上说完,下面我们直接来看代码。
代码主要有两个类,EnhanceRedis和RedisCacheSupport。
其中,EnhanceRedis负责Jedis的初始化,以及AOP部分
RedisCacheSupport负责具体业务的实现,比如setValue,getkey之类

//Component标签,表示这个类会被自动扫描
//可以看到这个类实现了很多接口,其中FactoryBean<EnhancerRedis>是为了实现生产bean,以便于可以自动装配(@autowired)EnhancerRedis的实例
//MethodInterceptor是cglib的一个接口,用来实现动态代理,从而截获jedis方法,达到AOP
//JedisCommands,MultiKeyCommands, AdvancedJedisCommands等等xxxxCommands都是Jedis的接口,用来实现不同的redis命令行
@Component
public class EnhancerRedis implements FactoryBean<EnhancerRedis>,  MethodInterceptor, JedisCommands,
    MultiKeyCommands, AdvancedJedisCommands, ScriptingCommands,
    BasicCommands, ClusterCommands{

    //这一部分我们通过value标签,在properties文件里面定义了redis的一些参数,比如host,最大等待时间
    @Value("${redis.maxTotal}")
    private int maxTotal;
    @Value("${redis.maxWaitMillis}")
    private int maxWaitMillis;
    @Value("${redis.maxIdle}")
    private int maxIdle;
    @Value("${redis.minIdle}")
    private int minIdle;
    @Value("${redis.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;
    @Value("${redis.hostName}")
    private String host;
    @Value("${redis.port}")
    private int port;
    @Value("${redis.password}")
    private String password;

    //Jedis的线程池
    private JedisPool pool = null;

    //PostConstruct标签表示,被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,servlet的init()方法之前执行。
    @PostConstruct
    public void init() {
        //对JedisPool的一些参数设置
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxWaitMillis(maxWaitMillis);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        if(password == null || password.trim().equals("")) {
            pool = new JedisPool(config, host, port);
        } else {
            pool = new JedisPool(config, host, port, Protocol.DEFAULT_TIMEOUT, password);
        }
    }


    //这个方法继承自FactoryBean接口,返回的是这个类通过beanfactory所获得的实例
    @Override
    public EnhancerRedis getObject() throws Exception {
        //Enhancer类来自cglib,用来创建一个代理(create方法),同时绑定委托类(setSuperclass),以及指定代理类(setCallback)。这里委托类和代理类都是EnhancerRedis本身。
        Enhancer enhancer = new  Enhancer();
        enhancer.setSuperclass(EnhancerRedis.class);
        enhancer.setCallback(this);
        return (EnhancerRedis) enhancer.create();
    }

   //这个方法继承自FactoryBean接口,表明这个类返回的bean的类型为EnhancerRedis
    @Override
    public Class<EnhancerRedis> getObjectType() {
        return EnhancerRedis.class;
    }

    //这个方法继承自FactoryBean接口,表示创建的bean是否是单例
    @Override
    public boolean isSingleton() {
        return true;
    }

    //这个方法继承自MethodInterceptor,用来拦截所有EnhancerRedis类的方法
    //因为上面的getObject()方法创建的代理,委托类和代理类都是EnhancerRedis类自身。所以这里相当于EnhancerRedis类自身拦截自身的方法,并替换成intercept里面的内容
    //这个方法主要做了两件事:1.把EnhancerRedis的实例执行的方法,改成由JedisPool里面的一个实例来执行,因为EnhancerRedis继承了一堆Jedis的接口,所以EnhancerRedis实例的方法跟JedisPool里面jedis实例的方法一致。这样做的好处是外部是不知道有JedisPool这个线程池存在的,也不需要关心它是否存在
    //2. 在方法执行后通过returnRedis方法释放了JedisPool里面的jedis实例,而不需要外部调用者去手动释放,同样外部调用者也感知不到释放实例,也不需要关心它是否存在
    //总之通过这个类,外部调用者可以用Jedis的线程池来调用jedis的方法,但是又无需关心Jedis线程池的存在
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        Jedis redis = getRedis();
        Object result = null;
        try {
            //获取到截获的方法(根据方法名和参数类型)
            Method methodRedis = redis.getClass().getMethod(method.getName(), method.getParameterTypes());
            //实际执行时,从JedisPool里面获取一个实例来执行方法(Jedis redis = getRedis())
            result = methodRedis.invoke(redis, args);
            //执行完毕后,释放JedisPool的实例
            returnRedis(redis);
        } catch (Exception e) {
            //如果出现错误,强制释放JedisPool的实例
            returnBrokenRedis(redis);
        }
        return result;
    }



    //从Jedis的线程池中去一个实例
    public Jedis getRedis() {
        return pool.getResource();
    }

    //释放线程池中指定的实例
    public void returnRedis(Jedis jed){
        pool.returnResource(jed);
    }

    //释放线程池中指定的实例
    public void returnBrokenRedis(Jedis jed){
        pool.returnBrokenResource(jed);
    }


    //redis的操作接口,并不需要实现, 最终将用jedis的实例来操作
    //下面有大量的方法,都继承自XXXCommands接口,都是redis的具体命令
    //而这些方法我们都没有具体去实现,因为这些方法实际上都不会走到它自身的方法体,而是通过intercept被截获,最终走的JedisPool里面jedis实例的方法,而jedis实例的方法则不是空的,也不会return null,所以我们无需去实现这里的这些方法
    @Override
    public String clusterNodes() {
        // TODO Auto-generated method stub
        return null;
    }


    @Override
    public String clusterMeet(String ip, int port) {
        // TODO Auto-generated method stub
        return null;
    }

EnhancerRedis类讲完,这个类主要是对jedis的初始化和封装。
下面说一下redis的实际操作类RedisCacheSupport :

public class RedisCacheSupport {
    final static Logger LOGGER = LoggerFactory.getLogger(RedisCacheSupport.class);
    protected static final String DEFAULT_CHARSET = "utf-8";

    //因为EnhancerRedis类已经实现了接口FactoryBean,所以我们可以使用Autowired进行自动装配。而且这样装配出来的bean的实例,实际上走的方法是EnhancerRedis的getObject()方法,也就是说实际上通过Autowired自动装配,这里EnhancerRedis的redis已经绑定了委托类和指定了代理类,这里的redis实例实际上已经是一个proxy
    @Autowired
    protected EnhancerRedis redis;

    //这里的两个常量,用作redis存储的key的前缀,来区分不同的业务
    /* 通用缓存KEY前缀 */
    private static final String KEY_COMM = "comm_";
    /* 团队数据缓存前缀 */
    private static final String KEY_TEAM = "team_";


    //下面几个方法是redis最简单的使用(String类型存储)
    //需要注意的是传进去的key要加前缀来区分业务,比如key=KEY_COMM + "001"
    /**
     * 根据key判断是否存在,存在返回true,反之返回false
     * @param key
     * @return
     */
    public boolean exists(String key) {
        return redis.exists(key);
    }

    /**
     * 根据Key获取缓存中的值
     * @param key
     * @return
     */
    public String get(String key) {
        return redis.get(key);
    }

    /**
     * 设置值到缓存中
     * @param key   缓存的键
     * @param value 缓存的值
     */
    public void put(String key, String value) {
        redis.set(key, value);
    }

    /**

EnhancerRedis类的代码可以看这里:http://download.csdn.net/download/l00149133/10158682
RedisCacheSupport类非常简单,不再给出代码。

猜你喜欢

转载自blog.csdn.net/l00149133/article/details/78782421