java cache详解,Java Cache详解及简单实现

java cache详解及简单实现

概要:

最近在做spring的项目,想做一个缓存,访问数据库,定期来做数据更新

要实现两个功能

可以通过http请求来立刻刷新缓存

缓存可以通过自己配置的时间间隔来定期刷新

通过controller来做

因为需要通过http来刷新缓存,所以第一个想法就是把缓存做成一个controller

controller的实现

controller最大的优势,就是可以通过spring的配置,注入很多依赖,比如对service的依赖,对数据库的依赖等。

大量的访问数据库跟服务层的代码,都可以进行复用

定义一个cache接口如下:

public interface cache {

/**

* refresh the cache. if succeed, return true, else return false;

*

* @return

*/

boolean refresh();

/**

* how much time it will refresh the cache.

*

* @return

*/

long interval();

}

但是这里碰到了问题,自己写的controller可以通过注入的方式轻而易举的与http服务跟service层,数据库层连接,但是如果cachecontroller实现cache接口,会发现很难调用interval函数来找到间隔的时间。

因为cachecontroller也是一个bean,需要通过spring找到这个bean来调用。无法找到bean,就不能调用interval,也就不能够顺势通过另外的线程来控制缓存刷新。为了获取这个bean可以将所有的cachecontroller都autowired到一个cachemanagercontroller之中

@controller

public class cachemanagercontroller {

@autowired

private cachecontroller cache;

private static scheduledexecutorservice executor = executors

.newscheduledthreadpool(1);

public cachemanagercontroller() {

executor.scheduleatfixedrate(() -> cache.refresh(), 0, cache.interval(),

timeunit.milliseconds);

}

}

曾经考虑这么做,但是发现一个问题,这样做,cachemanagercontroller在初始化的时候,也就是构造bean的时候,各种的cache还没有被注入cachecontroller,而如果不将方法放入构造函数,那么cachemanagercontroller是无法自动的调用调度服务的。需要手动调用才行。但是程序的入口不一定从哪一个controller进入,如果写拦截器,也是很繁琐,而且每次调用都会执行。

这个时候,就通过一个cacheservice来实现这个问题

public class cacheservice {

public static final long one_minute = 60 * 1000;

private static scheduledexecutorservice executor = executors

.newscheduledthreadpool(1);

public static void register(cache cache) {

executor.scheduleatfixedrate(() -> cache.refresh(), 0, cache.interval(),

timeunit.milliseconds);

}

}

@controller

public class cachecontroller implements cache {

// autowire 各种不同的service,或者是repo连接数据库

@autowired

private service service;

public cachecontroller() {

cacheservice.register(this);

}

// cache interface

@override

public long interval() {

return 1000;

}

@override

public boolean refresh() {

return true;

}

}

因为具体的cachecontroller是通过反射构造成bean由spring管理的,所以可以直接通过无参构造函数来注册一下,这样就没有问题了,当spring在加载cachecontroller的时候,就会直接调用cacheservice的注册方法,将缓存注册到cacheservice中定义的线程池当中,然后立刻执行刷新方法,同时还会根据时间间隔来自动的刷新。

至于获取指定的cache,更简单了,因为cache本身是一个controller,所以可以通过autowire自动注册到需要使用的其他controller之中。

当然了,目前这么写是没有什么问题,但是当refresh为立刻调用的时候,会无法拿到autowired注入的那些service。因为spring是统一全部实例化,然后再进行装载的,所以,如果refresh函数中调用了service,那么显然,程序肯定会报空指针异常的。这也是使用controller来做cache的问题。如果要获得全部的spring装载的实例,那么肯定就都要修改构造函数来将实例注入到统一的集合当中了,那样就跟前文提到的问题一样了,也就是获取bean。如果能够获取bean,那直接就能调用实例方法,也就没有这么多事情了。

总结

使用controller的特点如下:

代码复用,定义的repo层,service层代码都可以继续使用,不用重写

因为spring声明周期的问题,refresh操作立刻执行会抛异常,需要延时刷新

通过listener来做

listener有一个优势,就是可以通过一个写一个preloadlistener 实现servletcontextlistener,这样就能够利用tomcat加载web.xml的时候,将代码提前进行初始化了。

listener的实现

public class preloadlistener implements servletcontextlistener {

@override

public void contextinitialized(servletcontextevent servletcontextevent) {

cachefactory.init();

}

@override

public void contextdestroyed(servletcontextevent servletcontextevent) {

}

}

下面是web.xml的代码

// web.xml

com.sapphire.listener.preloadlistener

当然了,有优势肯定会存在劣势,因为使用listener的方式来提前加载,也会因为web的声明周期,产生问题。

tomcat在加载web.xml的时候,listener的初始化,会在spring容器启动之前,这样也就碰到一个问题。preloadlistener中可以调用的代码,肯定是无法autowire到任何的bean的。这也就是对比controller碰到的一个巨大的劣势了,需要自己重写那些service。

除此以外, 还需要单独写一个controller来刷新指定的缓存。

public class cachefactory {

private static concurrenthashmap caches = new concurrenthashmap<>();

private static scheduledexecutorservice executorservice = executors.newscheduledthreadpool(1);

private static void register(cache cache) {

caches.put(cache.category(), cache);

}

private static void registerall() {

register(new stockcache());

}

public static void init() {

registerall();

for (cache cache : caches.values()) {

executorservice.scheduleatfixedrate(new runnable() {

@override

public void run() {

cache.refresh();

}

}, 0, cache.interval(), timeunit.milliseconds);

}

}

public static cache getcache(string key) {

if (caches.contains(key)) {

return caches.get(key);

}

return null;

}

}

// cache接口除了需要提供interval和refresh以外,还需要提供一个category来区分不同的cache

public interface cache {

/**

* refresh the cache. if succeed, return true, else return false;

*

* @return

*/

boolean refresh();

/**

* how much time it will refresh the cache.

*

* @return

*/

long interval();

/**

* cache's category. each cache has distinct category.

*

* @return

*/

string category();

}

这样完成的cachefactory,可以在preloadlistener之中调用init方法来初始化所有的cache,来完成cache的启动。可以看出,所有的cachefactory之中的方法都是静态方法,可以直接由controller层随便调用。

之后,不同的cache就需要单独来写init方法,放到各自实现的refresh方法之中。跟数据库的链接等,都需要建立。不同的cache都需要重写各自的初始化方法,还需要写一个读取文件配置的东西读取数据库的一些配置信息。总之,感觉很麻烦。

总结

通过listener来实现,更加灵活,可以在容器启动之前就将需要的信息加载到内存之中,但是很多业务代码都需要重新来写,数据库的链接,解析property,灵活刷新的cachecontroller。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

希望与广大网友互动??

点此进行留言吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值