CPU须要数据时先从L1/L2中读取,若是没有到内存中找,若是尚未会到磁盘上找。java
还有如用过Maven的朋友都应该知道,咱们找依赖的时候,先从本机仓库找,再从本地服务器仓库找,最后到远程仓库服务器找。程序员
还有如京东的物流为何那么快?他们在各个地都有分仓库,若是该仓库有货物那么送货的速度是很是快的。算法
即【从缓存中读取数据的次数】与【总读取次数】的比率,命中率越高越好:spring
命中率 = 从缓存中读取次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])数据库
Miss率 = 没有从缓存中读取的次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])编程
这是一个很是重要的监控指标,若是作缓存必定要健康这个指标来看缓存是否工做良好。缓存
即若是缓存满了,从缓存中移除数据的策略;常见的有LFU、LRU、FIFO:服务器
FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;app
LRU(Least Recently Used):最久未使用算法,使用时间距离如今最久的那个被移除;less
LFU(Least Frequently Used):最近最少使用算法,必定时间段内使用次数(频率)最少的那个被移除。
存活期,即从缓存中建立时间点开始直到它到期的一个时间段(无论在这个时间段内有没有访问都将过时)
空闲期,即一个数据多久没被访问将从缓存中移除的时间。
在Java中,咱们通常对调用方法进行缓存控制,好比我调用"findUserById(Long id)",那么我应该在调用这个方法以前先从缓存中查找有没有,若是没有再掉该方法如从数据库加载用户,而后添加到缓存中,下次调用时将会从缓存中获取到数据。
Spring 3.1起,提供了基于注解的对Cache的支持。使用Spring Cache的好处:
基于注解,代码清爽简洁;
基于注解也能够实现复杂的逻辑;
能够对缓存进行回滚;
Spring Cache不是具体的缓存技术,而是基于具体的缓存产品(如Guava、EhCache、Redis等)的共性进行了一层封装,可是能够经过简单的配置切换底层使用的缓存。
package org.springframework.cache;
public interface Cache {
String getName(); //缓存的名字
Object getNativeCache(); //获得底层使用的缓存,如Ehcache
ValueWrapper get(Object key); //根据key获得一个ValueWrapper,而后调用其get方法获取值
<T> T get(Object key, Class<T> type);//根据key,和value的类型直接获取value
void put(Object key, Object value);//往缓存放数据
void evict(Object key);//从缓存中移除key对应的缓存
void clear(); //清空缓存
interface ValueWrapper { //缓存值的Wrapper
Object get(); //获得真实的value
}
}
ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap实现的Cache。
GuavaCache:对Guava com.google.common.cache.Cache进行的Wrapper,须要Google Guava 12.0或更高版本,@since spring 4。
EhCacheCache:使用Ehcache实现。
JCacheCache:对javax.cache.Cache进行的wrapper,@since spring 3.2;spring4将此类更新到JCache 0.11版本。
package org.springframework.cache;
import java.util.Collection;
public interface CacheManager {
Cache getCache(String name); //根据Cache名字获取Cache
Collection<String> getCacheNames(); //获得全部Cache的名字
}
ConcurrentMapCacheManager/ConcurrentMapCacheFactoryBean:管理ConcurrentMapCache;
GuavaCacheManager;
EhCacheCacheManager/EhCacheManagerFactoryBean;
JCacheCacheManager/JCacheManagerFactoryBean;
CompositeCacheManager用于组合CacheManager,便可以从多个CacheManager中轮询获得相应的Cache。
调用cacheManager.getCache(cacheName) 时,会先从第一个cacheManager中查找有没有cacheName的cache,若是没有接着查找第二个,若是最后找不到,由于fallbackToNoOpCache=true,那么将返回一个NOP的Cache不然返回null。
@Test
public void test() throws IOException {
//建立底层Cache
net.sf.ehcache.CacheManager ehcacheManager
= new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());
//建立Spring的CacheManager
EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager();
//设置底层的CacheManager
cacheCacheManager.setCacheManager(ehcacheManager);
Long id = 1L;
User user = new User(id, "zhang", "zhang@gmail.com");
//根据缓存名字获取Cache
Cache cache = cacheCacheManager.getCache("user");
//往缓存写数据
cache.put(id, user);
//从缓存读数据
Assert.assertNotNull(cache.get(id, User.class));
}
EhCacheCacheManager用来根据缓存名称(cacheName)建立缓存Cache,而后经过缓存Cache来进行缓存的操做。
spring提供EhCacheManagerFactoryBean来简化ehcache cacheManager的建立,这样注入configLocation,会自动根据路径从classpath下找,比编码方式简单多了,而后就能够从spring容器获取cacheManager进行操做了。此处的transactionAware表示是否事务环绕的,若是true,则若是事务回滚,缓存也回滚,默认false。
@Bean
public CacheManager cacheManager() {
try {
net.sf.ehcache.CacheManager ehcacheCacheManager
= new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());
EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager(ehcacheCacheManager);
return cacheCacheManager;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Spring是一种产品,提供这么多种方式是为了知足不一样的编程习惯的程序员的编程习惯。
代码方式最为灵活,也须要对源码有必定的了解,作深刻的、个性化需求的开发最好仍是采用对源码封装的方式,也就是直接操做代码的方式。
XML配置和注解方式较为简洁。可是作深刻的、个性化的开发,灵活性有些受制约。
还能够指定一个 key-generator,即默认的key生成策略。
@Configuration
@ComponentScan(basePackages = "com.sishuok.spring.service")
@EnableCaching(proxyTargetClass = true)
public class AppConfig implements CachingConfigurer {
@Bean
public CacheManager cacheManager() {
try {
net.sf.ehcache.CacheManager ehcacheCacheManager
= new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());
EhCacheCacheManager cacheCacheManager = new EhCacheCacheManager(ehcacheCacheManager);
return cacheCacheManager;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Bean
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
}
使用@EnableCaching启用Cache注解支持。注入须要的cacheManager。也能够对keyGenerator进行配置,不配置则采用默认的。从spring4开始默认的keyGenerator是SimpleKeyGenerator。
public @interface CachePut {
String[] value(); //缓存的名字,能够把数据写到多个缓存
String key() default ""; //缓存key,若是不指定将使用默认的KeyGenerator生成,后边介绍
String condition() default ""; //知足缓存条件的数据才会放入缓存,condition在调用方法以前和以后都会判断
String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行以后判断,此时能够拿到返回值result进行判断了
}
应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存。好比下面调用该方法时,会把user.id做为key,返回值做为value放入缓存
public @interface CacheEvict {
String[] value(); //请参考@CachePut
String key() default ""; //请参考@CachePut
String condition() default ""; //请参考@CachePut
boolean allEntries() default false; //是否移除全部数据
boolean beforeInvocation() default false;//是调用方法以前移除/仍是调用以后移除
}
应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据
public @interface Cacheable {
String[] value(); //请参考@CachePut
String key() default ""; //请参考@CachePut
String condition() default "";//请参考@CachePut
String unless() default ""; //请参考@CachePut
}
应用到读取数据的方法上,便可缓存的方法,如查找方法:先从缓存中读取,若是没有再调用方法获取数据,而后把数据添加到缓存中
实际上就是AOP,拦截处理,在 CacheAspectSupport 的 execute 方法中进行的处理。
处理逻辑以下(没细看,不知是否如此,并且不一样版本的Spring可能有所不一样,真想明白这个逻辑,请先本身仔细想一遍,手动实现一下,再比对Spring源码分析本身的处理逻辑跟Spring中的是否一致,谁的处理更好,为何?本身先想一想怎么处理这个逻辑才合理!!!)
若是有@CachePut操做,即便有@Cacheable也不会从缓存中读取;问题很明显,若是要混合多个注解使用,不能组合使用@CachePut和@Cacheable;官方说应该避免这样使用(解释是若是带条件的注解相互排除的场景);不过我的感受仍是不要考虑这个好,让用户来决定如何使用,不然一会介绍的场景不能知足。
Spring Cache提供了一些供使用的SpEL上下文数据,下表直接摘自Spring官方文档
名字 |
位置 |
描述 |
示例 |
methodName |
root对象 |
当前被调用的方法名 |
#root.methodName |
method |
root对象 |
当前被调用的方法 |
#root.method.name |
target |
root对象 |
当前被调用的目标对象 |
#root.target |
targetClass |
root对象 |
当前被调用的目标对象类 |
#root.targetClass |
args |
root对象 |
当前被调用的方法的参数列表 |
#root.args[0] |
caches |
root对象 |
当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache |
#root.caches[0].name |
argument name |
执行上下文 |
当前被调用的方法的参数,如findById(Long id),咱们能够经过#id拿到参数 |
#user.id |
result |
执行上下文 |
方法执行后的返回值(仅当方法执行以后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false) |
#result |
经过这些数据能够实现比较复杂的缓存逻辑。
若是在Cache注解上没有指定key的话@CachePut(value = "user"),会使用KeyGenerator进行生成一个key。默认提供了SimpleKeyGenerator生成器。
public interface KeyGenerator {
Object generate(Object target, Method method, Object... params);
}
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);
}
根据运行流程,以下@Cacheable将在执行方法以前( #result还拿不到返回值)判断condition,若是返回true,则查缓存
根据运行流程,以下@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,若是返回true,则放入缓存
根据运行流程,以下@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,若是返回false,则放入缓存;(即跟condition相反)
根据运行流程,以下@CacheEvict, beforeInvocation=false表示在方法执行以后调用(#result能拿到返回值了);且判断condition,若是返回true,则移除缓存
有时候可能组合多个Cache注解使用;好比用户新增成功后,要添加id-->user;username--->user;email--->user的缓存;此时就须要@Caching组合多个注解标签了。
如用户新增成功后,添加id-->user;username--->user;email--->user到缓存
@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
}
)
public User save(User user) { }
这样能够在根据id、根据username、根据email查找user的方法上使用@Cacheable,就可使用缓存了。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
上面的@Caching组合,会让方法上的注解显得整个代码比较乱,此时可使用自定义注解把这些注解组合到一个注解中。
这样在方法上使用以下代码便可,整个代码显得比较干净
@UserSaveCache
public User save(User user)
@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
}
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache { }
@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
}
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache { }
@UserSaveCache
public User saveUser(User user){}
@UserSaveCache
public User updateUser(User user){}
@Caching(
evict = {
@CacheEvict(value = "user", key = "#user.id"),
@CacheEvict(value = "user", key = "#user.username"),
@CacheEvict(value = "user", key = "#user.email")
}
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserDelCache { }
@UserDelCache
public User deleteUser(User user){}
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#id")
}
)
public User findById(final Long id)
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#username")
}
)
public User findByUsername(final String username)
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#email")
}
)
public User findByEmail(final String email)