2012-07-23 13 views
11

Sinossi della domanda originale: Utilizzo di transazioni Spring standard con proxy AOP, non è possibile chiamare un metodo @ Transactional contrassegnato da un valore non- @ Transactional- metodo contrassegnato nella stessa classe ed essere all'interno di una transazione (in particolare a causa del suddetto proxy). Questo è presumibilmente possibile con Transazioni di primavera in modalità AspectJ, ma come è fatto?Il vecchio "@Transactional all'interno della stessa classe" Situazione

Edit: La piena riduzione per le Operazioni di primavera in modalità AspectJ utilizzando load-time Tessitura:

Aggiungere il seguente alla META-INF/spring/applicationContext.xml:

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

<context:load-time-weaver /> 

(darò per scontato che già hanno un AnnotationSessionFactoryBean e un HibernateTransactionManager impostati nel contesto dell'applicazione. È possibile aggiungere transaction-manager="transactionManager" come attributo al tag <tx:annotation-driven />, ma se il valore del bean del gestore transazioni è 0 attributoè in realtà "transactionManager", allora è ridondante, come "transactionManager" è il valore di default che dell'attributo.)

Aggiungi META-INF/aop.xml. I contenuti sono i seguenti:

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

Aggiungi aspectjweaver-1.7.0.jar e spring-aspects-3.1.2.RELEASE.jar al vostro classpath. Io uso Maven come mio strumento di compilazione, per cui qui sono i <dependency /> dichiarazioni per POM.xml il file del progetto:

<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 non è necessaria come <dependency /> sul classpath, ma è ancora necessario che qualche parte in modo da poter punto con la bandiera -javaagent JVM, come segue:

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

sto lavorando in Eclipse Juno, quindi per impostare questo sono andato a Window -> Preferenze -> Java -> JRE installati. Poi ho fatto clic sul JRE controllato nella casella di riepilogo e ho fatto clic sul pulsante "Modifica ..." a destra della casella di riepilogo. La terza casella di testo nella finestra popup risultante è etichettata come "Argomenti VM predefiniti:". Questo è dove la bandiera -javaagent deve essere digitato o copia + incollato.

Ora, per le mie classi di codici di prova effettivi. In primo luogo, la mia classe principale, 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(); 
    } 
} 

E poi la mia classe transazionale, 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); 
    } 
} 

Il trucco è che se il TestClass è un campo in TestMain sua classe verrà caricata dal ClassLoader prima che venga caricato il contesto dell'applicazione. Poiché la tessitura è al momento del caricamento della classe e questa intrecciatura viene eseguita da Spring tramite il contesto dell'applicazione, non verrà tessuta perché la classe è già caricata prima che il contesto dell'applicazione venga caricato e ne venga a conoscenza.

Le ulteriori informazioni su TestObject e TestDao non sono importanti.Supponiamo che siano cablati con annotazioni JPA e Hibernate e usino Hibernate per la persistenza (perché lo sono, e lo fanno) e che tutti i requisiti <bean /> sono impostati nel file di contesto dell'applicazione.

Edit: La piena riduzione per le Operazioni di primavera in modalità AspectJ utilizzando Compile-Time Tessitura:

Aggiungere il seguente alla META-INF/spring/applicationContext.xml:

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

(darò per scontato che già hanno un AnnotationSessionFactoryBean e un HibernateTransactionManager impostati nel contesto dell'applicazione. È possibile aggiungere transaction-manager="transactionManager" come attributo al tag <tx:annotation-driven />, ma se il valore del gestore delle transazioni attributo del bean id è in realtà "transactionManager", allora è ridondante, come "transactionManager" è il valore di default che dell'attributo.)

Aggiungi spring-aspects-3.1.2.RELEASE.jar e aspectjrt-1.7.0.jar al vostro classpath. Io uso Maven come mio strumento di compilazione, quindi ecco le <dependency /> dichiarazioni per il file 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> 

In Eclipse Juno: Aiuto -> Eclipse Marketplace - casella di testo> etichettati "Trova:" -> tipo "ajdt" - > premi [Invio] -> "AspectJ Development Tools (Juno)" -> Installa -> Etc

Dopo aver riavviato Eclipse (ti farà), fai clic con il pulsante destro del mouse sul tuo progetto per visualizzare il menu di scelta rapida. Guarda in basso: Configura -> Converti in AspectJ Project.

Aggiungere il <plugin /> dichiarazione seguente nel POM.xml (ancora una volta con la 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> 

Alternativa: Fare clic con il progetto per far apparire il menu contestuale. Guarda in basso: AspectJ Tools -> Configura AspectJ Build Path -> scheda Aspect Path -> premi "Aggiungi JAR esterni ..." -> individua lo full/path/of/spring-aspects-3.1.2.RELEASE.jar -> premi "Apri" -> premi "OK".

Se si è scelto il percorso Maven, lo <plugin /> sopra deve essere fuori di testa. Per risolvere questo problema: Guida -> Installa nuovo software ... -> premi "Aggiungi ..." -> digita quello che vuoi nella casella di testo "Nome:" -> digita o copia + incolla http://dist.springsource.org/release/AJDT/configurator/ nella casella di testo etichettata "Location:" -> premere "OK" -> Aspetta un secondo -> selezionare la casella di controllo genitore accanto a "Maven integrazione per Eclipse AJDT integrazione" -> premere "next>" -> Installa -> Ecc

Quando il plugin è installato e hai riavviato Eclipse, gli errori nel tuo file POM.xml dovrebbero essere andati via. In caso contrario, fare clic con il tasto destro del mouse sul progetto per visualizzare il menu di scelta rapida: Maven -> Aggiorna progetto -> premere "OK".

Ora per la mia classe di codice di prova effettiva. Un solo questa volta, 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); 
    } 
} 

Non c'è trucco per questo; poiché la tessitura avviene al momento della compilazione, che è prima del caricamento della classe e del caricamento del contesto dell'applicazione, l'ordine di queste due cose non conta più. Ciò significa che tutto può andare nella stessa classe. In Eclipse, il codice viene costantemente ricompilato ogni volta che si preme Save (si è mai chiesto cosa stesse facendo mentre dice "Building workspace: (XX%)"?), Quindi è intessuto e pronto ad andare ogni volta che lo si fa.

Proprio come nell'esempio Load-Time: gli ulteriori dettagli di TestObject e TestDao non sono importanti. Supponiamo che siano cablati con annotazioni JPA e Hibernate e usino Hibernate per la persistenza (perché lo sono, e lo fanno) e che tutti i requisiti <bean /> sono impostati nel file di contesto dell'applicazione.

+0

Giusto per essere sicuro - presumo che tu abbia definito un transactionManager, avvolgendo nella sessione di sospensioneFactory: org.springframework.orm.hibernate3.HibernateTransactionManager. Inoltre, puoi mostrare l'implementazione all'interno del tuo TestDao.Puoi confermare ulteriormente che quando chiami 'transactionMethod' direttamente da un test @Transactional, funziona correttamente e il problema si verifica solo quando chiami dal metodo' test'? –

+0

Sarebbe un modo per iniettare l'oggetto in se stesso, e quindi chiamare il metodo sul riferimento iniettato, nella speranza che un proxy fosse stato iniettato? –

risposta

9

Leggendo la tua domanda non è molto chiaro dove sei bloccato, quindi ho intenzione di elencare brevemente ciò che è necessario per fare in modo che AspectJ intercetti i tuoi metodi @Transactional.

  1. <tx:annotation-driven mode="aspectj"/> nel file di configurazione di Spring.
  2. <context:load-time-weaver/> nel file di configurazione Spring.
  3. Un file aop.xml situato nella cartella META-INF direttamente nel classpath. Il formato di questo è anche spiegato here. Dovrebbe contenere una definizione di aspetto per che gestisce il @Transactional annotazione: <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
  4. L'elemento tessitore in quello stesso file deve anche avere un include la clausola che gli dice che le classi di tessere: <include within="foo.*"/>
  5. aspectjrt.jar, aspectjweaver.jar, spring-aspects.jar e spring-aop.jar in classpath
  6. Avvio dell'applicazione utilizzando il flag -javaagent:/path/to/spring-instrument.jar (o la primavera-agent, come viene chiamato nelle versioni precedenti)

il passo finale potrebbe non essere necessario. È una classe molto semplice che consente di utilizzare InstrumentationLoadTimeWeaver, ma se non è disponibile, Spring tenterà di utilizzare un altro tempo di caricamento. Non l'ho mai provato, però.

Ora, se si pensa di aver compiuto tutti i passi e ancora stanno avendo problemi, posso raccomandare che consente alcune opzioni sul tessitrice (definito in aop.xml):

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

Questo rende l'uscita tessitrice un mucchio di informazioni su cosa si sta intrecciando. Se vedi tessere le classi, puoi cercare il tuo TestClass lì. Quindi hai almeno un punto di partenza per continuare la risoluzione dei problemi.


Per quanto riguarda la tua seconda modifica, "E 'quasi come la tessitura non sta avvenendo abbastanza veloce per essere tessuto prima della classe tenta di eseguire.", La risposta è sì, , questo può accadere. I experienced a situation like this before.

Sono un po 'arrugginito sulle specifiche, ma fondamentalmente è qualcosa nelle linee che Spring non sarà in grado di tessere classi che vengono caricate prima che venga creato il contesto dell'applicazione. Come stai creando il tuo contesto applicativo? Se lo stai facendo in modo programmatico e quella classe ha un riferimento diretto a TestClass, allora questo problema potrebbe verificarsi, dal momento che TestClass verrà caricato troppo presto.

Sfortunatamente, ho scoperto che il debugging di AspectJ è un inferno.

+0

Questo è tutto per la trama a tempo pieno. Stai cercando di dire che Transazioni primaverili in modalità AspectJ ('') è possibile solo utilizzando la trama Load-Time? Perché, se è così, dimentica tutto, perché ho sperimentato un po 'con le cose '-javaagent' e non sono in un ambiente in cui posso inserire le bandiere JVM. –

+0

Inoltre: Si è dimenticato 'spring-aspects.jar' nel passaggio 5, poiché sto ricevendo una classe ClassNotFoundException:" org.springframework.transaction.aspectj.AnnotationTransactionAspect " –

+0

Ho modificato gli aspetti primaverili. Per quanto riguarda il tuo altro commento, beh, la tessitura in fase di compilazione è una bestia completamente diversa. Speriamo che qualcun altro possa rispondere a ciò che è richiesto per quello. Si noti tuttavia che la tessitura del tempo di caricamento potrebbe essere possibile senza i flag jvm (a seconda del proprio ambiente): vedere la Tabella 7.1 in http://static.springsource.org/spring/docs/3.0.x/reference/aop.html – waxwing

Problemi correlati