Spring AOP +EHcache为Service层方法增加缓存

       在铁科院做了一个关于医保报销的项目,在这个个系统中大量使用了下拉列表框,系统主要是给机关单位使用而且都是一些干部退休了啥的,年龄都比较大不愿意自己输入东西,因此界面上的很多值都是下拉列表框从数据字典表里面加载出来。

       如此以来字典表的数据量变的越来越大,在一个界面上往往需要频繁的与字典表交互,觉的很影响性能于是我们增加了缓存,即为service层中的指定方法缓存功能,具体实现是利用Spring AOP+EHcache来做。

       第一次执行某个方法的时候会去数据库里面查询,当第二次执行该方法时就会去从缓存里面查找,如有找到直接取值,找不到再去数据库里面查询。除此之外呢,我们还自定了tag标签,在界面上我们只需要引用一个写好的tag标签即可将一个下拉列表框加载到页面上,这样做也提高了下拉列表框的通用性,无论是前台还是后台将显示下拉列表的功能都抽象了出来,放到了一起,有益于代码的简洁和维护。

       让我们看看是怎么样一步一步来实现这个功能的,涉及到了一些知识点有spring aop、encache缓存、自定义标签tag、tld文件等。

第一步:AOP为service层指定方法增加拦截,这里我拦截的是查询字典表的方法。

       两种思路一种通过注解方式拦截,另一种通过配置文件方式但是我更倾向于使用配置文件,因为它灵活容易修改而且不需要改动代码,我把两种方式都试了一遍达到的效果是一样的。

配置文件如下;

1.通过配置文件<aop:config>来配置拦截

<span style="font-size:14px;">	<aop:config>
	    <aop:aspect id="cacheProcess" ref="cacheCheck">
		      <aop:after pointcut="execution(* com.zlwy.rcss.basedic.service.impl.BaseDicService.*(..))"  method="processCache" />
	    </aop:aspect>
  	</aop:config>
  	<!-- 		两种写法等价	 -->
  	<aop:config>
	    <aop:aspect id="cacheProcess" ref="cacheCheck">
		      <aop:pointcut id="target" expression="execution(* com.zlwy.rcss.basedic.service.impl.BaseDicService.*(..))"/>
		      <aop:after  method="processCache"  pointcut-ref="target"/>
	    </aop:aspect>
  	</aop:config></span>
       上面的两种写法是等价的,第一种将切点集合直接放到了<aop:after>里面,其实pointcut是<aop:after>标签的一个属性,下面的写法是把切点拿出来了,建议写第二种因为这种写好好理解,在写代码过程中易于阅读和方便理解也需要考虑。
2.通过注解实现

切入的类:切面类

<span style="font-size:14px;">package com.zlwy.rcss.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;

@Aspect
public class CacheInterceptor {

	@Pointcut("execution(* com.zlwy.rcss.basedic.service.impl.BaseDicService.*(..))")
	public void myMethod(){};
	
	
	@Before("myMethod()")
	public void processCache() throws Exception{
		
		System.out.println("开始执行拦截器的processCache()方法");
	
	}
	@Around("myMethod()")
    public Object doBasicProfiling() throws Throwable{  
        System.out.println("进入环绕通知");  
        System.out.println("退出方法");  
        return null;  
    } 

}
</span>
       在配置文件中需要开启切面注解如下

<span style="font-size:14px;"><aop:aspectj-autoproxy></aop:aspectj-autoproxy></span>

       在切入的时候,遇到了一个错误查找额很多资料发现参数是固定好了的,不可以随意更改,只可以传入JoinPoint和ProceedingJoinPoint这两个接口作为参数或者不传入参数,如果是其他的方法将报找不到切入点的错误。
<span style="font-size:14px;">error at ::0 formal unbound in point</span>

第二步:为拦截的方法增加缓存ehcache,拦截的方法会执行下面的方法,转到缓存类的处理中并将调用对象上下文内容传入到缓存处理中。

<span style="font-size:14px;">public class CacheInterceptor {

	public void processCache(ProceedingJoinPoint  pjp) throws Throwable{
		
		System.out.println("执行拦截器的processCache()方法----------开始");
		CacheHander cacheHander=CacheHander.getCacheHander();
		cacheHander.putResultToCache(pjp);
		System.out.println("执行拦截器的processCache()方法----------结束");

	}

}</span>

       在处理被拦截的方法之前会先处理这个方法,然后调用缓存类CacheHander将查询出来的结果添加到缓存中,当第二次再调用这个方法的时候就会从缓存中取出数据,缓存中没有的话再从数据库里面查询。

<span style="font-size:14px;">package com.zlwy.rcss.common;

import java.io.Serializable;
import java.net.URL;
import org.aspectj.lang.ProceedingJoinPoint;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.ObjectExistsException;

public class CacheHander {
	

	private CacheManager cacheManager;
	//缓存变量
	private Cache cache;
	
	private static CacheHander cacheHander=new CacheHander();
	//缓存名称
	private final String cacheName="DATA_METHOD_CACHE";
	/**
	 * 私有构造方法
	 */
	private CacheHander()
	{
		try {
			//1.创建cachemanager
			URL url=getClass().getResource("/customEHCache.xml");
			System.out.println("encache.xml url="+url);
			
			CacheManager cacheManager = CacheManager.create(url);
			this.cacheManager=cacheManager;
			cache=cacheManager.getCache(cacheName);
			
			if(cache==null){
				cache=new Cache("DATA_METHOD_CACHE", 10000, true, false, 600000, 300000);
				cacheManager.addCache(cache);
			}
			System.out.println("cache.getSize()="+cache.getSize());
			System.out.println("cache object="+cache);
			
		} catch (CacheException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 获取缓存类
	 * @returns
	 */
	public static CacheHander getCacheHander()
	{
		if (cacheHander==null) {
			cacheHander=new CacheHander();
		}
		return cacheHander;
	}
	
	
	public Object putResultToCache(ProceedingJoinPoint pjp) throws Throwable
	{

			//原实体类名(包括包名)
			String className=pjp.getTarget().getClass().getName();
			//原方法名
			String methodName=pjp.getSignature().getName();
			//原方法实参列表
			Object[] arguments=pjp.getArgs();
			
			if (methodName.startsWith("get")) 
			{
				String cacheKey=getCacheKey(className,methodName,arguments);
				Element element=cache.get(cacheKey);
				if (element==null) {
					// 执行目标方法,并保存目标方法执行后的返回值
					Object resuObject=pjp.proceed(); 
					element=new Element(cacheKey, (Serializable)resuObject);
					cache.put(element);
					System.out.println("将查询结果放到缓存里面,缓存key="+cacheKey);
				}else {
					System.out.println("已经存在从缓存中取出来="+cacheKey);
				}
				return element.getValue();
			}
			return	pjp.proceed();
	}

	/**
	 * @MethodName	: getCacheKey
	 * @Description	: 获得cache key的方法,cache key是Cache中一个Element的唯一标识 cache key包括
	 * 包名+类名+方法名+各个参数的具体指,如com.co.cache.service.UserServiceImpl.getAllUser
	 * @param targetName	类名
	 * @param methodName	方法名
	 * @param arguments		方法实参数组
	 * @return						cachekey
	 */
	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 String[]){
					String[] strArray = (String[])arguments[i];
					sb.append(".");
					for(String str : strArray){
						sb.append(str);
					}
				}else{
					sb.append(".").append(arguments[i]);
				}
			}
		}
		return sb.toString();
	}
	public Cache addCache(String cacheName) throws IllegalStateException, ObjectExistsException, CacheException
	{
		Cache cache=cacheManager.getCache(cacheName);
		if (cache==null) {
			cache=new Cache(cacheName,10000, true, false, 1000,100);
			cacheManager.addCache(cache);
		}
		return cache;
	}
	public Cache getCache() {
		return cache;
	}
	public void setCache(Cache cache) {
		this.cache = cache;
	}
	public static void setCacheHander(CacheHander cacheHander) {
		CacheHander.cacheHander = cacheHander;
	}
	
}
</span>

       利用AOP切入的关键是把切入前调用方法的上下文传入到切面类里面,比如调用该方法的对象、方法名、以及方法里面的执行参数等等,当我们缓存一个方法的查询结果的时候,需要给该结果指定一个唯一键值,方便我们从缓存中取出数据,这个键值相对于缓存的方法是唯一的,常常拿类的全名、方法名、传入的实参列表,三个参数当做缓存对象的key值。

      通过这种切入式编程可以动态给程序增加新的功能,而不用动以前的代码,是一种不错的编程模式。
      

总结:

       可以说AOP是面向对象编程OOP的补充和完善,OOP类设计好了之后结构是静态的、封闭的,任何需求的变化都可能对开发进度造成重要影响,试想一下OOP中引入了继承、封装、多太等特性来建立一种对象间的层次结构,在开发一个系统中对象会非常多,如果想为某些对象增加特殊功能则OOP无能为力,也可以增加进去但是一个一个增加很费事,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。

       例如日志功能,日志代码往往水平地散步在所有对象层次中,而与需要添加日志的类的核心功能毫无关系,再比如权限、事务等如果不实用AOP每一个方法都需要开启事务,在OOP中会导致大量重复性的代码,而不利用各个模块的重用。

      而AOP技术则恰恰相反,它利用了一种称为“横切”的技术,将多个类公共行为封装到了一个可重用的模块内部,并将其命名为“ASpect”,即方面。

      上面利用AOP为查询字典的方法增加了缓存功能是AOP技术的一种典型应用,它可以同其他一些技术结合实现为系统实现更多的新功能。

     

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李龙生的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值