• 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技术的一种典型应用,它可以同其他一些技术结合实现为系统实现更多的新功能。

         

  • 相关阅读:
    风云受邀参加CMAX2009技术峰会演讲嘉宾
    银光志—Silverlight 3.0技术详解与最佳实践上架中文亚马逊卓越网和当当网
    Silverlight进度条控件动画源代码
    Silverlight4 Tools下载
    在C#代码中获取Silverlight的初始化initparams参数
    css层的定位position、absolute、relative层叠加的五条叠加法则
    《银光志—Silverlight3.0技术详解与最佳实践》仅上架两周排名互动出版网第三名
    《银光志Silverlight 3.0开发详解与最佳实践》出版电子版——风云编著
    银客帝国招聘Silverlight兼职开发人员
    Silverlight 2使用C#遍历XML(兼容Silverlight3)
  • 原文地址:https://www.cnblogs.com/lilongsheng1125/p/4978507.html
Copyright © 2020-2023  润新知