摘要:利用做業務緩存和配置項來自背景從以前的應用到現在的,系統配置項都是必不可少的。思考緩存,通過,返回值。動態代理,通過方法,執行返回值。操作動態代理執行類主要用于執行業務緩存具體執行的對象不支持方法。還擴展了業務緩存,使其代碼集中。
利用redis做業務緩存和配置項
來自:https://github.com/018/RedisC...
背景? 從以前的C/S應用到現在的B/S,系統配置項都是必不可少的。一般都有一個SettingUtils類,提供read和write方法,然后就一大堆作為Key的常量。通過這樣來實現:
String ip = SettingUtils.read(SettingConsts.IP);//獲取ip SettingUtils.write(SettingConsts.IP, "127.0.0.1");//設置ip
? 然而,現在并發要求越來越高,緩存是個標配。無論是業務數據還是配置項,都可以往緩存里扔。緩存,也離不開Key,一大堆作為Key的常量。治理這些Key是個大問題。
遇到動態代理? 動態代理,早些年就了解過,可一直沒真正用到項目里,直到一次研究了一下mybatis源代碼,發現其核心代碼就是動態代理。那什么是動態代理呢?我就不詳細解釋了,對它不了解的還是乖乖的 百度一下動態代理 。這里從網上投了一張圖,如下:
它大概就是我們可以動態的自定義的控制實現。給你Object proxy、Method method、Object[] args三個參數,然后你自己決定怎么實現。給個簡單的例子:
/** * 接口 */ public interface Something { String get(String key); String set(String key, String value); } /** * 調用處理器 */ 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(); } }
以上代碼的輸出結果:
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創建一個Something的代理對象somethingProxy。
通過somethingProxy實例調用其方法get/set時,會執行MyInvocationHandler.invoke方法。
思考? 緩存,通過Key,返回值。
? 動態代理,通過Method(方法),執行返回值。
? 怎么把它們關聯起來呢?方法有方法名,那能不能把Method method的方法名對應到Key?能!!!
方案? 在最開始的例子獲取ip就應該這樣寫:
public interface DataSourceSettings { String getIp(); void setIp(String ip); int getPort(); void setPort(int port); // 其他項 ... } 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獲取值返回 } }
配置項完美解決了。
? 但業務緩存呢?相對來說,配置項的Key是固定的,而業務緩存的Key則是不固定的。比如緩存商品信息,商品id為001和002等等,得緩存不同的Key。就得有一個動態Key的解決方案,即productCaches.put(商品id, 商品實體)這樣的實現方式。
? 參考spring-data-redis的BoundHashOperations,可以對其進行擴展實現這一功能。這樣我們就可以這樣定義一個商品緩存接口:
public interface HashSessionOperationsextends BoundHashOperations { } public interface ProductCaches extends HashSessionOperations { }
緩存數據和獲取數據則如下:
productCaches.put(product1.prod_id, product1);//緩存數據 Product product = (Product)productCaches.get(prod_id);//獲取緩存數據
至此,業務緩存也完美解決。
? 當然,我們對BoundListOperations、BoundSetOperations、BoundValueOperations、BoundZSetOperations進行對應的擴展。這樣,這些不僅僅做業務緩存,也可以用它來作為redis的一個客戶端使用。
? 看到這里,只看到了接口,也即是結果,知道了怎么使用它應用到項目中。但,怎么實現的呢?但是是動態代理。來,廢話不多說,兩個InvocationHandler碼上來:
/** * 簡單的InvocationHandler * 主要用于執行配置項 */ 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, "方法聲明錯誤,正確為 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, "方法聲明錯誤,正確為 ? 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, "方法聲明錯誤,正確為 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, "方法聲明錯誤,正確為 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操作動態代理執行類 * 主要用于執行業務緩存 */ public class RedisSessionOperationInvocationHandler implements InvocationHandler { private static final String METHOD_TOSTRING = "toString"; BoundKeyOperations> sessionOperations; // 具體執行的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; } }
至于接口創建代理,就交給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"); // 掃描并創建接口的動態代理 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吧。
優點
代碼統一管理。一個包是系統配置項com.**.settings.*,一個包是業務緩存com.**.caches.*。
配置項隨時改隨時生效。有些調優的參數,有些在特殊時期需要即時調整的。通過用web管理界面,友好的解決。
擴展? 上面提到用web管理界面來即時修改配置項,即可以用一些特性,掃描所有配置項提供修改,分組、排序等等都是可以做到的。
? 還有,等等...
反思安全。原來配置項安安全全的在properties文件躺著,這樣強行把它拉到安全的問題上來,當然祈禱redis安全!
默認方法不行。接口上有默認方法,那段代碼就相對于廢了。
性能。沒實際做過壓測,但鑒于mybatis,如果出現性能問題,那就是我寫的代碼需要優化,不是方案問題。
總結通過mybatis的動態代理,實現基于redis的配置項即時修改生效。還擴展了業務緩存,使其代碼集中。該方案中核心是動態代理,依賴于spring-data-redis。
此方案供學習,也提供一種思路讓大家思考。如文中有bug,可以聯系我。如有更好的方案,也聯系我。如覺得不錯想打賞,非常感謝。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68150.html
摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數據可以用作消息隊列排行榜計數器還支持通知過期等等。比如利用布隆過濾器,內部維護一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...
摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數據可以用作消息隊列排行榜計數器還支持通知過期等等。比如利用布隆過濾器,內部維護一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...
閱讀 1388·2021-11-24 09:38
閱讀 2090·2021-09-22 15:17
閱讀 2385·2021-09-04 16:41
閱讀 3477·2019-08-30 15:56
閱讀 3516·2019-08-29 17:19
閱讀 1975·2019-08-28 18:09
閱讀 1253·2019-08-26 13:35
閱讀 1715·2019-08-23 17:52