Spring 3.1 版本引入基于 annotation 的 cache 技术,提供了一套抽象的缓存实现方案,通过注解方式使用缓存,基于配置的方式灵活使用不同缓存组件。代码具有相当的灵活性和扩展性,本文基于 Spring 5.x 源码一起分析 Spring Cache 的代码艺术。

开启 Spring Cache

想让 Spring 提供 Cache 能力很简单,只需要在启动类加上 @EnableCaching 注解即可:

@Configuration
@EnableCaching
public class ServerMain { }

通过在启动类上添加 EnableCaching 注解将 Cache 相关的组件注入到 Spring 启动中,通过 Proxy 或者 AspectJ 的方式获取 Cache 对应的执行信息。

如果你希望在启动时修改一些 Cache 底层对应的基础管理信息,可以通过在启动类上覆盖 CachingConfigurerSupport 提供的相关方法来实现:

@EnableCaching
@SpringBootApplication
public class WebDemoApplication extends CachingConfigurerSupport { public static void main(String[] args) {
SpringApplication.run(WebDemoApplication.class, args);
} @Override
public CacheManager cacheManager() {
return super.cacheManager();
} @Override
public KeyGenerator keyGenerator() {
return super.keyGenerator();
}
}

上面的示例代码中重写了 CacheManager 和 KeyGenerator 两个类的构建实现,分别实现的功能是 Cache 管理方式和 Cache key 生成方式。

从 Bean 加载机制了解 Spring 的 Cache 执行管理

从启动类入手我们很容易看到整体的 Cache 管理方式:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default 2147483647;
}
  • proxyTargetClass:false,表示使用 JDK 代理,true 表示使用 cglib 代理。

  • mode:指定 AOP 的模式,当值为 AdviceMode.PROXY 时表示使用 Spring aop,当值为当值为AdviceMode.ASPECTJ 时,表示使用 AspectJ。

EnableCaching 使用时导入 CachingConfigurationSelector 类,我们看看加载了什么信息:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

    public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return this.getProxyImports();
case ASPECTJ:
return this.getAspectJImports();
default:
return null;
}
}
......
......
...... static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration", classLoader);
} }

可以看到主要做了一件事,通过代理方式的不同加载对应的 CacheConfiguration :

  • 如果是 JDK Proxy,加载 AutoProxyRegistrar 类和 ProxyCachingConfiguration 类;
  • 如果是 AspectJ,则加载 AspectJCachingConfiguration 类。

Spring Boot 默认使用 JDK Proxy,我们就看 JDK Proxy 的使用。

AutoProxyRegistrar 的作用就是创建代理对象,内部通过调用 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry) 方法在 IOC 容器中注册一个 AutoProxyCreator。最终注入到容器中的 AutoProxyRegistrar 是一个 InfrastructureAdvisorAutoProxyCreator 类型:

@Nullable
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

InfrastructureAdvisorAutoProxyCreator 类型只会为基础设施类型的 Advisor 自动创建代理对象。它只会去找符合条件的 bean创建代理,从源码可见只有 role 为 BeanDefinition.ROLE_INFRASTRUCTURE 的满足条件。

ProxyCachingConfiguration 创建了 3 个 bean,CacheOperationSource 关注如何获取所有拦截的切面,

CacheInterceptor 解决要对拦截到的切面做什么。

public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

    @Bean(
name = {"org.springframework.cache.config.internalCacheAdvisor"}
)
@Role(2)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder((Integer)this.enableCaching.getNumber("order"));
} return advisor;
}
......
}

BeanFactoryCacheOperationSourceAdvisor 是 Spring Cache 自己实现的 Advisor,会对所有能取出 CacheOperation 的方法执行 CacheInterceptor 这个 Advice。

小知识:

Spring AOP 的创建过程本质是实现一个 BeanPostProcessor,在创建 bean 的过程中创建 Proxy,并且为 Proxy 绑定所有适用于该 bean 的 advisor,最终暴露给容器。

Spring 中 AOP 几个关键的概念 advisor, advice, pointcut

advice = 切面拦截中插入的行为

pointcut = 切面的切入点

advisor = advice + pointcut

BeanFactoryCacheOperationSourceAdvisor内部的切入点实现类是 CacheOperationSourcePointcut,切入的逻辑如下:

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

    private class CacheOperationSourceClassFilter implements ClassFilter {
private CacheOperationSourceClassFilter() {
} public boolean matches(Class<?> clazz) {
if (CacheManager.class.isAssignableFrom(clazz)) {
return false;
} else {
CacheOperationSource cas = CacheOperationSourcePointcut.this.getCacheOperationSource();
return cas == null || cas.isCandidateClass(clazz);
}
}
}
}

可以看到切入点主要就是判断被切入的方法上是否有注解:CacheOperationSourcePointcut.this.getCacheOperationSource()

ProxyCachingConfiguration 中还有另一个值得关注的类 - AnnotationCacheOperationSource

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}

CacheOperationSource 持有一个 CacheAnnotationParser 列表。 CacheAnnotationParser 只有一个实现类:SpringCacheAnnotationParser

public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {

    private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);

    static {
CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
}
......
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(
DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) { Collection<? extends Annotation> anns = (localOnly ?
AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
if (anns.isEmpty()) {
return null;
} final Collection<CacheOperation> ops = new ArrayList<>(1);
anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
anns.stream().filter(ann -> ann instanceof CachePut).forEach(
ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
anns.stream().filter(ann -> ann instanceof Caching).forEach(
ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
return ops;
}
......
}

可以看到 Parser 的作用就是将注解对应的方法解析成 CacheOperation 存起来。

看到这里我们开始有点眉目,开始知道去哪里找拦截了什么和操作了什么。接下来继续对 CacheOperationCacheInterceptor 进行深入。

CacheOperation

CacheOperationSource 接口中只有一个方法:

public interface CacheOperationSource {
default boolean isCandidateClass(Class<?> targetClass) {
return true;
} @Nullable
Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass);
}

该接口中只有一个方法:通过接口和类名获得对应的 CacheOperationCacheOperation 是对缓存操作的抽象封装,它的实现类有 3 个:

三个实现类分别对应着 @CacheEvict@CachePut@Cacheable 注解。

CacheInterceptor

CacheInterceptor 实现的功能就是对 目标方法的实际拦截操作:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

    @Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod(); CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
}; Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
try {
return execute(aopAllianceInvoker, target, method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
} }

CacheInterceptor 代码很简洁,采用函数的形式封装了真正要执行的函数逻辑,最终把此函数传交给父类的 execute()去执行。很显然最终执行目标方法的是 invocation.proceed()。我们直接去父类 CacheAspectSupport 看相关代码逻辑:

public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { protected final Log logger = LogFactory.getLog(getClass()); private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024); private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); @Nullable
private CacheOperationSource cacheOperationSource; private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); @Nullable
private SingletonSupplier<CacheResolver> cacheResolver; ......
}

上面摘录出 的一些属性:

Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache

这个 Map 缓存了所有被注解修饰的类或者方法对应的基本属性信息。

CacheOperationExpressionEvaluator evaluator

解析一些 condition、key、unless 等可以写 el 表达式的处理器。

SingletonSupplier<KeyGenerator> keyGenerator

key 生成器默认使用的 SimpleKeyGenerator,注意 SingletonSupplier 是 Spring5.1 的新类,实现了接口java.util.function.Supplier ,主要是对 null 值进行容错。

接着看相关的方法:

public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { // 这个接口来自于 SmartInitializingSingleton 在实例化完所有单例Bean后调用
//可以看到在这里实例化 CacheManager, CacheManager 我们后面会说作用
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.", ex);
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.", ex);
}
}
this.initialized = true;
} //根据CacheOperation 封装CacheOperationMetadata
protected CacheOperationMetadata getCacheOperationMetadata(
CacheOperation operation, Method method, Class<?> targetClass) { CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
if (metadata == null) {
KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) {
operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
}
else {
operationKeyGenerator = getKeyGenerator();
}
CacheResolver operationCacheResolver;
if (StringUtils.hasText(operation.getCacheResolver())) {
operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);
}
else if (StringUtils.hasText(operation.getCacheManager())) {
CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);
operationCacheResolver = new SimpleCacheResolver(cacheManager);
}
else {
operationCacheResolver = getCacheResolver();
Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set");
}
metadata = new CacheOperationMetadata(operation, method, targetClass,
operationKeyGenerator, operationCacheResolver);
this.metadataCache.put(cacheKey, metadata);
}
return metadata;
} //真正执行目标方法+ 缓存 的实现
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
// 如果已经初始化过(有CacheManager,CacheResolver),执行这里
if (this.initialized) {
//getTargetClass拿到原始Class 解剖代理
Class<?> targetClass = getTargetClass(target);
//简单的说就是拿到该方法上所有的CacheOperation缓存操作,最终一个一个的执行
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// CacheOperationContexts是非常重要的一个私有内部类
// 注意它是复数!不是CacheOperationContext单数
// 所以它就像持有多个注解上下文一样 一个个执行
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
// 若还没初始化 直接执行目标方法不执行缓存操作
return invoker.invoke();
} //内部调用的 execute 方法
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 判断是否同步执行
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
} // sync=false的情况,走这里 // Process any early evictions beforeInvocation=true的会在此处最先执行~~~ // 最先处理@CacheEvict注解~~~真正执行的方法请参见:performCacheEvict
// context.getCaches()拿出所有的caches,看看是执行cache.evict(key);方法还是cache.clear();
// 需要注意的的是context.isConditionPassing(result); condition条件此处生效,并且可以使用#result
// context.generateKey(result)也能使用#result
// @CacheEvict没有unless属性
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT); // 执行@Cacheable 看看缓存是否能够命中
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); // 如果缓存没有命中,那就准备一个cachePutRequest
// 因为@Cacheable首次进来肯定命中不了,最终肯定是需要执行一次put操作,这样下次进来就能命中
List<CachePutRequest> cachePutRequests = new ArrayList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
} Object cacheValue;
Object returnValue;
// 如果缓存命中了,并且并且没有@CachePut的话,也就直接返回
if (cacheHit != null && !hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 有 cachePut 的情况,先执行目标方法再 put 缓存
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
} // 封装cacheput对象
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // 真正统一执行 cacheput 的地方
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
} // 最后才执行 cacheEvict
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue;
} //缓存属性上下文对象
private class CacheOperationContexts { private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts;
//检查注解是否配置同步执行的开关
private final boolean sync; public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) { //将当前 method 上所有缓存操作封装到一个map 对象中
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
//同步执行与否取决于这个函数
this.sync = determineSyncFlag(method);
} public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.emptyList());
} public boolean isSynchronized() {
return this.sync;
} //只有@Cacheable有sync属性,所以只需要看CacheableOperation即可
private boolean determineSyncFlag(Method method) {
List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
if (cacheOperationContexts == null) { // no @Cacheable operation at all
return false;
}
boolean syncEnabled = false;
//只要有一个@Cacheable的sync=true了,那就为true 并且下面还有检查逻辑
for (CacheOperationContext cacheOperationContext : cacheOperationContexts) {
if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
syncEnabled = true;
break;
}
}
// 执行sync=true的检查逻辑
if (syncEnabled) {
// sync=true时候,不能还有其它的缓存操作 也就是说@Cacheable(sync=true)的时候只能单独使用
if (this.contexts.size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
}
//@Cacheable(sync=true)时,多个@Cacheable也是不允许的
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException(
"Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
}
// 拿到唯一的一个@Cacheable
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
//@Cacheable(sync=true)时,cacheName只能使用一个
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
}
//sync=true时,unless属性是不支持的,并且是不能写的
if (StringUtils.hasText(operation.getUnless())) {
throw new IllegalStateException(
"@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
}
return true;
}
return false;
}
} }

execute 相关的代码上面注解写的很清晰,大家可以跟着多看几遍。这里还有一点没有说,最终的 get Cache 或者 put Cache 的操作在哪里呢?刚才在 execute 方法中有一句代码:

Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

这里我们看到是先查缓存中是否有该 Cache,进去看有个 findCaches 方法:

@Nullable
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
for (Cache cache : context.getCaches()) {
Cache.ValueWrapper wrapper = doGet(cache, key);
if (wrapper != null) {
if (logger.isTraceEnabled()) {
logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
}
return wrapper;
}
}
return null;
}

重点关注 doGet 方法,可以看到这个方法是父类 AbstractCacheInvoker 中的,同类里面还有 doPut,doEvict 和 doClear。

小知识

@Cacheable 注解 sync=true 的效果

多线程环境下存在多个操作使用相同的参数同步调用相同的 key,默认情况下缓存不锁定任何资源所以可能导致多次计算。对于这种情况,sync 属性可以将底层锁住,使得只有一个线程进行操作,其他线程堵塞直到更新完缓存返回结果。

小结

至此我们把上面说过的内容总结一下:

BeanFactoryCacheOperationSourceAdvisor

配置 Cache 的切面和拦截实现。拦截的对象即解析出来的 CacheOperation 对象。

每一个 CacheOperation 在执行的时候被封装为 CacheOperationContext 对象(一个方法可能被多个注解修饰),最终通过 CacheResolver 解析出缓存对象 Cache。

CacheOperation

封装了@CachePut@Cacheable@CacheEvict的属性信息,以便于拦截的时候能直接操作此对象来执行逻辑。

解析注解对应的代码为 CacheOperation 的工作是 CacheAnnotationParser 来完成的。

CacheInterceptor

CacheInterceptor 执行真正的方法执行和 Cache 操作。最终调用其父类提供的四个 do 方法处理 Cache。

以上整体过程为 Spring 启动对相关注解所在类或者方法的拦截和注入,从而实现 Cache 逻辑。限于篇幅本篇暂讨论这些内容,下一篇我们来看 Spring 如何实现对多缓存底层方案的支持(本地 Cache,Redis,Guava Cache,Caffeine Cache)。

Spring Cache 带你飞(一)的更多相关文章

  1. Spring Cache 带你飞(二)

    接着上一篇讲了 Spring Cache 如何被 Spring Aop 代理加载对应的代码,以及何如注入相关界面逻辑. Spring Cache 带你飞(一) 本篇我们围绕两个要点展开: 一个数据是如 ...

  2. spring 缓存(spring自带Cache)(入门)源码解读

    spring自带的缓存类有两个基础类:Cache(org.springframework.cache.Cache)类,CacheManager(org.springframework.cache.Ca ...

  3. spring 缓存(spring自带Cache)(入门)

    spring的缓存机制,是方法纬度的缓存机制, 这就意味着我们并不用关注 底层是否使用了数据库以及通过什么方式访问的数据库: 因此,此缓存方法既适用于dao层,也适用于service层. spring ...

  4. 如何进行高效的源码阅读:以Spring Cache扩展为例带你搞清楚

    摘要 日常开发中,需要用到各种各样的框架来实现API.系统的构建.作为程序员,除了会使用框架还必须要了解框架工作的原理.这样可以便于我们排查问题,和自定义的扩展.那么如何去学习框架呢.通常我们通过阅读 ...

  5. Spring Cache抽象详解

    缓存简介 缓存,我的理解是:让数据更接近于使用者:工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存):缓存什么:那些经常读取且不经常修改的数据/那些昂贵(CPU/I ...

  6. 基于Redis的Spring cache 缓存介绍

    目录 Cache API及默认提供的实现 demo 依赖包安装 定义实体类.服务类和相关配置文件 Cache注解 启用Cache注解 @CachePut @CacheEvict @Cacheable ...

  7. 转:Spring Cache抽象详解

    缓存简介 缓存,我的理解是:让数据更接近于使用者:工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存):缓存什么:那些经常读取且不经常修改的数据/那些昂贵(CPU/I ...

  8. 以Spring Cache扩展为例介绍如何进行高效的源码的阅读

    摘要 日常开发中,需要用到各种各样的框架来实现API.系统的构建.作为程序员,除了会使用框架还必须要了解框架工作的原理.这样可以便于我们排查问题,和自定义的扩展.那么如何去学习框架呢.通常我们通过阅读 ...

  9. springboot 用redis缓存整合spring cache注解,使用Json序列化和反序列化。

    springboot下用cache注解整合redis并使用json序列化反序列化. cache注解整合redis 最近发现spring的注解用起来真的是很方便.随即产生了能不能吧spring注解使用r ...

随机推荐

  1. linux性能瓶颈排查--内存+cpu+网络+磁盘+应用瓶颈

    概述 作为运维人员,肯定遇到过以下场景,应用突然卡住了,或者异常退出,cpu占用过高等各种异常情况,一般遇到这些异常情况,该如何去查找具体原因呢? linux和jdk提供了一些命令和工具来查看内存.c ...

  2. 第十一章 Net 5.0 快速开发框架 YC.Boilerplate --图数据库模块Neo4j

    在线文档:http://doc.yc-l.com/#/README 在线演示地址:http://yc.yc-l.com/#/login 源码github:https://github.com/linb ...

  3. HDU - 2544最短路 (dijkstra算法)

    HDU - 2544最短路 Description 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以 ...

  4. 利用 uber-go/dig 库管理依赖

    利用 uber-go/dig 库管理依赖 github 地址 官方文档 介绍 dig 库是一个为 go 提供依赖注入 (dependency injection) 的工具包,基于 reflection ...

  5. DEDEcms手机网站添加详情内页上一页/下一页的翻页功能

    修改文件include/arc.archives.class.php文件. 1.搜索 function GetPreNext($gtype='') 2.将这个函数的所有内容替换为 function G ...

  6. jmeter监控linux服务器资源

    https://blog.csdn.net/weixin_38102592/article/details/100136375 https://blog.csdn.net/liuqiuxiu/arti ...

  7. JAVA-java内存分配

    二.java-class的内存分配 三.JAVA string类特别之处 String 通过构造方法创建是在堆内存中, 通过直接赋值对象是在方法区的常量里 四.字符串做拼接 非常耗时和浪费内存的原因 ...

  8. CF622F-The Sum of the k-th Powers【拉格朗日插值】

    正题 题目链接:https://www.luogu.com.cn/problem/CF622F 题目大意 给出\(n,k\),求 \[\sum_{i=1}^ni^k \] 解题思路 很经典的拉格朗日差 ...

  9. AT1983-[AGC001E]BBQ Hard【dp,组合数学】

    正题 题目链接:https://www.luogu.com.cn/problem/AT1983 题目大意 给出\(n\)个数对\((a_i,b_i)\) 求 \[\sum_{i=1}^n\sum_{j ...

  10. AOJ/树与二叉搜索树习题集

    ALDS1_7_A-RootedTree. Description: A graph G = (V, E) is a data structure where V is a finite set of ...