1.使用場景
springboot使用mybatis開啟redis二級緩存,使用的代碼如下:
ApplicationContextHolder.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 實現ApplicationContextAware接口的context注入函數, 將其存入靜態變量.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 取得存儲在靜態變量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 從靜態變量ApplicationContext中取得Bean, 自動轉型為所賦值對象的類型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 從靜態變量ApplicationContext中取得Bean, 自動轉型為所賦值對象的類型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
/**
* 清除applicationContext靜態變量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義SpringContextHolder");
}
}
}
RedisCacheServiceImpl.java
import dgis.lite.usgs.ApplicationContextHolder;
import dgis.lite.usgs.utils.SpringUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCacheServiceImpl implements Cache {
// 讀寫鎖
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplate<String, Object> redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
private String id;
public RedisCacheServiceImpl(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (value != null) {
// 向Redis中添加數據,有效時間是1小時
redisTemplate.opsForValue().set(key.toString(), value, 1, TimeUnit.HOURS);
}
}
@Override
public Object getObject(Object key) {
try {
if (key != null) {
Object obj = redisTemplate.opsForValue().get(key.toString());
return obj;
}
} catch (Exception e) {
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if (key != null) {
redisTemplate.delete(key.toString());
}
} catch (Exception e) {
}
return null;
}
@Override
public void clear() {
try {
Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
} catch (Exception e) {
}
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.dbSize();
}
});
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}

2.出現問題
applicaitonContext屬性未注入, 請在applicationContext.xml中定義SpringContextHolder.
這個異常其實是在 ApplicationContextHolder.java 中我們自己寫的異常,出現原因是因為 mapper中定義的緩存類型加載的比ApplicationContextHolder快,也就是說ApplicationContextHolder還沒有初始化好,mapper就開始初始化了。
3.解決方法
網上搜了一大堆方法,都是復制粘貼的,最后處理方法其實比較簡單,就是不使用ApplicationContextHolder這個類,直接使用SpringUtils.getBean類初始化redisTemplate。即:
private RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean("redisTemplate");
修改后的RedisCacheServiceImpl.java
import dgis.lite.usgs.ApplicationContextHolder;
import dgis.lite.usgs.utils.SpringUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCacheServiceImpl implements Cache {
// 讀寫鎖
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean("redisTemplate");
private String id;
public RedisCacheServiceImpl(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (value != null) {
// 向Redis中添加數據,有效時間是1小時
redisTemplate.opsForValue().set(key.toString(), value, 1, TimeUnit.HOURS);
}
}
@Override
public Object getObject(Object key) {
try {
if (key != null) {
Object obj = redisTemplate.opsForValue().get(key.toString());
return obj;
}
} catch (Exception e) {
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if (key != null) {
redisTemplate.delete(key.toString());
}
} catch (Exception e) {
}
return null;
}
@Override
public void clear() {
try {
Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
} catch (Exception e) {
}
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.dbSize();
}
});
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}
SpringUtils.java
import org.apache.commons.lang.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
/**
* Spring 應用上下文環境
*/
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
public SpringUtils() {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
}
/**
* 獲取對象
*
* @param name
* @return Object 一個以所給名字注冊的 bean 的實例
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
/**
* 獲取類型為 requiredType 的對象
*
* @param clz
* @return
* @throws BeansException
*/
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* 如果 BeanFactory 包含一個與所給名稱匹配的 bean 定義,則返回 true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return beanFactory.containsBean(name);
}
/**
* 判斷以給定名字注冊的 bean 定義是一個 singleton 還是一個 prototype。 如果與給定名字相應的 bean 定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws NoSuchBeanDefinitionException
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return beanFactory.isSingleton(name);
}
/**
* @param name
* @return Class 注冊對象的類型
* @throws NoSuchBeanDefinitionException
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getType(name);
}
/**
* 如果給定的 bean 名字在 bean 定義中有別名,則返回這些別名
*
* @param name
* @return
* @throws NoSuchBeanDefinitionException
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getAliases(name);
}
/**
* 獲取 aop 代理對象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker) {
return (T) AopContext.currentProxy();
}
/**
* 獲取當前的環境配置,無配置返回 null
*
* @return 當前的環境配置
*/
public static String[] getActiveProfiles() {
return applicationContext.getEnvironment().getActiveProfiles();
}
/**
* 獲取當前的環境配置,當有多個環境配置時,只獲取第一個
*
* @return 當前的環境配置
*/
public static String getActiveProfile() {
final String[] activeProfiles = getActiveProfiles();
return StringUtils.isNotEmpty(String.valueOf(activeProfiles)) ? activeProfiles[0] : null;
}
}
現在打包jar,運行正常。
