Старый "@Transactional из одного класса" Ситуация

Сводка исходного вопроса: Используя стандартные транзакции Spring с прокси-сервером AOP, невозможно вызывать метод с маркировкой @Transactional из метода, отличного от @Transactional, в том же класса и быть в рамках транзакции (в частности, из-за вышеупомянутого прокси). Возможно, это возможно с помощью Spring Транзакций в режиме AspectJ, но как это сделать?

Изменить: Полное расписание для Spring Транзакций в режиме AspectJ с использованием Время загрузки Ткачество:

Добавьте в 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..*" /><!--Whatever your package space is.-->
  </weaver>
</aspectj>

Добавьте aspectjweaver-1.7.0.jar и spring-aspects-3.1.2.RELEASE.jar в свой classpath. Я использую Maven в качестве моего инструмента построения, так что вот объявления <dependency /> для вашего проекта 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 не нужен как <dependency /> на вашем classpath, но вы все равно нуждаетесь в нем где-нибудь, чтобы вы могли указать на него с флагом -javaagent JVM следующим образом:

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

Я работаю в Eclipse Juno, поэтому, чтобы установить это, я перешел в Window → Preferences → Java → Установленные 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);
  }
}

Трюк здесь в том, что если TestClass является полем в TestMain, его класс будет загружен ClassLoader до того, как будет загружен контекст приложения. Так как плетение происходит во время загрузки класса, и это переплетение выполняется с помощью Spring через контекст приложения, оно не будет соткано, потому что класс уже загружен до того, как будет загружен контекст приложения и будет знать об этом.

Дальнейшие подробности TestObject и TestDao несущественны. Предположим, что они связаны с аннотациями JPA и Hibernate и используют Hibernate для сохранения (потому что они есть, и они это делают) и что все необходимые <bean /> настроены в файле контекста приложения.

Изменить: Полное расписание для транзакций Spring в режиме AspectJ с использованием Время компиляции Ткачество:

Добавьте в 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 в 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)" → Install → Etc.

После перезапуска Eclipse (он сделает вас), щелкните правой кнопкой мыши проект, чтобы открыть контекстное меню. Посмотрите внизу: Configure → Convert to AspectJ Project.

Добавьте следующее <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 Tools → Настроить путь сборки AspectJ → вкладка "Аспектный путь" → нажмите "Добавить внешние JAR..." → найдите full/path/of/spring-aspects-3.1.2.RELEASE.jar → нажмите "Открыть" → нажмите "ОК" .

Если вы взяли маршрут Maven, <plugin /> выше должен быть freaking. Чтобы исправить это: Справка → Установить новое программное обеспечение... → нажмите "Добавить..." → введите то, что вам нравится, в текстовое поле с надписью "Имя:" → введите или скопируйте + вставить http://dist.springsource.org/release/AJDT/configurator/ в текст в поле "Место:" → нажмите "ОК" → Подождите секунду → установите флажок родительского флажка рядом с "Интеграция Maven для интеграции AJDT Eclipse" → нажмите "Далее > " → Установить → Эт.

Когда плагин установлен, и вы перезапустили 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%)"?), Поэтому он соткан и готов к работе, когда вы находитесь.

Как и в примере Load-Time: дальнейшие подробности TestObject и TestDao несущественны. Предположим, что они связаны с аннотациями JPA и Hibernate и используют Hibernate для сохранения (потому что они есть, и они это делают) и что все необходимые <bean /> настроены в файле контекста приложения.

Ответ 1

Читая свой вопрос, не совсем понятно, где вы застряли, поэтому я кратко расскажу о том, что необходимо для того, чтобы AspectJ перехватил ваши методы @Transactional.

  • <tx:annotation-driven mode="aspectj"/> в конфигурационном файле Spring.
  • <context:load-time-weaver/>, а также в конфигурационном файле Spring.
  • Aop.xml, расположенный в папке META-INF непосредственно в вашем пути к классам. Формат этого также объясняется здесь. Он должен содержать определение аспекта, которое обрабатывает аннотацию @Transactional: <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
  • Элемент weaver в том же файле должен также иметь предложение include, которое сообщает ему, какие классы переплетаются: <include within="foo.*"/>
  • aspectjrt.jar, aspectjweaver.jar, spring-aspects.jar и spring-aop.jar в пути к классам
  • Запуск приложения с использованием флага -javaagent:/path/to/spring-instrument.jar (или spring -agent, как он называется в более ранних версиях)

Последний шаг может и не понадобиться. Это действительно простой класс, который позволяет использовать InstrumentationLoadTimeWeaver, но если он недоступен, Spring попытается использовать другой ткач времени загрузки. Я никогда не пробовал этого.

Теперь, если вы считаете, что выполнили все шаги и все еще имеете проблемы, я могу порекомендовать включить некоторые параметры в ткачике (определенные в aop.xml):

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

Это приводит к тому, что ткач выводит кучу информации, которая соткана. Если вы видите, что классы сотканы, вы можете найти свой TestClass там. Тогда у вас есть хотя бы отправная точка для продолжения устранения неполадок.


Что касается вашего второго редактирования: "Это почти похоже на то, что переплетение не происходит достаточно быстро, чтобы быть сотканным до того, как класс попытается выполнить". Ответ да, это может произойти. Я испытал такую ​​ситуацию раньше.

Я немного ржавый по специфике, но в основном это то, что в строках Spring не сможет сплести классы, которые загружаются до создания контекста приложения. Как вы создаете свой контекст приложения? Если вы делаете это программно, и этот класс имеет прямую ссылку на TestClass, тогда эта проблема может возникнуть, так как TestClass будет загружаться слишком рано.

К сожалению, я обнаружил, что отладка AspectJ - ад.