利用cglib实现aop,简化缓存的使用

利用cglib实现aop,简化缓存的使用

本文只适合java菜鸟看,大大请多指教。

关于AOP:

百度百科:面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),是软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

 

关于AOP的几个概念

百度百科:

Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
 
 
常用的AOP:
1)利用java动态代理,实现AOP
  优点:比较底层,灵活方便
  缺点:被代理的方法要实现接口,比较麻烦。
2)Spring自带AOP功能
  优点:
  缺点:
3)使用cglib
  优点:封装好,无需使用接口即可代理所有方法,易上手。
  缺点:
 
 
为何使用cglib实现AOP:

由于项目中使用到Redis缓存,经常引起一些纠结:在一个接口要获取数据时,先调用缓存,当缓存中不存在数据时,再查询数据库;而在接口要插入数据时,则先插入数据库,插入成功后,再讲数据存入缓存……

因此思考后,决定使用AOP解决该问题,使得代码稍优美些。
 
1)自定义注解:
package com.tim.cacherAop.annotiation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface DaoInsert {

}


//同时还有其他三种注解:DaoQuery,DaoUpdate,DaoDelete只需要改变类名即可,不再全部粘贴

  

 
2)先创建dao类:
package com.tim.cacherAop.dao;

import com.tim.cacherAop.annotiation.DaoDelete;
import com.tim.cacherAop.annotiation.DaoInsert;
import com.tim.cacherAop.annotiation.DaoQuery;
import com.tim.cacherAop.annotiation.DaoUpdate;


/**
 * 创建Dao类 模拟数据库的操作
 * @author Tim
 *
 */
public class TestDao {//implements DataInterface {
	
	public static final int NORMAL_INSERT = 1;
	

	public TestDao() {
	}

	//模拟插入操作
	@DaoInsert
	public int doInsert(String name){
		System.out.println("dao do insert :" + name);
		return 1;
	}

	//模拟更新操作
	@DaoUpdate
	public int doUpdate(String name,String id){
		System.out.println("dao do doUpdate name :" + name + " and id :" + id);
		return 1;
	}

	//模拟查询操作
	@DaoQuery
	public String doQuery(String id){
		System.out.println("dao do doQuery id :" + id);
		return id;
	}
	
	//模拟删除操作
	@DaoDelete
	public Object doDelete(String id){
		System.out.println("dao do doDelete id :" + id);
		return 1;
	}

	
	
}

其中几个public方法模拟最基本的SQL操作,同时用到第一步定义的@DaoInsert,@DaoQuery,@DaoUpdate,@DaoDelete四个注解,稍后解释。

3)然后创建Cacher类

package com.tim.cacherAop.cacher;


/**
 * cacher类 模拟针对缓存进行操作 适用于Redis等缓存
 * @author Tim
 *
 */
public class TestCacher {//implements DataInterface {

	
	public static final int NORMAL_INSERT = 1;

	
	
	public TestCacher() {
		// TODO Auto-generated constructor stub
	}


	
	public int doInsert(String name){
		System.out.println("cacher do insert :" + name);
		return 1;
	}

	public int doUpdate(String name,String id){
		System.out.println("cacher do doUpdate name :" + name + " and id :" + id);
		return 1;
	}

	public String doQuery(String id){
		System.out.println("cacher do doQuery id :" + id);
		return id;
	}
	
	
	public Object doDelete(String id){
		System.out.println("cacher do doDelete id :" + id);
		return 1;
	}
	
	

}

稍仔细观察下即可发现,cacher类和dao类中方法名和方法参数均相同,这样做是为了解耦(更准确的说是方便调用),在第四步即可发现。

4)创建Hanlder类

package com.tim.cacherAop.Hanlder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.tim.cacherAop.annotiation.DaoDelete;
import com.tim.cacherAop.annotiation.DaoInsert;
import com.tim.cacherAop.annotiation.DaoQuery;
import com.tim.cacherAop.annotiation.DaoUpdate;
import com.tim.cacherAop.cacher.TestCacher;
import com.tim.cacherAop.dao.TestDao;


/**
 * 代理类 继承 net.sf.cglib.proxy.MethodInterceptor
 * @author Tim
 *
 */
public class DaoCglibHanlder implements MethodInterceptor {

	public DaoCglibHanlder() {
		// TODO Auto-generated constructor stub
	}

	
	private TestCacher cacher;
	private TestDao dao;

	
	
	 public static Object getInstance(TestCacher cacher, TestDao dao) {  
		 
	        DaoCglibHanlder hanlder = new DaoCglibHanlder();
	        hanlder.cacher = cacher;
	        hanlder.dao = dao;
	        
	        
	        Enhancer enhancer = new Enhancer();  
	        enhancer.setSuperclass(dao.getClass());  
	        // 回调方法  
	        enhancer.setCallback(hanlder);  
	        // 创建代理对象  
	        return enhancer.create();  
	    } 
	 
	 
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		Object result = null;

		try {
			
			//遍历注解 
			Annotation []annArray =  method.getDeclaredAnnotations();
			//遍历注解 
			//doCacherFirst = -1 表示 无注解; 
			//doCacherFirst = 0  表示 查询;
			//doCacherFirst = 1  表示 增 |改| 删;
			int doCacherFirst = -1;
			for (Annotation an : annArray) {
				Class<?> annClass = an.annotationType();
				if(annClass == DaoQuery.class){
					doCacherFirst = 0;
					break;
				}else if(annClass == DaoInsert.class 
						|| annClass == DaoUpdate.class
						|| annClass == DaoDelete.class){
					doCacherFirst = 1;
				}
			}
			
			
			//获取cacher中对应的方法
			String methodName = method.getName();
			Class<?> [] paramTypes = method.getParameterTypes();
			Method cacheMethod = cacher.getClass().getMethod(methodName, paramTypes);
			
			//cacher中有同名同参数的方法
			if(cacheMethod != null){
				//doCacherFirst == 0 先调用query方法 
				if(doCacherFirst == 0){
					result = cacheMethod.invoke(cacher, args);
				}
				//不为null时候 说明cacher中有数据,不用调用dao
				if(result == null){
					method.invoke(dao, args);					
				}
				//增 |改| 删 在处理完数据库后处理缓存
				if(doCacherFirst == 1){
					result = cacheMethod.invoke(cacher, args);
				}
				
			}else {
				result = method.invoke(dao, args);
			}
			

		} catch (Exception e) {
			result = null;
			e.printStackTrace();
		}
		
		return result;
	}

}

该类中有两个变量:testCacher和testDao。只要在getInstance()方法中将cacher对象和dao对象传入即可。

在调用dao的任意方法时,若该方法存在@DaoInsert,@DaoQuery,@DaoUpdate,@DaoDelete四个自定义注解中的任意一个,即会去testCacher对象中寻找同名同参数的方法(也就是说,cacher和dao之间方法的解耦方式,是两方法同名并且同参数。同时dao的方法记得加上注解)。同时根据注解,判断调用cacher的时机(详细见代码中注释)。

 

5)创建测试类

package com.tim.cacherAop.run;

import com.tim.cacherAop.Hanlder.DaoCglibHanlder;
import com.tim.cacherAop.cacher.TestCacher;
import com.tim.cacherAop.dao.TestDao;

public class GOGOGO {

	public GOGOGO() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		TestDao dao = new TestDao();
		TestCacher cacher = new TestCacher();
		TestDao d = (TestDao) DaoCglibHanlder.getInstance(cacher, dao);
		//插入操作
		System.out.println(d.doInsert("hihi"));
		System.out.println("/////////////////////////////");
		//更新操作
		System.out.println(d.doUpdate("双蛋龙", "001"));
		System.out.println("/////////////////////////////");
		//查询操作
		System.out.println(d.doQuery("001"));
		System.out.println("/////////////////////////////");
		//删除操作
		System.out.println(d.doDelete("001"));
		System.out.println("/////////////////////////////");
		
		
		
		/**
		 输出结果
		 	dao do insert :hihi
			cacher do insert :hihi
			1
			/////////////////////////////
			dao do doUpdate name :双蛋龙 and id :001
			cacher do doUpdate name :双蛋龙 and id :001
			1
			/////////////////////////////
			cacher do doQuery id :001
			001
			/////////////////////////////
			dao do doDelete id :001
			cacher do doDelete id :001
			1
			/////////////////////////////
		 */
	}

}

在测试时,通过DaoCglibHanlder的getInstance()方法,获取dao对象,然后直接调用即可。  

遗留的问题:

1) 在Main方法的调用处,dao和cacher依然存在需要解耦的问题。(感觉可以使用配置文件)
2)是否考虑将dao的实例化步骤放入DaoCglibHanlder类处,对应的getInstance()方法只传Class对象或者类名。因为dao对象实例化的时候需传入Connection对象,若执行query时,在cacher中存在数据,则不需要再获取数据库链接。

源码,问题一已解决