Spring Cache 系列 & 0x02 組件

Spring Cache 系列 & 0x01 開篇
Spring Cache 系列 & 0x02 組件
Spring Cache 系列 & 0x03 注解

這一篇試著講解 Spring Cache 如何加載緩存實(shí)例的。
Spring Cache 是使用動(dòng)態(tài)代理完成的芝硬,下面一步一步剖析Spring 如何加載管理 Cache Bean 的。

0x01 需要的代碼

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@CacheConfig(cacheNames = {"CacheService"})
public class CacheService {

    @Cacheable(key = "#root.targetClass.getName() + '_' + #root.args[0]")
    public String get(Long value) {

        return "-1";
    }

    @CachePut(key = "#root.targetClass.getName() + '_' + #p0")
    public String update(Long value) {
        return "0";
    }

    @CacheEvict(key = "#root.targetClass.getName() + '_' + #a0")
    public void delete(Long value) {

    }
}

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfiguration {


    @Bean
    public CachingConfigurer cachingConfigurer() {
        return new CachingConfigurer() {
            @Override
            public CacheManager cacheManager() {
                return new ConcurrentMapCacheManager();
            }

            @Override
            public CacheResolver cacheResolver() {
                return null;
            }

            @Override
            public KeyGenerator keyGenerator() {
                return null;
            }

            @Override
            public CacheErrorHandler errorHandler() {
                return new SimpleCacheErrorHandler();
            }
        };
    }

    @Bean
    public CacheService cacheService() {
        return new CacheService();
    }


    public static void main(String[] args) {
        Long id = 10L;
        AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(CacheConfiguration.class);
        CacheService cacheService = app.getBean(CacheService.class);
        String s = cacheService.get(id);
        System.out.println("get: " + s);

        String update = cacheService.update(id);
        System.out.println("update: " + update);

        String s1 = cacheService.get(id);
        System.out.println("get: " + s1);
        
        cacheService.delete(id);

        String s2 = cacheService.get(id);
        System.out.println("get: " + s2);
    }

}

上面兩個(gè)類是演示如何使用 Spring Cache 的欺嗤;接下來對最要的類做進(jìn)一步分析交掏;

0x02 Cache 入口

0x021 EnableCaching

這個(gè)注解的表面意思是開啟緩存沈条;也就是加載酣难、管理緩存實(shí)例的入口让虐;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 這里會(huì)交由 Spring 加載、實(shí)例化 Import 里的類 CachingConfigurationSelector罢荡; 可以去網(wǎng)上搜索有關(guān) Import 的用途說明赡突;
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;

    int order() default Ordered.LOWEST_PRECEDENCE;
}

0x022 CachingConfigurationSelector

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

    private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
            "org.springframework.cache.jcache.config.ProxyJCacheConfiguration";

    private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
            "org.springframework.cache.aspectj.AspectJCachingConfiguration";

    private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
            "org.springframework.cache.aspectj.AspectJJCacheConfiguration";


    private static final boolean jsr107Present;

    private static final boolean jcacheImplPresent;

    static {
        ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
        jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
        jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
    }

   // 重寫父類方法
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
            // EnableCaching#mode() 的配置,這里默認(rèn)是 PROXY(JDK PROXY)
        switch (adviceMode) {
            case PROXY:
                            // 我們主要介紹的是 JDK PROXY
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null;
        }
    }
     
    private String[] getProxyImports() {
// 默認(rèn)加載兩個(gè)類AutoProxyRegistrar区赵、ProxyCachingConfiguration
        List<String> result = new ArrayList<>(3);
        result.add(AutoProxyRegistrar.class.getName());
        result.add(ProxyCachingConfiguration.class.getName());
// 這里我們不介紹 JCACHE惭缰;因?yàn)樗挥绊懳覀兪褂?Spring Cache
        if (jsr107Present && jcacheImplPresent) {
            result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
        }
        return StringUtils.toStringArray(result);
    }

    private String[] getAspectJImports() {
        List<String> result = new ArrayList<>(2);
        result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
        if (jsr107Present && jcacheImplPresent) {
            result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
        }
        return StringUtils.toStringArray(result);
    }

}

// 父類
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

     // 這個(gè)常量對應(yīng)的 是 EnableCaching#mode() 方法
    public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

    protected String getAdviceModeAttributeName() {
        return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
    }

    @Override
    public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
        Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");

        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
        if (attributes == null) {
            throw new IllegalArgumentException(String.format(
                    "@%s is not present on importing class '%s' as expected",
                    annType.getSimpleName(), importingClassMetadata.getClassName()));
        }
       // 獲取 EnableCaching#mode() 的配置
        AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
        String[] imports = selectImports(adviceMode);
        if (imports == null) {
            throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
        }
        return imports;
    }

    
         // @see CachingConfigurationSelector#selectImports(AdviceMode)
    @Nullable
    protected abstract String[] selectImports(AdviceMode adviceMode);

}

0x023 AutoProxyRegistrar

// ImportBeanDefinitionRegistrar 支持 Spring 動(dòng)態(tài)加載 Bean 的重要接口;
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    private final Log logger = LogFactory.getLog(getClass());
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
   // importingClassMetadata 表示的是當(dāng)前加載的 Bean笼才;也就是 CacheConfiguration 實(shí)例漱受;
   // 類 AutoProxyRegistrar 是通過 EnableCaching 注解加載的;而 EnableCaching 是在 CacheConfiguration 類上的骡送;
  // 所有 annTypes 獲取的 CacheConfiguration 類型上的 注解昂羡;也就是 @Configuration、@EnableCaching
        Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
        for (String annType : annTypes) {
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
            if (candidate == null) {
                continue;
            }
                // 這里獲取 EnableCaching 注解的屬性摔踱;
                // 通過這里我們可以自定義注解實(shí)現(xiàn)我們自己的業(yè)務(wù)虐先;配置 mode、proxyTargetClass 兩個(gè)屬性派敷,開通 AOP 的支持
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                    Boolean.class == proxyTargetClass.getClass()) {
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
                    // 這里加載一個(gè)重要的類 InfrastructureAdvisorAutoProxyCreator 蛹批;
                    // 而這個(gè)類有一個(gè)重要的接口 InstantiationAwareBeanPostProcessor,這個(gè)接口的有一個(gè)方法 postProcessBeforeInstantiation 是在 Spring 初始化完 Bean 實(shí)例化之前調(diào)的接口篮愉;
                   // AOP 就是在這里對符合條件(下面會(huì)介紹怎么符合條件)的 Bean 進(jìn)行動(dòng)態(tài)代理控制
                   // @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation 這是 InfrastructureAdvisorAutoProxyCreator 類的子類腐芍;
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        // @see org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
                        // 如果沒有這個(gè),Spring 默認(rèn)使用 JDK 動(dòng)態(tài)代理
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
}

0x024 ProxyCachingConfiguration

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
     // 實(shí)例化 AOP 緩存 切面
     // 如果不了解切面可以去網(wǎng)上搜索一下 AOP 介紹试躏;
    @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
            // 在類里轉(zhuǎn)換成切點(diǎn)猪勇,這里就是如何匹配符合條件的類;并對這些類代理
        advisor.setCacheOperationSource(cacheOperationSource());
           // 通知冗酿,執(zhí)行業(yè)務(wù)
        advisor.setAdvice(cacheInterceptor());
        if (this.enableCaching != null) {
            advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
        }
        return advisor;
    }
    // 這個(gè)類里加裝解析緩存用到的 注解 
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        return new AnnotationCacheOperationSource();
    }

    // 實(shí)例化 AOP 攔截器
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheInterceptor cacheInterceptor() {
        CacheInterceptor interceptor = new CacheInterceptor();
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        interceptor.setCacheOperationSource(cacheOperationSource());
        return interceptor;
    }

}

上面介紹了如何使用Spring Cache埠对,以及加載一個(gè)類似CacheService用到了那些Spring 組件;如果你熟悉了上面 EnableCaching 注解模式(Spring 4.x裁替、Spring Boot 大量使用)项玛、@Import 注解、ImportBeanDefinitionRegistrar 動(dòng)態(tài)加載 Bean 接口弱判、AOP(切面襟沮、切點(diǎn)、連接點(diǎn)、通知)开伏、BeanPostProcessor 組件膀跌、以及了解Spring 的加載過程;學(xué)習(xí) Spring Cache 不會(huì)有任何壓力固灵。

如果你已經(jīng)看過 Spring Cache 系列 & 0x01 開篇 這篇文章捅伤;你可以試著Debug InfrastructureAdvisorAutoProxyCreator#postProcessBeforeInstantiation(Class<?>, String) 方法,你就會(huì)知道如何創(chuàng)建代理類巫玻;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丛忆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仍秤,更是在濱河造成了極大的恐慌熄诡,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诗力,死亡現(xiàn)場離奇詭異凰浮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苇本,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門袜茧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人圈澈,你說我怎么就攤上這事惫周。” “怎么了康栈?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵递递,是天一觀的道長。 經(jīng)常有香客問我啥么,道長登舞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任悬荣,我火速辦了婚禮菠秒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氯迂。我一直安慰自己践叠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布嚼蚀。 她就那樣靜靜地躺著禁灼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轿曙。 梳的紋絲不亂的頭發(fā)上弄捕,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天僻孝,我揣著相機(jī)與錄音,去河邊找鬼守谓。 笑死穿铆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斋荞。 我是一名探鬼主播荞雏,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼平酿!你這毒婦竟也來了讯檐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤染服,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后叨恨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柳刮,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年痒钝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秉颗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡送矩,死狀恐怖蚕甥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栋荸,我是刑警寧澤菇怀,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站晌块,受9級特大地震影響爱沟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匆背,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一呼伸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钝尸,春花似錦括享、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至踢星,卻和暖如春澳叉,著一層夾襖步出監(jiān)牢的瞬間隙咸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工成洗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留五督,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓瓶殃,卻偏偏與公主長得像充包,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子遥椿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351