本文是《輕量級 Java Web 框架架構設計》的系列博文。 java
前幾天寫過一篇文章,關於如何實現 Cache Plugin,錯過的朋友能夠看這篇:《可否讓 Cache 變得更加優雅?》。 數據庫
今天,我想和你們分享一下這個 Cache Plugin 的實現過程。再次感謝 Bieber 的解決方案! 緩存
我以爲 Cache 應該是一種帶有 AOP 性質的 Plugin,相似地,還會有 Log Plugin、Permission Plugin 等。不妨先對這些 AOP Plugin 作一個通用的解決方案,或許之後實現其餘同類型的 Plugin 會更加輕鬆。 架構
咱們知道 AOP 內部其實使用了 Proxy,在 Smart 中已經對 AOP 作了一個改進,如今是具備 Proxy Chain 風格的 AOP 了,也就是說,AOP 就像 Chain 同樣能夠進行鏈式調用。對於這個思路還不太清楚的朋友,能夠參考這篇:《使用「鏈式代理」實現 AOP》。再次感謝 黎明偉 提供的解決方案! 框架
因而可知,首先須要對 Proxy 作一個擴展,好比增長一個 PluginAspect(與之前的 BaseAspect 同等級別),而後在 Cache Plugin 中去擴展這個 PluginAspect。此外,還須要對 AOP Aspect 的加載部位作一個手術,給它開一個口子,讓 PluginAspect 能夠自由地插入到 Smart AOP 的總體框架中去。 ide
好了,我想大體的意思已經表達清楚了,下面即是具體的實現過程。 優化
第一步:在 Smart Framework 中定義一個 PluginAspect。 spa
public abstract class PluginAspect implements Proxy { public abstract List<Class<?>> getTargetClassList(); }
可見這個 PluginAspect 這個抽象類實現了 Proxy 接口(它和 BaseAspect 同樣),並提供一個抽象方法 getTargetClassList,這個方法是讓該抽象類的子類(讓 Cache Plugin 來實現)來提供有關須要攔截的目標類。由於實現了 Proxy 接口,因此子類還要實現 doProxy 方法(還記得它長什麼樣子嗎?)。 .net
第二步:改造 AOPHelper,讓它加載 PluginAspect。 插件
如今輪到 AOPHelper 了,有必要對 Aspect 加載的代碼進行一個優化,如今的 Aspect 分爲三類:
須要對初始化 Aspect 的地方進行修改,代碼片斷以下:
public class AOPHelper { ... private Map<Class<?>, List<Class<?>>> createAspectMap() throws Exception { // 定義 Aspect Map Map<Class<?>, List<Class<?>>> aspectMap = new LinkedHashMap<Class<?>, List<Class<?>>>(); // 添加插件切面 addPluginAspect(aspectMap); // 添加用戶切面 addUserAspect(aspectMap); // 添加事務切面 addTransactionAspect(aspectMap); // 返回 Aspect Map return aspectMap; } private void addPluginAspect(Map<Class<?>, List<Class<?>>> aspectMap) throws Exception { // 獲取插件包名下父類爲 PluginAspect 的全部類(插件切面類) List<Class<?>> pluginAspectClassList = ClassUtil.getClassListBySuper("com.smart.plugin", PluginAspect.class); if (CollectionUtil.isNotEmpty(pluginAspectClassList)) { // 遍歷全部插件切面類 for (Class<?> pluginAspectClass : pluginAspectClassList) { // 建立插件切面類實例 PluginAspect pluginAspect = (PluginAspect) pluginAspectClass.newInstance(); // 將插件切面類及其所對應的目標類列表放入 Aspect Map 中 aspectMap.put(pluginAspectClass, pluginAspect.getTargetClassList()); } } } private void addUserAspect(Map<Class<?>, List<Class<?>>> aspectMap) throws Exception { // 獲取切面類 List<Class<?>> aspectClassList = ClassHelper.getInstance().getClassListBySuper(BaseAspect.class); // 排序切面類 sortAspectClassList(aspectClassList); // 遍歷切面類 for (Class<?> aspectClass : aspectClassList) { // 判斷 @Aspect 註解是否存在 if (aspectClass.isAnnotationPresent(Aspect.class)) { // 獲取 @Aspect 註解 Aspect aspect = aspectClass.getAnnotation(Aspect.class); // 建立目標類列表 List<Class<?>> targetClassList = createTargetClassList(aspect); // 初始化 Aspect Map aspectMap.put(aspectClass, targetClassList); } } } private void addTransactionAspect(Map<Class<?>, List<Class<?>>> aspectMap) { // 使用 TransactionAspect 橫切全部 Service 類 List<Class<?>> serviceClassList = ClassHelper.getInstance().getClassListBySuper(BaseService.class); aspectMap.put(TransactionAspect.class, serviceClassList); } ... }
注意以上的 createAspectMap 方法,在其中依次建立:插件切面、用戶切面、事務切面,這是爲了讓事務控制在 Proxy Chain 的末端,從而才能儘可能靠近目標方法,因此將其加載順序放在了最後。相關邏輯請參見代碼中的註釋,您若是有疑問,能夠給我留言。
如今框架性質的代碼終於完成了,剩下的就是插件的相關代碼了。首先要作的就是定義一個 CacheAspect,讓它去擴展 PluginAspect。
第三步:在 Cache Plugin 中定義一個 CacheAspect。
代碼量不算太多,直接貼出來吧:
public class CacheAspect extends PluginAspect { @Override public List<Class<?>> getTargetClassList() { // 設置目標類列表(獲取帶有 @Cachable 註解的目標類) return ClassHelper.getInstance().getClassListByAnnotation(Cachable.class); } @Override @SuppressWarnings("unchecked") public Object doProxy(ProxyChain proxyChain) throws Exception { // 定義方法返回值 Object result = null; // 獲取目標方法 Class<?> cls = proxyChain.getTargetClass(); Method method = proxyChain.getTargetMethod(); Object[] params = proxyChain.getMethodParams(); // 判斷不一樣類型的 Cache 註解 if (method.isAnnotationPresent(CachePut.class)) { // 若爲 @CachePut 註解,則首先從 Cache 中獲取 // 若 Cache 中不存在,則從 DB 中獲取,最後放入 Cache 中 Cache cache = getCache(cls, method); if (cache != null) { String cacheKey = getCacheKey(method, params); result = cache.get(cacheKey); // 從 Cache 中獲取 if (result == null) { result = proxyChain.doProxyChain(); // 從 DB 中獲取 if (result != null) { cache.put(cacheKey, result); // 放入 Cache 中 } } } } else if (method.isAnnotationPresent(CacheClear.class)) { // 若爲 @CacheRemove 註解,則首先進行數據庫操做,而後刷新緩存 result = proxyChain.doProxyChain(); clearCache(cls, method); } else { // 若不帶有任何的 Cache 註解,則直接進行數據庫操做 result = proxyChain.doProxyChain(); } // 返回結果 return result; } private <K, V> Cache<K, V> getCache(Class<?> cls, Method method) { // 從 @CachePut 註解中獲取 Cache Name,並經過 CacheManager 獲取所對應的 Cache CachePut cachePut = method.getAnnotation(CachePut.class); String cacheName = cachePut.value(); return CacheFactory.createCache(cls, cacheName); } private String getCacheKey(Method method, Object[] params) { // Cache Key = 方法名-參數 return method.getName() + "-" + Arrays.toString(params); } private void clearCache(Class<?> cls, Method method) { // 從 @CacheClear 註解中獲取相應的 Cache Name(一個或多個),並經過 CacheManager 銷燬所對應的 Cache CacheClear cacheClear = method.getAnnotation(CacheClear.class); String[] cacheNames = cacheClear.value(); if (ArrayUtil.isNotEmpty(cacheNames)) { CacheManager cacheManager = CacheFactory.getCacheManager(cls); for (String cacheName : cacheNames) { cacheManager.destroyCache(cacheName); } } } }
最核心的 Cache 邏輯放在了 doProxy 方法中,這個邏輯是 BaseAspect 沒法實現的,因此要單獨作一個 PluginAspect 出來,並讓它實現 Proxy 接口,這樣它的子類(也就是 CacheAspect)就有能力徹底控制 Proxy 的操做行爲了。相信代碼中的註釋,足夠讓您明白這是在作什麼,固然您的疑問對於我而言十分期待。
還要作什麼呢?不用作了。
難道不用在應用程序裏添加一點東西嗎?固然要,不然又怎麼加載 Cache Plugin 呢!
第四步:添加 Cache Plugin 依賴。
在 Maven 的 pom.xml 中添加 Cache Plugin 的依賴關係,以下:
... <dependencies> ... <dependency> <groupId>com.smart</groupId> <artifactId>smart-plugin-cache</artifactId> <version>1.0</version> </dependency> ... </dependencies> ...
至此,Cache Plugin 與 Smart Framework 無縫集成,在應用程序裏無需作任何事情,只需配置 Maven 依賴便可使用 Cache Plugin。
固然,以上基本實現了 Cache 註解的功能,但未必在實際場景中都會使用這種註解方式,畢竟控制粒度是方法級的(粒度較粗)。若是要控制細粒度級別的 Cache,建議直接使用 Smart Cache API,也就是說可經過這種方法來使用,請參見《Smart Plugin —— 從一個簡單的 Cache 開始 》。
您是否對以上實現持懷疑或否認態度呢?很是期待您的建議!相互討論,這樣才能共同進步。