旧的“@Transactional来自同一个班级”情况 [英] The Old "@Transactional from within the same class" Situation

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

问题描述

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



编辑: AspectJ模式下Spring Transactions的完整纲要使用加载时间编织:



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

 < tx:annotation-driven mode =aspectj/> 

< context:load-time-weaver />

(我假设你已经有一个 AnnotationSessionFactoryBean HibernateTransactionManager 在应用程序上下文中设置。您可以添加 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 .. */><! - 无论您的包裹空间是什么.-->
< / weaver>
< / aspectj>

添加 aspectjweaver-1.7.0.jar spring-aspects-3.1.2.RELEASE.jar 到您的类路径。我使用Maven作为我的构建工具,所以这里是项目的 POM.xml < dependency /> 声明> 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 类路径上不需要$ c>< dependency /> ,但您仍然需要它 somewhere 以便您可以使用 -javaagent JVM标志指向它,如下所示:

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

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



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

 打包我的。包; 

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 HibernateTransactionManager 在应用程序上下文中设置。您可以添加 transaction-manager =transactionManager作为属性为< tx:annotation-driven /> 标记,但是如果事务管理器bean的值为 id 属性实际上是 transactionManager ,然后它是多余的,因为 transactionManager 是该属性的默认值。)



添加 spring-aspects-3.1.2.RELEASE.jar aspectjrt-1.7。 0.jar 到您的类路径。我使用Maven作为我的构建工具,所以这里是<> 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项目。



POM.xml中添加以下< plugin /> 声明(再次使用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选项卡 - >按Add External JARs ... - >找到 full / path / of / spring-aspects-3.1 .2.RELEASE.jar - >按打开 - >按确定。



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



安装插件后,您重新启动了Eclipse , POM.xml 文件中的错误应该已经消失。如果没有,请右键单击您的项目以显示上下文菜单:Maven - > Update Project - >按OK。



现在我的实际测试代码类。这次只有一次, 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%)?),所以无论何时你都可以编织它。 / p>

就像在Load-Time示例中一样: TestObject TestDao <的更多细节/ code>并不重要。假设他们使用JPA和Hibernate注释并使用Hibernate进行持久化(因为他们是,他们这样做),以及所有必需的< bean /> ' s在应用程序上下文文件中设置。

解决方案

通过阅读你的问题,你不知道你被困在哪里,所以我我将简要列出使AspectJ拦截你的 @Transactional 方法所需的内容。


  1. < tx:Spring配置文件中的注释驱动模式=aspectj/>

  2. < context:load-time-weaver /> 以及Spring配置文件。

  3. 位于META-INF f中的aop.xml直接在您的类路径中更老。 此处也解释了这种格式。它应该包含一个方面定义,用于处理 @Transactional 注释:< aspect name =org.springframework.transaction.aspectj.AnnotationTransactionAspect/ >

  4. 同一文件中的weaver元素还应该有一个include子句,告诉它编织哪些类:< include在=foo。*/>

  5. aspectjrt.jar aspectjweaver.jar spring-aspects.jar spring-aop.jar in classpath

  6. 使用标志 -javaagent:/path/to/spring-instrument.jar (或spring-agent)启动应用程序,在早期版本中调用)

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



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

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

这使得织布工输出了大量正在编织的信息。如果您看到正在编织的类,您可以在那里查找 TestClass 。然后你至少有一个起点继续排除故障。






关于你的第二次编辑,它几乎就像编织isn在课程尝试执行之前,发生得足够快,可以编织。,答案是,这可能发生。 我之前遇到过类似的情况



我对细节有点生疏,但基本上它是行中的东西,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来自同一个班级”情况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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