成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

利用redis做業(yè)務(wù)緩存和配置項(xiàng)

mcterry / 2242人閱讀

摘要:利用做業(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 HashSessionOperations extends 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 List classInfos;

    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

相關(guān)文章

  • Redis閑談(1):構(gòu)建知識圖譜

    摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數(shù)據(jù)可以用作消息隊(duì)列排行榜計(jì)數(shù)器還支持通知過期等等。比如利用布隆過濾器,內(nèi)部維護(hù)一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...

    starsfun 評論0 收藏0
  • Redis閑談(1):構(gòu)建知識圖譜

    摘要:豐富的特性具有豐富的特性,比如可以用作分布式鎖可以持久化數(shù)據(jù)可以用作消息隊(duì)列排行榜計(jì)數(shù)器還支持通知過期等等。比如利用布隆過濾器,內(nèi)部維護(hù)一系列合法有效的,迅速判斷出請求所攜帶的是否合法有效。 showImg(https://segmentfault.com/img/remote/1460000019070847?w=750&h=300); 場景:Redis面試 showImg(http...

    ky0ncheng 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<