1. @EnableCaching ソースコード分析
@Cacheable アノテーションを使用する場合は、@EnableCaching アノテーションを導入してキャッシュ機能を有効にする必要があります。なぜ?ここで、キャッシュ機能を有効にするために @EnableCaching アノテーションを追加する必要がある理由を見てみましょう。ソースコードは次のとおりです。
@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 Ordered.LOWEST_PRECEDENCE;
}
CachingConfigurationSelector クラスが @Import アノテーションを通じてインポートされていることがわかりますが、このクラスはエントリ クラスまたはトリガー クラスである必要があると考えられます。ここでの mode() のデフォルトは であることに注意してくださいAdviceMode.PROXY
。
CachingConfigurationSelector クラスを入力します。ソース コードは次のとおりです。
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
}
getProxyImports() メソッドから、AutoProxyRegistrarクラスとProxyCachingConfigurationクラスの 2 つのクラスがインポートされたことがわかります。それでは、これら 2 つのクラスが何に使用されているかを分析することに焦点を当てましょう。
1、AutoProxyRegistrar
XxxRegistrar を参照すると、クラスを確実に登録できます。次のようにコア ソース コードを入力します。
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
}
@Nullable
public static BeanDefinition registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 缓存和事物都是通过这个 InfrastructureAdvisorAutoProxyCreator 基础增强类来生成代理对象
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}
}
ここには当然、トランザクション アスペクト エントリ クラスと同じ InfrastructureAdvisorAutoProxyCreator クラスが登録されます。このクラスはロジックを実行しません。すべてのロジックはその親クラスである AbstractAutoProxyCreator 内にあり、エントリ クラスとしてのみ機能します。
AbstractAutoProxyCreator クラスは、BeanPostProcessorインターフェースの別のアプリケーションです。したがって、このインターフェイスの 2 つのメソッドに注目してください。ここでは、2 番目のメソッド postProcessAfterInitialization() にのみ注意する必要があります。ソース コードは次のとおりです。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
/** 获取缓存 key */
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
/** 是否有必要创建代理对象 */
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
明らかに、wrapIfNecessary() メソッドは AbstractAutoProxyCreator 抽象クラスで呼び出され、現在の Bean に対してプロキシ オブジェクトを作成する必要があるかどうかを判断します。では、ここでの判断基準は何でしょうか?このメソッドを入力すると、コアのソース コードは次のようになります。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
/** 合适的通知不为空 */
if (specificInterceptors != DO_NOT_PROXY) {
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
return proxy;
}
return bean;
}
上記のロジックでは、 getAdvicesAndAdvisorsForBean() メソッドが判断の基準であることは明らかです。値がある場合はプロキシを作成する必要がありますが、それ以外の場合はプロキシを作成する必要はありません。次に、メソッドの内部ロジックに注目します。コアのソース コードは次のとおりです。
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
/** tc_tag-99: 查找适合这个类的 advisors 切面通知 */
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
引き続き、findEligibleAdvisors() の内部ロジックを入力します。コアのソース コードは次のとおりです。
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
findCandidateAdvisors() メソッドを参照してください。内部ロジックは次のとおりです。
protected List<Advisor> findCandidateAdvisors() {
return this.advisorRetrievalHelper.findAdvisorBeans();
}
public List<Advisor> findAdvisorBeans() {
String[] advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
}
return advisors;
}
上記のコードからわかるように、Spring は beanNamesForTypeincludeAncestors(Advisor.class) メソッドを呼び出すことによって、Spring コンテナ内の Advsior インターフェースを実装するすべての実装クラスを取得します。
それでは、ProxyCachingConfiguration クラスを見てみましょう。ソース コードは次のとおりです。
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
// cache_tag: 缓存方法增强器
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
// 缓存用来解析一些属性封装的对象 CacheOperationSource
advisor.setCacheOperationSource(cacheOperationSource);
// 缓存拦截器执行对象
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
// 先省略一部分...
}
BeanFactoryCacheOperationSourceAdvisor が Advisor インターフェースを実装していることは明らかです。その後、上記の beanNamesForTypeincludeAncestors(Advisor.class) メソッドを呼び出すときにインスタンスを取得できます。インスタンスは候補コレクション アドバイザに追加され、返されます。
findCandidateAdvisors() メソッドを読み、次に findAdvisorsThatCanApply() メソッドを見ると、前者のメソッドは Spring コンテナ内の Advisor インターフェイスのすべての実装を取得します。次に、findAdvisorsThatCanApply() メソッドを呼び出して、どのアドバイザが現在の Bean に適用できるかを判断します。findAdvisorsThatCanApply() の内部ロジックを入力します。コア ソース コードは次のとおりです。
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
/** 没有切面,匹配个屁 */
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
// tc_tag-96: 开始 for 循环 candidateAdvisors 每个增强器,看是否能使用与这个 bean
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
続けて canApply() を入力します。コア ロジックは次のとおりです。
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
/**
* 通过 Pointcut 获取到 ClassFilter 类的匹配器
* 然后匹配 targetClass 是否在 Pointcut 配置的包路径下面么?具体实现看 AspectJExpressionPointcut
*/
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
/**
* 通过 Pointcut 获取到 MethodMatcher 类的匹配器
* 然后判断这个类下面的方法是否在 Pointcut 配置的包路径下面么?
* 或者是这个方法上是否标注了 @Transactional、@Cacheable等注解呢?
*/
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
/**
* tc_tag1: 获取这个 targetClass 类下所有的方法,开始挨个遍历是否满 Pointcut 配置的包路径下面么?
* 或者是这个方法上是否标注了 @Transactional、@Cacheable等注解呢?
*/
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
/**
* tc_tag2: 注意对于 @Transactional 注解的 Pointcut 匹配还是比较复杂的,匹配逻辑在 TransactionAttributeSourcePointcut
*/
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
上記のソース コードを見ると、ClassFilter クラスと MethodMatcher クラスという 2 つのよく知られた古い友人が表示されます。ClassFilter クラスは、現在の Bean が配置されているクラスが @Caching、@Cacheable 、@CachePut でマークされているかどうかを判断するために使用されます。、および@CacheEvict .アノテーション。MethodMatcher クラスは、現在の Bean メソッドに@Caching、@Cacheable、@CachePut、および@CacheEvictアノテーションが付けられているかどうかを判断するために使用されます。
では、これら 2 つのフィルター オブジェクトはどこで作成されるのでしょうか?
ProxyCachingConfiguration クラスに戻ると、ソース コードは次のとおりです。
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
// cache_tag: 缓存方法增强器
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
// 缓存用来解析一些属性封装的对象 CacheOperationSource
advisor.setCacheOperationSource(cacheOperationSource);
// 缓存拦截器执行对象
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
}
BeanFactoryCacheOperationSourceAdvisor クラスを参照してください。Advisor は Advice と Pointcut で構成される必要があることはわかっています。
ポイントカットは ClassFilter と MethodMatcher で構成されている必要があります。このクラスを入力すると、ソースコードは次のようになります。
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
CacheOperationSourcePointcut クラスに注目し、次のようにソース コードを入力します。
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
protected CacheOperationSourcePointcut() {
setClassFilter(new CacheOperationSourceClassFilter());
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
@Nullable
protected abstract CacheOperationSource getCacheOperationSource();
private class CacheOperationSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
if (CacheManager.class.isAssignableFrom(clazz)) {
return false;
}
CacheOperationSource cas = getCacheOperationSource();
return (cas == null || cas.isCandidateClass(clazz));
}
}
}
ソース コードから、ClassFilter = CacheOperationSourceClassFilter、MethodMatcher = CacheOperationSourcePointcut であることがわかります。ここでの ClassFilter のmatches()メソッドはクラスをフィルタリングしません。このクラス フィルタリングは MethodMatcher のmatches() メソッドに配置され、メソッド フィルタリングと統合されます。
そこで、ここでは MethodMatcher#matches() マッチング メソッドに焦点を当て、それがどのように一致するかを確認します。コアのソースコードは次のとおりです。
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@Override
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
}
getCacheOperations() メソッドを入力します。コアのソース コードは次のとおりです。
@Override
@Nullable
public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
Object cacheKey = getCacheKey(method, targetClass);
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
if (cached != null) {
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
}
else {
// cache_tag: 计算缓存注解上面的配置的值,然后封装成 CacheOperation 缓存属性对象,基本和事物的一样
// 注意每个缓存注解对应一种不同的解析处理
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (logger.isTraceEnabled()) {
logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
this.attributeCache.put(cacheKey, cacheOps);
}
else {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}
このコードは @Transactional アノテーションとまったく同じに一致することがわかりました。実際、同じロジック セットが使用されていますが、アノテーションが異なります。マッチング プロセスはまったく同じです。computeCacheOperations() メソッドを参照してください。コア ソース コードは次のとおりです。
@Nullable
private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
if (opDef != null) {
return opDef;
}
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}
return null;
}
@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;
}
// cache_tag: 熟悉不能再熟悉的缓存注解 Cacheable/CacheEvict/CachePut/Caching
// 注意每一种类型的注解解析是不太一样的哦,具体看 parseCacheableAnnotation() 解析方法
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;
}
上記のソース コードから、現在の Bean メソッドに @Cacheable などのアノテーションがあるかどうかにかかわらず、一致ロジックが一致することがわかります。メソッドが見つからない場合は、現在の Bean が存在するクラスを検索します。メソッドが見つからない場合は、インターフェイス メソッドを検索します。見つからない場合は、インターフェイス クラスの検索を続けます。見つからない場合は、直接 null を返します。これは、彼の MethodMatcher#matches() マッチング プロセスです。
したがって、canApply() メソッド呼び出しの最下層では、MethodMatcher を呼び出してマッチングを実行します。findAdvisorsThatCanApply() メソッドは、このような照合プロセスである canApply() を呼び出します。一致が成功した場合は、Advisor を使用して現在の Bean を拡張できることを意味します。次に、createProxy() メソッドを呼び出してプロキシ オブジェクトを作成する必要があります。プロキシ オブジェクトが Advisor の拡張ロジックを呼び出し、実行後にターゲット メソッドを呼び出します。上位層に戻って getAdvicesAndAdvisorsForBean() を呼び出します。ソース コードは次のとおりです。
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
/** tc_tag-99: 查找适合这个类的 advisors 切面通知 */
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
最上位層に戻り、wrapIfNecessary() を呼び出します。ソース コードは次のとおりです。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
/** 合适的通知不为空 */
if (specificInterceptors != DO_NOT_PROXY) {
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
return proxy;
}
return bean;
}
最後に、createProxy() を呼び出してプロキシ オブジェクトを作成します。この時点で、Spring 起動時のプロキシ オブジェクトの作成が完了します。次に、呼び出しプロセスを分析する必要があります。
2、プロキシキャッシュ構成
このクラスは、キャッシュに必要なサポート クラスを提供します。例えば、Advisor は @Bean メソッドを通じて Spring コンテナに事前に登録されます。ソースコードは次のとおりです。
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
// cache_tag: 缓存方法增强器
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
// 缓存用来解析一些属性封装的对象 CacheOperationSource
advisor.setCacheOperationSource(cacheOperationSource);
// 缓存拦截器执行对象
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
// cache_tag: Cache 注解解析器
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
// cache_tag: 缓存拦截器执行器
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource);
return interceptor;
}
}
ここではアドバイザーにのみ注意する必要があります。CacheOperationSource オブジェクトは、@Cacheable およびその他のアノテーションを解析した後のプロパティのカプセル化にすぎません。
アドバイザー = アドバイス + ポイントカット
アドバイザ = BeanFactoryCacheOperationSourceAdvisor
アドバイス = キャッシュインターセプター
ポイントカット = CacheOperationSourcePointcut
ポイントカット = クラスフィルター + メソッドマッチャー
ClassFilter = CacheOperationSourceClassFilter
MethodMatcher = CacheOperationSourcePointcut
呼び出し処理については次の記事Spring の @Cacheable ソースコード解析 (その 2)へお進みください。
最終的に、@EnableCaching アノテーションは基本的に @EnableTransactionManagement と同じですが、アノテーションが変更され、@Transactional が @Cacheable およびその他のアノテーションに置き換えられる点が異なります。