Spring Cache

Spring Cache

 

Caching is a very commonly used method to improve performance in practical work, and we will use caching in many scenarios.

 

This article expands through a simple example. By comparing our original custom cache and spring's annotation-based cache configuration method, it shows the power of spring cache, and then introduces its basic principles, extension points and usage scenarios. limit. By reading this article, you should be able to master the powerful caching technology brought by spring in a short time, and provide caching capabilities for existing code with very little configuration.

 

 

 

Overview

 

Spring 3.1 introduces an exciting annotation-based cache technology, which is not essentially a specific cache implementation (such as EHCache or OSCache), but an abstraction for cache usage, which can be used in existing code. Add a small amount of various annotations it defines, that is, it can achieve the effect of caching the returned object of the method.

 

Features are as follows:

1. Existing code can be cached with a small amount of configuration annotation annotations

2. Supports Out-Of-The-Box out-of-the-box, i.e. caching can be used without installing and deploying additional third-party components

3. Support Spring Express Language, you can use any property or method of the object to define the key and condition of the cache

4. Support AspectJ and implement caching support for any method through it

5. Supports custom keys and custom cache managers, with considerable flexibility and extensibility

 

 

 

How did we implement caching ourselves before?

 

Here is a completely custom cache implementation, that is, a memory cache of a certain object is implemented without any third-party components.

 

The scenario is as follows:

Cache an account query method, using the account name as the key and the account object as the value. When the account is queried with the same account name, the result will be returned directly from the cache, otherwise the cache will be updated. The account query service also supports reload cache (that is, clearing the cache)

 

First define an entity class: account class, with basic id and name attributes, and with getter and setter methods

public class Account {

    private int id;
    private String name;

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

 

然后定义一个缓存管理器,这个管理器负责实现缓存逻辑,支持对象的增加、修改和删除,支持值对象的泛型。如下:

import com.google.common.collect.Maps;

import java.util.Map;

/**
 * @author wenchao.ren
 *         2015/1/5.
 */
public class CacheContext<T> {

    private Map<String, T> cache = Maps.newConcurrentMap();

    public T get(String key){
        return  cache.get(key);
    }

    public void addOrUpdateCache(String key,T value) {
        cache.put(key, value);
    }

    // 根据 key 来删除缓存中的一条记录
    public void evictCache(String key) {
        if(cache.containsKey(key)) {
            cache.remove(key);
        }
    }

    // 清空缓存中的所有记录
    public void evictCache() {
        cache.clear();
    }
}

 

好,现在我们有了实体类和一个缓存管理器,还需要一个提供账号查询的服务类,此服务类使用缓存管理器来支持账号查询缓存,如下:

import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author wenchao.ren
 *         2015/1/5.
 */
@Service
public class AccountService1 {

    private final Logger logger = LoggerFactory.getLogger(AccountService1.class);

    @Resource
    private CacheContext<Account> accountCacheContext;

    public Account getAccountByName(String accountName) {
        Account result = accountCacheContext.get(accountName);
        if (result != null) {
            logger.info("get from cache... {}", accountName);
            return result;
        }

        Optional<Account> accountOptional = getFromDB(accountName);
        if (!accountOptional.isPresent()) {
            throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
        }

        Account account = accountOptional.get();
        accountCacheContext.addOrUpdateCache(accountName, account);
        return account;
    }

    public void reload() {
        accountCacheContext.evictCache();
    }

    private Optional<Account> getFromDB(String accountName) {
        logger.info("real querying db... {}", accountName);
        //Todo query data from database
        return Optional.fromNullable(new Account(accountName));
    }

}

 

现在我们开始写一个测试类,用于测试刚才的缓存是否有效

import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class AccountService1Test {

    private AccountService1 accountService1;

    private final Logger logger = LoggerFactory.getLogger(AccountService1Test.class);

    @Before
    public void setUp() throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
        accountService1 = context.getBean("accountService1", AccountService1.class);
    }

    @Test
    public void testInject(){
        assertNotNull(accountService1);
    }

    @Test
    public void testGetAccountByName() throws Exception {
        accountService1.getAccountByName("accountName");
        accountService1.getAccountByName("accountName");

        accountService1.reload();
        logger.info("after reload ....");

        accountService1.getAccountByName("accountName");
        accountService1.getAccountByName("accountName");
    }
}

 

执行结果

首先从数据库查询,然后直接返回缓存中的结果,重置缓存后,应该先从数据库查询,然后返回缓存中的结果. 查看程序运行的日志如下:

00:53:17.166 [main] INFO  c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.168 [main] INFO  c.r.s.cache.example1.AccountService - get from cache... accountName
00:53:17.168 [main] INFO  c.r.s.c.example1.AccountServiceTest - after reload ....
00:53:17.168 [main] INFO  c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.169 [main] INFO  c.r.s.cache.example1.AccountService - get from cache... accountName

 

 

可以看出我们的缓存起效了,但是这种自定义的缓存方案有如下劣势:

 

1. 缓存代码和业务代码耦合度太高,如上面的例子,AccountService 中的 getAccountByName()方法中有了太多缓存的逻辑,不便于维护和变更

 

2. 不灵活,这种缓存方案不支持按照某种条件的缓存,比如只有某种类型的账号才需要缓存,这种需求会导致代码的变更

 

3. 缓存的存储这块写的比较死,不能灵活的切换为使用第三方的缓存模块

 

 

 

spring实现Cache

 

修改操作类

import com.google.common.base.Optional;
import com.rollenholt.spring.cache.example1.Account;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author wenchao.ren
 *         2015/1/5.
 */
@Service
public class AccountService3 {

    private final Logger logger = LoggerFactory.getLogger(AccountService3.class);

    // 使用了一个缓存名叫 accountCache
    @Cacheable(value="accountCache")
    public Account getAccountByName(String accountName) {

        // 方法内部实现不考虑缓存逻辑,直接实现业务
        logger.info("real querying account... {}", accountName);
        Optional<Account> accountOptional = getFromDB(accountName);
        if (!accountOptional.isPresent()) {
            throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
        }

        return accountOptional.get();
    }

    @CacheEvict(value="accountCache",key="#account.getName()")
    public void updateAccount(Account account) {
        updateDB(account);
    }

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

    private void updateDB(Account account) {
        logger.info("real update db...{}", account.getName());
    }

    private Optional<Account> getFromDB(String accountName) {
        logger.info("real querying db... {}", accountName);
        //Todo query data from database
        return Optional.fromNullable(new Account(accountName));
    }
}

 

@Cacheable(value="accountCache")

当调用这个方法前,就看缓存中是否有这个名字的数据,如果有就用缓存,如果没有就调用方法

 

@CacheEvict(value="accountCache",key="#account.getName()")

将某一条数据请出缓存

 

@CacheEvict(value="accountCache",allEntries=true)

清除所有缓存

 

需要缓存,需要加上下面配置

<context:component-scan base-package="com.rollenholt.spring.cache"/>

    <context:annotation-config/>

    <cache:annotation-driven/>

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

 

关键配置

<cache:annotation-driven />

这个配置项缺省使用了一个名字叫 cacheManager 的缓存管理器,这个缓存管理器有一个 spring 的缺省实现,即 org.springframework.cache.support.SimpleCacheManager,这个缓存管理器实现了我们刚刚自定义的缓存管理器的逻辑,它需要配置一个属性 caches,即此缓存管理器管理的缓存集合,除了缺省的名字叫 default 的缓存,我们还自定义了一个名字叫 accountCache 的缓存,使用了缺省的内存存储方案 ConcurrentMapCacheFactoryBean,它是基于 java.util.concurrent.ConcurrentHashMap 的一个内存缓存实现方案。

 

 

 

@Cacheable、@CachePut、@CacheEvict 注释介绍

 

1. @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

2. @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

3. @CachEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空

 

 

 

实现原理

其实spring缓存也是通过AOP实现的,所以应该多学习AOP。

 

 

 

spring cache的缺点

1. 不能持久化

2. 不能适应分布式系统的情况

3. 高可用性差

 

 

解决方法

使用第三方缓存系统,如:

1. EHCache、OSCache

2. memcache、redis

 

参考:

http://www.cnblogs.com/rollenholt/p/4202631.html

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326498122&siteId=291194637