您的位置:首页 > 编程语言 > Java开发

Spring4 整合EhCache实现页面缓存 零配置

2017-02-22 13:28 555 查看

前言

本文通过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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: