旧的“ @Transactional来自同一个班级”情况


问题内容

原始问题的提要:
使用具有AOP代理的标准Spring事务,不可能从同一类中的非@Transactional标记方法中调用@Transactional标记的方法,并且不能在事务内进行(特别是由于上述原因)代理)。据说在AspectJ模式下使用Spring
Transactions可以做到这一点,但是如何完成呢?

编辑: 使用 Load-Time Weaving 在AspectJ模式下进行Spring事务的完整摘要:

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

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

<context:load-time-weaver />

(我假设您已经在应用程序上下文中设置了AnnotationSessionFactoryBeanHibernateTransactionManager。您可以将其transaction- manager="transactionManager"作为属性添加到<tx:annotation-driven />标签中,但是如果您的事务管理器bean的id属性的值实际上是“ transactionManager”,那么就多余了,例如“
transactionManager”是该属性的默认值。)

添加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是不是需要为<dependency />你的classpath,但你仍然需要它 的地方 ,这样就可以在这点与-javaagentJVM标志,如下所示:

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

我正在Eclipse Juno中工作,因此要进行设置,我去了Window-> Preferences-> Java-> Installed
JRE。然后,我在列表框中单击选中的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);
  }
}

这里的技巧是,如果TestClassTestMain类中的is字段将在加载ClassLoader应用程序上下文之前由加载。由于编织是在类的加载时进行的,并且这种编织是通过Spring在应用程序上下文中完成的,因此不会被编织,因为在加载应用程序上下文之前就已经加载了该类并意识到了该类。

的进一步详情TestObjectTestDao不重要。假设它们与JPA和Hibernate批注连接在一起,并使用Hibernate进行持久化(因为它们确实存在,并且确实如此),并且所有必需项<bean />都已在应用程序上下文文件中设置。

编辑: 使用 编译时 编织在AspectJ模式下进行Spring事务的完整摘要:

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

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

(我假设您已经在应用程序上下文中设置了AnnotationSessionFactoryBeanHibernateTransactionManager。您可以将其transaction- manager="transactionManager"作为属性添加到<tx:annotation-driven />标签中,但是如果您的事务管理器bean的id属性的值实际上是“ transactionManager”,那么就多余了,例如“
transactionManager”是该属性的默认值。)

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项目。

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

<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构建路径-> Aspect
Path选项卡->按下“添加外部JAR …”->找到full/path/of/spring- aspects-3.1.2.RELEASE.jar->按下“打开”->按下“确定”。

如果您选择了Maven路线,那么<plugin />上面的内容应该很奇怪。为了解决这个问题:帮助- >安装新软件… - >按“添加…” -

键入任何你喜欢的标记文本框“名称:” - >键入或复制粘贴+
http://dist.springsource.org/release/AJDT/configurator/在标文本框“位置:“->按“确定”->等待一秒钟->选中“针对Eclipse
AJDT集成的Maven集成”旁边的父复选框->按“下一步>”->安装->等等。

安装插件后,并且重新启动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时,都会不断重新编译您的代码(曾经想知道它在做什么时会说“正在构建工作区:(XX%)”吗?),因此它可以随时随地进行编译。

就像在装载时例如:进一步细节TestObjectTestDao不重要。假设它们与JPA和Hibernate批注连接在一起,并使用Hibernate进行持久化(因为它们确实存在,并且确实如此),并且所有必需项<bean />都已在应用程序上下文文件中设置。


问题答案:

通过阅读您的问题,还不清楚您到底处在何处,因此我将简要列出使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.jaraspectjweaver.jarspring-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很麻烦。