spring和aspectj的结合inject原理(上篇)

     最近研究下aspectj,特别是对里面的代码编织和上下文控制有兴趣。后面看了下spring对aspectj的支持,以及简单了解了一下spring与aspectj结合的产品spring roo。这里主要介绍一下由aspectj支持的@transactional注释和@configurable原理,具体它们之间的关系以及由此衍生的domain静态编织等。

     首先,介绍一下spring最常用的即是它的事务控制,我想除了依赖注入之外,这也是使用srping的第二个原因了,同时,也是我们的service存在的很大的理由了。很多的项目,均存在一层service事务控制层,通常情况下,相应的事务即是通过在service里写事务访问脚本,然后在外层包装事务的具体控制。如下的一句控制,就是一个很强大的控制了:

<aop:config proxy-target-class="true">
		<aop:pointcut id="servicePointcut"
					  expression="execution(public * com.greejoy..BaseService+.*(..)) and !execution(public void com.greejoy..BaseService+.set*(..))"/>
		<aop:advisor advice-ref="serviceAssertExceptionThrowsInterceptor" pointcut-ref="servicePointcut"/>
		<aop:advisor advice-ref="tx" pointcut-ref="servicePointcut"/>
	</aop:config>

    在上面的xml代码中,首先声明了事务控制点(这里没有使用接口),即所有的service实现类(即所有继承了指定service类)的方法,并且不对set*方法进行事务控制(因为这是spring的依赖注入方法)。这样,spring就会将现有系统中的所有service追加上事务,并且在将来的service也同样不需要再进行声明,即加入了相应的事务控制代码。
    不过,需要说明的即是,这里实现的事务控制,是由spring应用proxy模式来实现的,即spring通过建立proxy并接管了原来的类来达到程序中进行事务控制的目的。如果,不使用proxy模式怎么做呢,那就使用代码编织技术,直接将相应的事务控制代码编织入原来的代码中,这样最终运行的service代码就是已经有事务控制的了。
    ps:这里的proxy和代码编织不是同一个意思,proxy是在保持原有class不变的情况下,新生成一个proxy类来进行相应的操作;而代码编织是直接修改原有的class,来达到代码增强的目的。

     A:对Service代码的事务支持
    使用spring2.5以上的朋友均知道,对于一些没有进行spring事务配置的方法,只需要在方法上追加一句@Transactional注解,同样可以达到事务控制的目的。当然,还需要在xml文件中声明一些spring的aspectj支持。如下所示:

/** 保存 */
	@Transactional
	public void save() {
		directSave();
	}
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<!-- 或者 -->
<bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
	<property name="transactionManager" ref="transactionManager"/>
</bean>

     对于java代码,没什么说的。对于xml代码,两种配置方式是一样的。使用第一种配置时,spring实际上就会创建一个第二种配置的一个bean,并交由spring进行初始化。我们都知道由aspectj来进行代码编织的话,就应该声明相应的编织器,即将哪些代码编织至目标类中。寻找aop.xml文件,在spring-aspectj jar的META-INF中寻找到spring自身声明的aop.xml文件,如下所示:

<aspects>
		<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/> <!-- 类2    第二种注入方式 -->
		<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>    <!-- 类1    第一种注入方式 -->
	</aspects>

        查看类1,这个类就是我们在xml中声明的类,这个类是由spring使用aspectj语法书写,并最终通过ajc编译之后装入jar中的。asjectj在进行代码编织中,并由这个类对符合相应切入点的方法进行编织。一般来说,对于一个@transactional方法,主要的编织就是在进入方法前创建并开启transaction,在完成之后提交事务(当然还要处理异常等信息)。创建事务,开启事务,提交事务,回滚事务这些方法都可以由抽象方法进行实现,而实际的类只需要将这些事务方法编织(或者插入)到需要事务的方法前后即可。这个抽象类即是org.springframework.transaction.interceptor.TransactionAspectSupport,可以具体查看其中的相应代码。
    在proxy模式中,TransactionInterceptor会负责提供相应的interceptor并将需要进行事务控制的service一起生成proxy;而在aspectj中,由是由AnnotationTransactionAspect负责绑定相应的pointcut并调用相应的事务方法,相应point如下所示:

//绑定声明类上有@Transactional的类的任何方法
private pointcut executionOfAnyPublicMethodInAtTransactionalType() :
		execution(public * ((@Transactional *)+).*(..)) && @this(Transactional);
//绑定声明的方法上有@Transactional注解的方法
private pointcut executionOfTransactionalMethod() :
		execution(* *(..)) && @annotation(Transactional);
//以上两个方法的组合
protected pointcut transactionalMethodExecution(Object txObject) :
		(executionOfAnyPublicMethodInAtTransactionalType()
		 || executionOfTransactionalMethod() )
		 && this(txObject);
//进入方法前,创建事务
before(Object txObject) : transactionalMethodExecution(txObject) {
		MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
		Method method = methodSignature.getMethod();
		TransactionInfo txInfo = createTransactionIfNecessary(method, txObject.getClass());
	}
//当有异常发生时,处理事务
after(Object txObject) throwing(Throwable t) : transactionalMethodExecution(txObject) {
      completeTransactionAfterThrowing(TransactionAspectSupport.currentTransactionInfo(), t);
}
//当成功返回时,提交事务
after(Object txObject) returning() : transactionalMethodExecution(txObject) {
	commitTransactionAfterReturning(TransactionAspectSupport.currentTransactionInfo());
}
//最后,清理相关资源
after(Object txObject) : transactionalMethodExecution(txObject) {
	cleanupTransactionInfo(TransactionAspectSupport.currentTransactionInfo());
}

     熟悉aspectj的朋友均可以看懂上面的语句,即是绑定切入点,并编织相应的代码。这些绑定由aspectj来进行,并不需要spring的干预。spring只需要将aspectj通过某种方式引入至项目中并进行编织即可(如通过ltw方式进行编织)。重点在于,我们都知道,事务控制需要有transactionManager,在这些代码中并没有声明transactionManager,而aspectj也不可能通过spring来进行所谓的依赖注入,所以上面这些代码在运行时不会起任何作用,除非能够获取到相应的transactionManager。这时,需要注意的是,我们在spring中有相应的配置,即配置这个AnnotationTransactionAspect类的transactionManager属性,并且声明这个类为eager load且初始化方法为工厂方法aspect-of。
    其实对aspectj更熟悉的人应该知道,aspectj中对于aspect在运行时只有一个实例,即它是singleton的,而获取这个singleton的方式就是通过工厂方法aspect-of。这样,我们有个大胆的猜测:spring获取的实例就是aspectj实际使用的实例,即它们两个是共用实例的!更直接一点,aspectj在进行编织之后,spring再取得相应的aspect实例,并注入相应的属性,这样在运行时,相应的编织类即可直接获取spring容器中的对象了!实际上的确是这样!
    spring容器在初始化之后,会默认的初始化所有的single实例,这里它就会初始化xml文件中声明的AnnotationTransactionAspect实例,而这个实例通过工厂方法aspect-of获取。而aspect-of方法(如果你通过工具对其反编译),会返回内部的一个单例对象(它不同于普通的工厂模式会new一个对象),这里就获取了实际的aspect对象。然后,spring再通过配置文件注入相应的属性,这里就是我们所需要的transactionManager。所以,之所以在xml对这个annotationAspect进行配置的根本目的,就在于运用spring的属性注入,而不是通常情况下所需要的容器对象。
    实际上,可以简单的通过测试代码或者debug进行测试,当在xml中声明配置信息之后,在进入到事务方法中时,通过debug可以看出相应的aspect已经有transactionManager属性了。而没有配置时,则不会有相应的transactionManager属性,并且如果在spring debug模式下,你会看到终端有一个提示(即没有transactionManager属性,该事务将路过的意思,只不过通常情况下看不到,会有一种没有进行事务编织的假象)。

     至于类2,则在下一篇进行讲解,即新建的对象,即可直接方法对象内的spring对象(即在建立之后就自动的进行了注入),同时有一个简单的rich domain例子:)
    下一篇地址

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/the-theory-about-spring-and-aspectj-pre.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注