Spring4 整合EhCache實現頁面緩存 零配置

前言

本文通過spring4 以java配置類方式 整合EhCache來實現頁面整體緩存及頁面局部緩存。同時提供源碼。因爲使用了零配置,所以要求tomcat7以上的版本。
原理是添加攔截器,在請求從用戶瀏覽器到controller之間攔截直接返回數據,減輕服務器的壓力,也加快了訪問。

頁面緩存介紹

緩存中的元素是被壓縮過的,如果客戶瀏覽器支持壓縮的話,filter會直接返回壓縮過的流,這樣節省了帶寬,把解壓的工作交給了客戶瀏覽器,如果客戶的瀏覽器不支持gzip ,那麼filter 會把緩存的元素拿出來解壓後再返回給客戶瀏覽器。
ehcache-web這個包中給我們提供了一些filter來處理頁面緩存。如下圖:
image
在這裏介紹SimplePageCachingFilter和SimplePageFragmentCachingFilter。分別對應頁面整體緩存和頁面局部緩存,這兩個類都繼承CachingFilter,並且各自實現了CachingFilter中calculateKey()方法。該方法就是用來計算保存在緩存中的key。這兩個類計算key的方式基本都是獲取請求時的URI及後面的查詢字符串作爲key,不過SimplePageCachingFilter多了一個請求方法,所以不依賴於主機名和端口號。以下是SimplePageCachingFilter的calculateKey()方法

protected String calculateKey(HttpServletRequest httpRequest) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(httpRequest.getMethod()).append(httpRequest.getRequestURI()).append(httpRequest.getQueryString());
        String key = stringBuffer.toString();
        return key;
    }

SimplePageFragmentCachingFilter的calculateKey()方法如下:

protected String calculateKey(HttpServletRequest httpRequest) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(httpRequest.getRequestURI()).append(httpRequest.getQueryString());
        String key = stringBuffer.toString();
        return key;
    }

有必要的話,可以自己實現calculateKey()方法來計算key。比如ajax訪問的時候有些會在後面的參數添加時間戳,這樣會導致計算的key每次都不一樣,所以緩存也就沒有意義了。不過本文直接使用SimplePageCachingFilter和SimplePageFragmentCachingFilter。

maven 配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>ehcache-web</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>  
        <!-- spring版本號 -->  
        <spring.version>4.3.5.RELEASE</spring.version>  
        <junit.version>4.12</junit.version>
  </properties> 

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
         <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-tx</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-jdbc</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
         <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-aop</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
         <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context-support</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
         <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-test</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
             <scope>provided</scope>
        </dependency>

        <!-- tomcat jstl -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

   <!-- ehcache 相關依賴  -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.7.5</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-web</artifactId>
            <version>2.0.4</version>
        </dependency>

         <!-- 日誌文件管理包 -->  
         <!--ehcache依賴slf4j-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.18</version>
            </dependency>
            <!--ehcache依賴slf4j-->

            <!--slf4j需要log4j-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.18</version>
            </dependency>
            <!--slf4j需要log4j-->

            <!--log4j-->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.5</version>
            </dependency>
            <!--log4j-->


    </dependencies>

</project>

ehcache.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="false" monitoring="autodetect"
         dynamicConfig="true">

      <!--   
        diskStore :指定數據存儲位置,可指定磁盤中的文件夾位置   <diskStore path="E:/cachetmpdir"/> 
        defaultCache : 默認的管理策略  

        以下屬性是必須的:  
            name: Cache的名稱,必須是唯一的(ehcache會把這個cache放到HashMap裏)。maxElementsInMemory:在內存中緩存的element的最大數目。   
            maxElementsOnDisk:在磁盤上緩存的element的最大數目,默認值爲0,表示不限制。   
            eternal:設定緩存的elements是否永遠不過期。如果爲true,則緩存的數據始終有效,如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷。   
            overflowToDisk: 如果內存中數據超過內存限制,是否要緩存到磁盤上。   

        以下屬性是可選的:  
            timeToIdleSeconds: 對象空閒時間,指對象在多長時間沒有被訪問就會失效。只對eternal爲false的有效。默認值0,表示一直可以訪問。  
            timeToLiveSeconds: 對象存活時間,指對象從創建到失效所需要的時間。只對eternal爲false的有效。默認值0,表示一直可以訪問。  
            diskPersistent: 是否在磁盤上持久化。指重啓jvm後,數據是否有效。默認爲false。   
            diskExpiryThreadIntervalSeconds: 對象檢測線程運行時間間隔。標識對象狀態的線程多長時間運行一次。   
            diskSpoolBufferSizeMB: DiskStore使用的磁盤大小,默認值30MB。每個cache使用各自的DiskStore。   
            memoryStoreEvictionPolicy: 如果內存中數據超過內存限制,向磁盤緩存時的策略。默認值LRU,可選FIFO、LFU。   

            緩存的3 種清空策略 :  
            FIFO ,first in first out (先進先出).  
            LFU , Less Frequently Used (最少使用).意思是一直以來最少被使用的。緩存的元素有一個hit 屬性,hit 值最小的將會被清出緩存。  
            LRU ,Least Recently Used(最近最少使用). (ehcache 默認值).緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。  
-->  

    <diskStore path="java.io.tmpdir"/>

    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="true"  
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true"  
        maxElementsOnDisk="10000000" 
        diskPersistent="false"  
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU" />

   <cache name="baseCache" 
        maxElementsInMemory="10000"  
        maxElementsOnDisk="1000" 
        eternal="false" 
        overflowToDisk="true"  
        diskSpoolBufferSizeMB="20" 
        timeToIdleSeconds="300" 
        timeToLiveSeconds="600"  
        memoryStoreEvictionPolicy="LFU" />  

     <!-- 頁面全部緩存 -->
    <cache name="SimplePageCachingFilter" 
        maxElementsInMemory="10000"  
        maxElementsOnDisk="1000" 
        eternal="false" 
        overflowToDisk="true"  
        diskSpoolBufferSizeMB="20" 
        timeToIdleSeconds="2" 
        timeToLiveSeconds="4"  
        memoryStoreEvictionPolicy="LFU" />  

     <!-- 頁面局部緩存 -->
   <cache name="SimplePageFragmentCachingFilter"
           maxElementsInMemory="10000"    
           maxElementsOnDisk="1000"      
           eternal="false"
           overflowToDisk="false"
           timeToIdleSeconds="6"
           timeToLiveSeconds="12"
           memoryStoreEvictionPolicy="LFU">
    </cache>

</ehcache>

spring 搭建

1.用於替代web.xml的WebProjectConfigInitializer

package com.test.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


public class WebProjectConfigInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {


    /** 
     *  加載驅動應用後端的中間層和數據層組件
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }
    /** 指定配置類
     *  加載包含web組件的bean,如控制機器、視圖解析器以及映射處理器
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    //將DispatcherServlet 映射到“/”
    @Override
    protected String[] getServletMappings() {
         return new String[] { "/" }; 
    }


}

2.RootConfig代碼:

package com.test.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"com.test"},excludeFilters={@Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class)})
public class RootConfig {

}

3.WebConfig代碼

package com.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan("com.test.controller")
public class WebConfig extends WebMvcConfigurerAdapter {

    //配置jsp視圖解析器
    @Bean 
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/jsp/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        resolver.setViewClass(JstlView.class);
        return resolver;
    }

}

4.controller代碼

package com.test.controller;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

    private final static Logger log = Logger.getLogger(UserController.class);

    @RequestMapping("/index.html")
    public String index(Model model){
        log.info("進入方法");
        model.addAttribute("date",System.currentTimeMillis());
        return "index";
    }
}

配置整體頁面緩存

在應用中添加filter:

package com.test.config;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;


public class MyServletInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        //配置頁面整體緩存
        FilterRegistration.Dynamic pageCachingFilter = servletContext.addFilter("pageCachingFilter",net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter.class);
        pageCachingFilter.addMappingForUrlPatterns(null, true, "/*");



        FilterRegistration.Dynamic characterEncoding=servletContext.addFilter("characterEncoding", CharacterEncodingFilter.class);  
        characterEncoding.setInitParameter("forceEncoding", "true");  
        characterEncoding.setInitParameter("encoding", "UTF-8");  
        characterEncoding.addMappingForUrlPatterns(null, true, "/*");  
    }

}

可以看到並沒有指定ehcache.xml中的緩存,因爲在ehcache.xml中的名字是SimplePageCachingFilter,此時在filter中是可以不指定cacheName的,ehcache默認會使用SimplePageCachingFilter。

編寫index.jsp頁面:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">

    <title>My JSP 'index.jsp' starting page</title>

    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

  </head>

  <body>
  <h1>緩存測試</h1>
  <h2>時間:${date }</h2>

<%--  <jsp:include page="/jsp/include/index-include.jsp" /> --%>
  </body>
</html>

運行結果如圖:
image

控制檯打印:

[INFO ] 2017-02-22 12:58:30,827 method:com.test.controller.UserController.index(UserController.java:16)
進入方法
[DEBUG] 2017-02-22 12:58:30,829 method:org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1251)
Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/jsp/index.jsp]] in DispatcherServlet with name 'dispatcher'
[DEBUG] 2017-02-22 12:58:30,829 method:org.springframework.web.servlet.view.AbstractView.exposeModelAsRequestAttributes(AbstractView.java:432)
Added model object 'date' of type [java.lang.Long] to request in view with name 'index'
[DEBUG] 2017-02-22 12:58:30,830 method:org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:166)
Forwarding to resource [/jsp/index.jsp] in InternalResourceView 'index'
[DEBUG] 2017-02-22 12:58:30,833 method:org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1000)
Successfully completed request
[DEBUG] 2017-02-22 12:58:30,835 method:net.sf.ehcache.constructs.web.filter.CachingFilter.buildPageInfo(CachingFilter.java:250)
PageInfo ok. Adding to cache SimplePageCachingFilter with key GET/ehcache-web/user/index.htmlnull
[DEBUG] 2017-02-22 12:58:30,835 method:net.sf.ehcache.store.disk.Segment.put(Segment.java:432)
put added 0 on heap
[DEBUG] 2017-02-22 12:58:30,836 method:net.sf.ehcache.constructs.web.filter.Filter.logRequestHeaders(Filter.java:288)
Request Headers: host -> localhost:8080: connection -> keep-alive: cache-control -> max-age=0: upgrade-insecure-requests -> 1: user-agent -> Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36: accept -> text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8: accept-encoding -> gzip, deflate, sdch: accept-language -> zh-CN,zh;q=0.8: cookie -> JSESSIONID=F8D4E5ADD89B56C6005F7F594E9A4A68
[DEBUG] 2017-02-22 12:58:30,837 method:net.sf.ehcache.constructs.web.filter.Filter.logRequestHeaders(Filter.java:288)
Request Headers: host -> localhost:8080: connection -> keep-alive: cache-control -> max-age=0: upgrade-insecure-requests -> 1: user-agent -> Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36: accept -> text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8: accept-encoding -> gzip, deflate, sdch: accept-language -> zh-CN,zh;q=0.8: cookie -> JSESSIONID=F8D4E5ADD89B56C6005F7F594E9A4A68

因爲上面配置過:

  <!-- 頁面全部緩存 -->
    <cache name="SimplePageCachingFilter" 
        maxElementsInMemory="10000"  
        maxElementsOnDisk="1000" 
        eternal="false" 
        overflowToDisk="true"  
        diskSpoolBufferSizeMB="20" 
        timeToIdleSeconds="2" 
        timeToLiveSeconds="4"  
        memoryStoreEvictionPolicy="LFU" />  

測試時一直刷新4秒的話之後才時間會變,2秒內不訪問,再刷新,時間也會變。第二次訪問的時候控制檯會報Thread http-apr-8080-exec-6 has been marked as visited.。

局部頁面緩存

配置SimplePageFragmentCachingFilter

            //配置局部頁面整體緩存
FilterRegistration.Dynamic pageFragmentCachingFilter = servletContext.addFilter("pageFragmentCachingFilter",net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter.class);
        pageFragmentCachingFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.INCLUDE), true, "/jsp/include/*");
//      Map<String, String> initParameters = new HashMap<String, String>();
//      initParameters.put("cacheName", "SimplePageFragmentCachingFilter");
//      pageFragmentCachingFilter.setInitParameters(initParameters);

上面配置中的註釋部分在自己指定filter時使用,如果想自己指定filter可以將fiter換成自己的,並且要指定cacheName.要注意的是,局部頁面緩存需要設置DispatcherType.INCLUDE的屬性,否則無效果。

被包含的頁面:
index-include.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>

    <title>My JSP 'index-include.jsp' starting page</title>

    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

  </head>

  <body>
    <h3>這是被包含的頁面</h3>
    <h4>包含的時間:${date }</h4>
  </body>
</html>

把上面index.jsp中包含頁面的部分的註釋打開

測試運行項目第一次結果爲:
image
第二次訪問:
image
執行結果分析:
從ehcache.xml文件中可以看到,局部頁面的緩存時間比整體頁面的緩存時間長。所以即使整體頁面的時間變了,局部頁面的時間還是沒有變。
注意:上面ehcache.xml中的緩存時間都比較短,是爲了本項目測試,真實項目中請根據實際情況決定緩存時間。
源代碼:http://download.csdn.net/detail/poorcoder_/9760607
轉載請標明出處:http://blog.csdn.net/poorcoder_/article/details/56483954

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章