Spring-data-redis 的实现原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-data-redis 的实现原理相关的知识,希望对你有一定的参考价值。

参考技术A

强烈建议阅读 Spring-session原理 的"2.spring-session重写servlet request 及 redis实现存储相关问题"

另外在写作中,适当参考了下面的文章。 它们的分析并不完全符合本文的场景,但都有值得参考之处

我们知道,引入spring-data-redis后,request.getSession()的行为将会从redis中寻找,为什么会这样呢?简单来说,这是因为SessionRepositoryFilter被添加到Servlet拦截链,将request和response替换了。
它是这样起作用的:

SessionRepositoryFilter是如何被生成、加入拦截链的呢?
参考 https://zhuanlan.zhihu.com/p/75776430 ,一切得从@EnableRedisHttpSession讲起:

@EnableRedisHttpSession直接引入了RedisHttpSessionConfiguration。

看下该类的定义:

上面两张图暴露两个信息:

跟着第一点的思路,我们看下父类SpringHttpSessionConfiguration。生成了SessionRepositoryFilter,我们看下父类:

可知,该方法会接受springboot自动注入的SessionRepository<S>为参数,并以此构建SessionRepositoryFilter。而回忆前面提到的,这个参数就是RedisHttpSessionConfiguration中提供的RedisOperationsSessionRepository。

这一章的总结了包含了RedisOperationsSessionRepository的SessionRepositoryFilter,是如何被生成为Bean的。我们之后会提到,这个Bean是如何加入拦截链的

首先,spring-boot-autoconfigure里的spring.factories里有提到SessionAutoConfiguration。springboot就会自动加载该类(不知道为什么会自动加载的话,建议先百度springboot自动加载原理)。

我们看下该类的定义:

其中的 @Import... 导入了SessionRepositoryFilterConfiguration。看下定义:

SessionRepositoryFilterConfiguration是一个 @Configuration ,我们分析下这个类:

在此处debug,会得到以下信息,供读者参考:

接下来我们要分析FilterRegistrationBean。

根据Servlet3.0规范 ,这种Bean会被自动调用onStartup方法。这个方法的行为是把内部的filter注册,而上文提到这里的filter就是SessionRepositoryFilter。

关于Servlet3.0,可参考 https://www.cnblogs.com/duanxz/archive/2012/10/25/2738173.html

之后,ServletRegistrationBean::onStartup到底是如何被调用的呢?它内部又是如何注册filter的呢?这就不是本文需要关注的了。 我们只需要知道,SessionRepositoryFilter在此会被加入拦截链,而它之后又会在拦截链中替换Request、Response,从而更改Session的行为是查看redis

Spring集成Redis方案(spring-data-redis)(基于Jedis的单机模式)(待实践)

说明:请注意Spring Data Redis的版本以及Spring的版本!最新版本的Spring Data Redis已经去除Jedis的依赖包,需要自行引入,这个是个坑点。并且会与一些低版本的Spring有冲突,要看官方文档和不断的测试。

继上一篇文章http://www.cnblogs.com/EasonJim/p/7625738.html中提到的几款客户端,它们基本都能和Spring集成。

下面介绍的是基于Spring原生的spring-data-redis去集成。

还要注意的是,Spring整合了Jedis框架进去,所以下面配置上还会基于Jedis去实现的,但是Spring在上层已经集成了很好用的工具类。

而整个框架基于Spring Data,里面集成了主流的应用,比如Redis,MongoDB等,而基于这些应用的一些非常好用的框架也集成了,比如Jedis这些。

我猜测,基于Spring Data Redis去集成的,底层还是用Jedis去实现,应该是可以实现客户端集群的,下次我再实践详细分析一下。

Spring Data Redis 

项目结构:

POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.x.redis</groupId>
    <artifactId>Spring_redis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring_redis</name>
    <url>http://maven.apache.org</url>

    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
       <dependency>  
          <groupId>org.springframework.data</groupId>  
          <artifactId>spring-data-redis</artifactId>  
          <version>1.0.2.RELEASE</version>  
      </dependency>  
      <dependency>  
          <groupId>org.springframework</groupId>  
          <artifactId>spring-core</artifactId>  
          <version>3.1.2.RELEASE</version>  
      </dependency>  
      
      <dependency>  
          <groupId>redis.clients</groupId>  
          <artifactId>jedis</artifactId>  
          <version>2.1.0</version>  
      </dependency>  
        
       <dependency>  
          <groupId>junit</groupId>  
          <artifactId>junit</artifactId>  
          <version>4.8.2</version>  
          <scope>test</scope>  
      </dependency>  
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.6.1</version>
      </dependency>
      <!-- 将现有的jakarta commons logging的调用转换成lsf4j的调用。 -->
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jcl-over-slf4j</artifactId>
          <version>1.6.1</version>
      </dependency>
      <!-- Hack:确保commons-logging的jar包不被引入,否则将和jcl-over-slf4j冲突 -->
      <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.1.1</version>
          <scope>provided</scope>
      </dependency>
      <!-- slf4j的实现:logback,用来取代log4j。更快、更强! -->
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>0.9.24</version>
          <scope>runtime</scope>
      </dependency>
    </dependencies>
</project>

applicationContext.xml:

context:property-placeholder标签用来导入properties文件。从而替换${redis.maxIdle}这样的变量。

context:component-scan是为了在com.x.redis.dao报下的类能够实用spring的注解注入的方式。

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="  
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  
    <context:property-placeholder location="classpath:redis.properties" />  
    <context:component-scan base-package="com.x.redis.dao">
    </context:component-scan>
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
        <property name="maxIdle" value="${redis.maxIdle}" />  
        <property name="maxActive" value="${redis.maxActive}" />  
        <property name="maxWait" value="${redis.maxWait}" />  
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
    </bean>  
      
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  
        p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"  p:pool-config-ref="poolConfig"/>  
      
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
        <property name="connectionFactory"   ref="connectionFactory" />  
    </bean>         
      
    <bean id="userDAO" class="com.x.redis.dao.impl.UserDAOImpl" />   
</beans>

redis.properties:

# Redis settings
#redis.host=192.168.20.101
#redis.port=6380
#redis.pass=foobared
redis.host=127.0.0.1
redis.port=6379
redis.pass=
  
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true

UserDAOImpl:

Spring对DAO层的封装很多用了类似于下面代码的模板方式。

RedisTemplate就是Spring对Redis的一个封装。

public class UserDAOImpl implements UserDAO {

    @Autowired
    protected RedisTemplate<Serializable, Serializable> redisTemplate;

    public void saveUser(final User user) {
        redisTemplate.execute(new RedisCallback<Object>() {

            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(redisTemplate.getStringSerializer().serialize("user.uid." + user.getId()),
                               redisTemplate.getStringSerializer().serialize(user.getName()));
                return null;
            }
        });
    }

    @Override
    public User getUser(final long id) {
        return redisTemplate.execute(new RedisCallback<User>() {
            @Override
            public User doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] key = redisTemplate.getStringSerializer().serialize("user.uid." + id);
                if (connection.exists(key)) {
                    byte[] value = connection.get(key);
                    String name = redisTemplate.getStringSerializer().deserialize(value);
                    User user = new User();
                    user.setName(name);
                    user.setId(id);
                    return user;
                }
                return null;
            }
        });
    }

}

User:

public class User {

    private long id;
    private String name;
    
    public long getId() {
        return id;
    }
    
    public void setId(long id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

测试代码:

public static void main(String[] args) {
        ApplicationContext ac =  new ClassPathXmlApplicationContext("classpath:/applicationContext.xml");
        UserDAO userDAO = (UserDAO)ac.getBean("userDAO");
        User user1 = new User();
        user1.setId(1);
        user1.setName("obama");
        userDAO.saveUser(user1);
        User user2 = userDAO.getUser(1);
        System.out.println(user2.getName());
}

 

参考:

http://www.cnblogs.com/tankaixiong/p/3660075.html(以上内容转自此篇文章)

http://blog.csdn.net/defonds/article/details/48716161

http://blog.csdn.net/albertfly/article/details/51494080

http://www.cnblogs.com/mrlinfeng/p/5857775.html

http://blog.csdn.net/fighterandknight/article/details/53432276/

http://www.cnblogs.com/cuglkb/p/6862609.html

http://snowolf.iteye.com/blog/1666908

http://www.cnblogs.com/wuxinliulei/p/5216712.html

http://blog.csdn.net/tomcat_2014/article/details/55260306

https://my.oschina.net/u/866380/blog/521658

http://blog.csdn.net/u013725455/article/details/52129283

http://blog.sina.com.cn/s/blog_630d50dc0102wwmi.html

以上是关于Spring-data-redis 的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-redis 可以实现消息队列吗

Spring之Redis访问(Spring-data-redis)

mybatis+redis实现二级缓存

spring-data-redis 使用过程中踩过的坑

spring-data-redis时效设置

spring-data-redis 2.0 的使用