二,动态缓存
方案1:使用自定义annotation接口进行aspectj动态缓存
由于系统需求需要对各个接口进行key-value缓存(以参数为key,返回的对象为value),当然对于这种情况首先考虑到的是使用aop,前段时间看过aspectj的一些介绍,借此机会正好加以应用和体会一下,aspectj是AOP最早成熟的java实现,它稍微扩展了一下java语言,增加了一些keyword等,具体的aspectj的基本语法见这里,进行缓存的框架使用较成熟的ehcache.
下面开始进行配置
首先是ehcache的配置文件
- <?xml version="1.0" encoding="UTF-8"?>
- <ehcache>
- <diskStore path="/home/workspace/gzshine/trunk/ehcache"/>
- <cache name="DEFAULT_CACHE"
- maxElementsInMemory="10000"
- eternal="false"
- timeToIdleSeconds="3600"
- timeToLiveSeconds="3600"
- overflowToDisk="true"
- />
- </ehcache>
这个的DEFAULT_CACHE是默认配置,最大的缓存数为10000,时间为一个小时
接下来的是spring下的配置
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd">
-
-
-
- <aop:aspectj-autoproxy proxy-target-class="true"/>
- <bean id = "methodCacheAspectJ" class="com.***.shine.aspectj.MethodCacheAspectJ" >
- <property name="cache">
- <ref local="methodCache" />
- </property>
- </bean>
-
- <bean id="cacheManager"
- class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
- <property name="configLocation">
- <value>classpath:ehcache.xml</value>
- </property>
- </bean>
-
-
-
- <bean id="methodCache"
- class="org.springframework.cache.ehcache.EhCacheFactoryBean">
- <property name="cacheManager">
- <ref local="cacheManager" />
- </property>
- <property name="cacheName">
- <value>DEFAULT_CACHE</value>
- </property>
- </bean>
<aop:aspectj-autoproxy proxy-target-class="true"/>
是为aspectj在所有class下开启自动动态代理
<bean id="cacheManager">指定刚刚的ehcache配置文件
接下来编写一个自定义的annotation
- package com.***.shine.cache;
-
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target({ElementType.METHOD,ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface MethodCache {
- int second() default 0;
- }
<bean id = "methodCacheAspectJ">是一个aspectj进行Pointcuts和Advice的类需注入methodCache
- package com.***.shine.aspectj;
-
- @Aspect
- public class MethodCacheAspectJ {
- Log logger = LogFactory.getLog(MethodCacheAspectJ.class);
-
- private Cache cache;
-
-
-
-
- public void setCache(Cache cache) {
- this.cache = cache;
- }
-
- @Pointcut("@annotation(com.***.shine.cache.MethodCache)")
- public void methodCachePointcut(){
- }
-
- @Around("methodCachePointcut()")
- public Object methodCacheHold(ProceedingJoinPoint joinPoint) throws Throwable{
- String targetName = joinPoint.getTarget().getClass().getName();
- String methodName = joinPoint.getSignature().getName();
- Object[] arguments = joinPoint.getArgs();
- Object result = null;
- String cacheKey = getCacheKey(targetName, methodName, arguments);
- Element element = cache.get(cacheKey);
- if (element == null) {
- try{
- result = joinPoint.proceed();
- }catch(Exception e){
- logger.info(e);
- }
- if(result!=null){
- try{
- element = new Element(cacheKey, (Serializable) result);
- Class targetClass = Class.forName(targetName);
- Method[] method = targetClass.getMethods();
- int second = 0;
- for(Method m:method){
- if (m.getName().equals(methodName)) {
- Class[] tmpCs = m.getParameterTypes();
- if(tmpCs.length==arguments.length){
- MethodCache methodCache = m.getAnnotation(MethodCache.class);
- second = methodCache.second();
- break;
- }
- }
- }
- if(second>0){
- element.setTimeToIdle(second);
- element.setTimeToLive(second);
- }
- cache.put(element);
- }catch(Exception e){
- logger.info("!!!!!!!!!"+cacheKey+"!!!!!!!!!未能执行方法缓存"+e);
- }
- }
- }
- return element.getValue();
- }
-
- private String getCacheKey(String targetName, String methodName,
- Object[] arguments) {
- StringBuffer sb = new StringBuffer();
- sb.append(targetName).append(".").append(methodName);
- if ((arguments != null) && (arguments.length != 0)) {
- for (int i = 0; i < arguments.length; i++) {
- if (arguments[i] instanceof Date) {
- sb.append(".").append(
- DateUtil.datetoString((Date) arguments[i]));
- } else {
- sb.append(".").append(arguments[i]);
- }
- }
- }
- return sb.toString();
- }
- }
@Pointcut("@annotation(com.netease.shine.cache.MethodCache)")
对有应用com.netease.shine.cache.MethodCache进行注解的方法进行横切面拦截
@Around("methodCachePointcut()")
并在Advice中处理这个Pointcut,这里的的Advice使用的是Around(环绕通知)
String cacheKey = getCacheKey(targetName, methodName, arguments);
接下来使用类型,方法名,参数为key进入缓存处理
Element element = cache.get(cacheKey);
当然如果在cache队列中取得非null对象则直接返回该对象
MethodCache methodCache = m.getAnnotation(MethodCache.class);
second = methodCache.second();
取得second的值(缓存的时间,如在@annotation中无重写只为int second() default 0)
element.setTimeToIdle(second);
element.setTimeToLive(second);
如果非零则重新设置缓存时间
- @MethodCache(second=300)
- public List<Sort> getSort(int type,int parentid){
- System.out.println("!!!!!!!!!!!!!没缓存到");
- Row row = new Row();
- row.put("type", type);
- row.put("parentid", parentid);
- return (List<Sort>)gz_Template.queryForList("sort.getSort", row);
- }
最后需要将@MethodCache要缓存方法的实现类
方案2 web应用的java动态缓存
可以实现不等待,线程自动更新缓存
java动态缓存jar包请下载。
源代码:
001 |
CacheData.java 存放缓存数据的Bean |
006 |
package com.cari.web.cache; |
012 |
public class CacheData { |
021 |
public CacheData(Object data, long time, int count) { |
027 |
public CacheData(Object data) { |
029 |
this .time = System.currentTimeMillis(); |
033 |
public void addCount() { |
037 |
public int getCount() { |
040 |
public void setCount( int count) { |
043 |
public Object getData() { |
046 |
public void setData(Object data) { |
049 |
public long getTime() { |
052 |
public void setTime( long time) { |
059 |
CacheOperation.java 缓存处理类 |
061 |
package com.cari.web.cache; |
063 |
import java.lang.reflect.Method; |
064 |
import java.util.ArrayList; |
065 |
import java.util.Arrays; |
066 |
import java.util.Hashtable; |
068 |
import org.apache.commons.logging.Log; |
069 |
import org.apache.commons.logging.LogFactory; |
074 |
public class CacheOperation { |
075 |
private static final Log log = LogFactory.getLog(CacheOperation. class ); |
076 |
private static CacheOperation singleton = null ; |
078 |
private Hashtable cacheMap; |
080 |
private ArrayList threadKeys; |
082 |
public static CacheOperation getInstance() { |
083 |
if (singleton == null ) { |
084 |
singleton = new CacheOperation(); |
089 |
private CacheOperation() { |
090 |
cacheMap = new Hashtable(); |
091 |
threadKeys = new ArrayList(); |
096 |
* 与方法getCacheData(String key, long intervalTime, int maxVisitCount)配合使用 |
100 |
public void addCacheData(String key, Object data) { |
101 |
addCacheData(key, data, true ); |
104 |
private void addCacheData(String key, Object data, boolean check) { |
105 |
if (Runtime.getRuntime().freeMemory() < 5L*1024L*1024L) { |
106 |
log.warn( "WEB缓存:内存不足,开始清空缓存!" ); |
107 |
removeAllCacheData(); |
109 |
} else if (check && cacheMap.containsKey(key)) { |
110 |
log.warn( "WEB缓存:key值= " + key + " 在缓存中重复, 本次不缓存!" ); |
113 |
cacheMap.put(key, new CacheData(data)); |
118 |
* 与方法addCacheData(String key, Object data)配合使用 |
120 |
* @param intervalTime 缓存的时间周期,小于等于0时不限制 |
121 |
* @param maxVisitCount 访问累积次数,小于等于0时不限制 |
124 |
public Object getCacheData(String key, long intervalTime, int maxVisitCount) { |
125 |
CacheData cacheData = (CacheData)cacheMap.get(key); |
126 |
if (cacheData == null ) { |
129 |
if (intervalTime > 0 && (System.currentTimeMillis() - cacheData.getTime()) > intervalTime) { |
130 |
removeCacheData(key); |
133 |
if (maxVisitCount > 0 && (maxVisitCount - cacheData.getCount()) <= 0 ) { |
134 |
removeCacheData(key); |
137 |
cacheData.addCount(); |
139 |
return cacheData.getData(); |
143 |
* 当缓存中数据失效时,用不给定的方法线程更新数据 |
144 |
* @param o 取得数据的对像(该方法是静态方法是不用实例,则传Class实列) |
145 |
* @param methodName 该对像中的方法 |
146 |
* @param parameters 该方法的参数列表(参数列表中对像都要实现toString方法,若列表中某一参数为空则传它所属类的Class) |
147 |
* @param intervalTime 缓存的时间周期,小于等于0时不限制 |
148 |
* @param maxVisitCount 访问累积次数,小于等于0时不限制 |
151 |
public Object getCacheData(Object o, String methodName,Object[] parameters, |
152 |
long intervalTime, int maxVisitCount) { |
153 |
Class oc = o instanceof Class ? (Class)o : o.getClass(); |
154 |
StringBuffer key = new StringBuffer(oc.getName()); |
155 |
key.append( "-" ).append(methodName); |
156 |
if (parameters != null ) { |
157 |
for ( int i = 0 ; i < parameters.length; i++) { |
158 |
if (parameters[i] instanceof Object[]) { |
159 |
key.append( "-" ).append(Arrays.toString((Object[])parameters[i])); |
161 |
key.append( "-" ).append(parameters[i]); |
166 |
CacheData cacheData = (CacheData)cacheMap.get(key.toString()); |
167 |
if (cacheData == null ) { |
168 |
Object returnValue = invoke(o, methodName, parameters, key.toString()); |
169 |
return returnValue instanceof Class ? null : returnValue; |
171 |
if (intervalTime > 0 && (System.currentTimeMillis() - cacheData.getTime()) > intervalTime) { |
172 |
daemonInvoke(o, methodName, parameters, key.toString()); |
173 |
} else if (maxVisitCount > 0 && (maxVisitCount - cacheData.getCount()) <= 0 ) { |
174 |
daemonInvoke(o, methodName, parameters, key.toString()); |
176 |
cacheData.addCount(); |
178 |
return cacheData.getData(); |
186 |
* @return 若反射调用方法返回值为空则返回该值的类型 |
188 |
private Object invoke(Object o, String methodName,Object[] parameters, String key) { |
189 |
Object returnValue = null ; |
192 |
if (parameters != null ) { |
193 |
pcs = new Class[parameters.length]; |
194 |
for ( int i = 0 ; i < parameters.length; i++) { |
195 |
if (parameters[i] instanceof MethodInfo) { |
196 |
MethodInfo pmi = (MethodInfo)parameters[i]; |
197 |
Object pre = invoke(pmi.getO(), pmi.getMethodName(), pmi.getParameters(), null ); |
200 |
if (parameters[i] instanceof Class) { |
201 |
pcs[i] = (Class)parameters[i]; |
202 |
parameters[i] = null ; |
204 |
pcs[i] = parameters[i].getClass(); |
208 |
Class oc = o instanceof Class ? (Class)o : o.getClass(); |
210 |
Method m = matchMethod(oc, methodName, pcs); |
211 |
returnValue = m.invoke(o, parameters); |
212 |
if (key != null && returnValue != null ) { |
213 |
addCacheData(key, returnValue, false ); |
215 |
if (returnValue == null ) { |
216 |
returnValue = m.getReturnType(); |
218 |
} catch (Exception e) { |
219 |
log.error( "调用方法失败,methodName=" + methodName); |
221 |
removeCacheData(key); |
222 |
log.error( "更新缓存失败,缓存key=" + key); |
230 |
* 找不到完全匹配的方法时,对参数进行向父类匹配 |
231 |
* 因为方法aa(java.util.List) 与 aa(java.util.ArrayList)不能自动匹配到 |
237 |
* @throws NoSuchMethodException |
238 |
* @throws NoSuchMethodException |
240 |
private Method matchMethod(Class oc, String methodName, Class[] pcs |
241 |
) throws NoSuchMethodException, SecurityException { |
243 |
Method method = oc.getDeclaredMethod(methodName, pcs); |
245 |
} catch (NoSuchMethodException e) { |
246 |
Method[] ms = oc.getDeclaredMethods(); |
247 |
aa: for ( int i = 0 ; i < ms.length; i++) { |
248 |
if (ms[i].getName().equals(methodName)) { |
249 |
Class[] pts = ms[i].getParameterTypes(); |
250 |
if (pts.length == pcs.length) { |
251 |
for ( int j = 0 ; j < pts.length; j++) { |
252 |
if (!pts[j].isAssignableFrom(pcs[j])) { |
260 |
throw new NoSuchMethodException(); |
265 |
* 新启线程后台调用给定方法更新缓存中数据据 |
271 |
private void daemonInvoke(Object o, String methodName,Object[] parameters, String key) { |
272 |
if (!threadKeys.contains(key)) { |
273 |
InvokeThread t = new InvokeThread(o, methodName, parameters, key); |
279 |
* 些类存放方法的主调对像,名称及参数数组 |
283 |
public class MethodInfo { |
285 |
private String methodName; |
286 |
private Object[] parameters; |
287 |
public MethodInfo(Object o, String methodName,Object[] parameters) { |
289 |
this .methodName = methodName; |
290 |
this .parameters = parameters; |
292 |
public String getMethodName() { |
295 |
public void setMethodName(String methodName) { |
296 |
this .methodName = methodName; |
298 |
public Object getO() { |
301 |
public void setO(Object o) { |
304 |
public Object[] getParameters() { |
307 |
public void setParameters(Object[] parameters) { |
308 |
this .parameters = parameters; |
311 |
public String toString() { |
312 |
StringBuffer str = new StringBuffer(methodName); |
313 |
if (parameters != null ) { |
315 |
for ( int i = 0 ; i < parameters.length; i++) { |
316 |
if (parameters[i] instanceof Object[]) { |
317 |
str.append(Arrays.toString((Object[])parameters[i])).append( "," ); |
319 |
str.append(parameters[i]).append( "," ); |
324 |
return str.toString(); |
333 |
private class InvokeThread extends Thread { |
335 |
private String methodName; |
336 |
private Object[] parameters; |
338 |
public InvokeThread(Object o, String methodName,Object[] parameters, String key) { |
340 |
this .methodName = methodName; |
341 |
this .parameters = parameters; |
347 |
invoke(o, methodName, parameters, key); |
348 |
threadKeys.remove(key); |
356 |
public void removeCacheData(String key) { |
357 |
cacheMap.remove(key); |
364 |
public void removeAllCacheData() { |
368 |
public String toString() { |
369 |
StringBuffer sb = new StringBuffer( "************************ " ); |
370 |
sb.append( "正在更新的缓存数据: " ); |
371 |
for ( int i = 0 ; i < threadKeys.size(); i++) { |
372 |
sb.append(threadKeys.get(i)).append( " " ); |
374 |
sb.append( "当前缓存大小:" ).append(cacheMap.size()).append( " " ); |
375 |
sb.append( "************************" ); |
376 |
return sb.toString(); |
378 |
}
用法:
例1:代码片段如下:
07 |
public void getData() { |
09 |
DataCreator c = new DataCreator(); |
11 |
String result = c.initUrlData(urlStr,encoding); |
13 |
System.out.println(result); |
每次执行上面代码时都要通过调用 initUrlData方法取得数据,假设此方法很耗资源而耗时间,但对数据时实性要求不高,就是可以用以下方式进行缓存处理,保证很快地取得数据,并根据设置的参数自动更新缓存中数据
注意:initUrlData方法参数值一样时才属于同一个缓存,否则会生成一个新的缓存,也就是说从缓存中取数据与initUrlData方法参数值有关
01 |
public void getData() { |
03 |
DataCreator data = new DataCreator(); |
05 |
CacheOperation co = CacheOperation.getInstance(); |
07 |
String str = (String)co.getCacheData(data, "initUrlData" , new Object[]{urlStr, encoding}, 120000 , 100 ); |
09 |
System.out.println(result); |
getCacheData方法返回值与initUrlData方法返回类型一样,参数说明:
data:调用initUrlData方法的实列,如果该方法是静态的,则传类的类型,如(DataCreator .class);
"initUrlData":方法名称;
new Object[]{urlStr, encoding}:initUrlData方法的参数数组,如果某一参数为空则传该参数的类型,若encoding 为空,则为new Object[]{urlStr, String.class}或new Object[]{urlStr, ""};
120000:缓存时间,单位:豪秒,即过两分钟更新一次缓存;值为0时为不限,即不更新缓存;
100:访问次数,当缓存中数据被访问100次时更新一次缓存;值为0时为不限,即不更新缓存;
例2:代码片段如下:
1 |
String province = request.getParameter( "province" ); |
3 |
String city= request.getParameter( "city" ); |
5 |
String county= request.getParameter( "county" ); |
7 |
Document doc = XMLBuilder.buildLatelyKeyword(kwm.latelyKeyword(province, city, county)); |
做缓存并两分钟更新一次,如下:
01 |
String province = request.getParameter( "province" ); |
03 |
String city= request.getParameter( "city" ); |
05 |
String county= request.getParameter( "county" ); |
07 |
CacheOperation co = CacheOperation.getInstance(); |
09 |
MethodInfo mi = co. new MethodInfo(kwm, "latelyKeyword" , new Object[]{province, city, county}); |
11 |
Document doc = (Document )co.getCacheData(XMLBuilder. class , "buildLatelyKeyword" , new Object[], 120000 , 0 ); |
以上方法是嵌套调用, 要先定义内部方法说明即MethodInfo,此类是CacheOperation 的一个内部类。
|