前言
1.项目场景:开发中遇到使用Mybatis实现二级缓存。项目中涉及>到使用Redis来存储二次查询的数据,但对于存储的对象类型我们需要去自定义一个RedisCache类并实现’'Cache"以此来重新里面>的"put"和“get”方法。
2 但是出现一个问题,我们在自定义这个RedisCache中使用@Autowired注解注入操作Redis的"RedisTemplate对象"的时候,
显示为null,通过研究发现我发先’非Spring容器管理的类中’去使用
/注入由Spring容器管理的RedisTemplate对象。
3.解决方案为:创建一个Spring工厂,然后在RedisCache这个自定义类中引入此工厂,在通过工厂的getBean() 获取到RedisTemplate这个对象即可。
//自定义Redis缓存实现
public class RedisCache implements Cache {
//当前放入缓存的mapper的namespace
private final String id;
//必须存在构造方法
public RedisCache(String id) {
System.out.println("id:=====================> " + id);
this.id = id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}
//缓存放入值 redis RedisTemplate StringRedisTemplate
@Override
public void putObject(Object key, Object value) {
System.out.println("key:" + key.toString());
System.out.println("value:" + value);
// //通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//使用redishash类型作为缓存存储模型 key hashkey value
getRedisTemplate().opsForHash().put(id.toString(),getKeyToMD5(key.toString()),value);
//根据UserDAO模块,设置对应的缓存超时时间
if(id.equals("com.zk.dao.UserDAO")){
//缓存超时 client 用户 client 员工
getRedisTemplate().expire(id.toString(),1, TimeUnit.HOURS);
}
//若是CityDAO模块,设置另一个超时时间
if(id.equals("com.zk.dao.CityDAO")){
//缓存超时 client 用户 client 员工
getRedisTemplate().expire(id.toString(),30, TimeUnit.MINUTES);
}
//.....根据不同业务模块,设置不同缓存超时时间
}
//获取中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key:" + key.toString());
// //通过application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//根据key 从redis的hash类型中获取数据
return getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(key.toString()));
}
/**
* 1. removeObject()此方法暂时没什么用
* 注意:这个方法为mybatis保留方法 默认没有实现 后续版本可能会实现
*/
@Override
public Object removeObject(Object key) {
System.out.println("根据指定key删除缓存");
return null;
}
/**
* 1. 我们执行redis的删除方法,默认走的是clear()这个方法
* 对于redis执行增删改操作,都会走此方法将redis的缓存清空掉
*/
@Override
public void clear() {
System.out.println("清空缓存~~~");
//清空namespace
getRedisTemplate().delete(id.toString());//清空缓存
}
//用来计算缓存数量
@Override
public int getSize() {
//获取hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}
//封装redisTemplate。定义成私有,表示该方法只能内部被调用使用
private RedisTemplate getRedisTemplate(){
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
package com.zk.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
//用来获取springboot创建好的工厂
/**
* 1. 由于我们在使用分布式缓存时,需要用到'RedisTemplate'或'StringRedisTemplate'其中的一个对象。
* 我们这里分布式缓存用到对象,所以这里使用"RedisTemplate"这个对象。
* ➀. 如果RedisTemplate这个类是工厂管理,我们就可以直接使用@Autowired注入这个对象,但是这个类的实例化
* 是由mybatis实例化的(创建的)-->而mybatis中的Cache对象不是由工厂去管理的对象,我们要在自定义的
* RedisCache这个类下面去注入并使用'RedisTemplate',是无法注入的。
*
* ➁. 因此若要拿到'RedisTemplate'这个对象,就需要到Spring的工厂中去获取,而"ApplicationContext这个工厂"是
* 我们最大的工厂,因此我们就需要获取到ApplicationContext这个工厂,然后根据这个工厂中的"getbean(String bean)"
* 传入要获取的对象字符串名,去工厂中获取'RedisTemplate'这个对象。
*
*
* 2. 所以我们可以去创建类,比如创建一个(ApplicationContextUtils)类,并让这个类去实现'ApplicationContextAware接口',
* 此时当我们去实现'ApplicationContextAware接口后'就代表springboot它会自动帮我们创建一个工厂,当springboot帮我们创建号
* 工厂后,springboot会通过"setApplicationContext()"这个方法以参数的形式给我们返回创建号的这个工厂。
*
* 3. 之后我们就可以获取到这个"ApplicationContext工厂"-->并调用getBean()获取我们需要的"RedisTemplate对象"执行
* redis中的方法。
* --->你日后如果给我传进来的是"RedisTemplate",那么入参就是小写的'redisTemplate'。
*
*
*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
//
/**
* 1. 将springboot给我们创建的工厂定义为全局静态变量,将此工厂对象保留下来
* 2. 下面"setApplicationContext()"给我们返回的工厂我们就用此对象去接受springboot
* 给我们返回的这个'ApplicationContext'工厂。
*/
private static ApplicationContext applicationContext;
//将创建好工厂以参数形式传递给这个类
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 1. 日后我们可以通过springboot给我们创建的这个工厂,去获取对应我们所需要的
* 对象的方法,通过getBean()去获取
* 2. 下面方法的入参我们定义称我们锁需要获取的对象的小写形式,他就会给我们返回具体的对象。
*/
//提供在工厂中获取对象的方法 //RedisTemplate redisTemplate
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
}
1. RedisTemplate依赖注入问题
⑴ 我们现在使用Spring框架进行开发时,都会使用Spring为我们提供的
Springboot框架作为作为快速开发的框架去构建项目环境。
⑵ Spring官方为了更好的去操作Redis,Spring官方中有这样一个框
架,'Spring Date框架'。这个'Spring Date'中的'Date'(数据)就是
专门用来操作"各种各样的关系型数据库和非关系型数据库"。
--->所谓关系型数据库就是:Mysql,Oracle,sqlserver
--->所谓的非关系型数据库就是:Redis,ES,MongDB
因此'Spring Date'它就是可以用来操作今天说的'Redis'以
及'ES,mongDB'它都是可以操作的.
⑶ 当'Spring Date与Redis集成'之后它为我们提供了两个对象:
RedisTemplate和StringRedisTemplate
这两个对象是天生只要你'Spring Date与Redis集成好了之后',它就自
动在工厂为你创建好了,这两个对象就是"RedisTemplate和
StringRedisTemplate"
-----------------------------------------------------------
2. RedisTemplate和StringRedisTemplate介绍
⑴ RedisTemplate就相当于一个Redis的模板,这个模板它实际上就封装
了操作Redis里面的各种各样的类型。如果操作的是"关系型数据库"那基
本上就叫:JdbcTemplate而对于我们今天讲的Redis来说,
RedisTemplate它操作的就是Redis的各种各样的五种类型
(String,List,Map,Set,Zset)
⑵ StringRedisTemplate 是RedisTmeplate的子类,这两个对象的方
法基本一致。我们在学习这两个对象中,只要学习其中一个就可以了,因
为无论是'RedisTemplate'还是'StringRedisTemplate'无非都是去操
作Redis中的这五种数据类型对应的数据。
---------------------------------------------------------
3. RedisTemplate和StringRedisTemplate的异同点详解
⑴ RedisTemplate和StringRedisTemplate 不同之处就在于:操作的
数据类型不同。
➀ 首先:Redis是一个key-value。因此它的value类型可以有很
多,value类型:(String,List,Map,Set,Zset)。
➁ 其次:我们日后通过java去操作Redis的时候,
- 如果我们操作的:key是字符串,那它对应的值也是字符串。
- 如果我们操作的:key是字符串,但是对于的值就可以是一个集
合,集合值比如为:{"zhangsan","lisi"}
- 因此无论是value是针对于'String,List,Map,Set,Zset'哪一个
类型,在我们java中默认存储的value值都是字符串类型,可能没有
办法去直接放一个'实体对象'。
➂ 然后:由于对于java而言'一切皆对象'的原则。日后如果我们想
让Redis去帮我们做更复杂的处理,那我就应该把一个对象放
入'Redis'中,所以在原生的java中如果要将对象存储到Redis中的
话,就需要'通过对象序列化'的方式再将我们的对象放入Redis中。
➃ 之后:所以针对于java去操作redis,里面放的全部都是字符串,
如果此时要将value这个值改成对应的对象或者集合,我们就应该采
用'序列化的方式'。而我们的'Spring Date'他就考虑到了这个事情
(java是一切皆对象),我们肯定是处理对象更好处理,所以'Spring
Date'给我们提供了两个操作Redis的对象:RedisTemplate对象和
StringRedisTemplate对象。
➄ 最后:RedisTemplate对象和StringRedisTemplate对象他们主
要是操作数据类型的'泛型'不同。
- RedisTemplate它的key和value都允许放的值类型为:Object。
RedisTemplate<Object,Object> -->日后我们java只需要将对象
放进去就行了,RedisTmeplate在往Redis中存的时候会自动给我们
去'序列化处理',同时它还没在取的时候自动'反序列化处理'。
(意思就是说:使用RedisTemplate你存进去是一个对象或集合,取
出来的同样也是一个对象或集合)
- StringRedisTemplate它的key和value的值类型为:String 。
- 他是RedisTempalte下面提供的一个子类。
这个对象在往Redis存值时它的key是String,值也是Stirng。
➅ 小结:因此'Spring Date'为我们提供了两种强大的操作
RedisTemplate和StringRedisTemplate一种是站在对象的角
度,你可以往Redis中放对象另外一种你可以用原始的角度,去直
接往Redis中放字符串
----------------------------------------------------------
3. 注意点:在使用'RedisTemplate'时-->使用RedisTemplate默认是
将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口的。
4. 记住:Spring Date就是Spring官方为我们提供的一个专门用来操作
数据(持久层/数据库)的一个框架。这个框架可以操作我们的'关系型数据
库和非关系型数据库',功能非常的强大。
1. 在不被Spring容器管理的对象中无法注入Spring管理的对象问题
的小结。
⑴ 简单来说就是:对象RedisCache独立于spring容器(RedisCache实际
上是由mybatis框架实现初始化,myabtis所管理的),而处理Redis的
RedisTemplate实例由spring容器管理。如果直接在RedisCache类中使
用@Autowired注入RedisTemplate对象,在运行时RedisTemplate实例
对象将是null。
⑵ 导致错误。因为RedisTemplate实例对象只有在spring容器中才能使
用,那么在RedisCache这个非spring容器管理的对象想要使用的话,就
要先取得spring的上下文对象,然后再通过上下文对象去获取
RedisTemplate实例对象即可。
1.1 上下文对象的获取,项目采用的是springboot,可以通过以下方式拿到spring的上下对象。
⑴ 方式一:定义一个持有上下文对象的类,将该类交由spring容器管理,然后通过该类去获取上下文对象
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
⑶ 目标对象的生成
➀ 通过调用context的getBean方法获取在spring容器中管理的对
象。下面获取RedisTemplate对象的方法。
# 通过方法ApplicationContextHolder中的静态context对象:
private static SimpMessagingTemplate simpMessagingTemplate = ApplicationContextHolder.getContext()
.getBean(RedisTemplate.class);
-