概要:
最近在做spring的項目,想做一個緩存,訪問數(shù)據(jù)庫,定期來做數(shù)據(jù)更新
要實現(xiàn)兩個功能
- 可以通過http請求來立刻刷新緩存
- 緩存可以通過自己配置的時間間隔來定期刷新
通過Controller來做
因為需要通過http來刷新緩存,所以第一個想法就是把緩存做成一個Controller
Controller的實現(xiàn)
Controller最大的優(yōu)勢,就是可以通過Spring的配置,注入很多依賴,比如對Service的依賴,對數(shù)據(jù)庫的依賴等。
大量的訪問數(shù)據(jù)庫跟服務(wù)層的代碼,都可以進行復(fù)用
定義一個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服務(wù)跟Service層,數(shù)據(jù)庫層連接,但是如果CacheController實現(xiàn)Cache接口,會發(fā)現(xiàn)很難調(diào)用interval函數(shù)來找到間隔的時間。
因為CacheController也是一個Bean,需要通過Spring找到這個bean來調(diào)用。無法找到Bean,就不能調(diào)用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); } } |
曾經(jīng)考慮這么做,但是發(fā)現(xiàn)一個問題,這樣做,CacheManagerController在初始化的時候,也就是構(gòu)造Bean的時候,各種的Cache還沒有被注入CacheController,而如果不將方法放入構(gòu)造函數(shù),那么CacheManagerController是無法自動的調(diào)用調(diào)度服務(wù)的。需要手動調(diào)用才行。但是程序的入口不一定從哪一個Controller進入,如果寫攔截器,也是很繁瑣,而且每次調(diào)用都會執(zhí)行。
這個時候,就通過一個CacheService來實現(xiàn)這個問題
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連接數(shù)據(jù)庫 @Autowired private Service service; public CacheController() { CacheService.register( this ); } // cache interface @Override public long interval() { return 1000 ; } @Override public boolean refresh() { return true ; } } |
因為具體的CacheController是通過反射構(gòu)造成Bean由Spring管理的,所以可以直接通過無參構(gòu)造函數(shù)來注冊一下,這樣就沒有問題了,當(dāng)Spring在加載CacheController的時候,就會直接調(diào)用CacheService的注冊方法,將緩存注冊到CacheService中定義的線程池當(dāng)中,然后立刻執(zhí)行刷新方法,同時還會根據(jù)時間間隔來自動的刷新。
至于獲取指定的Cache,更簡單了,因為Cache本身是一個Controller,所以可以通過Autowire自動注冊到需要使用的其他Controller之中。
當(dāng)然了,目前這么寫是沒有什么問題,但是當(dāng)refresh為立刻調(diào)用的時候,會無法拿到Autowired注入的那些Service。因為Spring是統(tǒng)一全部實例化,然后再進行裝載的,所以,如果refresh函數(shù)中調(diào)用了service,那么顯然,程序肯定會報空指針異常的。這也是使用Controller來做Cache的問題。如果要獲得全部的Spring裝載的實例,那么肯定就都要修改構(gòu)造函數(shù)來將實例注入到統(tǒng)一的集合當(dāng)中了,那樣就跟前文提到的問題一樣了,也就是獲取Bean。如果能夠獲取Bean,那直接就能調(diào)用實例方法,也就沒有這么多事情了。
總結(jié)
使用Controller的特點如下:
- 代碼復(fù)用,定義的repo層,service層代碼都可以繼續(xù)使用,不用重寫
- 因為Spring聲明周期的問題,refresh操作立刻執(zhí)行會拋異常,需要延時刷新
通過Listener來做
Listener有一個優(yōu)勢,就是可以通過一個寫一個PreloadListener 實現(xiàn)ServletContextListener,這樣就能夠利用Tomcat加載web.xml的時候,將代碼提前進行初始化了。
Listener的實現(xiàn)
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> |
當(dāng)然了,有優(yōu)勢肯定會存在劣勢,因為使用Listener的方式來提前加載,也會因為Web的聲明周期,產(chǎn)生問題。
Tomcat在加載Web.xml的時候,Listener的初始化,會在Spring容器啟動之前,這樣也就碰到一個問題。PreloadListener中可以調(diào)用的代碼,肯定是無法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來區(qū)分不同的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之中調(diào)用init方法來初始化所有的Cache,來完成Cache的啟動??梢钥闯觯械腃acheFactory之中的方法都是靜態(tài)方法,可以直接由Controller層隨便調(diào)用。
之后,不同的Cache就需要單獨來寫init方法,放到各自實現(xiàn)的refresh方法之中。跟數(shù)據(jù)庫的鏈接等,都需要建立。不同的Cache都需要重寫各自的初始化方法,還需要寫一個讀取文件配置的東西讀取數(shù)據(jù)庫的一些配置信息??傊?,感覺很麻煩。
總結(jié)
通過Listener來實現(xiàn),更加靈活,可以在容器啟動之前就將需要的信息加載到內(nèi)存之中,但是很多業(yè)務(wù)代碼都需要重新來寫,數(shù)據(jù)庫的鏈接,解析Property,靈活刷新的CacheController。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
原文鏈接:http://blog.csdn.net/ethanwhite/article/details/51278133