10.5.声明式事务管理
10.5. 声明式事务管理
大多数Spring用户选择声明式事务管理。这是对应用代码影响最小的选择,因此也最符合非侵入式轻量级容器的理念。
Spring的声明式事务管理是通过Spring AOP实现的,因为事务方面的代码与Spring绑定并以一种样板式风格使用,不过尽管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的声明式事务管理。
从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上下文调用setRollbackOnly()
方法。不同之处在于:
不像EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作。
Spring的声明式事务管理可以被应用到任何类(以及那个类的实例)上,不仅仅是像EJB那样的特殊类。
Spring提供了声明式的回滚规则:EJB没有对应的特性,我们将在下面讨论。回滚可以声明式的控制,不仅仅是编程式的。
Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用EJB CMT,除了使用
setRollbackOnly()
,你没有办法能够影响容器的事务管理。Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如果你需要这些特性,我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我们并不希望事务跨越远程调用。
TransactionProxyFactoryBean
在哪儿?
Spring2.0及以后的版本中声明式事务的配置与之前的版本有相当大的不同。主要差异在于不再需要配置TransactionProxyFactoryBean
了。
Spring2.0之前的旧版本风格的配置仍然是有效的;你可以简单地认为新的<tx:tags/>
替你定义了TransactionProxyFactoryBean
。
回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwables)将导致自动回滚。我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用TransactionStatus
的setRollbackOnly()
方法编程式地回滚当前事务。通常,我们定义一条规则,声明MyApplicationException
应该总是导致事务回滚。这种方式带来了显着的好处,它使你的业务对象不必依赖于事务设施。典型的例子是你不必在代码中导入Spring API,事务等。
对EJB来说,默认的行为是EJB容器在遇到系统异常(通常指运行时异常)时自动回滚当前事务。EJB CMT遇到应用异常(例如,除了java.rmi.RemoteException
外别的checked exception)时并不会自动回滚。默认式Spring处理声明式事务管理的规则遵守EJB习惯(只在遇到unchecked exceptions时自动回滚),但通常定制这条规则会更有用。
10.5.1. 理解Spring的声明式事务管理实现
本节的目的是消除与使用声明式事务管理有关的神秘性。简单点儿总是好的,这份参考文档只是告诉你给你的类加上@Transactional
注解,在配置文件中添加('<tx:annotation-driven/>'
)行,然后期望你理解整个过程是怎么工作的。此节讲述Spring的声明式事务管理内部的工作机制,以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静的水域。
提示
阅读Spring源码是理解清楚Spring事务支持的一个好方法。Spring的Javadoc提供的信息丰富而完整。我们建议你在开发自己的Spring应用时把日志级别设为'DEBUG'
级,这样你能更清楚地看到幕后发生的事。
在理解Spring的声明式事务管理方面最重要的概念是:Spring的事务管理是通过AOP代理实现的。其中的事务通知由元数据(目前基于XML或注解)驱动。代理对象与事务元数据结合产生了一个AOP代理,它使用一个PlatformTransactionManager
实现品配合TransactionInterceptor
,在方法调用前后实施事务。
注意
尽管使用Spring声明式事务管理不需要AOP(尤其是Spring AOP)的知识,但了解这些是很有帮助的。你可以在第 7 章 使用Spring进行面向切面编程(AOP)章找到关于Spring AOP的全部内容。
概念上来说,在事务代理上调用方法的工作过程看起来像这样:
10.5.2. 第一个例子
请看下面的接口和它的实现。这个例子的意图是介绍概念,使用Foo
和Bar
这样的名字只是为了让你关注于事务的用法,而不是领域模型。
<!-- 我们想做成事务性的服务/门面接口 --> package x.y.service; public interface FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
<!-- an implementation of the above interface --> package x.y.service; public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { throw new UnsupportedOperationException(); } public Foo getFoo(String fooName, String barName) { throw new UnsupportedOperationException(); } public void insertFoo(Foo foo) { throw new UnsupportedOperationException(); } public void updateFoo(Foo foo) { throw new UnsupportedOperationException(); } }
(对该例的目的来说,上例中实现类(DefaultFooService
)的每个方法在其方法体中抛出UnsupportedOperationException
的做法是恰当的,我们可以看到,事务被创建出来,响应UnsupportedOperationException
的抛出,然后回滚。)
我们假定,FooService
的前两个方法(getFoo(String)
和getFoo(String, String)
)必须执行在只读事务上下文中,其余方法(insertFoo(Foo)
和updateFoo(Foo)
)必须执行在读写事务上下文中。
使用XML方式元数据的声明式配置的话,你得这么写(不要想着一次全部理解,所有内容会在后面的章节详细讨论):
<!-- from the file'context.xml'
--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService"/> <!-- the transactional advice (i.e. what 'happens'; see the<aop:advisor/>
bean below) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with'get'
are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of an operation defined by theFooService
interface --> <aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.FooService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config> <!-- don't forget theDataSource
--> <bean id="dataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> <!-- similarly, don't forget the (particular)PlatformTransactionManager
--> <bean id="txManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- other<bean/>
definitions here --> </beans>
我们来分析一下上面的配置。我们要把一个服务对象('fooService'
bean)做成事务性的。我们想施加的事务语义封装在<tx:advice/>
定义中。<tx:advice/>
“ 把所有以'get'
开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中 ”。其中的'transaction-manager'
属性被设置为一个指向PlatformTransactionManager
bean的名字(这里指'txManager'
),该bean将实际上实施事务管理。
提示
事实上,如果PlatformTransactionManager
bean的名字是'transactionManager'
的话,你的事务通知(<tx:advice/>)中的'transaction-manager'
属性可以忽略。否则你则需要像上例那样明确指定。
配置中最后一段是<aop:config/>
的定义,它确保由'txAdvice'
bean定义的事务通知在应用中合适的点被执行。首先我们定义了 一个切面,它匹配FooService
接口定义的所有操作,我们把该切面叫做'fooServiceOperation'
。然后我们用一个通知器(advisor)把这个切面与'txAdvice'
绑定在一起,表示当'fooServiceOperation'
执行时,'txAdvice'
定义的通知逻辑将被执行。
<aop:pointcut/>
元素定义是AspectJ的切面表示法,可参考Spring 2.0 第 7 章 使用Spring进行面向切面编程(AOP)章获得更详细的内容。
一个普遍性的需求是让整个服务层成为事务性的。满足该需求的最好方式是让切面表达式匹配服务层的所有操作方法。例如:
<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>
(这个例子中假定你所有的服务接口定义在'x.y.service'
包中。你同样可以参考第 7 章 使用Spring进行面向切面编程(AOP)章获得更详细内容。)
现在,既然我们已经分析了整个配置,你可能会问了,“ 好吧,但是所有这些配置做了什么? ”。
上面的配置将为由'fooService'
定义的bean创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。
我们来看看下面的例子,测试一下上面的配置。
public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class); FooService fooService = (FooService) ctx.getBean("fooService"); fooService.insertFoo (new Foo()); } }
运行上面程序的输出结果看起来像这样(注意为了看着清楚,Log4j的消息和异常堆栈信息被省略了)。
<!-- the Spring container is starting up... --> [AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors <!-- theDefaultFooService
is actually proxied --> [JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService] <!-- ... theinsertFoo(..)
method is now being invoked on the proxy --> [TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo <!-- the transactional advice kicks in here... --> [DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo] [DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction <!-- theinsertFoo(..)
method fromDefaultFooService
throws an exception... --> [RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException [TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException] <!-- and the transaction is rolled back (by default,RuntimeException
instances cause rollback) --> [DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] [DataSourceTransactionManager] - Releasing JDBC Connection after transaction [DataSourceUtils] - Returning JDBC Connection to DataSource Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14) <!-- AOP infrastructure stack trace elements removed for clarity --> at $Proxy0.insertFoo(Unknown Source) at Boot.main(Boot.java:11)
10.5.3. 为不同的bean应用不同的事务语义
现在我们考虑一下这样的场景,你有许多服务对象,而且想为不同组的对象设置 完全不同 的事务语义。在Spring中,你可以通过定义各自特定的 <aop:advisor/>
元素,每个advisor采用不同的 'pointcut'
和 'advice-ref' 属性,来达到目的。
借助于一个例子,我们假定你所有的服务层类定义在以 'x.y.service'
为根的包内,为了让service包(或子包)下所有名字以 'Service'
结尾的类的对象(或者,更好的做法是,接口的实现类的对象)拥有默认的事务语义,你可以配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="serviceOperationWithDefaultTxSemantics"
expression="execution(* x.y.service..*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceOperationWithDefaultTxSemantics"
advice-ref="txAdvice"/>
</aop:config>
<!-- these two beans will have the transactional advice applied to them -->
<bean id="fooService"/>
<bean id="barService"/>
<!-- ...and these two beans won't -->
<bean id="fooService"/> <!-- (not in the right package) -->
<bean id="barService"/> <!-- (doesn't end in 'Service') -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a
PlatformTransactionManager
omitted... -->
</beans>
10.5.4. 使用@Transactional
注意
注意:@Transactional
注解及其支持类仅适用于Java5(Tiger)。
除了基于XML文件的声明式事务配置外,你也可以采用注解式的事务配置方法——通过@Transactional
注解。
直接在Java源代码中声明事务语义的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,典型情况下,被部署为事务性的代码几乎总是运行在事务环境中。
下面的例子很好地演示了 @Transactional
的易用性,随后解释其中的细节。先看看其中的接口定义:
<!-- the service (facade) interface that we want to make transactional --> @Transactional public interface FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
在Spring配置文件中,上面 FooService
的实现类的bean可以仅仅通过一 行xml配置为事务性的。如下:
<!-- from the file'context.xml'
--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven/> <!-- aPlatformTransactionManager
is still required --> <bean id="txManager"> <!-- sourced from somewhere else --> <property name="dataSource" ref="dataSource"/> </bean> <!-- other<bean/>
definitions here --> </beans>
@Transactional
可以被应用于接口定义和接口方法、类定义和类方法上。
注意
仅仅 @Transactional
的出现不足于开启事务行为,它仅仅是一种元数据,能够被可识别该注解并应用事务行为的代码所使用。
上面的例子中,其实正是 <tx:annotation-driven/>
元素的出现 开启 了事务行为。
为了符合Spring的核心原则之一,即“按直觉做事”,Spring中对@Transactional
的处理方式考虑了继承性,这是有意义的。如果你在类层次上给一个接口加了 @Transactional
注解,那么所有实现该接口的类将继承施加在接口上的事务设置。这与注解本来的含义截然不同,通常加在接口和方法上的注解 从不 会被继承。使用Spring,你可以通过指定自己的 @Transactional
来覆盖从接口或超类自动继承的事务设置。基本上,确定一个方法的事务语义时最优先考虑继承树上最末端的类。在下面的例子中,FooService
接口在类层次被注解为默认事务设置,但其实现类 DefaultFooService
的方法 updateFoo(Foo)
上的 @Transactional
却有更高的优先级,覆盖了从接口继承来的默认设置。
@Transactional(readOnly = true) public interface FooService { Foo getFoo(String fooName); void updateFoo(Foo foo); }
public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // do something } // these settings have precedence @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // do something } }
注意
注意,这种“纯粹直觉式”继承@Transactional
只适用于Spring AOP驱动的事务管理环境。如果你采用别的事务管理策略,例如AspectJ驱动事务,则关于Java注解的一般规则仍然起效(即:没有继承)。10.5.4.1. @Transactional
有关的设置
在最简单的形式下,@Transactional
指定一个接口、类、方法必须是事务性的,其默认事务语义为:read/write,PROPAGATION_REQUIRED
,ISOLATION_DEFAULT
,TIMEOUT_DEFAULT
,而且仅当遇到RuntimeException
时回滚,而不是Exception
。
改变事务设置的其他可选属性
表 10.1. Transactional
注解的属性
属性 | 类型 | 描述 |
---|---|---|
传播性 | 枚举型:Propagation | 可选的传播性设置 (默认值:PROPAGATION_REQUIRED ) |
隔离性 | 枚举型:Isolation | 可选的隔离性级别(默认值:ISOLATION_DEFAULT ) |
只读性 | 布尔型 | 读写型事务 vs. 只读型事务(默认值:false ,即只读型事务) |
回滚异常类(rollbackFor) | 一组 Class 类的实例,必须是Throwable 的子类 | 一组异常类,遇到时 确保 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException 的子类)才进行事务回滚。 |
回滚异常类名(rollbackForClassname) | 一组 Class 类的名字,必须是Throwable 的子类 | 一组异常类名,遇到时 确保 进行回滚 |
不回滚异常类(noRollbackFor) | 一组 Class 类的实例,必须是Throwable 的子类 | 一组异常类,遇到时确保 不 回滚。 |
不回滚异常类名(noRollbackForClassname) | 一组 Class 类的名字,必须是Throwable 的子类 | 一组异常类,遇到时确保 不 回滚 |
我们推荐你参考 @Transactional
注解的javadoc,其中详细列举了上述各项属性及其可选值。
10.5.5. 插入事务操作
考虑这样的情况,你有一个类的实例,而且希望 同时插入事务性通知(advice)和一些简单的剖析(profiling)通知。那么,在<tx:annotation-driven/>
环境中该怎么做?
我们调用 updateFoo(Foo)
方法时希望这样: a)、剖析(profiling)方面(aspect)的代码启动,然后 b)、进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),然后 c)、原始对象的方法执行,然后 d)、事务提交(我们假定这里一切正常),最后 e)、剖析方面(aspect)报告整个执行过程花了多少时间。
注意
这章不是专门讲述AOP的(除了应用于事务方面的之外)。请参考第 7 章 使用Spring进行面向切面编程(AOP) 章以获得对各种AOP配置及其一般概念的详细叙述。
这里有一份简单的(还不怎么成熟)剖析方面(profiling aspect)的代码。(注意,通知的顺序由 Ordered
接口控制。要想了解更多细节,请参考第 7.2.4.7 节 “通知(Advice)顺序”节。)
package x.y; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; import org.springframework.core.Ordered; public class SimpleProfiler implements Ordered { private int order; public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } public Object profile(ProceedingJoinPoint call) throws Throwable { Object returnValue; StopWatch clock = new StopWatch(getClass().getName()); try { clock.start(call.toShortString()); returnValue = call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } return returnValue; } }
这里是帮助满足我们上述要求的配置数据。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="fooService"/> <!-- the profiling advice --> <bean id="profiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <aop:config> <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/> <!-- will execute after the profiling advice (c.f. the order attribute) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/> <!-- order value is higher than the profiling aspects --> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- other <bean/> definitions such as aDataSource
and aPlatformTransactionManager
here --> </beans>
上面配置的结果是创建了一个 'fooService'
bean,剖析方面和事务方面被 依照顺序 施加其上。如果我们希望剖析通知在目标方法执行之前 后于 事务通知执行,而且在目标方法执行之后 先于 事务通知,我们可以简单地交换两个通知bean的order值。
如果配置中包含更多的方面,它们将以同样的方式受到影响。
10.5.6. 结合AspectJ使用@Transactional
通过 spring-aspects.jar
提供的AspectJ方面,你也可以在Spring容器之外使用Spring的 @Transactional
功能。要使用这项功能首先你得给相应的类型和方法加上 @Transactional
注解,然后把 spring-aspects.jar
中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect
方面连接进(织入)你的应用。同样,该方面必须配置一个事务管理器;你当然可以通过Spring注入,但因为我们这里关注于在Spring容器之外运行应用,我们将向你展示如何通过手动书写代码来完成。
注意
在我们继续之前,你可能需要好好读一下前面的第 10.5.4 节 “使用@Transactional
” 和 第 7 章 使用Spring进行面向切面编程(AOP) 两章。
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configure the AnnotationTransactionAspect
to use it, this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager (txManager);
使用此方面(aspect),@Transactional
可以把一个类或接口注解为事务性的,随后该类型定义的任何 公有 操作将拥有事务语义。单独一个 公有 方法的事务语义可以通过单独注解相应的方法定义。注意,如果注解了接口成员(不同于接口方法的实现方法),接口本身也应该被加上@Transactional
注解。
要把AspectJ织入你的应用,你或者基于AspectJ构建你的应用(参考AspectJ Development Guide),或者采取“载入时织入”(load-time weaving),参考第 7.7.4 节 “在Spring应用中使用AspectJ Load-time weaving(LTW)” 获得关于使用AspectJ进行“载入时织入”的讨论。