最近在研究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类非常简单,不再给出代码。