背景
在SpringBoot开发中,通过@Cacheable
注解便能够实现办法级别缓存,如下
@GetMapping(value = "/user/detail") @Cacheable(value = "user", key = "#uid") public User deteail(@RequestParam(value = "uid") String uid) {...}
Cacheable的逻辑
- 如果缓存中没有key为
#uid
的数据就执行detail函数并且把后果放到缓存中 - 如果缓存中存在key为
#uid
的数据就间接返回,不执行detail函数
通过Cacheable咱们能够十分不便的在代码中应用缓存,那么Cacheable是如何实现的,一开始认为是通过AOP实现,然而通过查看源码,发现跟AOP又有点不一样。
Cacheable原理
如果要应用Cacheable
就必须在启动类上加上@EnableCaching()
,该注解定义如下
@Import(CachingConfigurationSelector.class)public @interface EnableCaching {...}
CachingConfigurationSelector继承了AdviceModeImportSelector,次要看selectImports办法
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); }}
- 判断实现模式是基于代理(PROXY)还是ASPECTJ,Spring-AOP模式应用时代理模式,所以这边会走到getProxyImports这里
- getProxyImports中退出两个代理类,咱们次要看ProxyCachingConfiguration
@Configuration@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public class ProxyCachingConfiguration extends AbstractCachingConfiguration { @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() { BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource()); advisor.setAdvice(cacheInterceptor()); if (this.enableCaching != null) { advisor.setOrder(this.enableCaching.<Integer>getNumber("order")); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheOperationSource cacheOperationSource() { return new AnnotationCacheOperationSource(); } @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; }}
这里创立了几个类,咱们重点关注以下两个类
- cacheAdvisor:缓存加强类,能够了解为AOP中的Aspect,能够定义切点(Pointcut)和告诉(advice)
- cacheInterceptor:缓存中断器,缓存逻辑的具体执行,能够了解为AOP中的告诉(advice)
BeanFactoryCacheOperationSourceAdvisor
局部代码如下
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private CacheOperationSource cacheOperationSource; private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() { @Override @Nullable protected CacheOperationSource getCacheOperationSource() { return cacheOperationSource; } }; //....}
在这里定义了一个切点(pointcut),加上下面代码中定义了一个告诉
advisor.setAdvice(cacheInterceptor());
所以这就是一个残缺的Aspect,切点负责定义拦挡的类,CacheOperationSourcePointcut局部代码如下
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override public boolean matches(Method method, Class<?> targetClass) { if (CacheManager.class.isAssignableFrom(targetClass)) { return false; } CacheOperationSource cas = getCacheOperationSource(); return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass))); } //...}
其中matchs就是负责过滤的类和办法,如果返回true那么该办法就会被拦挡,拦挡形式对应AOP中的Around
,具体过滤规定咱们就不持续往下看,总之Cacheable的实现能够概括如下
定义Advisor->定义中断(interceptor)->定义切点(Pointcut)
那么接下来咱们模拟Cacheable实现日志的打印,在办法进入前打印日志,在办法执行后打印日志
模拟Cacheable实现日志打印
- 定义注解,作用相似Cacheable
Logable.java
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface Logable { String value() default "";}
- 定义配置类,为了简略这里就不采纳
EnableCache
的形式,间接定义配置类
LogProxyConfiguration.java
@Configuration@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public class LogProxyConfiguration { @Bean(name = "com.poc.aop.log.LogAdvisor") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public LogAdvisor logAdvisor() { LogAdvisor advisor = new LogAdvisor(); advisor.setAdvice(new LogInterceptor()); return advisor; }}
- 定义日志加强类LogAdvisor
LogAdvisor.java
public class LogAdvisor extends AbstractBeanFactoryPointcutAdvisor { LogPointcut pointcut=new LogPointcut(); @Override public Pointcut getPointcut() { return pointcut; }}
- 定义日志切点
LogPointcut.java
public class LogPointcut extends StaticMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { return method.getAnnotation(Logable.class) != null; }}
这里逻辑判断很简略,只有带有Logable的办法就会被拦挡
- 定义日志办法中断,也就是告诉advice
public class LogInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("执行办法==>" + invocation.getMethod().getName()); System.out.println("办法参数:"); for (Object arg : invocation.getArguments()) { System.out.println("参数:" + arg); } Object returnValue = invocation.proceed(); System.out.println("返回值===>" + returnValue); return returnValue; }}
测试
咱们筹备一个测试接口
@GetMapping(value = "/user/detail")@Logablepublic User deteail(@RequestParam(value = "uid") String uid) { User u = new User(); u.setUid(uid); u.setAge(10); u.setEmail("jianfeng.zheng@definesys.com"); u.setBirthday(Calendar.getInstance().getTime()); return u;}
申请接口,进入LogInterceptor.invoke办法,打印如下
执行办法==>deteail办法参数:参数:004返回值===>User{uid='004', name='null', email='jianfeng.zheng@definesys.com', birthday=Mon Mar 15 19:32:22 CST 2021, age=10}
用Spring AOP能不能实现
当然是能够的,只有编写一个Aspect类就行
@Aspect@Componentpublic class LogAspect { @Pointcut("@annotation(com.poc.aop.log.Logable)") public void logPointCut() { } @Around("logPointCut()") public void aroundAdvice(ProceedingJoinPoint joinPoint) { System.out.println("执行办法==>" + joinPoint.getSignature().getName()); System.out.println("办法参数:"); for (Object arg : joinPoint.getArgs()) { System.out.println("参数:" + arg); } Object returnValue = null; try { returnValue = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("返回值===>" + returnValue); }}
一样的成果,那为什么Cacheable要应用下面那种形式?我猜是因为advisor这种代理的形式切点灵活性更高,如下
public boolean matches(Method method, Class<?> targetClass)
能够依据method和targetClass灵便定义切点,当然我还是更喜爱Aspect的形式