Hibernate缓存之Aop+cache
在上一篇涉及到查询缓存的功能时除了需要在配置文件中开启缓存外,还需要在业务代码中显示调用setCacheable(boolean)才可以打开查询缓存的功能,这样做,无疑是破坏了封装性,所以就诞生了利用AOP切面编程来实现查询缓存。原理:在执行查询操作之前根据Key(类名+方法名+参数列表)判断二级缓存中是否有,如果没有,则执行查询操作,并将结果保存到二级缓存中,如果有,则直接从二级缓存中获取数据。下面就通过两种方式来利用aop实现查询缓存的功能。
(一)、配置文件拦截器实现方式
cacheContext.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- 引用ehCache的配置 --> <bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation"> <value>classpath:ehcache.xml</value> </property> </bean> <!-- 定义ehCache的工厂,并设置所使用的Cache name --> <bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager"> <ref local="myCacheManager" /> </property> <property name="cacheName"> <value>DEFAULT_CACHE</value> </property> </bean> <!--cache拦截器 --> <bean id="methodCacheInterceptor" class="com.myoracle.interceptor.MethodCacheInterceptor"> <property name="methodCache"> <ref local="ehCache" /> </property> </bean> <!-- 以下针对MethodCacheInterceptor方法使用到的配置 --> <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="methodCacheInterceptor" /> </property> <property name="patterns"> <list> <value>.*search.*</value> </list> </property> </bean> </beans>
ehcache.xml放在src文件夹目录下面,同时在hibernate.cfg.xml中是否开启二级缓存都不影响以下代码执行效果(即使:<property name="hibernate.cache.use_second_level_cache">false</property>执行效果也一致)
applicationContext.xml中在上一篇的基础上加入以下配置代码
<import resource="cacheContext.xml" /> <aop:config proxy-target-class="true" /> <!-- 切面编程声明 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- spring annotation --> <context:annotation-config /> <!-- 以下针对MethodCacheInterceptor方法使用到的配置 --> <bean id="userServiceImpl" class="com.myoracle.service.UserServiceImpl" /> <bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref local="userServiceImpl" /> </property> <property name="interceptorNames"> <list> <value>methodCachePointCut</value> </list> </property> </bean>
接着,编写代码拦截器的主要代码:
/**
* 和cacheContext.xml中的methodCacheInterceptor中一一对应
*/
public class MethodCacheInterceptor implements MethodInterceptor { //在cacheContext.xml中完成注入
private Cache methodCache; public Cache getMethodCache() { return methodCache; } public void setMethodCache(Cache methodCache) { this.methodCache = methodCache; } public MethodCacheInterceptor() { super(); } public Object invoke(MethodInvocation invocation) throws Throwable { try { System.out.println("*******进入拦截器执行******"); String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); Object result; String cacheKey = getCacheKey(targetName, methodName, arguments); Element element = methodCache.get(cacheKey); if (null == element) { result = invocation.proceed(); element = new Element(cacheKey, (Serializable) result); methodCache.put(element); } return element.getValue(); } catch (Exception e) { System.out.println("拦截器中出现异常"); return new Object(); } } private String getCacheKey(String targetName, String methodName, Object[] arguments) { try { StringBuffer sb = new StringBuffer(); sb.append(targetName).append(".").append(methodName); if ((null != arguments) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sb.append(".").append(arguments[i]); } } return sb.toString(); } catch (Exception e) { System.out.println("拦截器getCacheKey中出现异常"); return ""; } } }
执行效果如下:(此时是在屏蔽setCacheable(boolean)的情况下执行的,如果不加拦截器,正常的执行效果是发出两条sql语句)
(二)、注解方式拦截器实现方式
在上面的实现过程中明显感觉配置文件太繁琐,而且如果希望可以针对具体的类、方法进行拦截处理,在上面的配置文件中可能涉及到编写正则表达式,编写容易发生错误,所以想到可以自定义一个注解,在需要拦截的类或者方法中定义此注解即可实现拦截效果,而且也不需要大量的配置文件,所以下面可以称之为是简化版.....
首先定义一个注解,方式定义了该注解的方法和类均可以完成拦截
/**
* @Target指明作用的范围,针对方法和类
*/
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface MethodCache { int expire() default 0; // 过期时间 }
看主要的代码,注意,在cacheContext.xml中对于ehcache的声明还是需要的,因为在下面的代码中,需要注入ehcache
@Component("methodCacheAspectJ")
@Aspect
public class MethodCacheAspectJ {
private Cache cache;
public Cache getCache() {
return cache;
}
//注意cacheContext.xml中的ehcahe的声明需要保留
@Resource(name = "ehCache")
public void setCache(Cache cache) {
this.cache = cache;
}
//定义方法切入点,凡是注解了MethodCache的类均被拦截
@Pointcut("@annotation(com.myoracle.annotation.MethodCache)")
public void methodCachePointcut() {
}
@Around("methodCachePointcut()")
public Object methodCacheHold(ProceedingJoinPoint joinPoint)
throws Throwable {
System.out.println("*************进入面向切面编程****************");
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 (null == element) {
try {
result = joinPoint.proceed();// 执行方法
} catch (Exception e) {
System.out.println("执行方法失败");
}
if (null != result) {
try {
element = new Element(cacheKey, (Serializable) result);
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
int expire = 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);
expire = methodCache.expire();
break;
}
}
}
if (expire > 0) {
element.setTimeToIdle(expire);
element.setTimeToLive(expire);
}
cache.put(element);
} catch (Exception e) {
System.out.println("cachekey=" + cacheKey + " 为执行缓存");
}
}
}
return element.getValue();
}
private String getCacheKey(String targetName, String methodName,
Object[] arguments) {
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((null != arguments) && (0 != arguments.length)) {
for (int i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Date) {
sb.append(".").append(((Date) arguments[i]).toString());
} else {
sb.append(".").append(arguments[i]);
}
}
}
return sb.toString();
}
}
以上代码执行效果图:
可以看出同样达到了使用查询缓存的效果........
从上面代码中结合上一篇章中对查询缓存概念的讲解可以深入理解其实现原理,进一步了解缓存实现的机制.

浙公网安备 33010602011771号