Smart是一個輕量級的java web框架,裏面已經實現了IOC,AOP以及事務管理,同時也實現了MVC模塊。本插件是基於Smart的AOP來進行實現的。目標是:在不須要改變當前業務代碼的狀況下將緩存功能嵌入進去,本實現參考了Spring以及JCache,最終但願達到靈活,易用目的。 java
該插件是基於Smart的AOP來實現,關於Smart的AOP能夠經過《使用「鏈式代理」實現 AOP》 和《AOP實現原理》來了解。在BaseAspect基礎上再構造了一個抽象類,粘出代碼: web
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的緩存容器進行刪除。 app
爲了可以使得獲取須要緩存的內容,須要對Smart的AOP基礎類BaseAspect的after方法多加入一個參數,以下: 框架
public void after( Class<?> cls, Method method, Object[] params,Object methodResult) { }
在原有的基礎上加了一個methodResult參數,用於得到方法返回的值。同時爲了可以獲取緩存切面從緩存中獲取的緩存內容,在smart-framework中定義了一個ThreadLocal容器,代碼以下: ide
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接口內容便可得到緩存內容。 this
接下來進行談談具體實現:先粘貼出對ProxyChain的調整 spa
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。 .net
對於緩存模塊的實現,這裏就不粘貼出代碼了,基本想法上面已經描述了。下面談談這樣作怎麼調用緩存。上代碼:
@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; } } }