项目遇到的问题:1. 在不被Spring容器管理的对象中无法注入Spring管理的对象

前言

  1. Redis缓存实现
    1.1 自定义Redis缓存实现类
    1.2 创建生成Spring工厂工具类
    1.3 Spring框架集成Redis关系说明
  2. 小结

前言

1.项目场景:开发中遇到使用Mybatis实现二级缓存。项目中涉及>到使用Redis来存储二次查询的数据,但对于存储的对象类型我们需要去自定义一个RedisCache类并实现’'Cache"以此来重新里面>的"put"和“get”方法。


2 但是出现一个问题,我们在自定义这个RedisCache中使用@Autowired注解注入操作Redis的"RedisTemplate对象"的时候,
显示为null,通过研究发现我发先’非Spring容器管理的类中’去使用
/注入由Spring容器管理的RedisTemplate对象。


3.解决方案为:创建一个Spring工厂,然后在RedisCache这个自定义类中引入此工厂,在通过工厂的getBean() 获取到RedisTemplate这个对象即可。

1. Redis缓存实现

1.1 自定义Redis缓存实现类

//自定义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;
    }
}



1.2 创建生成Spring工厂工具类

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.3 Spring框架集成Redis关系说明

1. RedisTemplate依赖注入问题
⑴ 我们现在使用Spring框架进行开发时,都会使用Spring为我们提供的
Springboot框架作为作为快速开发的框架去构建项目环境。

⑵ Spring官方为了更好的去操作RedisSpring官方中有这样一个框
架,'Spring Date框架'。这个'Spring Date'中的'Date'(数据)就是
专门用来操作"各种各样的关系型数据库和非关系型数据库"--->所谓关系型数据库就是:MysqlOracle,sqlserver
--->所谓的非关系型数据库就是:Redis,ES,MongDB
因此'Spring Date'它就是可以用来操作今天说的'Redis'以
及'ES,mongDB'它都是可以操作的.

⑶ 当'Spring Date与Redis集成'之后它为我们提供了两个对象:
RedisTemplateStringRedisTemplate
这两个对象是天生只要你'Spring Date与Redis集成好了之后',它就自
动在工厂为你创建好了,这两个对象就是"RedisTemplateStringRedisTemplate"

-----------------------------------------------------------

2. RedisTemplateStringRedisTemplate介绍
⑴ RedisTemplate就相当于一个Redis的模板,这个模板它实际上就封装
了操作Redis里面的各种各样的类型。如果操作的是"关系型数据库"那基
本上就叫:JdbcTemplate而对于我们今天讲的Redis来说,
RedisTemplate它操作的就是Redis的各种各样的五种类型
(String,List,Map,Set,Zset)StringRedisTemplateRedisTmeplate的子类,这两个对象的方
法基本一致。我们在学习这两个对象中,只要学习其中一个就可以了,因
为无论是'RedisTemplate'还是'StringRedisTemplate'无非都是去操
作Redis中的这五种数据类型对应的数据。

---------------------------------------------------------

3. RedisTemplateStringRedisTemplate的异同点详解
⑴ RedisTemplateStringRedisTemplate 不同之处就在于:操作的
数据类型不同。
    ➀ 首先: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都允许放的值类型为:ObjectRedisTemplate<Object,Object> -->日后我们java只需要将对象		
	放进去就行了,RedisTmeplate在往Redis中存的时候会自动给我们
	去'序列化处理',同时它还没在取的时候自动'反序列化处理'(意思就是说:使用RedisTemplate你存进去是一个对象或集合,取
    出来的同样也是一个对象或集合)
        
    - StringRedisTemplate它的key和value的值类型为:String- 他是RedisTempalte下面提供的一个子类。
	这个对象在往Redis存值时它的key是String,值也是Stirng。
     
      ➅ 小结:因此'Spring Date'为我们提供了两种强大的操作
      RedisTemplateStringRedisTemplate一种是站在对象的角
      度,你可以往Redis中放对象另外一种你可以用原始的角度,去直
      接往Redis中放字符串


----------------------------------------------------------


3. 注意点:在使用'RedisTemplate'-->使用RedisTemplate默认是
将对象序列化到Redis,所以放入的对象必须实现对象序列化接口的。

4. 记住:Spring Date就是Spring官方为我们提供的一个专门用来操作
数据(持久层/数据库)的一个框架。这个框架可以操作我们的'关系型数据
库和非关系型数据库',功能非常的强大。


2. 问题小结

1. 在不被Spring容器管理的对象中无法注入Spring管理的对象问题
的小结。

⑴ 简单来说就是:对象RedisCache独立于spring容器(RedisCache实际
上是由mybatis框架实现初始化,myabtis所管理的),而处理RedisRedisTemplate实例由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);


-

你可能感兴趣的:(项目问题整理,redis,mybatis,spring,依赖注入,spring,boot)