摘要:利用做業(yè)務(wù)緩存和配置項(xiàng)來自背景從以前的應(yīng)用到現(xiàn)在的,系統(tǒng)配置項(xiàng)都是必不可少的。思考緩存,通過,返回值。動態(tài)代理,通過方法,執(zhí)行返回值。操作動態(tài)代理執(zhí)行類主要用于執(zhí)行業(yè)務(wù)緩存具體執(zhí)行的對象不支持方法。還擴(kuò)展了業(yè)務(wù)緩存,使其代碼集中。
利用redis做業(yè)務(wù)緩存和配置項(xiàng)
來自:https://github.com/018/RedisC...
背景? 從以前的C/S應(yīng)用到現(xiàn)在的B/S,系統(tǒng)配置項(xiàng)都是必不可少的。一般都有一個(gè)SettingUtils類,提供read和write方法,然后就一大堆作為Key的常量。通過這樣來實(shí)現(xiàn):
String ip = SettingUtils.read(SettingConsts.IP);//獲取ip SettingUtils.write(SettingConsts.IP, "127.0.0.1");//設(shè)置ip
? 然而,現(xiàn)在并發(fā)要求越來越高,緩存是個(gè)標(biāo)配。無論是業(yè)務(wù)數(shù)據(jù)還是配置項(xiàng),都可以往緩存里扔。緩存,也離不開Key,一大堆作為Key的常量。治理這些Key是個(gè)大問題。
遇到動態(tài)代理? 動態(tài)代理,早些年就了解過,可一直沒真正用到項(xiàng)目里,直到一次研究了一下mybatis源代碼,發(fā)現(xiàn)其核心代碼就是動態(tài)代理。那什么是動態(tài)代理呢?我就不詳細(xì)解釋了,對它不了解的還是乖乖的 百度一下動態(tài)代理 。這里從網(wǎng)上投了一張圖,如下:
它大概就是我們可以動態(tài)的自定義的控制實(shí)現(xiàn)。給你Object proxy、Method method、Object[] args三個(gè)參數(shù),然后你自己決定怎么實(shí)現(xiàn)。給個(gè)簡單的例子:
/** * 接口 */ public interface Something { String get(String key); String set(String key, String value); } /** * 調(diào)用處理器 */ public class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + " doing ..."); System.out.println("proxy is " + proxy.getClass().getName()); System.out.println("method is " + method.getName()); System.out.println("args is " + Arrays.toString(args)); System.out.println(method.getName() + " done!"); return method.getName() + " invoked"; } } public class Test { public static void main(String[] args) { Something somethingProxy = (Something) java.lang.reflect.Proxy.newProxyInstance(Something.class.getClassLoader(), new Class>[]{Something.class}, new MyInvocationHandler()); System.out.println("somethingProxy.get("name"): " + somethingProxy.get("name")); System.out.println(); System.out.println("somethingProxy.set("name", "018"): " + somethingProxy.set("name", "018")); System.out.println(); } }
以上代碼的輸出結(jié)果:
get doing ... proxy is com.sun.proxy.$Proxy0 method is get args is [name] get done! somethingProxy.get("name"): get invoked set doing ... proxy is com.sun.proxy.$Proxy0 method is set args is [name, 018] set done! somethingProxy.set("name", "018"): set invoked
通過Proxy.newProxyInstance創(chuàng)建一個(gè)Something的代理對象somethingProxy。
通過somethingProxy實(shí)例調(diào)用其方法get/set時(shí),會執(zhí)行MyInvocationHandler.invoke方法。
思考? 緩存,通過Key,返回值。
? 動態(tài)代理,通過Method(方法),執(zhí)行返回值。
? 怎么把它們關(guān)聯(lián)起來呢?方法有方法名,那能不能把Method method的方法名對應(yīng)到Key?能?。?!
方案? 在最開始的例子獲取ip就應(yīng)該這樣寫:
public interface DataSourceSettings { String getIp(); void setIp(String ip); int getPort(); void setPort(int port); // 其他項(xiàng) ... } public class SettingsInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // TODO: // 1、去掉get/set,截圖后面的字符串作為Key // 2、redis客戶端通過Key獲取值返回 } }
配置項(xiàng)完美解決了。
? 但業(yè)務(wù)緩存呢?相對來說,配置項(xiàng)的Key是固定的,而業(yè)務(wù)緩存的Key則是不固定的。比如緩存商品信息,商品id為001和002等等,得緩存不同的Key。就得有一個(gè)動態(tài)Key的解決方案,即productCaches.put(商品id, 商品實(shí)體)這樣的實(shí)現(xiàn)方式。
? 參考spring-data-redis的BoundHashOperations,可以對其進(jìn)行擴(kuò)展實(shí)現(xiàn)這一功能。這樣我們就可以這樣定義一個(gè)商品緩存接口:
public interface HashSessionOperationsextends BoundHashOperations { } public interface ProductCaches extends HashSessionOperations { }
緩存數(shù)據(jù)和獲取數(shù)據(jù)則如下:
productCaches.put(product1.prod_id, product1);//緩存數(shù)據(jù) Product product = (Product)productCaches.get(prod_id);//獲取緩存數(shù)據(jù)
至此,業(yè)務(wù)緩存也完美解決。
? 當(dāng)然,我們對BoundListOperations、BoundSetOperations、BoundValueOperations、BoundZSetOperations進(jìn)行對應(yīng)的擴(kuò)展。這樣,這些不僅僅做業(yè)務(wù)緩存,也可以用它來作為redis的一個(gè)客戶端使用。
? 看到這里,只看到了接口,也即是結(jié)果,知道了怎么使用它應(yīng)用到項(xiàng)目中。但,怎么實(shí)現(xiàn)的呢?但是是動態(tài)代理。來,廢話不多說,兩個(gè)InvocationHandler碼上來:
/** * 簡單的InvocationHandler * 主要用于執(zhí)行配置項(xiàng) */ public class SimpleSessionOperationInvocationHandler implements InvocationHandler { private static final String METHOD_SET = "set"; private static final String METHOD_GET = "get"; private static final String METHOD_TOSTRING = "toString"; private DefaultSimpleSessionOperations defaultSimpleSessionOperations; public SimpleSessionOperationInvocationHandler(DefaultSimpleSessionOperations defaultSimpleSessionOperations) { this.defaultSimpleSessionOperations = defaultSimpleSessionOperations; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Class> cls = method.getDeclaringClass(); String group = cls.getSimpleName().replace("Settings", "").replace("Setting", ""); String methodName = method.getName(); String methodString = null; String item = null; Object value = null; if (METHOD_TOSTRING.equals(methodName)) { return cls.getName(); } else if (METHOD_SET.equals(methodName)) { // void set(String item, ? value) if (method.getParameterCount() != 2 || !method.getParameterTypes()[0].getSimpleName().equals(String.class.getSimpleName()) || !method.getParameterTypes()[1].getSimpleName().equals(Object.class.getSimpleName()) || args == null || args.length != 2) { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "方法聲明錯(cuò)誤,正確為 void set(String key, ? value)。"); } methodString = METHOD_SET; item = args[0].toString(); value = args[1]; } else if (METHOD_GET.equals(methodName)) { // ? get(String item) if (method.getParameterCount() != 1 || !method.getParameterTypes()[0].getSimpleName().equals(String.class.getSimpleName()) || args == null || args.length != 1) { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "方法聲明錯(cuò)誤,正確為 ? get(String item)。"); } methodString = METHOD_GET; item = args[0].toString(); } else if (methodName.startsWith(METHOD_SET)) { // void setXXX(? value) if (method.getParameterCount() != 1 || args == null || args.length != 1) { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "方法聲明錯(cuò)誤,正確為 void setXXX(? value)。"); } methodString = METHOD_SET; item = methodName.substring(METHOD_SET.length()); value = args[0]; } else if (methodName.startsWith(METHOD_GET)) { // Object getXXX() if (method.getParameterCount() != 0 || (args != null && args.length != 0)) { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "方法聲明錯(cuò)誤,正確為 Object getXXX()。"); } methodString = METHOD_GET; item = methodName.substring(METHOD_GET.length()); } else { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "不支持的方法,只能是void set(String item, ? value)、? get(String item)、void setXXX(? value)、? getXXX()。"); } switch (methodString) { case (METHOD_GET): Object val = this.defaultSimpleSessionOperations.get(group, item); return val; case (METHOD_SET): this.defaultSimpleSessionOperations.put(group, item, value); } return null; } }
/** * redis操作動態(tài)代理執(zhí)行類 * 主要用于執(zhí)行業(yè)務(wù)緩存 */ public class RedisSessionOperationInvocationHandler implements InvocationHandler { private static final String METHOD_TOSTRING = "toString"; BoundKeyOperations> sessionOperations; // 具體執(zhí)行的redis對象 public RedisSessionOperationInvocationHandler(BoundKeyOperations> sessionOperations) { this.sessionOperations = sessionOperations; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Class> cls = method.getDeclaringClass(); String methodName = method.getName(); Class> targetCls = sessionOperations.getClass(); Method methodTarget = targetCls.getDeclaredMethod(methodName, method.getParameterTypes()); if (methodTarget == null) { throw new NonsupportMethodException(cls.getPackage().getName(), cls.getSimpleName(), methodName, "不支持" + methodName + "方法。"); } if (METHOD_TOSTRING.equals(methodName)) { return cls.getName(); } Object result = methodTarget.invoke(sessionOperations, args); return result; } }
至于接口創(chuàng)建代理,就交給ClassScannerConfigurer吧。
/** * 類掃描配置類 */ public class ClassScannerConfigurer implements InitializingBean, ApplicationContextAware, BeanFactoryAware, BeanNameAware { /** * 待掃描的包 */ private String basePackage; private String beanName; private DefaultListableBeanFactory beanFactory; private ApplicationContext applicationContext; private SessionOperationsFactory sessionOperationsFactory; private ListclassInfos; public String getBasePackage() { return basePackage; } public void setBasePackage(String basePackage) { this.basePackage = basePackage; } public SessionOperationsFactory getSessionOperationsFactory() { return sessionOperationsFactory; } public void setSessionOperationsFactory(SessionOperationsFactory sessionOperationsFactory) { this.sessionOperationsFactory = sessionOperationsFactory; } @Override public void setBeanName(String name) { this.beanName = name; } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(this.basePackage, "Property "basePackage" is required"); // 掃描并創(chuàng)建接口的動態(tài)代理 ClassPathScanner scanner = new ClassPathScanner(); scanner.setResourceLoader(this.applicationContext); Set classes = scanner.doScans(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); if (Objects.isNull(classInfos)) { classInfos = new ArrayList<>(classes.size()); } for (Class> cls : classes) { Object proxyObject = ProxyManager.newProxyInstance(this.sessionOperationsFactory, cls); String beanName = cls.getSimpleName().substring(0, 1).toLowerCase() + cls.getSimpleName().substring(1); this.beanFactory.registerSingleton(beanName, proxyObject); ClassInfo classInfo = new ClassInfo(beanName, cls, proxyObject); classInfos.add(classInfo); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (DefaultListableBeanFactory) beanFactory; } }
怎么加載這些呢,交給spring吧。
優(yōu)點(diǎn)
代碼統(tǒng)一管理。一個(gè)包是系統(tǒng)配置項(xiàng)com.**.settings.*,一個(gè)包是業(yè)務(wù)緩存com.**.caches.*。
配置項(xiàng)隨時(shí)改隨時(shí)生效。有些調(diào)優(yōu)的參數(shù),有些在特殊時(shí)期需要即時(shí)調(diào)整的。通過用web管理界面,友好的解決。
擴(kuò)展? 上面提到用web管理界面來即時(shí)修改配置項(xiàng),即可以用一些特性,掃描所有配置項(xiàng)提供修改,分組、排序等等都是可以做到的。
? 還有,等等...
反思安全。原來配置項(xiàng)安安全全的在properties文件躺著,這樣強(qiáng)行把它拉到安全的問題上來,當(dāng)然祈禱redis安全!
默認(rèn)方法不行。接口上有默認(rèn)方法,那段代碼就相對于廢了。
性能。沒實(shí)際做過壓測,但鑒于mybatis,如果出現(xiàn)性能問題,那就是我寫的代碼需要優(yōu)化,不是方案問題。
總結(jié)通過mybatis的動態(tài)代理,實(shí)現(xiàn)基于redis的配置項(xiàng)即時(shí)修改生效。還擴(kuò)展了業(yè)務(wù)緩存,使其代碼集中。該方案中核心是動態(tài)代理,依賴于spring-data-redis。
此方案供學(xué)習(xí),也提供一種思路讓大家思考。如文中有bug,可以聯(lián)系我。如有更好的方案,也聯(lián)系我。如覺得不錯(cuò)想打賞,非常感謝。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68150.html
摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數(shù)據(jù)可以用作消息隊(duì)列排行榜計(jì)數(shù)器還支持通知過期等等。比如利用布隆過濾器,內(nèi)部維護(hù)一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...
摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數(shù)據(jù)可以用作消息隊(duì)列排行榜計(jì)數(shù)器還支持通知過期等等。比如利用布隆過濾器,內(nèi)部維護(hù)一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...
閱讀 1394·2021-11-24 09:38
閱讀 2098·2021-09-22 15:17
閱讀 2402·2021-09-04 16:41
閱讀 3496·2019-08-30 15:56
閱讀 3527·2019-08-29 17:19
閱讀 1983·2019-08-28 18:09
閱讀 1263·2019-08-26 13:35
閱讀 1722·2019-08-23 17:52