gpt4 book ai didi

Java Cache详解及简单实现

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Java Cache详解及简单实现由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

 Java Cache详解及简单实现 。

概要:

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

要实现两个功能 。

  1. 可以通过http请求来立刻刷新缓存
  2. 缓存可以通过自己配置的时间间隔来定期刷新

通过Controller来做 。

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

Controller的实现 。

Controller最大的优势,就是可以通过Spring的配置,注入很多依赖,比如对Service的依赖,对数据库的依赖等.

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

定义一个Cache接口如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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之中 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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来实现这个问题 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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的实现 。

?
1
2
3
4
5
6
7
8
9
10
11
public class PreloadListener implements ServletContextListener {
   @Override
   public void contextInitialized(ServletContextEvent servletContextEvent) {
    CacheFactory.init();
   }
 
   @Override
   public void contextDestroyed(ServletContextEvent servletContextEvent) {
 
   }
}

下面是web.xml的代码 。

?
1
2
3
4
// 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来刷新指定的缓存.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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://blog.csdn.net/ethanwhite/article/details/51278133 。

最后此篇关于Java Cache详解及简单实现的文章就讲到这里了,如果你想了解更多关于Java Cache详解及简单实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com