zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

Spring性能 优化 -- 实战 之道 EhCache
2023-09-14 09:08:54 时间

在做系统集成平台项目的时候遇到了一个比較麻烦的问题。原因是使用考试系统的时候所依赖的是基础系统公布的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缓存在程序中已经起作用了。