Spring Cache 系列 & 0x01 开篇
Spring Cache 系列 & 0x02 组件
Spring Cache 系列 & 0x03 注解
Spring 缓存对现存的缓存框架做了进一步封装,使得接入和使用第三方缓存框架更加简便,同时可以实现自定义缓存。
接下来会参考 Spring Framework(4.3.16.RELEASE
) 源码来介绍 Spring Cache。
Spring Cache Mode
- Proxy
- AspectJ
如果不理解介绍的这两种模式,可以去了解一下 Spring Aop。
这里为什么会提到 AOP 呢?不是在讲 Cache 吗?
进入正题......
Spring 如何实现缓存的?
传统实现方式
public class ExampleService {
@Autowire
private Cache cache;
@Autowire
private ExampleDao exampleDao;
public List getResult() {
List result = cache.get("cache-key");
if (result == null) {
result = exampleDao.getResult();
cache.put("cache-key", result);
}
return result;
}
}
相信上面的伪代码大家都能理解吧;
ExampleService#getResult
方法先去缓存中取数据,如果缓存中没有,查询数据库,并且把查询结果放到缓存中。
如果业务中使用缓存的地方很多的话,每次都会getCache
、putCache
是不是会让你觉得不是很舒服。不但这些代码完全和业务没有关系,而且还破坏面向对象 - 单一原则。
我们知道 java 有三大特性:封装
、继承
、多态
。是不是可以考虑把getCache
、putCache
的部分封装起来,提供公共服务,避免业务中充斥着无关的代码。
Spring 实现方式
Spring Cache 要做的就是,把和业务无关的
getCache
、putCache
代码封装成更简便的服务。
在看一下上面提到的伪代码ExampleService#getResult()
,如果要封装,该如何封装?
一点一点分解代码.....
- 方式一
我们上面讲过了,侵入代码的方式
- 方式二
组合;也可以看作静态代理
业务代码
import java.util.List;
public interface ExampleService {
List getResult();
}
import org.springframework.stereotype.Component;
import java.util.List;
@Component("ExampleServiceImpl")
public class ExampleServiceImpl implements ExampleService {
@Override
public List getResult() {
return null;
}
}
- 创建一个接口
ExampleService
,只有一个方法 - 创建
ExampleServiceImpl
去实现ExampleService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.List;
@Primary
@Component
public class ExampleProxyService implements ExampleService {
@Autowired
@Qualifier("ExampleServiceImpl")
private ExampleService exampleService;
@Autowired
private Cache cache;
@Override
public List getResult() {
Cache.ValueWrapper key = cache.get("key");
if (key.get() == null) {
List
- 重新建一个实现类
- 原来侵入代码的部分,放到这了代理类里;这种方式可以自由的注入
ExampleService
- 如果一个类里要使用缓存,都需要建立这样一个代理类,不但增加了开发成本,还带来维护难;这种方式也不是最佳方案。
但是通过这种方式,我们是不是可以联想到使用动态代理(dynamic proxy)的方式来做。
- 方式三
代理
** 创建代理入口 **
import com.keng.spring.cache.proxy.aop.advisor.CacheAdvisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class CacheBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class> clazz = bean.getClass();
Class>[] interfaces = clazz.getInterfaces();
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterfaces(interfaces);
proxyFactoryBean.setTarget(bean);
proxyFactoryBean.addAdvisor(new CacheAdvisor());
return proxyFactoryBean.getObject();
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
** 增强 **
public class CacheAdvisor implements Advisor {
@Override
public Advice getAdvice() {
return new CacheInterptor();
}
@Override
public boolean isPerInstance() {
return false;
}
}
** 通知 **
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cache.Cache;
import java.lang.reflect.Method;
public class CacheInterptor implements MethodInterceptor {
private Cache cache;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// key 如何获取
Cache.ValueWrapper valueWrapper = cache.get("key");
if (valueWrapper == null || valueWrapper.get() == null) {
return invocation.proceed();
}
return valueWrapper.get();
}
public Cache getCache() {
return cache;
}
}
以上是使用 Spring Aop 实现的一个简易版缓存机制。
- BeanPostProcessor
该接口会在 Spring 容器初始化完 Bean 调用,我们拿来对初始化后的 Bean 创建代理
- Advisor
Aop 接口
- MethodInterceptor
Aop 接口
但是上面有个问题,在类CacheInterptor
中注释的地方;我们如何知道当前方法返回值要缓存的 key 呢?
注解,一个很简单的实现方式。
改造最终版......
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheKey {
String value();
}
import java.util.List;
public interface ExampleService {
List getResult();
}
import com.keng.spring.cache.annotation.CacheKey;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("ExampleServiceImpl")
public class ExampleServiceImpl implements ExampleService {
@Override
@CacheKey(value = "getResult")
public List getResult() {
return null;
}
}
import com.keng.spring.cache.annotation.CacheKey;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cache.Cache;
import java.lang.reflect.Method;
public class CacheInterptor implements MethodInterceptor {
private Cache cache;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheKey cacheKey = method.getAnnotation(CacheKey.class);
if (cacheKey == null) {
return invocation.proceed();
}
Cache.ValueWrapper valueWrapper = cache.get(cacheKey.value());
if (valueWrapper == null || valueWrapper.get() == null) {
return invocation.proceed();
}
return valueWrapper.get();
}
public Cache getCache() {
return cache;
}
}
import com.keng.spring.cache.proxy.aop.interceptor.CacheInterptor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
public class CacheAdvisor implements Advisor {
@Override
public Advice getAdvice() {
return new CacheInterptor();
}
@Override
public boolean isPerInstance() {
return false;
}
}
import com.keng.spring.cache.proxy.aop.advisor.CacheAdvisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class CacheBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class> clazz = bean.getClass();
Class>[] interfaces = clazz.getInterfaces();
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterfaces(interfaces);
proxyFactoryBean.setTarget(bean);
proxyFactoryBean.addAdvisor(new CacheAdvisor());
return proxyFactoryBean.getObject();
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
以上就是 Spring Cache 大致实现方法,上面写这么多还没进入介绍 Spring Cache 就是为了让你对 Spring Cache 有一个基本认识,方便来理解 Spring Cache 的实现方式。