menu

Samson

还没长大就老了

Avatar

AOP的应用

1 关于AOP

AOP(Aspect Oriented Programing),面向切面编程,是一个编程思想。简单地说就是在做一件事情之前、之中或之后插入别的要做的事情。

具体地,可以定义一个方法执行之前要做什么事情,执行完成后要做什么事情,有必要的话还能定义在这个方法执行之中要做什么事情。

AOP需要有技术来支持。目前用于支持AOP思想的技术主要有三种:AspectJ、CGLib和java动态代理。其中,

AspectJ是一种语言,可以说AspectJ是最贴近AOP思想的。AspectJ有自己的一套关键字来定义AOP的相关元素,比如一个Aspect切面。AspectJ是对一些类和方法定义切面,编译的时候是先把“被定义”的类(目标类)编译成class文件,然后再结合定义好的切面信息对之前的这些class文件再次编译,生成新的class文件。那些定义的切面信息其实已经被直接写入到最后编译出来的class文件里。个人认为这个过程要对在目标类的class文件进行再次编译解析成新的class,具体使用起来有些不太方便。

AspectJ是直接修改目标类的class文件来达到AOP的效果的,而CGLib和java动态代理都是通过代理目标类来实现的。

CGLib(Code Generation Library),字节码生成库,是第三方写的开源包。原理是使用字节码技术动态创建目标类的子类,运行时的类型已经不是目标类,而是目标类的子类。然后,CGLib允许定制子类的执行方法,在执行父类(目标类)的方法之前和之后切入(插入)要做的工作。所以CGLib是对类进行代理,通过动态创建该类的子类来达到代理的效果。下面说到的java动态代理则是对接口进行代理。

Java动态代理是java jdk自带的,就在reflect包里。Java动态代理是对接口进行代理,根据传入的接口类型动态生成一个实现类,然后可以定制这个实现类的一个回调接口。在定制这个回调接口时,就可以在调用目标类方法时,在目标类方法之前和之后加入一些要做的工作。

目前用的比较多是CGLib和java动态代理,spring和hibernate都有用到。

2 具体应用

2.1 数据库连接和事务管理

利用AOP的相关实现技术,获取和释放数据库连接、申请事务、提交事务或撤销事务这种工作可以放在一个集中的地方来做。把这些工作放在主流程之外去定义。然后在主流程的代码里再也看不到connection这种东西,当然也不用去关心跟connection有关的工作了。

具体做法上,首先对业务方法进行配置,定义该方法是否需要事务。配置内容如下:
<class>
<name>com.xx.xx.BankBizlogic</name>
<method>
<name>add</name>
<transaction>true</transaction>
……

上面配置的意思是定义BankBizlogic类的add方法是需要事务的。

这样如果add方法正常执行完成则提交事务,如果中间抛出异常未能正常执行完则会撤销事务。

这个具体是采用CGLib对业务类进行代理,即目标类是业务类。下面的代码是CGLib回调接口的定制实现,先来看看:

/**
	* CGLib回调函数
*/
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable
{
     //目标类方法执行前要做的工作
     //比如获取连接,是否要申请事务等
    MethodConfigInfo methodConfigInfo = 
              ResourceManager.beforeBizMethod(superClass,obj,method,args);

	 Object result = null;
	 try
	 {
         //通过代理类调用父类即目标类中的方法
	     result = proxy.invokeSuper(obj, args); 

	      //目标类方法正常执行完后要做的工作
	    //比如提交事务,关闭连接				 
               ResourceManager.afterBizMethodForNormal(methodConfigInfo);
	  }catch(Exception e)
	  {	
         //目标类方法出现异常,则做撤销事务等工作
	     ResourceManager.afterBizMethodForException(methodConfigInfo,
	 e);
	        throw e;
	  }			
	  return result;
}


上面的方法是定制的CGLib回调接口。可见其中beforeBizMethod、afterBizMethodForNormal和afterBizMethodForException分别在调用目标类方法proxy.invokerSuper之前和之后执行。

beforeBizMethod会根据配置文件信息,决定该目标类的业务方法是否需要事务,如果需要则获取连接,启动事务(把连接的自动提交属性设为false)。来看看beforeBizMethod的部分代码:

if(methodConfigInfo != null)
{
	//判断当前线程是否需要启动事务,如果需要:	
	if(Boolean.valueOf(methodConfigInfo.getTransaction()).booleanValue())
	{				
		  //如果还没初始化过,则通知DBConnManager启动一个事务
		  if(resourceInfo.getConnection() == null)
		  {
		        DBConnManager.startTransaction();
		  }
	 }
	}


afterBizMethodForNormal/ForException则做一些数据库连接的收尾工作,比如提交/撤销事务,释放连接。这里不再细述。

这里会有一个问题,就是怎么可以使目标类里的方法使用的数据库连接就是之前beforeBizMethod方法获取的连接,而afterBizMethod方法释放的也是同一个连接呢?

这里其实还用到了java jdk提供的一个类ThreadLocal,这个类的作用是保存线程的资源。在对一个线程执行过程中,之前在某个地方往ThreadLocal实例里存放了资源对象,之后在别的地方仍然可以通过ThreadLocal拿回之前存放的资源。

其实ThreadLocal的基本原理也就是一个Map,然后key值是Thread.currentThread。所以每个线程都能随时随地访问和设置各自的资源,而不会搞串。只是这个Map其实是存放在current thread中的,是当前Thread对象的一个包可见属性,所以当current thread执行完毕要被回收时,这个Map也会被回收。

2.2 日志功能

跟传统的日志不一样,这里说的日志是可以根据请求处理类型分类产生不同的日志文件。

具体的做法和上面的数据库连接管理类似。也是根据配置文件的信息,决定日志文件的输出位置。

和上面数据库连接的管理一样,日志文件的建立和关闭这些工作在一般编程时也不用关心,只需要调用日志工具类提供的info或error方法进行日志输出。

配置文件内容如下:
<class>
<name>com.xx.xx.BankBizlogic</name>
<method>
<name>add</name>
<transaction>true</transaction>
<logdir>addbank</logdir>
<newfile>true</newfile>
</method>
<method>
<name>findByID</name>
<transaction>false</transaction>
<logdir>querybank</logdir>
<newfile>false</newfile>
</method>
</class>

这个配置文件和上面提到的配置文件是同一个配置文件。其中配置项logdir是方法的日志输出目录,配置项newfile意思为是否该方法每次执行时都需要新建日志文件,因为有些请求处理类型是没有必要每次处理请求都需要新建日志文件的。

日志文件的文件名格式是:HHMMSS_NO,其中NO是请求序号,为了防止文件重名。另外同一天产生的日志会被放在同一个文件夹下,比如在2007-10-6 12:47:30发生第一次add方法的请求,日志会输出在serverlog/20071006/addbank/124730_1.txt文件里。

这样上面的配置指明add方法日志将被输出到addbank目录下,而且每次add被执行时都会新建一个日志文件,findByID方法的日志会被输出到querybank目录下,所有该方法的日志会被输出到同一个文件(因为配置项newfile为false)。

而编程时,在日志的使用上则非常简单,只需要像使用传统的日志一样。目前的日志只提供了两个方法:

LogTracker.info(Class exeClass, String content)
LogTracker.error(Class exeClass, String content, Exception exception)

这两个方法都是静态方法,可以直接调用。

另外,对于没有配置的方法,它们的日志会被输出到当天的一个公共文件里。下图是一个日志输出示例:

serverlog是日志的根目录,这个目录下面是按日期产生的每天的日志,然后日期文件夹下是各类请求的日志。

3 小结

虽然上面说的一些功能效果比较好,但是还是有条件的。就是要遵循本身框架的一些约定来进行,比如要实现对Bizlogic层(业务层) 的代理就得调用Bizlogic层的代理类,所以层间调用会有严格的要求。

另外,上面日志有一个地方是可以改进的,目前的做法是所有日志文件的文件名格式都是HHMMSS_NO,而有些频繁发生的业务可能要求用到别的信息来命名日志文件,比如业务编号等。所以还是可以对日志文件名进行定制和配置。

呵呵,不错,偶正在看Spring的AOP

夫唱妇随啊~好不亲切。连沙发都不让给外人~~~只好坐门口了

顺便说一下,AspectJ是我本科毕业时候学习的,翻译的英文文章就是关于AspectJ的,

突然想唱歌:“树上的鸟儿成双对,绿水青山啊把家还啊……”

v似乎正在逃离技术。这到底是好是坏呢?~

毕业就搞了,Richard好前卫啊

v,逃离技术也没什么不好的,如果可以的话,我就开个台球馆养老了

台球?!汗……还好我会打~