2012-07-23 13 views
11

Sinopsis de la pregunta original: Usando transacciones estándar de primavera con proxy AOP, no es posible llamar a @ Transaccional-marcado método de un @ Transactional- método marcado en la misma clase y dentro de una transacción (específicamente debido al proxy mencionado anteriormente). Esto es supuestamente posible con Spring Transactions en modo AspectJ, pero ¿cómo se hace?El viejo "@Transactional desde dentro de la misma clase" Situación

Editar: El resumen completo de las transacciones de primavera en el modo AspectJ utilizando de carga en tiempo tejer:

Añadir lo siguiente a META-INF/spring/applicationContext.xml:

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

<context:load-time-weaver /> 

(voy a suponer que ya tienen un AnnotationSessionFactoryBean y una HibernateTransactionManager establecido en el contexto de aplicación. Se pueden añadir transaction-manager="transactionManager" como un atributo de la etiqueta <tx:annotation-driven />, pero si el valor de su gestor de beans de transacción id atributo es en realidad "transactionManager", entonces es redundante, ya que "transactionManager" es el valor por defecto de ese atributo.)

Añadir META-INF/aop.xml. Contenido es el siguiente:

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

Añadir aspectjweaver-1.7.0.jar y spring-aspects-3.1.2.RELEASE.jar a su classpath. Yo uso Maven como mi herramienta de construcción, así que aquí están los <dependency /> declaraciones para POM.xml de archivos de su proyecto:

<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 no es necesaria como <dependency /> en su classpath, pero todavía lo necesito algún lugar, para que pueda punto en el que con la bandera -javaagent JVM, de la siguiente manera:

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

estoy trabajando en Eclipse Juno, por lo que establece este fui a la ventana -> Preferencias -> Java -> JRE instalados. Luego hice clic en el JRE marcado en el cuadro de lista y presioné el botón "Editar ..." a la derecha del cuadro de lista. El tercer cuadro de texto en la ventana emergente resultante está etiquetado como "Argumentos VM por defecto". Aquí es donde la bandera -javaagent se debe escribir o copiar + pegar.

Ahora para mis clases de código de prueba reales. En primer lugar, mi clase principal, 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(); 
    } 
} 

Y entonces mi clase de transacciones, 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); 
    } 
} 

El truco aquí es que si el TestClass es un campo en TestMain su clase será cargado por el ClassLoader antes de que se cargue el contexto de la aplicación. Como el entrelazado está en el tiempo de carga de la clase, y este entrelazado lo realiza Spring en el contexto de la aplicación, no se teje porque la clase ya está cargada antes de que el contexto de la aplicación se cargue y esté al tanto.

Los detalles adicionales de TestObject y TestDao no son importantes.Supongamos que están conectados con anotaciones JPA e Hibernate y usan Hibernate para la persistencia (porque lo son, y lo hacen), y que todos los requisitos <bean /> están configurados en el archivo de contexto de la aplicación.

Editar: El resumen completo de las transacciones de primavera en el modo AspectJ utilizando de tiempo de compilación tejer:

Añadir lo siguiente a META-INF/spring/applicationContext.xml:

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

(voy a suponer que ya tienen AnnotationSessionFactoryBean y HibernateTransactionManager configurados en el contexto de la aplicación. Puede agregar transaction-manager="transactionManager" como un atributo a su etiqueta <tx:annotation-driven />, pero si el valor de su administrador de transacciones atributo de frijol id es en realidad "transactionManager", entonces es redundante, ya que "transactionManager" es el valor por defecto de ese atributo.)

Añadir spring-aspects-3.1.2.RELEASE.jar y aspectjrt-1.7.0.jar a su classpath. Yo uso Maven como mi herramienta de construcción, así que aquí tiene las <dependency /> declaraciones para el archivo 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> 

En Eclipse Juno: Ayuda -> Eclipse del mercado -> cuadro de texto llamado "Encontrar:" -> tipo "ajdt" - > pulse [Entrar] -> "Herramientas de desarrollo AspectJ (Juno)" -> Instalar -> Etc.

Después de reiniciar Eclipse (lo hará), haga clic derecho en su proyecto para que aparezca el menú contextual. Mire cerca de la parte inferior: Configurar -> Convertir a Proyecto AspectJ.

Añadir la <plugin /> declaración siguiente en su POM.xml (de nuevo con el 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: Haga clic derecho en su proyecto para que aparezca el menú contextual. Mire cerca de la parte inferior: AspectJ Herramientas -> Configure AspectJ Build Path -> Aspect Path tab -> presione "Add External JARs ..." -> locate the full/path/of/spring-aspects-3.1.2.RELEASE.jar -> presione "Abrir" -> presione "OK".

Si tomó la ruta Maven, el <plugin /> anterior debería estar volviendo loco. Para solucionarlo: Ayuda -> Instalar nuevo software ... -> presione "Agregar ..." -> escriba lo que quiera en el cuadro de texto etiquetado "Nombre:" -> escriba o copie + pegue http://dist.springsource.org/release/AJDT/configurator/ en el cuadro de texto etiquetado "Ubicación:" -> presione "OK" -> Espere un segundo -> marque la casilla de verificación principal al lado de "Integración de Maven para Integración AJDT de Eclipse" -> presione "Siguiente>" -> Instalar -> Etc.

Cuando el complemento está instalado y reinició Eclipse, los errores en su archivo POM.xml deberían haber desaparecido. De lo contrario, haga clic con el botón derecho en su proyecto para que aparezca el menú contextual: Maven -> Actualizar proyecto -> presione "Aceptar".

Ahora para mi clase de código de prueba real. Sólo uno de este tiempo, 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); 
    } 
} 

No hay truco a éste; dado que el entrelazado ocurre en el momento de la compilación, que es anterior a la carga de clases y a la carga del contexto de la aplicación, el orden de estas dos cosas ya no importa. Esto significa que todo puede ir en la misma clase. En Eclipse, su código se vuelve a compilar constantemente cada vez que presiona Guardar (¿alguna vez se preguntó qué estaba haciendo mientras decía "Construir espacio de trabajo: (XX%)"?), Por lo que está entretejido y listo para donde quiera que esté.

Al igual que en el ejemplo de la carga-tiempo: los detalles adicionales de TestObject y TestDao no son importantes. Supongamos que están conectados con anotaciones JPA e Hibernate y usan Hibernate para la persistencia (porque lo son, y lo hacen), y que todos los requisitos <bean /> están configurados en el archivo de contexto de la aplicación.

+0

Solo para asegurarte: supongo que tienes un transactionManager definido, envolviendo en hibernate sessionFactory: org.springframework.orm.hibernate3.HibernateTransactionManager. Además, ¿puedes mostrar la implementación dentro de tu TestDao?¿Puedes confirmar que cuando llamas 'transactionMethod' directamente desde dentro de una prueba @Transactional, ¿funciona correctamente y el problema es solo cuando llamas desde el método' test'? –

+0

¿Sería una forma de evitar esto inyectar el objeto en sí mismo, y luego llamar al método en la referencia inyectada, con la esperanza de que se haya inyectado un proxy? –

Respuesta

9

Al leer su pregunta, no está claro dónde se encuentra atascado, por lo que voy a enumerar brevemente lo que se necesita para que AspectJ intercepte sus métodos @Transactional.

  1. <tx:annotation-driven mode="aspectj"/> en su archivo de configuración de Spring.
  2. <context:load-time-weaver/> y en su archivo de configuración de Spring.
  3. Un aop.xml ubicado en la carpeta META-INF directamente en su classpath. El formato de esto también se explica here. Debe contener una definición del aspecto de que se encarga de la @Transactional anotación: <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
  4. El elemento tejedor en ese mismo archivo también debe tener un incluyen la cláusula que le dice qué clases de tejer: <include within="foo.*"/>
  5. aspectjrt.jar, aspectjweaver.jar, spring-aspects.jar y spring-aop.jar en la ruta de clase
  6. Inicio de la aplicación utilizando la bandera -javaagent:/path/to/spring-instrument.jar (o primavera-agente, como se le llama en versiones anteriores)

el paso final puede no ser necesario. Es una clase realmente simple que permite usar el InstrumentationLoadTimeWeaver, pero si no está disponible, Spring intentará usar otro tejedor de tiempo de carga. Aunque nunca lo intenté.

Ahora, si usted cree que ha cumplido todos los pasos y todavía tiene problemas, puedo recomendar que permite algunas opciones en el tejedor (definido en aop.xml):

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

Esto hace que la salida de un tejedor montón de información de lo que se está tejiendo. Si ves clases tejidas, puedes buscar tu TestClass allí. Entonces, al menos, tiene un punto de partida para continuar con la solución de problemas.


En cuanto a su segunda edición, "Es casi como si el tejido no está sucediendo lo suficientemente rápido para ser tejida antes de la clase trata de ejecutar.", La respuesta es sí , esto puede suceder. I experienced a situation like this before.

Estoy un poco oxidado sobre los detalles, pero básicamente es algo en las líneas que Spring no podrá tejer las clases que se cargan antes de que se cree el contexto de la aplicación. ¿Cómo estás creando el contexto de tu aplicación? Si lo está haciendo programáticamente, y esa clase tiene una referencia directa a TestClass, entonces este problema podría ocurrir, ya que TestClass se cargará demasiado pronto.

Desafortunadamente, he encontrado que la depuración AspectJ es un infierno.

+0

Eso es todo para Weaving-Time Weaving. ¿Estás tratando de decir que las transacciones de primavera en modo AspectJ ('') solo son posibles usando el Tejido en tiempo de carga? Porque, si es así, olvídate de todo, porque experimenté un poco con las cosas '-javaagent' y no estoy en un entorno en el que pueda insertar indicadores JVM. –

+0

También: Olvidó 'spring-aspects.jar' en el paso 5, ya que estoy obteniendo una ClassNotFoundException:" org.springframework.transaction.aspectj.AnnotationTransactionAspect " –

+0

He editado en aspectos de primavera. En cuanto a tu otro comentario, bueno, el tejido en tiempo de compilación es una bestia completamente diferente. Con suerte, alguien más puede responder a lo que se requiere para eso. Tenga en cuenta, sin embargo, que es posible tejer el tiempo de carga sin indicadores jvm (dependiendo de su entorno): consulte la Tabla 7.1 en http://static.springsource.org/spring/docs/3.0.x/reference/aop.html – waxwing

Cuestiones relacionadas