java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言, 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台 (即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se)) 的总称
这篇文章主要介绍了 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
- <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
来源: http://www.phperz.com/article/18/0217/358618.html