前言
本文通過spring4 以java配置類方式 整合EhCache來實現頁面整體緩存及頁面局部緩存。同時提供源碼。因爲使用了零配置,所以要求tomcat7以上的版本。
原理是添加攔截器,在請求從用戶瀏覽器到controller之間攔截直接返回數據,減輕服務器的壓力,也加快了訪問。
頁面緩存介紹
緩存中的元素是被壓縮過的,如果客戶瀏覽器支持壓縮的話,filter會直接返回壓縮過的流,這樣節省了帶寬,把解壓的工作交給了客戶瀏覽器,如果客戶的瀏覽器不支持gzip ,那麼filter 會把緩存的元素拿出來解壓後再返回給客戶瀏覽器。
ehcache-web這個包中給我們提供了一些filter來處理頁面緩存。如下圖:
在這裏介紹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>
運行結果如圖:
控制檯打印:
[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中包含頁面的部分的註釋打開
測試運行項目第一次結果爲:
第二次訪問:
執行結果分析:
從ehcache.xml文件中可以看到,局部頁面的緩存時間比整體頁面的緩存時間長。所以即使整體頁面的時間變了,局部頁面的時間還是沒有變。
注意:上面ehcache.xml中的緩存時間都比較短,是爲了本項目測試,真實項目中請根據實際情況決定緩存時間。
源代碼:http://download.csdn.net/detail/poorcoder_/9760607
轉載請標明出處:http://blog.csdn.net/poorcoder_/article/details/56483954