Spring实战——缓存

缓存

提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis…… 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等。 当然,这里要讲的不是一级二级,也不是redis,而是Spring的缓存支持。当时基于工作上的业务场景,考虑需要用到缓存技术,但是并不清楚该用什么样的缓存技术,起初甚至有想过把信息写到redis中,然后读redis的信息(现在想想,真是小题大做),后来发现Spring提供了缓存的解决方案——Spring Cache。虽然最终找到了一个更加简便讨巧的方式解决了问题,但是今天还是把自己零碎的Spring Cache知识稍稍梳理下。 这里大概介绍下业务场景:

  • 现在有功能模块一和功能模块二,分别负责两件事;
  • 执行功能模块二的时候,可以用到模块一的执行结果,当然不用也可以,那就再执行一遍;
  • 读取功能模块一执行后存放在Cache中的数据,这时候就省去重新执行模块一的冗余操作

流程图如下:

Spring Cache

有关Spring Cache的理论详细介绍可以看官方文档或者参阅《Spring实战》第十三章,这里偏代码实战看看Spring Cache如何使用。

声明启用缓存

就像如果要启用AOP需要添加

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

一样,如果要用cache,也需要声明

<cache:annotation-driven />

这样还不够,除此以外,我们还需要一个缓存管理器

<bean id="cacheManager"
          class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean
                        class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                        p:name="default" />

                <bean
                        class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                        p:name="personCache" />
            </set>
        </property>
    </bean>

这个缓存管理器是Spring缓存抽象的核心,可以继承多个流行的缓存实现。从上面的声明可以看出,这里声明了ConcurrentMapCacheManager管理器,从字面就可以看出其内容实现应该是通过ConcurrentHashMap来做缓存的。(除了这个简单的缓存管理器,当然还有如NoOpCacheManager、EhCacheCacheManager、RedisCacheManager、CompositeCacheManager等管理器) 这里的personCache就是本例中用到的声明的管理器 以上的xml声明可以存放到一个spring-cache.xml中,完整内容如下

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/cache
     http://www.springframework.org/schema/cache/spring-cache.xsd">

    <cache:annotation-driven />

    <bean id="personService" class="com.jackie.service.PersonService" />

    <!-- generic cache manager -->
    <bean id="cacheManager"
          class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean
                        class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                        p:name="default" />

                <bean
                        class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                        p:name="personCache" />
            </set>
        </property>
    </bean>
</beans>

编写需要缓存的方法

  • 在此之前,我们需要一个bean对象Person,其有id,age,name三个属性
@Component
public class Person {

    private int id;
    private int age;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 准备需要缓存的接口方法
@Service("personService")
public class PersonService {

    @Cacheable(value = "personCache")
    public Person findPerson(int i) {
        System.out.println("real query person object info");

        Person person = new Person();
        person.setId(i);
        person.setAge(18);
        person.setName("Jackie");
        return person;
    }

}

findPerson()方法很简单,主要是根据id查找到相应的Person对象。 这里第一行加上了打印信息,用于区分每次调用是否走缓存了。如果直接用的是缓存结果,则不会打印这句话,如果没有缓存,则需要执行函数体,从而打印改行显示。 @Cacheable注解:主要用来配置方法,能够根据方法的请求参数对其结果进行缓存。即当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。简而言之就是如果缓存有则取出,如果没有则执行方法。

验证

到此我们就可以验证了。编写代码如下

public class SpringCache {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "spring-cache.xml");// 加载 spring 配置文件

        PersonService personService = (PersonService) context.getBean("personService");
        // 第一次查询,应该真实查询
        System.out.println(personService.findPerson(18));
        // 第二次查询,应该直接返回缓存的值
        System.out.println(personService.findPerson(18));
    }
}

最终执行结果如下

real query person object info
Person{id=18, age=18, name='Jackie'}
Person{id=18, age=18, name='Jackie'}

可以看出第一次结果显示前打印出了real query person object info说明真实执行了findPerson()方法; 第二次打印的结果没有包含这行信息,说明是从缓存中直接取的数据,而且通过调试打断点确实发现第二次并未进入findPerson()方法。

@CacheEvict

之所以能够不用执行方法直接拿到缓存结果,是因为将执行结果存到了缓存中。既然有存缓存的过程,自然有删除缓存的过程,而这个操作就要用到@CacheEvict这个注解了。 还是通过实例来看,这里举出两个例子——根据方法清除缓存和清除全部缓存。 我们在PersonService中分别加上这两个接口方法

@CacheEvict(value = "personCache", key = "#person.getId()")
    public void updatePerson(Person person) {
        System.out.println("update: " + person.getName());
    }

    @CacheEvict(value = "personCache", allEntries = true)
    public void reload() {

    }

稍稍解释下,当updatePerson()方法被调用时,其会根据key来取出相应的缓存记录删除;而对于方法reload()则是在该方法被调用时,清空所有缓存记录。

测试代码如下

Person lucy = personService.findPerson(1);
        Person lily = personService.findPerson(2);

        lucy.setName("Tracy");
        personService.updatePerson(lucy);
        System.out.println(personService.findPerson(1));

        System.out.println(personService.findPerson(2));

        personService.reload();

        System.out.println(personService.findPerson(1));
        System.out.println(personService.findPerson(2));

执行结果如下

real query person object info
real query person object info
update: Tracy
real query person object info
Person{id=1, age=18, name='Jackie'}
Person{id=2, age=18, name='Jackie'}
real query person object info
Person{id=1, age=18, name='Jackie'}
real query person object info
Person{id=2, age=18, name='Jackie'}

第1,2行分别是因为针对id=1,2都是第一次查询,这时候没有缓存记录,所以都真实执行了方法findPerson(); 第3行是调用方法updatePerson()打印的信息 第4,5行是再次查询id=1的Person信息,这里之所以打印出real query person object info是因为之前调用了id=1时的updatePerson()方法,该方法触发,根据getId()找到id=1的缓存记录进行删除,所以这时候查找id=1的时候,缓存记录已经不存在,故而要执行findPerson()方法。 第6行是再次查询id=2的Person信息,因为之前已经查过一次,并且没有删除过这条缓存记录,所以再次查找时,直接用缓存结果。 第7,8行是再次查询id=1的Person信息,注意再次之前,执行了reload()方法,这个方法会清楚所有的缓存信息,所以对于id=1和2的缓存记录都已经清空,这里就又重新执行findPerson()方法 第9,10行同7,8

当然,关于Spring Cache还有很多灵活的应用以及功能强大的注解和用法,这里只是通过实例了解Spring Cache有什么用,如何用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏武军超python专栏

2018年8月29日学习mysql数据库的笔记

今天遇到的新单词: manual n手工的 correspond v符合一致 reject v拒绝 exist  v存在 solid adj固体的 ...

22350
来自专栏UDNZ

记一次 .NET Framework 不兼容 HTTP COOKIE 协议标准的问题跟踪

我们在后端系统实现了 HTTP 请求的代理类,用于请求其他第三方系统。

32780
来自专栏Android 研究

Retrofit解析2之使用简介

前面介绍完RESTful之后,我们先来初步认识下Retrofit的使用"姿势"。本文的主要内容如下:

1.1K30
来自专栏逸鹏说道

C# 温故而知新: 线程篇(四)

线程同步篇 (中):同步工具类的介绍 1 上篇回顾 2 继续介绍基元内核模式中的 monitor类 3 同步句柄:WaitHandle 4 EventW...

32460
来自专栏JackieZheng

Spring实战——缓存

缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis…… 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓...

229100
来自专栏Linux驱动

41.Linux应用调试-修改内核来打印用户态的oops

1.在之前第36章里,我们学习了通过驱动的oops定位错误代码行 第36章的oops代码如下所示: Unable to handle kernel paging...

32150
来自专栏Java帮帮-微信公众号-技术文章全总结

Java并发学习2【面试+工作】

  关键字synchronized的作用是实现进程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性(即同步块每...

11820
来自专栏吴生的专栏

谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)

1:什么是JVM 大家可以想想,JVM 是什么?JVM是用来干什么的?在这里我列出了三个概念,第一个是JVM,第二个是JDK,第三个是JRE。相信大家对这三个不...

40260
来自专栏一枝花算不算浪漫

页面静态化技术Freemarker技术的介绍及使用实例.

43860
来自专栏zingpLiu

浅析Python多线程

学习Python多线程的资料很多,吐槽Python多线程的博客也不少。本文主要介绍Python多线程实际应用,且假设读者已经了解多线程的基本概念。如果读者对进程...

29380

扫码关注云+社区

领取腾讯云代金券