首页 > 代码库 > spring+ehcache实战--性能优化之道

spring+ehcache实战--性能优化之道

在做系统集成平台项目的时候遇到了一个比较麻烦的问题,原因是使用考试系统的时候所依赖的是基础系统发布的webservice来获取基础数据,webservice的跨网络传输本身或多或少会对系统性能产生一定影响再加上传输的数据量比较大这样对系统性能的影响就更大了,但是导致系统性能下降的另外一个原因就是频繁的打开关闭数据库。针对这两个问题我们采取了两个解决方案以期将性能影响降至最低第一就是webservice由原先的传输序列化对象改为传输json串,第二个就是针对数据库连接的开闭问题作了缓存处理。本文我们主要探讨第二个解决方案ehcache。

ehcache是一个非常不错的缓存框架,配置前来简单并且功能强大,在项目中加缓存的地方主要有两处,第一是缓存实体对象,这层缓存加在实体层,主要使用的是hibernate的二级缓存(同时一定要开启查询缓存)利用spring的AOP注解即可简单搞定,而在其他查询方法上主要用的就是ehcache,用来缓存方法返回的各种对象。开启hibernate的查询缓存和二级缓存比较简单,在此不做过多介绍,我们主要来看ehcache的用法。

1.首先我们用到的是Interceptor,定义两个拦截器MethodCacheInterceptor和MethodCacheAfterAdvice,前者主要用来拦截以get和find开头的方法(用于缓存结果),而第二个拦截器主要用来拦截以update开头的方法,用来清除缓存,下面让我们来看一下具体的代码:

public class MethodCacheInterceptor implements MethodInterceptor,
		InitializingBean {
	private static final Log logger = LogFactory
			.getLog(MethodCacheInterceptor.class);

	private Cache cache;

	public void setCache(Cache cache) {
		this.cache = cache;
	}

	public MethodCacheInterceptor() {
		super();
	}

	/**
	 * 拦截Service/DAO 的方法,并查找该结果是否存在,如果存在就返回cache 中的值, 31 *
	 * 否则,返回数据库查询结果,并将查询结果放入cache 32
	 */
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String targetName = invocation.getThis().getClass().getName();
		String methodName = invocation.getMethod().getName();
		Object[] arguments = invocation.getArguments();
		Object result;

		logger.debug("Find object from cache is " + cache.getName());

		String cacheKey = getCacheKey(targetName, methodName, arguments);
		Element element = cache.get(cacheKey);

		if (element == null) {
			logger
					.debug("Hold up method , Get method result and create cache........!");
			result = invocation.proceed();
			element = new Element(cacheKey, (Serializable) result);
			System.out.println("-----非缓存中查找,查找后放入缓存");
			cache.put(element);
		}else{
			System.out.println("----缓存中查找----");
		}
		return element.getValue();
	}
	

	/**
	 * 获得cache key 的方法,cache key 是Cache 中一个Element 的唯一标识 55 * cache key
	 * 包括包名+类名+方法名,如 com.co.cache.service.UserServiceImpl.getAllUser 56
	 */
	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++) {
				sb.append(".").append(arguments[i]);
			}
		}
		return sb.toString();
	}

	/**
	 * implement InitializingBean,检查cache 是否为空 70
	 */
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(cache,
				"Need a cache. Please use setCache(Cache) create it.");
	}

}
第二个拦截器的代码如下:

public class MethodCacheAfterAdvice implements AfterReturningAdvice,
		InitializingBean {
	private static final Log logger = LogFactory
			.getLog(MethodCacheAfterAdvice.class);

	private Cache cache;

	public void setCache(Cache cache) {
		this.cache = cache;
	}

	public MethodCacheAfterAdvice() {
		super();
	}

	public void afterReturning(Object arg0, Method arg1, Object[] arg2,
			Object arg3) throws Throwable {
		String className = arg3.getClass().getName();
		List list = cache.getKeys();
		for (int i = 0; i < list.size(); i++) {
			String cacheKey = String.valueOf(list.get(i));
			if (cacheKey.startsWith(className)) {
				cache.remove(cacheKey);
				System.out.println("------清除缓存----");
				logger.debug("remove cache " + cacheKey);
			}
		}
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(cache,
				"Need a cache. Please use setCache(Cache) create it.");
	}

}
有了这两个拦截器,接下来我们所要做的就是为将这两个拦截器引入进项目让其发挥作用,这些配置都在ehcache.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="defaultCacheManager"
		class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
		<property name="configLocation">
			<value>ehcache.xml</value>
		</property>
	</bean>

	<!-- 定义ehCache 的工厂,并设置所使用的Cache name -->
	<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
		<property name="cacheManager">
			<ref local="defaultCacheManager" />
		</property>
		<property name="cacheName">
			<value>DEFAULT_CACHE</value>
		</property>
	</bean>

	<!-- find/create cache 拦截器-->
	<bean id="methodCacheInterceptor" class="com.co.cache.ehcache.MethodCacheInterceptor">
		<property name="cache">
			<ref local="ehCache" />
		</property>
	</bean>
	<!-- flush cache 拦截器-->
	<bean id="methodCacheAfterAdvice" class="com.co.cache.ehcache.MethodCacheAfterAdvice">
		<property name="cache">
			<ref local="ehCache" />
		</property>
	</bean>

	<bean id="methodCachePointCut"
		class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
		<property name="advice">
			<ref local="methodCacheInterceptor" />
		</property>
		<property name="patterns">
			<list>
				<value>.*find.*</value>
				<value>.*get.*</value>
			</list>
		</property>
	</bean>
	<bean id="methodCachePointCutAdvice"
		class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
		<property name="advice">
			<ref local="methodCacheAfterAdvice" />
		</property>
		<property name="patterns">
			<list>
				<value>.*create.*</value>
				<value>.*update.*</value>
				<value>.*delete.*</value>
			</list>
		</property>
	</bean>
</beans>

这样就将拦截器的配置以及缓存配置引入导了项目中,缓存配置信息主要放在ehcache.xml文件中,详细信息如下:

<ehcache>
	<diskStore path="H:\\temp\\cache" />
	<defaultCache maxElementsInMemory="1000" eternal="false"
		timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
	<cache name="DEFAULT_CACHE" maxElementsInMemory="10000" eternal="false"
		timeToIdleSeconds="300000" timeToLiveSeconds="600000" overflowToDisk="true" />
</ehcache>  

至此我们的相应配置都已经做好了,下面让我们建立测试类来测试缓存是否起作用,在这里我们主要用的类有三个,来看具体代码:

public interface TestService {
	public List getAllObject();

	public void updateObject(Object Object);
}
TestService是调用接口,而下面的TestServiceImpl是其实现,代码如下:

public class TestServiceImpl implements TestService {
	public List getAllObject() {
		System.out.println("---TestService:Cache 内不存在该element,查找并放入Cache!");
		return null;
	}

	public void updateObject(Object Object) {
		System.out.println("---TestService:更新了对象,这个Class 产生的cache 都将被remove!");
	}
}
下面的JunitTestClass为真正的测试类,代码如下:

public class JunitTestClass {
	
	@Test
	public void testRun(){
		String DEFAULT_CONTEXT_FILE = "/applicationContext.xml";
		ApplicationContext context = new ClassPathXmlApplicationContext(
				DEFAULT_CONTEXT_FILE);
		TestService testService = (TestService) context.getBean("testService");

		//第一次查找
		testService.getAllObject();

		//第二次查找
		testService.getAllObject();

		//执行update方法(应该清除缓存)
		testService.updateObject(null);

		//第三次查找
		testService.getAllObject();
	}
}

分析测试代码,当第一次执行getAllObject()方法的时候由于是第一次执行查询操作,会被MethodCacheInterceptor拦截,当MethodCacheInterceptor发现没有命中缓存的时候,执行invoke()方法,让程序去数据库查询(本程序中只是模拟了对数据库的查询,并没有真正查询数据库,不过其所表达的意思是与查询数据库没有区别的),我们看到这是会执行TestServiceImpl的getAllObject()方法,打印出一条语句同时打印拦截器中的“-----非缓存中查找,查找后放入缓存”语句,当第二次执行该方法的时候由于已经存在了缓存,所以不再执行TestServiceImpl的getAllObject()方法,同时只打印拦截器中的“----缓存中查找----”语句,当执行updateObject()方法的时候会被MethodCacheAfterAdvice拦截,并执行TestServiceImpl的updateObject()方法,所以会打印“---TestService:更新了对象,这个Class 产生的cache 都将被remove”语句以及拦截器中的“------删除缓存----”语句,当执行第三次查找的时候,由于缓存已经被清除,所以会在此输出和第一次一样的语句,下面来验证一下我们的推测是否正确:


输出结果与我们猜测的一样,也就是说此时ehcache缓存在程序中已经起作用了。

spring+ehcache实战--性能优化之道