springboot 学习之路注解版缓存

SpringBoot 缓存介绍

  1. spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口来统一不同的缓存技术;
  2. CacheManager: 缓存管理器, 管理各种缓存(Cache)组件;如: RedisCache, EhCacheCache...等.
  3. 每次调用需要缓存功能的方法时,spring 会检查目标方法是否已经被调用过. 如果被调用过, 会直接从缓存中获取数据, 如果没有会访问数据库并将结果缓存;

SprongBoot 缓存使用

  1. 创建 springboot 项目,并导入 cache, mysql, mybatis, web 模块

      2. main 类中开启基于注解的缓存

@SpringBootApplication
@MapperScan("com.yangyun.cache.mapper") // 扫描mapper
@EnableCaching //开启基于注解的缓存
public class Springboot01CacheApplication {

	public static void main(String[] args) {
		SpringApplication.run(Springboot01CacheApplication.class, args);
	}
}

      3. 缓存注解的使用

  • @Cacheable 主要针对方法配置
  • @CachePut 既调用方法, 也会更新缓存中的数据
  • @CacheEvict 清除缓存
  • @Caching 组合缓存
package com.yangyun.cache.service;

import com.yangyun.cache.bean.Employee;
import com.yangyun.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

/**
 * @author yangyun
 * @create 2018-12-18-10:38
 */
@CacheConfig(cacheNames = "emp") // 共享属性配置
@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 将方法的结果进行缓存, 以后再要相同的数据, 直接从缓存中获取
     * CacheManager 管理多个 Cache(key,value) 组件, 对缓存的真正 CRUD 操作在 Cache 组件中, 没一个缓存组件有自己唯一的名字
     * 几个属性
     *  cacheNames/value: 指定缓存的名字
     *  key: 缓存数据使用key; 默认使用方法参数 id=1 key=1
     *
     *  key/keyGenerator 二选一使用, key 指定具体的值可以使用 spEL 表达式, keyGenerator 自定义的方式
     *  condition 条件成立才缓存 condition = "#a0>1" 第一个参数的值 > 1 才进行缓存
     *  unless 条件为 true 不缓存 unless = "#a0 == 2" 如果第一个参数的值是 2, 就不缓存
     *  sync 是否使用异步缓存, 默认为 false, 如果使用 sync 那么 unless 就没有效果了
     * @param id
     * @return
     */
    @Cacheable(
        value = {"emp"},
        key = "#id"
    )
    public Employee getEmp(Integer id){
         return employeeMapper.getEmpById(id);
    }

    /**
     * @CachePut 既调用方法, 又更新缓存数据, 修改数据库的某个数据, 同事更新缓存
     * 运行时机:
     *      1. 先调用目标方法
     *      2. 将目标方法的结果缓存
     * 注意: 更新和查询缓存的 key 要一致, 在更新的时候才能保证缓存中的数据被更新, cachePut 中 value 属性值为单个
     * @param employee
     * @return
     */
    @CachePut(value = {"emp"}, key = "#employee.id") // #result.id
    public Employee updateEmp(Employee employee) {
        employeeMapper.updateEmp(employee);
        return employee;
    }

    /**
     * @CacheEvict 清除缓存
     *      key: 指定要清除的数据
     *      allEntries 是否清除当前缓存所有数据默认 false
     *      beforeInvocation 是否在方法前清除缓存默认 false
     * @param id
     */
    @CacheEvict(value = {"emp"}, key = "#id")
    public void deleteEmployee (Integer id){
        System.out.println("remove employee " + employeeMapper.getEmpById(id));
//        employeeMapper.deleteEmpById(id);
    }

    /**
     * @Caching  使用组合缓存注解
     * @param lastName
     * @return
     */
    @Caching(
            cacheable = {/* 缓存根据 lastName 作为 key 获取缓存中的数据 */
                @Cacheable(value = "emp", key = "#lastName")
            },
            put = { /* 会将返回后的结果按 email 和 id 作为 缓存的 key 保存 */
                @CachePut(value = "emp", key = "#result.email"),
                @CachePut(value = "emp", key = "#result.id")
            }
    )
    public Employee getEmpByLastName (String lastName){
        Employee emp = employeeMapper.getEmpByLastName(lastName);
        return emp;
    }
}

缓存原理

springboot 如果要分析某个点, 可以根据 XXXAutoConfiguration 来切入

 自动配置类: CacheAutoConfiguration

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {

    /**
	 * {@link ImportSelector} to add {@link CacheType} configuration classes.
        通过 ImportSelector 添加 配置类
	 */
	static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			for (int i = 0; i < types.length; i++) {
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}

	}

}

 通过 CacheType CacheConfigurations 可以看到加载了哪些配置类

final class CacheConfigurations {

	private static final Map<CacheType, Class<?>> MAPPINGS;

	static {
		Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>();
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		addGuavaMapping(mappings);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}

	@Deprecated
	private static void addGuavaMapping(Map<CacheType, Class<?>> mappings) {
		mappings.put(CacheType.GUAVA, GuavaCacheConfiguration.class);
	}
}

 通过在 application.properties 中设置

debug=true// 可以在控制台看到, springboot 默认选择的配置类

 

 

@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

	private final CacheProperties cacheProperties;

	private final CacheManagerCustomizers customizerInvoker;

	SimpleCacheConfiguration(CacheProperties cacheProperties,
			CacheManagerCustomizers customizerInvoker) {
		this.cacheProperties = cacheProperties;
		this.customizerInvoker = customizerInvoker;
	}

    // 在容器中添加缓存管理器
	@Bean
	public ConcurrentMapCacheManager cacheManager() {
		ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			cacheManager.setCacheNames(cacheNames);
		}
		return this.customizerInvoker.customize(cacheManager);
	}

}
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

    // 用来保存 cache 组件
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
    
    // 根据 cacheNames 和 value 指定缓存名获取缓存组件 
    @Override
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
                    // 如果是第一次调用方, cache 为 null 就新创建
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

	/**
	 * Create a new ConcurrentMapCache instance for the specified cache name.
	 * @param name the name of the cache
	 * @return the ConcurrentMapCache (or a decorator thereof)
	 */
	protected Cache createConcurrentMapCache(String name) {
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
				isAllowNullValues(), actualSerialization);

	}
}

 ConcurrentMapCacheManager 获取 ConcurrentMapCache 缓存组件, ConcurrentMapCache 用来缓存数据

public class ConcurrentMapCache extends AbstractValueAdaptingCache {

	private final String name;

    // 存取数据的集合
	private final ConcurrentMap<Object, Object> store;

	private final SerializationDelegate serialization;
    @Override
	protected Object lookup(Object key) {
		return this.store.get(key);
	}

    // 获取
	@SuppressWarnings("unchecked")
	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		// Try efficient lookup on the ConcurrentHashMap first...
// 会调用父类 AbstractValueAdaptingCache get 方法,
// 然后子类 ConcurrentMapCache 重写 lookup 方法
		ValueWrapper storeValue = get(key);
		if (storeValue != null) {
			return (T) storeValue.get();
		}

		// No value found -> load value within full synchronization.
		synchronized (this.store) {
			storeValue = get(key);
			if (storeValue != null) {
				return (T) storeValue.get();
			}

			T value;
			try {
				value = valueLoader.call();
			}
			catch (Throwable ex) {
				throw new ValueRetrievalException(key, valueLoader, ex);
			}
			put(key, value);
			return value;
		}
	}

    // 保存
	@Override
	public void put(Object key, Object value) {
		this.store.put(key, toStoreValue(value));
	}
    // 清除
    @Override
	public void evict(Object key) {
		this.store.remove(key);
	}
    // 清空
	@Override
	public void clear() {
		this.store.clear();
	}

	@Override
	protected Object toStoreValue(Object userValue) {
		Object storeValue = super.toStoreValue(userValue);
		if (this.serialization != null) {
			try {
				return serializeValue(storeValue);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Failed to serialize 
cache value '" + userValue + "'. Does it implement Serializable?", ex);
			}
		}
		else {
			return storeValue;
		}
	}
}

运行流程


     * 运行流程:
     *      ① 进入 @Cacheable 目标方法前,ConcurrentMapCacheManager 根据 cacheNames 获取缓存组件
   
     *      ② 第一次获取没有 Cache 组件会自动创建
    @Override
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}
     *      ③ 从缓存组件中获取数据, 如果没有指定 key 的值,根据 key 的生成策略,会使用方法参数作为 key;
     *          key 的生成策略:
     *              默认使用 SimpleKeyGenerator 生成 key
     *              SimpleKeyGenerator 生成 key 策略:
     *                  如果没有参数,就返回一个 SimpleKey 对象
     *                  如果有一个参数, 直接使用这个参数
     *                  如果多参数,会将所有参数进行 hash 处理
     *      ④ 如果缓存中没有数据调用目标方法, 将目标方法返回结果放入缓存中
     *      再次调用就可以使用缓存中的数据了
public class SimpleKeyGenerator implements KeyGenerator {

	@Override
	public Object generate(Object target, Method method, Object... params) {
		return generateKey(params);
	}

	/**
	 * Generate a key based on the specified parameters.
	 */
	public static Object generateKey(Object... params) {
		if (params.length == 0) {
			return SimpleKey.EMPTY;
		}
		if (params.length == 1) {
			Object param = params[0];
			if (param != null && !param.getClass().isArray()) {
				return param;
			}
		}
		return new SimpleKey(params);
	}

}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值