前言
本文通过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.0modelVersion>
<groupId>com.testgroupId>
<artifactId>ehcache-webartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>warpackaging>
<properties>
<spring.version>4.3.5.RELEASEspring.version>
<junit.version>4.12junit.version>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.1.3version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>2.7.5version>
<type>jartype>
<scope>compilescope>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcache-webartifactId>
<version>2.0.4version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.18version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.18version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.5version>
dependency>
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 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页面:
"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+"/";
%>
<html><head><base href=""><title>My JSP 'index.jsp' starting pagetitle><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">head><body><h1>缓存测试h1><h2>时间:${date }h2>"/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 initParameters = new HashMap();
// initParameters.put("cacheName", "SimplePageFragmentCachingFilter");
// pageFragmentCachingFilter.setInitParameters(initParameters);
上面配置中的注释部分在自己指定filter时使用,如果想自己指定filter可以将fiter换成自己的,并且要指定cacheName.要注意的是,局部页面缓存需要设置DispatcherType.INCLUDE的属性,否则无效果。
被包含的页面:
index-include.jsp
"java" import="java.util.*" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
<html><head><title>My JSP 'index-include.jsp' starting pagetitle><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">head><body><h3>这是被包含的页面h3><h4>包含的时间:${date }h4>body>html>
把上面index.jsp中包含页面的部分的注释打开
测试运行项目第一次结果为:
第二次访问:
执行结果分析:
从ehcache.xml文件中可以看到,局部页面的缓存时间比整体页面的缓存时间长。所以即使整体页面的时间变了,局部页面的时间还是没有变。
程序员,你下班的 时候关电脑了吗?
15天的性能优化工作,5方面的调优经验
Java NIO?看这一篇就够了!
Redis 5.0.4-搭建3主3从3哨兵,实现高可用性与故障转移
BAT一线互联网企业Java工程师超硬核面试题总结
一定要关注公众号,资料随时更新,自助领取
点亮 ,告诉大家你也在看