Spring Boot with Redis

作者: 程序熊大 | 来源:发表于2015-12-03 20:13 被阅读29118次

    Spring Boot简介

    Spring Boot是为了简化Spring开发而生,从Spring 3.x开始,Spring社区的发展方向就是弱化xml配置文件而加大注解的戏份。最近召开的SpringOne2GX2015大会上显示:Spring Boot已经是Spring社区中增长最迅速的框架,前三名是:Spring Framework,Spring Boot和Spring Security,这个应该是未来的趋势。

    我学习Spring Boot,是因为通过cli工具,spring boot开始往flask(python)、express(nodejs)等web框架发展和靠近,并且Spring Boot几乎不需要写xml配置文件。感兴趣的同学可以根据spring boot quick start这篇文章中的例子尝试下。

    学习新的技术最佳途径是看官方文档,现在Spring boot的release版本是1.3.0-RELEASE,相应的参考文档是Spring Boot Reference Guide(1.3.0-REALEASE),如果有绝对英文比较吃力的同学,可以参考中文版Spring Boot参考指南。在前段时间阅读一篇技术文章,介绍如何阅读ios技术文档,我从中也有所收获,那就是我们应该重视spring.io上的guides部分——Getting Started Guides,这部分都是一些针对特定问题的demo,值得学习。

    Spring Boot的项目结构

    com
     +- example
         +- myproject
             +- Application.java
             |
             +- domain
             |   +- Customer.java
             |   +- CustomerRepository.java
             |
             +- service
             |   +- CustomerService.java
             |
             +- web
                 +- CustomerController.java
    

    如上所示,Spring boot项目的结构划分为web->service->domain,其中domain文件夹可类比与业务模型和数据存储,即xxxBean和Dao层;service层是业务逻辑层,web是控制器。比较特别的是,这种类型的项目有自己的入口,即主类,一般命名为Application.java。Application.java不仅提供入口功能,还提供一些底层服务,例如缓存、项目配置等等。

    例子介绍

    本文的例子是取自我的side project之中,日报(report)的查询,试图利用Redis作为缓存,优化查询效率。

    知识点解析

    1. 自定义配置

    Spring Boot允许外化配置,这样你可以在不同的环境下使用相同的代码。你可以使用properties文件、yaml文件,环境变量和命令行参数来外化配置。使用@Value注解,可以直接将属性值注入到你的beans中。
    Spring Boot使用一个非常特别的PropertySource来允许对值进行合理的覆盖,按照优先考虑的顺序排位如下:

    1. 命令行参数
    2. 来自java:comp/env的JNDI属性
    3. Java系统属性(System.getProperties())
    4. 操作系统环境变量
    5. 只有在random.*里包含的属性会产生一个RandomValuePropertySource
    6. 在打包的jar外的应用程序配置文件(application.properties,包含YAML和profile变量)
    7. 在打包的jar内的应用程序配置文件(application.properties,包含YAML和profile变量)
    8. 在@Configuration类上的@PropertySource注解
    9. 默认属性(使用SpringApplication.setDefaultProperties指定)
    

    使用场景:可以将一个application.properties打包在Jar内,用来提供一个合理的默认name值;当运行在生产环境时,可以在Jar外提供一个application.properties文件来覆盖name属性;对于一次性的测试,可以使用特病的命令行开关启动,而不需要重复打包jar包。

    具体的例子操作过程如下:

    • 新建配置文件(application.properties)
    spring.redis.database=0
    spring.redis.host=localhost
    spring.redis.password= # Login password of the redis server.
    spring.redis.pool.max-active=8
    spring.redis.pool.max-idle=8
    spring.redis.pool.max-wait=-1
    spring.redis.pool.min-idle=0
    spring.redis.port=6379
    spring.redis.sentinel.master= # Name of Redis server.
    spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs.
    spring.redis.timeout=0
    
    • 使用@PropertySource引入配置文件
    @Configuration
    @PropertySource(value = "classpath:/redis.properties")
    @EnableCaching
    public class CacheConfig extends CachingConfigurerSupport {
        ......
    }
    
    • 使用@Value引用属性值
    @Configuration
    @PropertySource(value = "classpath:/redis.properties")
    @EnableCaching
    public class CacheConfig extends CachingConfigurerSupport {
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private int port;
        @Value("${spring.redis.timeout}")
        private int timeout;
        ......
    }
    

    2. redis使用

    • 添加pom配置
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>
    
    • 编写CacheConfig
    @Configuration
    @PropertySource(value = "classpath:/redis.properties")
    @EnableCaching
    public class CacheConfig extends CachingConfigurerSupport {
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private int port;
        @Value("${spring.redis.timeout}")
        private int timeout;
        @Bean
        public KeyGenerator wiselyKeyGenerator(){
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(target.getClass().getName());
                    sb.append(method.getName());
                    for (Object obj : params) {
                        sb.append(obj.toString());
                    }
                    return sb.toString();
                }
            };
        }
        @Bean
        public JedisConnectionFactory redisConnectionFactory() {
            JedisConnectionFactory factory = new JedisConnectionFactory();
            factory.setHostName(host);
            factory.setPort(port);
            factory.setTimeout(timeout); //设置连接超时时间
            return factory;
        }
        @Bean
        public CacheManager cacheManager(RedisTemplate redisTemplate) {
            RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
            // Number of seconds before expiration. Defaults to unlimited (0)
            cacheManager.setDefaultExpiration(10); //设置key-value超时时间
            return cacheManager;
        }
        @Bean
        public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
            StringRedisTemplate template = new StringRedisTemplate(factory);
            setSerializer(template); //设置序列化工具,这样ReportBean不需要实现Serializable接口
            template.afterPropertiesSet();
            return template;
        }
        private void setSerializer(StringRedisTemplate template) {
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            template.setValueSerializer(jackson2JsonRedisSerializer);
        }
    }
    
    • 启动缓存,使用@Cacheable注解在需要缓存的接口上即可
    @Service
    public class ReportService {
        @Cacheable(value = "reportcache", keyGenerator = "wiselyKeyGenerator")
        public ReportBean getReport(Long id, String date, String content, String title) {
            System.out.println("无缓存的时候调用这里---数据库查询");
            return new ReportBean(id, date, content, title);
        }
    }
    

    参考资料

    1. spring boot quick start
    2. Spring Boot参考指南
    3. Spring Boot Reference Guide(1.3.0-REALEASE)
    4. Getting Started Guides
    5. Caching Data in Spring Using Redis
    6. Spring boot使用Redis做缓存
    7. redis设计与实现

    相关文章

      网友评论

      • b0620b5303c2:楼主你好 redis注解方式的缓存感觉还是有点乱 用方法的缓存你觉得怎么样,不过要自己先封装好。
        程序熊大:@b0620b5303c2 嗯,有了更新的用法,改天再更新下
      • Nathans:如果通过这样写的话,jackson会将写入对象的类型同时写入到redis,序列化的时候根据这个类型进行序列化,如果路径发生改变或者其他系统同时也到此redis来获取数据,则序列化都会导致失败,这样好吗?
      • fb597ee5cbd9:我的报这个错
        Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool] with root cause

        java.net.UnknownHostException: localhost
      • 汤圆学Java:你好,我想转载到CSDN,可以吗
        汤圆学Java:@杜琪 哦可
        程序熊大:@jalon2015 著名出处就欧克
      • 39e63d28bdee:我用的hibernate的一对多,怎么样做缓存?我希望在读取缓存把one和many都读出来?
      • AbsurdOS:你好询问一下 在redisTemplate构造方法里template.setEnableTransactionSupport(true);会让我开启multi。我就加上了template.multi();我想让它支持事务该怎么配置呢
      • 快看看:我配置后报这个错误:
        at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
        at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
        at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
        at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
        Caused by: java.lang.IllegalArgumentException: Incorrect numb
      • a564ff3ab64e:使用
        一定要加@Cacheable吗?
        若想增加一些工具类,需要怎么操作呢

        @Component
        @AutoConfigureAfter(RedisConfig.class)
        public class RedisBaseDAO {
        @Autowired
        private RedisTemplate<Serializable, Serializable> redisTemplate;
      • 591a2dcf32c4:我用的spring data jpa,返回Page<User>对象,然后它可以放进redis,但是取不出来。报错说是Page接口的实现类PageImpl没有无参构造器,这个咋整呀……
        591a2dcf32c4:@absurd1350 我是自己写了个类PageImplBean继承PageImpl,提供无参构造器。反序列化的时候,用这个PageImplBean。
        AbsurdOS:@我就想知道一共能输入多少个字符 你好,你解决了吗,我和你遇到同样的问题,我也没解决。现在我用 template.setValueSerializer(new JdkSerializationRedisSerializer());jdk序列化。
        程序熊大:@我就想知道一共能输入多少个字符 嗨,我还不是很清楚你的问题,你可以给PageImpl加个无参构造器。如果不能修改PageImpl的源码,则考虑重新设计放入缓存的数据结构。

      本文标题:Spring Boot with Redis

      本文链接:https://www.haomeiwen.com/subject/qzdshttx.html