Smart是一个轻量级的java web框架,里面已经实现了IOC,AOP以及事务管理,同时也实现了MVC模块。本插件是基于Smart的AOP来进行实现的。目标是:在不需要改变当前业务代码的情况下将缓存功能嵌入进去,本实现参考了Spring以及JCache,最终希望达到灵活,易用目的。
该插件是基于Smart的AOP来实现,关于Smart的AOP可以通过《使用“链式代理”实现 AOP》 和《AOP实现原理》来了解。在BaseAspect基础上再构造了一个抽象类,粘出代码:
public abstract class DefaultCacheAspact extends BaseAspect {
private static CacheManager cacheManager = new DefaultCacheManager();
/*
* (non-Javadoc)
*
* @see com.smart.framework.base.BaseAspect#before(java.lang.Class,
* java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public final void before(Class<?> cls, Method method, Object[] params) {
………
}
/*
* (non-Javadoc)
*
* @see com.smart.framework.base.BaseAspect#after(java.lang.Object,
* java.lang.Class, java.lang.reflect.Method, java.lang.Object[],
* java.lang.Object)
*/
@Override
public final void after(Class<?> cls, Method method, Object[] params,
Object methodResult) {
……
}
private String generalKey(Class<?> cls, Method method, Object[] params)
throws IOException {
………
}
}
主要实现了三个方法,before,after以及generalKey,before方法是在执行方法之前检查在缓存容器总是否存在当前方法只需的缓存,而after主要是将方法执行完后的结果加入到缓存容器中,generalKey则是更加当前执行方法上下文,创建一个缓存对应的键值。
为了标记哪些方法需要缓存,创建了一个注解,如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
public @interface Cacheable {
public String value();
public String key() default "default";
public CacheIntent intent() default CacheIntent.NORMAL;
}
这里面主要有三个参数,value则表示当前方法缓存是处于哪个缓存容器中,key,表示当前执行的方法缓存key的生成规则,此处采用了Spring的模式,例如:#id+getCustomerById,其中#id是方法getCustomerById的一个参数名,表示这个方法的缓存key的生成规则是通过调用方法getCustomerById的参数id的值再加上getCustomerById进行生成,这样就可以达到不同的id拥有各自的缓存内容。Key是可选的,当key为默认时候,则将方法名当作key来进行和缓存内容关联。最后一个参数是CacheIntent类型的参数,表示当前方法执行的意图,该枚举类型定义如下
public enum CacheIntent {
NORMAL,DELETE,UPDATE
}
Normal表示一般的操作,及添加缓存,而Delete和Update则表示当前方法是删除操作和更新操作,这种情况需要将对应的内容进行删除,当为Delete或者Update的时候,如果也配置了key的生成规则,那么将会对应该规则生成key的缓存进行删除,否则将整个value的缓存容器进行删除。
为了能够使得获取需要缓存的内容,需要对Smart的AOP基础类BaseAspect的after方法多加入一个参数,如下:
public void after( Class<?> cls, Method method, Object[] params,Object methodResult) {
}
在原有的基础上加了一个methodResult参数,用于获得方法返回的值。同时为了能够获取缓存切面从缓存中获取的缓存内容,在smart-framework中定义了一个ThreadLocal容器,代码如下:
public class CacheResult {
private static ThreadLocal<Object> cacheResult = new ThreadLocal<Object>();
public static void put(Object result){
cacheResult.set(result);
}
public static Object get(){
return cacheResult.get();
}
public static void clear(){
cacheResult.remove();
}
}
这样就可以得到不调整ProxyChain接口内容即可获得缓存内容。
接下来进行谈谈具体实现:先粘贴出对ProxyChain的调整
public class ProxyChain {
………
public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy, List<Proxy> proxyList) {
……
}
……
public void doProxyChain() throws Exception {
if (currentProxyIndex < proxyList.size()) {
proxyList.get( currentProxyIndex++).doProxy(this);
} else {
try { if(CacheResult.get()!=null){
this.methodResult=CacheResult.get();
}else{
methodResult = methodProxy.invokeSuper(targetObject, methodParams);
} } catch (Throwable throwable) {
throw new RuntimeException(throwable);
} finally{
CacheResult.clear();
} }
}
}
这里主要是对标红部分进行调整,则判断ThreadLocal中是否有只,如果有,则不执行方法,直接用缓存中的内容,别忘记最后清空一下ThreadLocal。
对于缓存模块的实现,这里就不粘贴出代码了,基本想法上面已经描述了。下面谈谈这样做怎么调用缓存。上代码:
@Aspect(pkg="com.smart.sample.service.impl")
public class CacheAspect extends DefaultCacheAspact {
}
这就是将缓存插件加载到你的应用中唯一做的事情,就是实现DefaultCacheAspect抽象类,这样做只是需要你配置一下需要缓存类的包名即可。配置这个Smart的AOP将会自动加载,并解析为一个切面。
这里配置之后下面谈谈怎么讲缓存配置到具体的类和方法中去,还是上代码:
@Bean
public class CustomerServiceCacheImpl implements CustomerService {
@Override
@Cacheable(value="customer_list_cache")
public List<Customer> getCustomerList() {
……
}
@Override
@Cacheable(value="customer_cache,customer_list_cache",intent=CacheIntent.DELETE)
public boolean deleteCustomer(long id) {
……
}
@Override
@Cacheable(value="customer_cache",key="#id+getCustomer")
public Customer getCustomer(long id) {
……
}
@Override
@Cacheable(value="customer_cache,customer_list_cache",intent=CacheIntent.UPDATE)
public boolean updateCustomer(long id, Map<String, Object> fieldMap) {
……
}
@Override
@Cacheable(value="customer_list_cache",intent=CacheIntent.UPDATE)
public boolean createCustomer(Map<String, Object> fieldMap) {
……
}
}
通过Cacheable注解来告诉缓存插件怎么缓存,以及缓存相关的配置信息。这样就用上了Smart的缓存插件。
在实现过程中遇到一个问题,关于Smart的事务详细实现原理可以通过《事务管理实现原理》来了解,通过下面代码可以看出:
public class InitHelper {
private static final Logger logger = Logger.getLogger(InitHelper.class);
public static void init() {
try {
Class<?>[] classList = {
DBHelper.class,
EntityHelper.class,
ActionHelper.class,
BeanHelper.class,
ServiceHelper.class,
IOCHelper.class,
AOPHelper.class,
};
for (Class<?> cls : classList) {
Class.forName(cls.getName());
}
} catch (Exception e) {
logger.error("加载 Helper 出错!", e);
}
}
}
Smart整个框架加载过程是最后加载AOP组件,而其中一个ServiceHelper则是Smart的事务组件,下面粘贴出Smart的事务组件的实现:
public class ServiceHelper {
private static final Logger logger = Logger.getLogger(ServiceHelper.class);
static {
if (logger.isDebugEnabled()) {
logger.debug("初始化 ServiceHelper");
}
try {
// 获取并遍历所有的 Service 类
List<Class<?>> serviceClassList = ClassHelper.getClassListBySuper(BaseService.class);
for (Class<?> serviceClass : serviceClassList) {
// 获取目标实例
Object targetInstance = BeanHelper.getBean(serviceClass);
// 创建代理实例
Object proxyInstance = TransactionProxy.getInstance().getProxy(serviceClass);
// 复制目标实例中的字段到代理实例中
ObjectUtil.copyFields(targetInstance, proxyInstance);
// 用代理实例覆盖目标实例(放入 IOC 容器中)
BeanHelper.getBeanMap().put(serviceClass, proxyInstance);
}
} catch (Exception e) {
logger.error("初始化 ServiceHelper 出错!", e);
}
}
}
可以看出Smart的事务是只要类继承了BaseService,那么这个类的方法将会有事务管理,在看看TransactionProxy
public class TransactionProxy implements MethodInterceptor {
private static final Logger logger = Logger.getLogger(TransactionProxy.class);
private static final TransactionProxy instance = new TransactionProxy();
private TransactionProxy() {
}
public static TransactionProxy getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
……
}
}
这个事务返回的也是一个Cglib的代理,那么就可以了解,实现了BaseService的类,在Smart的IOC容器中,这个类的实体对象是一个Cglib的代理对象。通过《使用“链式代理”实现 AOP》知道,Cglib的代理类是不能再被Cglib代理的,于是Smart的AOP是通过责任连的方式来实现。刚刚看到在InitHelper中是先加载Service然后再加载Aop的组件,那么如果一个类继承了BaseService,又想用AOP进行代理,是不能进行的。所以本人建议Smart的事务管理可以使用Smart的AOP方式来实现。而不需要自己独立去产生一个CGLIB代理类。
到此,关于Smart的缓存插件已经描述完毕,这里只是实现了基本的缓存功能,还没有加入淘汰的算法以及缓存的持久化,后面会继续在此基础上进行调整。如果有什么不明白或者存在哪些漏洞可以提出来。
粘贴一个实际运行图:
public class CustomerTest extends BaseTest {
private CustomerService customerService = BeanHelper.getBean(CustomerServiceCacheImpl.class);
private Customer customer = null;
@Test
@Order(1)
public void getProductListTest() {
//customer = customerService.getCustomer(3l);
}
@Test
@Order(2)
public void getProductListTest1() {
customer = customerService.getCustomer(3l);
Customer temp = customerService.getCustomer(3l);
System.out.println(temp.hashCode());
System.out.println(customer.hashCode());
}
}
控制台输出内容:
初始化 DBHelper
初始化 EntityHelper
初始化 ActionHelper
初始化 BeanHelper
初始化 ServiceHelper
初始化 IOCHelper
beforePropertiesSet
初始化 AOPHelper
SQL: select * from customer where id = 3
-637456962
-637456962
可以看到两次查询的对象的hashCode是一样的同时也只执行了一次查询操作,所以两次查询的对象是一样的,表明缓存生效。
下面粘贴出DefaultCacheAspact的实现:
public abstract class DefaultCacheAspact extends BaseAspect {
private static CacheManager cacheManager = new DefaultCacheManager();
/*
* (non-Javadoc)
*
* @see com.smart.framework.base.BaseAspect#before(java.lang.Class,
* java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public final void before(Class<?> cls, Method method, Object[] params) {
if (method.isAnnotationPresent(Cacheable.class)) {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
String[] cacheNames = cacheable.value().split(",");
CacheIntent intent = cacheable.intent();
if (intent == CacheIntent.DELETE || intent == CacheIntent.UPDATE) {
return;
}
for (String cacheName : cacheNames) {
if (cacheManager.getCache(cacheName) == null) {
cacheManager.createCache(cacheName);
} else {
Cache<Object, Object> cache = cacheManager
.getCache(cacheName);
try {
String cacheKey = generalKey(cls, method, params);
if (cache.get(cacheKey) != null) {
CacheResult.put(cache.get(cacheKey) );
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
return;
}
}
/*
* (non-Javadoc)
*
* @see com.smart.framework.base.BaseAspect#after(java.lang.Object,
* java.lang.Class, java.lang.reflect.Method, java.lang.Object[],
* java.lang.Object)
*/
@Override
public final void after(Class<?> cls, Method method, Object[] params,
Object methodResult) {
try {
if (method.isAnnotationPresent(Cacheable.class)) {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
String[] cacheNames = cacheable.value().split(",");
CacheIntent intent = cacheable.intent();
if (intent == CacheIntent.NORMAL) {
String cacheKey = generalKey(cls, method, params);
for (String cacheName : cacheNames) {
Cache<Object, Object> currentMethodCache = cacheManager
.getCache(cacheName);
if (currentMethodCache.get(cacheKey) == null) {
currentMethodCache.put(cacheKey, methodResult);
}
}
} else if (intent == CacheIntent.DELETE
|| intent == CacheIntent.UPDATE) {
String key = cacheable.key();
if (key.equals("default")) {
for (String cacheName : cacheNames) {
cacheManager.destroyCache(cacheName);
}
} else {
String cacheKey = generalKey(cls, method, params);
for (String cacheName : cacheNames) {
Cache<Object, Object> cache = cacheManager
.getCache(cacheName);
cache.remove(cacheKey);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private String generalKey(Class<?> cls, Method method, Object[] params)
throws IOException {
if (method.isAnnotationPresent(Cacheable.class)) {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
String key = cacheable.key();
Map<String, Object> parameterMap = CacheUtil
.getMethodParameterName(cls, method, params);
StringBuffer keyStr = new StringBuffer();
if (key.equals("default")) {
for (Entry<String, Object> entry : parameterMap.entrySet()) {
keyStr.append(entry.getKey()).append(
entry.getValue().toString());
}
keyStr.append(method.getName());
} else {
String[] keys = key.split("\\+");
for (String subKey : keys) {
if (subKey.startsWith("#")) {
subKey = subKey.substring(1);
keyStr.append(parameterMap.get(subKey).toString());
} else {
keyStr.append(subKey);
}
}
}
return keyStr.toString();
} else {
return null;
}
}
}