12

原始问题的概要:使用带有AOP代理的标准Spring Transactions,不可能从同一类中的非@Transactional-marked方法调用@Transactional-marked方法并且在事务中(特别是由于前面提到的代理人)。这在 AspectJ 模式下的 Spring Transactions 中是可能的,但它是如何完成的呢?

编辑:在 AspectJ 模式下使用Load-Time Weaving的 Spring Transactions 的完整纲要:

将以下内容添加到META-INF/spring/applicationContext.xml

<tx:annotation-driven mode="aspectj" />

<context:load-time-weaver />

(我假设您已经在应用程序上下文中设置了一个AnnotationSessionFactoryBean和是该属性的默认值。)HibernateTransactionManagertransaction-manager="transactionManager"<tx:annotation-driven />idtransactionManagertransactionManager

添加META-INF/aop.xml. 内容如下:

<aspectj>
  <aspects>
    <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
  </aspects>
  <weaver>
    <include within="my.package..*" /><!--Whatever your package space is.-->
  </weaver>
</aspectj>

aspectjweaver-1.7.0.jar将和添加spring-aspects-3.1.2.RELEASE.jar到您的classpath. 我使用 Maven 作为我的构建工具,所以这里是<dependency />你项目POM.xml文件的声明:

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.7.0</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>3.1.2.RELEASE</version>
</dependency>

spring-instrument-3.1.2.RELEASE.jar不需要作为 a<dependency />在您的 上classpath,但您仍然需要它以便您可以使用-javaagentJVM 标志指向它,如下所示:

-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar

我在 Eclipse Juno 中工作,所以要设置它,我转到 Window -> Preferences -> Java -> Installed JREs。然后我点击列表框中选中的JRE,然后点击列表框右侧的“编辑...”按钮。结果弹出窗口中的第三个文本框标记为“默认 VM 参数:”。这是-javaagent应该输入或复制+粘贴标志的地方。

现在是我的实际测试代码类。首先,我的主要课程TestMain.java

package my.package;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMain {
  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
    TestClass testClass = applicationContext.getBean(TestClass.class);
    testClass.nonTransactionalMethod();
  }
}

然后是我的事务类TestClass.java

package my.package;

import my.package.TestDao;
import my.package.TestObject;
import org.springframework.transaction.annotation.Transactional;

public void TestClass {
  private TestDao testDao;

  public void setTestDao(TestDao testDao) {
    this.testDao = testDao;
  }

  public TestDao getTestDao() {
    return testDao;
  }

  public void nonTransactionalMethod() {
    transactionalMethod();
  }

  @Transactional
  private void transactionalMethod() {
    TestObject testObject = new TestObject();
    testObject.setId(1L);
    testDao.save(testObject);
  }
}

这里的技巧是,如果它TestClass是其类中的一个字段,TestMain则将在加载ClassLoader应用程序上下文之前加载。由于编织是在类的加载时进行的,并且这种编织是由 Spring 通过应用程序上下文完成的,所以它不会被编织,因为在加载应用程序上下文并知道它之前,该类已经加载。

TestObject和的进一步细节TestDao并不重要。假设它们与 JPA 和 Hibernate 注释连接起来,并使用 Hibernate 进行持久性(因为它们是,而且它们确实如此),并且所有必需<bean />的 's 都在应用程序上下文文件中设置。

编辑:在 AspectJ 模式下使用编译时编织的 Spring Transactions 的完整纲要:

将以下内容添加到META-INF/spring/applicationContext.xml

<tx:annotation-driven mode="aspectj" />

(我假设您已经在应用程序上下文中设置了一个AnnotationSessionFactoryBean和是该属性的默认值。)HibernateTransactionManagertransaction-manager="transactionManager"<tx:annotation-driven />idtransactionManagertransactionManager

spring-aspects-3.1.2.RELEASE.jar将和添加aspectjrt-1.7.0.jar到您的classpath. 我使用 Maven 作为我的构建工具,所以这里是文件的<dependency />声明POM.xml

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.7.0</version>
</dependency>

在 Eclipse Juno 中:帮助 -> Eclipse Marketplace -> 标有“查找:”的文本框 -> 键入“ajdt” -> 点击 [Enter] -> “AspectJ 开发工具 (Juno)” -> 安装 -> 等等。

重新启动 Eclipse(它会让你)后,右键单击你的项目以调出上下文菜单。查看底部附近:配置 -> 转换为 AspectJ 项目。

在您的(再次使用 Maven!)中添加以下<plugin />声明:POM.xml

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>aspectj-maven-plugin</artifactId>
  <version>1.4</version>
  <configuration>
    <aspectLibraries>
      <aspectLibrary>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
      </aspectLibrary>
    </aspectLibraries>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
        <goal>test-compile</goal>
      </goals>
    </execution>
  </executions>
</plugin>

替代方法:右键单击您的项目以调出上下文菜单。查看底部附近:AspectJ 工具 -> 配置 AspectJ 构建路径 -> 方面路径选项卡 -> 按“添加外部 JAR...” -> 找到full/path/of/spring-aspects-3.1.2.RELEASE.jar-> 按“打开” -> 按“确定”。

如果你走 Maven 路线,<plugin />上面的内容应该会吓坏了。要解决此问题:帮助 -> 安装新软件... -> 按“添加...” -> 在标有“名称:”的文本框中键入您喜欢的任何内容 -> 在标有“的文本框中键入或复制+http://dist.springsource.org/release/AJDT/configurator/粘贴位置:”-> 按“确定”-> 稍等-> 选中“Maven Integration for Eclipse AJDT Integration”旁边的父复选框-> 按“下一步>”-> 安装-> 等等。

安装插件并重新启动 Eclipse 后,POM.xml文件中的错误应该已经消失。如果没有,请右键单击您的项目以调出上下文菜单:Maven -> 更新项目 -> 按“确定”。

现在是我的实际测试代码类。这次只有一个,TestClass.java

package my.package;

import my.package.TestDao;
import my.package.TestObject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.annotation.Transactional;

public void TestClass {
  private TestDao testDao;

  public void setTestDao(TestDao testDao) {
    this.testDao = testDao;
  }

  public TestDao getTestDao() {
    return testDao;
  }

  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
    TestClass testClass = applicationContext.getBean(TestClass.class);
    testClass.nonTransactionalMethod();
  }

  public void nonTransactionalMethod() {
    transactionalMethod();
  }

  @Transactional
  private void transactionalMethod() {
    TestObject testObject = new TestObject();
    testObject.setId(1L);
    testDao.save(testObject);
  }
}

这个没有技巧。由于编织发生在编译时,即在类加载和应用程序上下文加载之前,这两件事的顺序不再重要。这意味着所有东西都可以放在同一个类中。在 Eclipse 中,每次您点击 Save 时,您的代码都会不断地重新编译(有没有想过它在说“Building workspace: (XX%)”时它在做什么?),所以它已经被编织并随时可以使用。

就像在加载时间示例中一样: 和 的更多细节TestObject并不TestDao重要。假设它们与 JPA 和 Hibernate 注释连接起来,并使用 Hibernate 进行持久性(因为它们是,而且它们确实如此),并且所有必需<bean />的 's 都在应用程序上下文文件中设置。

4

1 回答 1

9

通过阅读您的问题,您并不清楚您卡在哪里,所以我将简要列出让 AspectJ 拦截您的@Transactional方法所需的内容。

  1. <tx:annotation-driven mode="aspectj"/>在你的 Spring 配置文件中。
  2. <context:load-time-weaver/>以及在您的 Spring 配置文件中。
  3. 直接位于类路径中 META-INF 文件夹中的 aop.xml。这里也解释了它的格式。它应该包含处理 @Transactional注释的方面定义:<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
  4. 同一个文件中的 weaver 元素还应该有一个 include 子句,告诉它要编织哪些类:<include within="foo.*"/>
  5. aspectjrt.jar, aspectjweaver.jar,spring-aspects.jarspring-aop.jar在类路径中
  6. 使用标志启动应用程序-javaagent:/path/to/spring-instrument.jar(或 spring-agent,因为它在早期版本中被调用)

最后一步可能不是必需的。这是一个非常简单的类,可以使用InstrumentationLoadTimeWeaver,但如果不可用,Spring 将尝试使用另一个加载时间编织器。不过,我从未尝试过。

现在,如果你认为你已经完成了所有步骤但仍然有问题,我可以建议在 weaver 上启用一些选项(在 aop.xml 中定义):

<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">

这使得编织器输出一堆正在编织的信息。如果您看到正在编织的课程,您可以在TestClass那里寻找。然后,您至少有一个起点来继续进行故障排除。


关于您的第二次编辑,“在班级尝试执行之前,几乎就像编织的速度不够快,无法编织。”,答案是肯定的,这可能会发生。我以前经历过这样的情况

我对细节有点生疏,但基本上是 Spring 无法编织在创建应用程序上下文之前加载的类。您是如何创建应用程序上下文的?如果您以编程方式执行此操作,并且该类具有对 的直接引用TestClass,则可能会出现此问题,因为TestClass加载得太早了。

不幸的是,我发现调试 AspectJ 简直就是地狱。

于 2012-07-26T18:48:34.157 回答