场景一
自定义封装一个Redis方法的工具类——取名 RedisCacheUtil
。最基础的调用,一般会先注入RedisTemplate
对象,然后在具体方法中调用ValueOperations opsForValue = redisTemplate.getOpsForValue();
,由此可以来调用Redis提供的方法操作Redis。由于每次在代码中想使用Redis的方法操作redis的数据时,会创建重复的代码(如下):
public class SpringbootXxlJobTestApplicationTests {
@Resource
private RedisTemplate redisTemplate;
@Test
void testGenerateId(){
ValueOperations opsForValue = redisTemplate.getOpsForValue();
Object mhd = opsForValue.get("mhd");
System.out.println(mhd);
}
}
public class SpringbootXxlJobTestApplicationTests2 {
@Resource
private RedisTemplate redisTemplate;
@Test
void testGenerateId(){
ValueOperations opsForValue = redisTemplate.getOpsForValue();
Object mhd = opsForValue.get("mhd");
System.out.println(mhd);
}
}
使用操作方法之前都得 redisTemplate.opsForValue()
,封装之后,每次在其他代码方法里使用 Redis,就不用每次都注入对象RedisTemplate
,只需要在RedisCacheUtil
类中注入一次RedisTemplate
,其他代码使用时注入 RedisCacheUtil
即可:
@Component
public class RedisCacheUtil {
@Resource
private RedisTemplate redisTemplate;
/**
* redis 设置 key-value 永不过期
*
* @param key
* @param value
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param timeout 时间
* @param timeUnit 时间单位
* @return
*/
public boolean expire(String key, long timeout, TimeUnit timeUnit) {
return redisTemplate.expire(key, timeout, timeUnit);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public long getExpireTime(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 设置value的自增值
* 给 key 的 value储存的数字加上指定的增量值,如果 key 不存在,则 key 的值会先被初始化为 0,然后再执行 INCR 操作
*
* @param key 键
* @param delta 自增数值
* @return 自增后的 value
*/
public long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 设置 redis key-value 过期时间
*
* @param key
* @param value
* @param time
* @param timeUnit
*/
public void set(String key, Object value, long time, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
}
/**
* 移除指定key 的过期时间
*
* @param key
* @return
*/
public boolean persist(String key) {
return redisTemplate.boundValueOps(key).persist();
}
}
一开始,封装的 redis 工具类中封装的方法采取的是 非静态方法,这时候在其他静态方法中调用 RedisCacheUtil
中的非静态方法时,会出现 RedisCacheUtil 类调用出现空指针异常:
错误代码示例: redisCacheUtil
会空指针
@Slf4j
public class IdGeneratorUtil {
@Resource
private RedisCacheUtil redisCacheUtil;
public static String generateId() {
ID_PREFIX = "ID";
String datetime = "";
try {
....
Long autoID = RedisCacheUtil.increment(key, 1);
// 如果 autoID = 1,则 key不存在,给 key设置过期时间
if (autoID == 1) {
// 设置key 1小时过期
RedisCacheUtil.expire(key, REDIS_TIMEOUT, TimeUnit.SECONDS);
}
// 获取订单号,时间戳 + 唯一自增Id(最少4位数,不够前方补0)
return datetime + String.format("%04d", autoID);
} catch (Exception e) {
log.error("Redis is abnormal, the orderNo will be generated with ObjectId.");
//12位时间戳 datetime = datetime + ObjectId.get();
return datetime;
}
}
}
以上代码即使使用 @Resource
注入 RedisCacheUtil
注入也会失败,在静态方法
中调用也会导致空指针异常。
即使使用 static 修饰也不行,private static RedisCacheUtil redisCacheUtil;
然后,将 RedisCacheUtil 类中的非静态方法改成静态方法,加上 static 也不行。最后采取以下方式解决,注意:
@Component
注解。@Resource
注入 RedisTemplate
对象,一个定义静态的成员变量 static RedisTemplate redisTempStatic
。@PostConstruct
注解修饰,方法中将注入的非静态实例对象 redisTemplate
赋值给静态的成员变量 redisTempStatic
。@Component
public class RedisCacheUtil {
@Resource
private RedisTemplate redisTemplate;
private static RedisTemplate redisTempStatic;
@PostConstruct
public void setRedisTemplate() {
redisTempStatic = this.redisTemplate;
}
/**
* redis 设置 key-value 永不过期
*
* @param key
* @param value
*/
public static void set(String key, Object value) {
redisTempStatic.opsForValue().set(key, value);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTempStatic.opsForValue().get(key);
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param timeout 时间
* @param timeUnit 时间单位
* @return
*/
public static boolean expire(String key, long timeout, TimeUnit timeUnit) {
return redisTempStatic.expire(key, timeout, timeUnit);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public static long getExpireTime(String key) {
return redisTempStatic.getExpire(key, TimeUnit.SECONDS);
}
/**
* 设置value的自增值
* 给 key 的 value储存的数字加上指定的增量值,如果 key 不存在,则 key 的值会先被初始化为 0,然后再执行 INCR 操作
*
* @param key 键
* @param delta 自增数值
* @return 自增后的 value
*/
public static long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTempStatic.opsForValue().increment(key, delta);
}
/**
* 设置 redis key-value 过期时间
*
* @param key
* @param value
* @param time
* @param timeUnit
*/
public static void set(String key, Object value, long time, TimeUnit timeUnit) {
redisTempStatic.opsForValue().set(key, value, time, timeUnit);
}
/**
* 移除指定key 的过期时间
*
* @param key
* @return
*/
public static boolean persist(String key) {
return redisTempStatic.boundValueOps(key).persist();
}
}
@Slf4j
public class IdGeneratorUtil {
public static String generateId() {
ID_PREFIX = "ID";
String datetime = "";
try {
....
Long autoID = RedisCacheUtil.increment(key, 1);
// 如果 autoID = 1,则 key不存在,给 key设置过期时间
if (autoID == 1) {
// 设置key 1小时过期
RedisCacheUtil.expire(key, REDIS_TIMEOUT, TimeUnit.SECONDS);
}
// 获取订单号,时间戳 + 唯一自增Id(最少4位数,不够前方补0)
return datetime + String.format("%04d", autoID);
} catch (Exception e) {
log.error("Redis is abnormal, the orderNo will be generated with ObjectId.");
//12位时间戳 datetime = datetime + ObjectId.get();
return datetime;
}
}
}
以上代码,可实现在其他类的静态方法中通过 RedisCacheUtil 直接调用当中的方法。
一般思路是在类中 注入 Mapper 实例对象,然后在静态方法中通过实例对象调用方法。但不幸的是,这是不行的,会报空指针。
错误示例:
@Slf4j
public class IdGeneratorUtil {
@Resource
private static IdGenerateMapper idGenerateMapper;
public static String generateId() {
ID_PREFIX = "ID";
String datetime = "";
datetime = idGenerateMapper.getCurrentDateTime();
}
}
在静态方法中使用Mapper,可以先在 static 代码块中将要注入的mapper实例对象赋值,通过 Spring 应用上下文中获取 Bean 实例和其他资源。
修改后的代码:
@Slf4j
public class IdGeneratorUtil {
private static IdGenerateMapper idGenerateMapper;
static {
idGenerateMapper = SpringUtils.getBeanByClass(IdGenerateMapper.class);
try {
// redis当前最大时间 key
REDIS_TIME_MAX_KEY = "mhd";
} catch (Exception e) {
log.error("Failed to create a jedisTemplate.");
}
}
public static String generateId() {
ID_PREFIX = "ID";
String datetime = "";
datetime = idGenerateMapper.getCurrentDateTime();
}
}
package com.example.springbootxxljobtest.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* @Description:
* @title: SpringContextUtils
* @Author mhd
* @Date: 2024/9/23 9:45
* @Version 1.0
*/
@Component
public final class SpringContextUtils implements DisposableBean, ApplicationContextAware {
private static ApplicationContext applicationContext;
public SpringContextUtils() {
}
@Override
public void destroy() throws Exception {
clearContext();
}
private static void clearContext() {
applicationContext = null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
public static String getProperty(String property) {
Environment var1 = (Environment) getBean(Environment.class);
return var1.getProperty(property);
}
public static <T> T getProperty(String property, Class<T> tClass) {
Environment var2 = (Environment) getBean(Environment.class);
return var2.getProperty(property,tClass);
}
}
SpringUtils
和 SpringContextUtils
工具类,二选一即可。注意:
自己封装定义的这两个工具类的名称涉及到Spring框架中的关键字,在取名的时候建议避免和Spring的名字区分开,避免类名冲突。比如 getSpringUtils ,虽然不咋优雅,但能避坑。
package com.example.springbootxxljobtest.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext对象
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(String id, Class<T> clazz) {
return (T) getBeanById(id);
}
public static <T> T getBean(String id) {
return (T) getBeanById(id);
}
/**
* 根据bean的id来查找对象
*
* @param id
* @return
*/
public static Object getBeanById(String id) {
return applicationContext.getBean(id);
}
/**
* 根据bean的class来查找对象
*
* @param c
* @return
*/
public static <T> T getBeanByClass(Class<T> c) {
return applicationContext.getBean(c);
}
/**
* 根据bean的class来查找所有的对象(包括子类)
*
* @param c
* @return
*/
public static <T> Map<String, T> getBeansByClass(Class<T> c) {
return applicationContext.getBeansOfType(c);
}
}