java基于filter的应用缓存框架

java web 基于filter的缓存框架







目标、解决的问题:
浏览器客户端向服务器发起许多参数相同的请求,在服务器端的处理之后,在相同参数的情况下,返回的结果一致的情况下,使用该缓存框架,可以提高web服务器的性能。
在java的web开发的时候,有许多请求,在后台处理之后,可能返回相同的数据到浏览器端,或者在一定时间内,返回的结果可能都是一样的。因此可以将执行后的返回结果缓存起来,再下一次相同请求,相同参数的情况向可以直接从缓存中获取到处理结果。例如在一个用户管理系统中,普通的操作有添加、修改、删除、查询4个操作,在执行查询操作的时候,实际上,只要数据库中数据没有发生变化,则返回到浏览器端的数据都是一样的,因此可以给用户的查询增加缓存。
第一步:如下代码:
public String query() {
		String result = ERROR;
		try{
			List<User> allUser = userService.query();
			ServletActionContext.getRequest().setAttribute("allUser", allUser);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

代码中,使用userService从数据库中查询到用户列表,则该用户列表,当数据库中数据没有变化的时候,浏览器端访问该地址都将获取到相同的结果,因此可以将List<User> allUser缓存起来,等待下一次执行该请求的时候,直接从缓存中获取到用户列表,然后跳转到jsp页面做显示。但这种方式,对于单个的方法,需要在业务逻辑增添加代码,而且若这样的需要缓存的地址较多,则是一个比较繁琐的过程,维护多个地方的东西,也不容易。这种方法不是最理想的。考虑到web的请求都是request/response结构的,即都是浏览器端发起请求,由java的web后台处理,完成之后返回浏览器端一个response对象,因此可以直接将response中所有的内容进行缓存,下一次请求的时候直接将上次一次的处理结果中response的内容,设置到新的一次请求返回的response中,这一步可以在更高一层统一完成(filter层)。
查找HttpServletResponse的api后,发现没有直接从response中获取到返回内容的方法,因此采用代理的方式,自己写一个HttpServletResponseProxy,该类代理了所有HttpServletResponse的普通方法,并截获对应的getWriter()、getServletOutputStream()方法,到这一步骤之后,仍旧不能获取到在整个filter业务处理的过程中,往response中写入的内容,因此还需要增加对获取到的Writer的代理、ServletOutputStream的代理,新增加的两个代理PrintWriterProxy、ServletOutputStreamProxy,中便可以获取到写入response的内容,在代理写入方法的时候,写入的同时,将写入的内容copy一份,以便后续使用。那么在第一次请求的时候,所有写入到response的内容就可以获取到,则在第二次请求的时候,可以讲上一次的response中的内容取出并返回给浏览器端。
增加FilterCache.java,可以通过uri地址,判断该地址是否已经请求过,并且缓存尚未过期,没有过期则将缓存内容取出并返回。
第二步:完成到如上步骤,只是完成了简单的缓存,
public String query() {
		String result = ERROR;
		try{
			String username = ServletActionContext.getRequest().getParameter("username");
			List<User> allUser = userService.query(username);
			ServletActionContext.getRequest().setAttribute("allUser", allUser);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

在该段代码中,需要从request中获取到用户名作为查询条件,那么就需要把request中参数考虑到缓存存储的键的生成策略中,而不仅仅只使用请求的地址uri作为键的生成策略,考虑到request中请求的参数很多,为了不时缓存的键过长,需要将request中所有的参数拼接成一个字符串,并同uri地址连接,形成如同"userAction!query.action?username=a"这样的字符串,然后将该字符串进行md5编码,那么就可以得到一个定长且不是太长的字符串作为缓存的键。在拼接字符串的过程中,"userAction!query.action?username=a&groupname=b"和"userAction!query.action?groupname=b&username=a"其实是一样的,因此对从request中获取参数并拼接字符串的过程中,完成之后还需要对参数进行排序,这个可以使用key的字符串大小(英文排序)来确定,将以上可以遇到的两种情况排序后成为一种,然后才可以避免相同地址请求、相同参数的情况下导致缓键生成不同,而实际是相同的情况下,缓存了两个不同的结果的情况。
同样,session中的参数也需要拼接到请求参数中,并影响缓存键的生成。例如:
public String query() {
		String result = ERROR;
		try{
			String username = ServletActionContext.getRequest().getSession().getAttribute("username").toString();
			List<User> allUser = userService.query(username);
			ServletActionContext.getRequest().setAttribute("allUser", allUser);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

第三步:request、session中的参数,有的时候会影响业务逻辑执行的结果,有的时候不会,因此可以将是否影响作为配置项,在缓存策略配置文件中进行配置,那么,各个不同的请求地址的缓存键生成策略部同,可以增加缓存框架的易用性。
第四步:考虑到如果当一个请求正在执行,还没有执行完成,另一个相同请求地址、参数、session参数的请求也请求到服务器端,那么这种情况,将会导致缓存没有起到作用,两次请求都会被web系统执行,消耗了系统资源。因此增加一个同步机制,相同请求地址、参数、session参数即缓存键相同时,后到达的请求,将以线程同步的方式,等待上次执行结果完成,而并不直接调用业务逻辑,等待从缓存中获取到结果。如此,可以在系统缓存到期的情况下,有效防止由于缓存过期引起的性能下降问题。
第五步:在最先提到用户的增加、修改、删除,都会影响到查询的结果。那么还应该设置缓存击穿策略,即当用户增加的时候,击穿查询的缓存结果,当执行增加用户后,上一次的查询结果就失效了,当再次有查询请求道web服务器的时候,将不再从缓存中获取结果,而是再次执行业务逻辑,完成之后将结果缓存。同样用户修改、删除之后也会击穿用户查询的缓存。





使用方式:
1、将jar包和依赖jar包copy到classpath路径中
2、在web.xml中增加缓存过滤器
<!-- 缓存过滤器  -->
	<filter>
		<filter-name>cacheFilter</filter-name>
		<filter-class>
			com.cjnetwork.cache.filter.CacheFilter
		</filter-class>
		<init-param>
			<param-name>configFileLocation</param-name>		<!-- 缓存策略配置文件地址,默认为cache.xml  -->
			<param-value></param-value>
		</init-param>
		<init-param>
			<param-name>requestUriIncludePattern</param-name>	<!-- 缓存过滤器,需要处理的请求的地址的正则表达式  -->
			<param-value>.*?!.*?</param-value>
		</init-param>
		<init-param>
			<param-name>requestUriExcludePattern</param-name>	<!-- 缓存过滤器,需要排除的请求的地址的正则表达式  -->
			<param-value></param-value>
		</init-param>
		<init-param>
			<param-name>xmlReloadable</param-name>	<!-- 策略文件是否为重新导入模式,即当配置文件变化的时候,不需要重新启动应用程序,缓存框架会自动检测到,该功能在开发调制阶段非常有用(推荐在生产环境设置为false)  -->
			<param-value>true</param-value>  
		</init-param>
		<init-param>
			<param-name>cacheProvider</param-name>		<!-- 缓存提供者,可以自定义缓存数据存放方式(内存、其他KeyValue缓存框架等)  -->
			<param-value>com.cjnetwork.cache.provider.SimpleMemeryCacheProvider</param-value>
		</init-param>
	</filter>

3、将filter过滤配置到真正执行业务逻辑的filter之前,如struts框架中,将缓存filter配置到struts的filter之前
<filter-mapping>
		<filter-name>cacheFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

4、修改缓存策略文件cache.xml,该文件可以有filter定义中的初始化参数修改,可以是其他文件名
里面的具体配置参数,请直接查看配置文件中的注释或查看缓存策略配置说明章节。







缓存策略配置说明
通过修改该配置文件,可以调整缓存框架的性能,默认文件为cache.xml文件。
1、修改注解监控的包
<annotationPackage>		<!-- 注解所在的包 -->
		<package uriPrefix="" uriSeperator="!" uriSuffix=".action">com.cjnetwork.gis.action</package>
	</annotationPackage>

annotationPackage节点定义系统中需要扫描的包,在这里设置之后,可以通过注解@Cache完成缓存的配置。节点内的package节点可以定义多个,监听多个包中的注解。
package节点中:
uriPrefix:uri的前缀
uriSeperator:类与方法的分隔符
uriSuffix:uri的后缀

2、配置默认的缓存策略
<defaultCache cacheable="true" cacheTime="180">	<!-- cacheable:默认是否缓存true/false 	cacheTime:默认缓存时间(秒)-->
		<keyFromRequestIncludePattern></keyFromRequestIncludePattern>	<!-- 可存在多个,request的请求参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含 -->
		<keyFromRequestExcludePattern></keyFromRequestExcludePattern>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(包含) -->
		<keyValuePairFromRequestExclude>page=1</keyValuePairFromRequestExclude>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
		<keyValuePairFromRequestExclude>pageSize=10</keyValuePairFromRequestExclude>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
		<keyFromSessionIncludePattern></keyFromSessionIncludePattern>	<!-- 可存在多个,session的参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含 -->
		<keyFromSessionExcludePattern></keyFromSessionExcludePattern>	<!-- 可存在多个,session的参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(包含) -->
		<keyValuePairFromSessionExclude></keyValuePairFromSessionExclude>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
	</defaultCache>


3、修改特殊请求uri的缓存策略
<cache uri="testAction!test.action" cacheable="true" cacheTime="180">	<!-- cacheable:是否缓存true/false 	cacheTime:缓存时间(秒)-->
		<keyFromRequestIncludePattern></keyFromRequestIncludePattern>	<!-- 可存在多个,request的请求参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含 -->
		<keyFromRequestExcludePattern></keyFromRequestExcludePattern>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(不包含) -->
		<keyValuePairFromRequestExclude>page=1</keyValuePairFromRequestExclude>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
		<keyValuePairFromRequestExclude>pageSize=10</keyValuePairFromRequestExclude>	<!-- 可存在多个,request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
		<keyFromSessionIncludePattern></keyFromSessionIncludePattern>	<!-- 可存在多个,session的参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含 -->
		<keyFromSessionExcludePattern></keyFromSessionExcludePattern>	<!-- 可存在多个,session的参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(不包含) -->
		<keyValuePairFromSessionExclude></keyValuePairFromSessionExclude>	<!-- 可存在多个,session的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含 -->
		<effects>
			<effect uri="testAction!test1.action" />	<!-- 可存在多个,缓存击穿地址,表示当目前uri地址testAction!test.action如果真正执行,将击穿uri="testAction!test1.action在系统中的缓存 -->
			<effectBy uri="testAction!test2.action" />	<!-- 可存在多个,缓存击穿地址,表示当testAction!tes2t.action如果真正执行,将击穿目前uri地址testAction!test.action在系统中的缓存 -->
		<effects>
	</cache>


4、注解配置方式(可选)
如果需要使用注解配置,则需要完成配置1中的配置,设置好相关的属性
@com.cjnetwork.cache.annotation.Cache具有如下属性
uri:请求的地址
cacheable:是否缓存true/false
cacheTime:缓存时间
keyFromRequestIncludePattern:request的请求参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含
keyFromRequestExcludePattern:request的请求参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(不包含)
keyValuePairFromRequestExclude:request的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含
keyFromSessionIncludePattern:session的参数中,需要包含在缓存主键生成中,不配置表示请求中所有参数都要包含
keyFromSessionExcludePattern:session的参数中,需要从缓存主键生成中排除,不配置表示请求中所有参数都不排除(不包含)
keyValuePairFromSessionExclude:可存在多个,session的请求参数中,需要从缓存主键生成中排除的特殊键值对,例如在请求某个查询的时候,有page=1的参数和不带该参数的查询,其结果应该是一样的,不配置表示请求中所有参数都要包含
effect:缓存击穿地址
effectBy:被其他uri地址击穿,其他地址将影响该地址的其他的地址









修改记录:
2012-08-10 cjnetwork
FilterCache-V1.0.1-realease.jar
1、修正相同请求地址,不同参数之间数据不同,但获取到相同结果bug
2、修正缓存测量文件中,无注解配置项是初始化异常

2012-05-17 cjnetwork
FilterCache-V1.0.0-realease.jar
1、完成第一个版本,通过配置能够完成缓存功能,正确配置缓存参数,可以提高web可访问性数倍;
2、该版本没有缓存击穿功能,下一版本将实现该功能;

猜你喜欢

转载自cjnetwork.iteye.com/blog/1628511