简介:
程序运行中,在内存保持一定时间不变的数据就是缓存。简单到写一个Map,里面放着一些key,value数据,就已经是个缓存了
所以缓存并不是什么高大上的技术,只是个概念,把要多次使用的东西存在一个变量里,时不时取出来使用,就达到了缓存的目的,缓存就是存放数据的容器
那为什么要用缓存呢,是因为要多次使用。一个程序总有一些数据时可预见被多次使用(预见的准不准就是常说的命中率)
比如一个复杂的计算结果,一次数据库访问取得的数据等耗时耗资源的数据就能放入缓存,目的就是为了节省开销,我们要用有限的资源(CPU,内存,带宽等等)尽量做最多的事情。
简而言之:从内存中取数据,要不从磁盘中直接取数据,要快很多。
为什么使用Spring Cache
如果我们要设计一个缓存,最基本的功能是存和取:
1.能在缓存里存放数据
2.能在缓存里取出数据
可是这不够呀,比如以下的思考
1.取数据时判断,数据是否存在,如果不存在是不是要数据库取
2.如果是过期的内容是不是要更新
3.如果我有多个缓存,一个是我自己设计的HashMap缓存,一个是名声很大的redis,还有....,那需要个缓存管理器呀
为了让缓存更好用,更“智能”,越来越多的需求就会被提出来,而缓存就是这样一步步演变直到SpringCache横空出世,功能十分强大(说白了就是我们少写很多代码)
SpringCache的好处
SpringCache包含两个顶级接口,Cache(缓存)和CacheManager(缓存管理器),顾名思义,用CacheManager去管理一堆Cache。
最最关键的地方:抱紧了Spring的大腿,可以使用注解就能完成数据进入缓存!!
举个例子,就知道简单很多了
首先,Springboot中会自动加载一个CacheManager(它有默认的实现类),所以只要写好一个自定义的Cache即可(如果想用系统定义好的或者第三方如RedisCache也行,记得向Spring注册这个bean即可)
@Component
public class CustomCache implements Cache {
/*
实现接口方法,一些关于数据set和get的方法
CacheManager是根据Cache的名字进行管理的
所以假设这个Cache名为MyCache
*/
}
然后在得出数据的方法上写上注释即可
@Cacheable(value = "CustomCache",key = "#id")
public String getNavegationURLs(String id) {
//获取数据的方法
}
这样就会在调用这个方法时,会以id为key值,在名为MyCache的Cache容器中查找(注解中value就是缓存名字,不同名字指定使用不同的缓存)
如果没查到,则执行方法 getNavegationURLs,将返回值存入缓存
如果找到了,就直接将从缓存取值,直接返回,不用执行方法 getNavegationURLs
注解是如何起作用的?原理是什么?
由于SpringCache是不支持灵活的缓存时间设置的,所以想了解大概的来龙去脉去实现一个支持缓存过期时间设置和自动更新的类(SpringCache自定义过期时间及自动刷新)。
通过此次探索,来了解下Spring对类的管理机制,接触下AOP。
SpringCache源码简单分析
Cache源码:
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache;
import java.util.concurrent.Callable;
import org.springframework.lang.Nullable;
/**
* Interface that defines common cache operations.
*
* <b>Note:</b> Due to the generic use of caching, it is recommended that
* implementations allow storage of <tt>null</tt> values (for example to
* cache methods that return {@code null}).
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
*/
public interface Cache {
/**
* Return the cache name.
*/
String getName();
/**
* Return the underlying native cache provider.
*/
Object getNativeCache();
/**
* Return the value to which this cache maps the specified key.
* <p>Returns {@code null} if the cache contains no mapping for this key;
* otherwise, the cached value (which may be {@code null} itself) will
* be returned in a {@link ValueWrapper}.
* @param key the key whose associated value is to be returned
* @return the value to which this cache maps the specified key,
* contained within a {@link ValueWrapper} which may also hold
* a cached {@code null} value. A straight {@code null} being
* returned means that the cache contains no mapping for this key.
* @see #get(Object, Class)
* @see #get(Object, Callable)
*/
@Nullable
ValueWrapper get(Object key);
/**
* Return the value to which this cache maps the specified key,
* generically specifying a type that return value will be cast to.
* <p>Note: This variant of {@code get} does not allow for differentiating
* between a cached {@code null} value and no cache entry found at all.
* Use the standard {@link #get(Object)} variant for that purpose instead.
* @param key the key whose associated value is to be returned
* @param type the required type of the returned value (may be
* {@code null} to bypass a type check; in case of a {@code null}
* value found in the cache, the specified type is irrelevant)
* @return the value to which this cache maps the specified key
* (which may be {@code null} itself), or also {@code null} if
* the cache contains no mapping for this key
* @throws IllegalStateException if a cache entry has been found
* but failed to match the specified type
* @since 4.0
* @see #get(Object)
*/
@Nullable
<T> T get(Object key, @Nullable Class<T> type);
/**
* Return the value to which this cache maps the specified key, obtaining
* that value from {@code valueLoader} if necessary. This method provides
* a simple substitute for the conventional "if cached, return; otherwise
* create, cache and return" pattern.
* <p>If possible, implementations should ensure that the loading operation
* is synchronized so that the specified {@code valueLoader} is only called
* once in case of concurrent access on the same key.
* <p>If the {@code valueLoader} throws an exception, it is wrapped in
* a {@link ValueRetrievalException}
* @param key the key whose associated value is to be returned
* @return the value to which this cache maps the specified key
* @throws ValueRetrievalException if the {@code valueLoader} throws an exception
* @since 4.3
* @see #get(Object)
*/
@Nullable
<T> T get(Object key, Callable<T> valueLoader);
/**
* Associate the specified value with the specified key in this cache.
* <p>If the cache previously contained a mapping for this key, the old
* value is replaced by the specified value.
* <p>Actual registration may be performed in an asynchronous or deferred
* fashion, with subsequent lookups possibly not seeing the entry yet.
* This may for example be the case with transactional cache decorators.
* Use {@link #putIfAbsent} for guaranteed immediate registration.
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @see #putIfAbsent(Object, Object)
*/
void put(Object key, @Nullable Object value);
/**
* Atomically associate the specified value with the specified key in this cache
* if it is not set already.
* <p>This is equivalent to:
* <pre><code>
* ValueWrapper existingValue = cache.get(key);
* if (existingValue == null) {
* cache.put(key, value);
* }
* return existingValue;
* </code></pre>
* except that the action is performed atomically. While all out-of-the-box
* {@link CacheManager} implementations are able to perform the put atomically,
* the operation may also be implemented in two steps, e.g. with a check for
* presence and a subsequent put, in a non-atomic way. Check the documentation
* of the native cache implementation that you are using for more details.
* <p>The default implementation delegates to {@link #get(Object)} and
* {@link #put(Object, Object)} along the lines of the code snippet above.
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @return the value to which this cache maps the specified key (which may be
* {@code null} itself), or also {@code null} if the cache did not contain any
* mapping for that key prior to this call. Returning {@code null} is therefore
* an indicator that the given {@code value} has been associated with the key.
* @since 4.1
* @see #put(Object, Object)
*/
@Nullable
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
ValueWrapper existingValue = get(key);
if (existingValue == null) {
put(key, value);
}
return existingValue;
}
/**
* Evict the mapping for this key from this cache if it is present.
* <p>Actual eviction may be performed in an asynchronous or deferred
* fashion, with subsequent lookups possibly still seeing the entry.
* This may for example be the case with transactional cache decorators.
* Use {@link #evictIfPresent} for guaranteed immediate removal.
* @param key the key whose mapping is to be removed from the cache
* @see #evictIfPresent(Object)
*/
void evict(Object key);
/**
* Evict the mapping for this key from this cache if it is present,
* expecting the key to be immediately invisible for subsequent lookups.
* <p>The default implementation delegates to {@link #evict(Object)},
* returning {@code false} for not-determined prior presence of the key.
* Cache providers and in particular cache decorators are encouraged
* to perform immediate eviction if possible (e.g. in case of generally
* deferred cache operations within a transaction) and to reliably
* determine prior presence of the given key.
* @param key the key whose mapping is to be removed from the cache
* @return {@code true} if the cache was known to have a mapping for
* this key before, {@code false} if it did not (or if prior presence
* could not be determined)
* @since 5.2
* @see #evict(Object)
*/
default boolean evictIfPresent(Object key) {
evict(key);
return false;
}
/**
* Clear the cache through removing all mappings.
* <p>Actual clearing may be performed in an asynchronous or deferred
* fashion, with subsequent lookups possibly still seeing the entries.
* This may for example be the case with transactional cache decorators.
* Use {@link #invalidate()} for guaranteed immediate removal of entries.
* @see #invalidate()
*/
void clear();
/**
* Invalidate the cache through removing all mappings, expecting all
* entries to be immediately invisible for subsequent lookups.
* @return {@code true} if the cache was known to have mappings before,
* {@code false} if it did not (or if prior presence of entries could
* not be determined)
* @since 5.2
* @see #clear()
*/
default boolean invalidate() {
clear();
return false;
}
/**
* A (wrapper) object representing a cache value.
*/
@FunctionalInterface
interface ValueWrapper {
/**
* Return the actual value in the cache.
*/
@Nullable
Object get();
}
/**
* Wrapper exception to be thrown from {@link #get(Object, Callable)}
* in case of the value loader callback failing with an exception.
* @since 4.3
*/
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
@Nullable
private final Object key;
public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
@Nullable
public Object getKey() {
return this.key;
}
}
}
CacheManager源码:
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache;
import java.util.Collection;
import org.springframework.lang.Nullable;
/**
* Spring's central cache manager SPI.
*
* <p>Allows for retrieving named {@link Cache} regions.
*
* @author Costin Leau
* @author Sam Brannen
* @since 3.1
*/
public interface CacheManager {
/**
* Get the cache associated with the given name.
* <p>Note that the cache may be lazily created at runtime if the
* native provider supports it.
* @param name the cache identifier (must not be {@code null})
* @return the associated cache, or {@code null} if such a cache
* does not exist or could be not created
*/
@Nullable
Cache getCache(String name);
/**
* Get a collection of the cache names known by this manager.
* @return the names of all caches known by the cache manager
*/
Collection<String> getCacheNames();
}
从上面的例子以及查看CahcheManager源码发现,Cache和CacheManager是怎样做关联的,其实是Spring扫包实现的
凡是继承了Cache接口的类,都会被自动注入进CacheManager中,最终存储于CacheManager的实现类中
由于我们常使用Redis作为缓存,通过查看源码得知相关类的结构
凡是继承了Cache接口的类,都会被自动注入进CacheManager中,最终存储于CacheManager的实现类中
接着会生成被@Cacheable(或者其他SpringCache注解修饰过)的代理类,并会将管理它的CacheManager赋值进去
如果要设置多个CacheManager,就得在众多实现类的其中一个加上@Primary,不然会Spring会报错能选择的Bean太多而不知道用哪个
代理类生成后(包括会根据不同的注解生成信息类CacheOperationMetadata,到时候就会根据这个类的内容进行缓存操作,说白了就是调用我们实现Cache里面的各种方法)
Springboot底层初始化完成后,进入我们写的代码逻辑
代码跟进去,你会神奇的发现进入了代理类的intercept方法,怎么进去的呢~(具体原理看下面3.0)
这里面就会根据注解类型,进行缓存的逻辑判断,然后决定会不会调用我们写的方法~
代理类原理介绍(AOP切面之类的都是通过代理哦)
Spring代理分为两种:
1.JDK原生动态代理,要求被代理的类需要实现接口(通过接口来实现的代理)
那么代理类满足以下条件:
首先实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。
意思是:对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体
2.CGLIB动态代理,不要求被代理的类需要实现接口,但是final的方法无法被代理(通过继承来实现代理)
那么代理类满足以下条件:
实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法
具体内容可以参考这篇精品博客:https://www.cnblogs.com/CarpenterLee/p/8241042.html