Java Cache详解及简单实现
概要:
最近在做spring的项目,想做一个缓存,访问数据库,定期来做数据更新
要实现两个功能
通过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的特点如下:
通过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 <listener> <listener-class>com.sapphire.listener.PreloadListener</listener-class> </listener>
当然了,有优势肯定会存在劣势,因为使用Listener的方式来提前加载,也会因为Web的声明周期,产生问题。
Tomcat在加载Web.xml的时候,Listener的初始化,会在Spring容器启动之前,这样也就碰到一个问题。PreloadListener中可以调用的代码,肯定是无法Autowire到任何的Bean的。这也就是对比Controller碰到的一个巨大的劣势了,需要自己重写那些Service。
除此以外, 还需要单独写一个Controller来刷新指定的缓存。
public class CacheFactory { private static ConcurrentHashMap<String, Cache> 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。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
本文向大家介绍python getopt详解及简单实例,包括了python getopt详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 python getopt详解 函数原型: 参数解释: args:args为需要解析的参数列表。一般使用sys.argv[1:],这样可以过滤掉第一个参数(ps:第一个参数是脚本的名称,它不应该作为参数进行解析) shortopts:简写参数列表 lon
本文向大家介绍linux 下实现sleep详解及简单实例,包括了linux 下实现sleep详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 linux 下实现sleep详解及简单实例 sleep: 普通版本 1、基本设计思路: 1>注册SIGALRM信号的处理函数; 2>调用alarm(nsecs)设定闹钟; 3>调⽤pause等待,内核切换到别的进程运行; 4>n
本文向大家介绍Android 中Seekbar详解及简单实例,包括了Android 中Seekbar详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 Android 中Seekbar详解及简单实例 做到音频播放和音乐播放时,大多数都要用到Seekbar。现在我先简单介绍下Seekbar的几个重要属性。 android:max 设置值的大小 . android:thumb=”@drawable
本文向大家介绍SQL GROUP BY 详解及简单实例,包括了SQL GROUP BY 详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 GROUP BY 语句用于结合 Aggregate 函数,根据一个或多个列对结果集进行分组。 SQL GROUP BY 语法 演示数据库 在本教程中,我们将使用众所周知的 Northwind 样本数据库。 下面是选自 "Orders" 表的数据: Or
本文向大家介绍Android 回调详解及简单实例,包括了Android 回调详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 Android 回调 前言: Android中的回调最经典的就是点击事件设置监听(一般通过switch(v.getId()))这里写个最基本的 view对外暴露了一个接口onClick 我们在用的时候实现具体方法写了处理时,当前的Actvity是没有对这个方法做
本文向大家介绍Android中SharedPreference详解及简单实例,包括了Android中SharedPreference详解及简单实例的使用技巧和注意事项,需要的朋友参考一下 Android中SharedPreference详解 SharedPreference是Android提供的一种轻量级的数据存储方式,主要用来存储一些简单的配置信息,例如,默认欢迎语,登录用户名和密码等。