旧的“@Transactional from the same class"情况 [英] The Old "@Transactional from within the same class" Situation

查看:27
本文介绍了旧的“@Transactional from the same class"情况的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

在 AspectJ 模式下使用 Load-Time 编织的 Spring 事务的完整纲要:

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

<context:load-time-weaver/>

(我假设您已经在应用程序上下文中设置了一个 AnnotationSessionFactoryBean 和一个 HibernateTransactionManager.您可以添加 transaction-manager="transactionManager" 作为 <tx:annotation-driven/> 标记的属性,但是如果您的事务管理器 bean 的 id 属性的值实际上是transactionManager",那么它是多余的,因为 "transactionManager" 是该属性的默认值.)

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

<方面><aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/></方面><织布工><include inside="my.package..*"/><!--无论您的包裹空间是什么.--></weaver></aspectj>

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

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

spring-instrument-3.1.2.RELEASE.jar 不需要作为 在您的 classpath 上,但您仍然需要某处,以便您可以使用 -javaagent JVM 标志指向它,如下所示:

-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;导入 org.springframework.context.ApplicationContext;导入 org.springframework.context.support.ClassPathXmlApplicationContext;公共类 TestMain {公共静态无效主(字符串 [] args){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");TestClass testClass = applicationContext.getBean(TestClass.class);testClass.nonTransactionalMethod();}}

然后是我的事务类,TestClass.java:

package my.package;导入 my.package.TestDao;导入 my.package.TestObject;导入 org.springframework.transaction.annotation.Transactional;公共无效测试类{私有TestDao testDao;public void setTestDao(TestDao testDao) {this.testDao = testDao;}公共 TestDao getTestDao() {返回 testDao;}public void nonTransactionalMethod() {交易方法();}@交易私有无效事务方法(){TestObject testObject = new TestObject();testObject.setId(1L);testDao.save(testObject);}}

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

TestObjectTestDao 的进一步细节并不重要.假设它们与 JPA 和 Hibernate 注释连接并使用 Hibernate 进行持久化(因为它们是,而且它们确实如此),并且所有必需的 都设置在应用上下文文件.

使用编译时编织的 AspectJ 模式下 Spring 事务的完整纲要:

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

(我假设您已经在应用程序上下文中设置了一个 AnnotationSessionFactoryBean 和一个 HibernateTransactionManager.您可以添加 transaction-manager="transactionManager" 作为 <tx:annotation-driven/> 标记的属性,但是如果您的事务管理器 bean 的 id 属性的值实际上是transactionManager",那么它是多余的,因为 "transactionManager" 是该属性的默认值.)

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

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

在 Eclipse Juno 中:Help -> Eclipse Marketplace -> 标记为Find:"的文本框 -> 输入ajdt" -> 按 [Enter] -> AspectJ Development Tools (Juno)" -> Install -> Etc.

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

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

<groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.4</version><配置><方面库><方面库><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></aspectLibrary></aspectLibraries></配置><执行><执行><目标><目标>编译</目标><目标>测试编译</目标></目标></执行></执行></插件>

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

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

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

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

package my.package;导入 my.package.TestDao;导入 my.package.TestObject;导入 org.springframework.context.ApplicationContext;导入 org.springframework.context.support.ClassPathXmlApplicationContext;导入 org.springframework.transaction.annotation.Transactional;公共无效测试类{私有TestDao testDao;public void setTestDao(TestDao testDao) {this.testDao = testDao;}公共 TestDao getTestDao() {返回 testDao;}公共静态无效主(字符串 [] args){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");TestClass testClass = applicationContext.getBean(TestClass.class);testClass.nonTransactionalMethod();}public void nonTransactionalMethod() {交易方法();}@交易私有无效事务方法(){TestObject testObject = new TestObject();testObject.setId(1L);testDao.save(testObject);}}

这个没有窍门;由于编织发生在编译时,即在类加载和应用程序上下文加载之前,这两件事的顺序不再重要.这意味着所有东西都可以放在同一个类中.在 Eclipse 中,每次点击保存"时,您的代码都会不断地重新编译(有没有想过它在做什么时说正在构建工作区:(XX%)"?),所以它已经编织好了,随时可以使用.

就像在加载时间示例中一样:TestObjectTestDao 的进一步细节并不重要.假设它们与 JPA 和 Hibernate 注释连接并使用 Hibernate 进行持久化(因为它们是,而且它们确实如此),并且所有必需的 都设置在应用上下文文件.

解决方案

通过阅读您的问题,您并不清楚您卡在哪里,因此我将简要列出让 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 inside="foo.*"/>
  5. aspectjrt.jaraspectjweaver.jarspring-aspects.jarspring-aop.jar在类路径中
  6. 使用标志 -javaagent:/path/to/spring-instrument.jar(或 spring-agent,在早期版本中被调用)启动应用程序

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

现在,如果您认为您已完成所有步骤但仍有问题,我建议您在织布机上启用一些选项(在 aop.xml 中定义):

这使得编织器输出一堆正在编织的信息.如果您看到正在编织的类,您可以在那里查找您的 TestClass.那么您至少有一个起点可以继续进行故障排除.

<小时>

关于您的第二次编辑,这几乎就像编织的速度不够快,无法在班级尝试执行之前编织.",答案是,这可能会发生.我之前遇到过这样的情况.

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

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

Synopsis of the original question: Using standard Spring Transactions with AOP proxying, it is not possible to call an @Transactional-marked method from a non-@Transactional-marked method in the same class and be within a transaction (specifically due to the aforementioned proxy). This is supposedly possible with Spring Transactions in AspectJ mode, but how is it done?

Edit: The full rundown for Spring Transactions in AspectJ mode using Load-Time Weaving:

Add the following to META-INF/spring/applicationContext.xml:

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

<context:load-time-weaver />

(I'll assume you already have an AnnotationSessionFactoryBean and a HibernateTransactionManager set up in the application context. You can add transaction-manager="transactionManager" as an attribute to your <tx:annotation-driven /> tag, but if the value of your transaction manager bean's id attribute is actually "transactionManager", then it's redundant, as "transactionManager" is that attribute's default value.)

Add META-INF/aop.xml. Contents are as follows:

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

Add aspectjweaver-1.7.0.jar and spring-aspects-3.1.2.RELEASE.jar to your classpath. I use Maven as my build tool, so here are the <dependency /> declarations for your project's POM.xml file:

<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 is not needed as a <dependency /> on your classpath, but you still need it somewhere so that you can point at it with the -javaagent JVM flag, as follows:

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

I'm working in Eclipse Juno, so to set this I went to Window -> Preferences -> Java -> Installed JREs. Then I clicked on the checked JRE in the list box and clicked the "Edit..." button to the right of the list box. The third text box in the resulting popup window is labeled "Default VM arguments:". This is where the -javaagent flag should be typed or copy+pasted in.

Now for my actual test code classes. First, my main class, 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();
  }
}

And then my transactional class, 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);
  }
}

The trick here is that if the TestClass is a field in TestMain its class will be loaded by the ClassLoader before the application context is loaded. Since the weaving is at the load-time of the class, and this weaving is done by Spring through the application context, it won't get woven because the class is already loaded before the application context is loaded and aware of it.

The further particulars of TestObject and TestDao are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />'s are set up in the application context file.

Edit: The full rundown for Spring Transactions in AspectJ mode using Compile-Time Weaving:

Add the following to META-INF/spring/applicationContext.xml:

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

(I'll assume you already have an AnnotationSessionFactoryBean and a HibernateTransactionManager set up in the application context. You can add transaction-manager="transactionManager" as an attribute to your <tx:annotation-driven /> tag, but if the value of your transaction manager bean's id attribute is actually "transactionManager", then it's redundant, as "transactionManager" is that attribute's default value.)

Add spring-aspects-3.1.2.RELEASE.jar and aspectjrt-1.7.0.jar to your classpath. I use Maven as my build tool, so here's the <dependency /> declarations for the POM.xml file:

<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>

In Eclipse Juno: Help -> Eclipse Marketplace -> text box labeled "Find:" -> type "ajdt" -> hit [Enter] -> "AspectJ Development Tools (Juno)" -> Install -> Etc.

After restarting Eclipse (it will make you), right-click your project to bring up the context menu. Look near the bottom: Configure -> Convert to AspectJ Project.

Add the following <plugin /> declaration in your POM.xml (again with the 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>

Alternative: Right-click your project to bring up the context menu. Look near the bottom: AspectJ Tools -> Configure AspectJ Build Path -> Aspect Path tab -> press "Add External JARs..." -> locate the full/path/of/spring-aspects-3.1.2.RELEASE.jar -> press "Open" -> press "OK".

If you took the Maven route, the <plugin /> above should be freaking out. To fix this: Help -> Install New Software... -> press "Add..." -> type whatever you like in the text box labeled "Name:" -> type or copy+paste http://dist.springsource.org/release/AJDT/configurator/ in the text box labeled "Location:" -> press "OK" -> Wait a second -> check the parent checkbox next to "Maven Integration for Eclipse AJDT Integration" -> press "Next >" -> Install -> Etc.

When the plugin is installed, and you've restarted Eclipse, the errors in your POM.xml file should have gone away. If not, right-click your project to bring up the context menu: Maven -> Update Project -> press "OK".

Now for my actual test code class. Only one this time, 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);
  }
}

There is no trick to this one; since the weaving happens at compile time, which is before both the class loading and application context loading, the order of these two things no longer matters. This means that everything can go in the same class. In Eclipse, your code is constantly being re-compiled each time you hit Save (ever wondered what it was doing while it says "Building workspace: (XX%)"?), so it's woven and ready to go whenever you are.

Just like in the Load-Time example: the further particulars of TestObject and TestDao are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />'s are set up in the application context file.

解决方案

By reading your question it's not really clear where you are stuck, so I am going to briefly list what is needed to get AspectJ intercept your @Transactional methods.

  1. <tx:annotation-driven mode="aspectj"/> in your Spring configuration file.
  2. <context:load-time-weaver/> as well in your Spring configuration file.
  3. An aop.xml located in the META-INF folder directly in your classpath. The format of this is also explained here. It should contain an aspect definition for that handles the @Transactional annotation: <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
  4. The weaver element in that same file should also have an include clause that tells it which classes to weave: <include within="foo.*"/>
  5. aspectjrt.jar, aspectjweaver.jar, spring-aspects.jar and spring-aop.jar in the classpath
  6. Starting the application using the flag -javaagent:/path/to/spring-instrument.jar (or spring-agent, as it is called in earlier releases)

The final step may not be necessary. It is a really simple class that enables using the InstrumentationLoadTimeWeaver, but if not available Spring will try to use another load time weaver. I have never tried that, though.

Now, if you think you have fulfilled all steps and still are having problems, I can recommend enabling some options on the weaver (defined in aop.xml):

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

This makes the weaver output a bunch of information what is being weaved. If you see classes being weaved, you can look for your TestClass there. Then you at least have a starting point to continue troubleshooting.


Regarding your second edit, "It's almost like the weaving isn't happening fast enough to be woven before the class tries to execute.", the answer is yes, this can happen. I experienced a situation like this before.

I am a little rusty on the specifics, but basically it is something in the lines that Spring will not be able to weave classes that are loaded before the application context is being created. How are you creating your application context? If you are doing it programatically, and that class has a direct reference to TestClass, then this problem could occur, since TestClass will be loaded too early.

Unfortunately, I have found that debugging AspectJ is hell.

这篇关于旧的“@Transactional from the same class"情况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆