El tejido en tiempo de carga (LTW) se refiere al proceso de vincular aspectos de AspectJ a archivos de clase de aplicación mientras se cargan en la máquina virtual Java (JVM). Esta sección se centra en la configuración y el uso del mecanismo LTW en el contexto específico de Spring Framework. Esta sección no es una introducción general al enlace en el momento del arranque. Para obtener detalles completos sobre los detalles específicos del mecanismo de enlace en el momento del arranque y su configuración usando solo AspectJ (sin Spring involucrado en absoluto), consulte Sección sobre enlace de tiempo de carga en la Guía del entorno de desarrollo de AspectJ.

El valor de Spring Framework para el enlace de tiempo de carga de AspectJ es que permite un control mucho más detallado del proceso de encuadernación. El mecanismo básico de enlace en el momento del arranque de AspectJ se realiza mediante un agente Java (5+), que se habilita especificando un argumento de máquina virtual cuando se inicia la JVM. Así que esta es una configuración para toda la JVM que puede ser buena en algunas situaciones, pero a menudo es demasiado tosca. El mecanismo LTW habilitado para Spring permite habilitarlo por ClassLoader, lo cual es una configuración más matizada y puede tener mucho más sentido en un entorno con una JVM pero muchas aplicaciones (como una típica entorno del servidor de aplicaciones).

Además, en ciertos entornos, este soporte permite el enlace en el momento del arranque sin realizar los cambios en el script de inicio del servidor de aplicaciones necesarios para agregar -javaagent:path/to/aspectjweaver.jar o (como se describe más adelante en esta sección) -javaagent:path/to/spring-instrument.jar. Los desarrolladores configuran el contexto de la aplicación para habilitar el enlace en el momento del arranque en lugar de depender de los administradores que normalmente son responsables de la configuración de la implementación, como los scripts de inicio.

Ahora que hemos terminado de exagerar, veamos un LTW rápido. ejemplo de AspectJ usando Spring y luego entre en detalles sobre los elementos presentados en el ejemplo.

Primer ejemplo

Suponga que es un desarrollador de aplicaciones encargado de diagnosticar la causa de algún rendimiento del sistema. problemas. En lugar de utilizar una herramienta de creación de perfiles, incluiremos un aspecto de creación de perfiles simple para obtener rápidamente algunas métricas de rendimiento. Inmediatamente después de esto, puede aplicar una herramienta de creación de perfiles más matizada a esa área específica.

El ejemplo presentado aquí utiliza la configuración XML. También puede configurar y utilizar @AspectJ mediante la configuración de Java. En particular, puede utilizar la anotación @EnableLoadTimeWeaving como alternativa a <context:load-time-weaver/>.

El siguiente ejemplo muestra el aspecto menos sofisticado de la creación de perfiles. Este es un generador de perfiles controlado por tiempo que utiliza el estilo @AspectJ para declarar aspectos:

Java

package foo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;
@Aspect
public class ProfilingAspect {
    @Around("methodsToBeProfiled()")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch sw = new StopWatch(getClass().getSimpleName());
        try {
            sw.start(pjp.getSignature().getName());
            return pjp.proceed();
        } finally {
            sw.stop();
            System.out.println(sw.prettyPrint());
        }
    }
    @Pointcut("execution(public * foo..*.*(..))")
    public void methodsToBeProfiled(){}
}
Kotlin

package foo
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order
@Aspect
class ProfilingAspect {
    @Around("methodsToBeProfiled()")
    fun profile(pjp: ProceedingJoinPoint): Any {
        val sw = StopWatch(javaClass.simpleName)
        try {
            sw.start(pjp.getSignature().getName())
            return pjp.proceed()
        } finally {
            sw.stop()
            println(sw.prettyPrint())
        }
    }
    @Pointcut("execution(public * foo..*.*(..))")
    fun methodsToBeProfiled() {
    }
}

También necesitamos crear un archivo META-INF/aop.xml para indicarle a la herramienta de enlace AspectJ que queremos vincular nuestro ProfilingAspect a nuestras clases. Esta convención de archivos, es decir, tener un archivo (o archivos) en la ruta de clases de Java llamado META-INF/aop.xml, es un estándar de AspectJ. El siguiente ejemplo muestra el archivo aop.xml:


<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <!-- we only bundle classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>
    <aspects>
        <!-- we connect only this aspect... -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>
</aspectj>

Ahora podemos pasar a la parte de la configuración específica de Spring. Necesitamos configurar LoadTimeWeaver (se explica más adelante). Esta herramienta de vinculación en tiempo de carga es el componente principal responsable de vincular la configuración de aspectos en uno o más archivos META-INF/aop.xml a sus clases de aplicación. Afortunadamente, no requiere una configuración complicada (hay algunas opciones más que se pueden configurar, pero se tratarán en detalle más adelante), como puede ver en el siguiente ejemplo:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http:// www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- service object; we will profile its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>
    <!-- this enables binding at boot time -->
    <context:load-time-weaver/>
</beans>

Ahora que todos los artefactos necesarios (aspecto, archivo META-INF/aop.xml y configuración Spring) en su lugar, podemos crear la siguiente clase de controlador con un método main(..) para demostrar el mecanismo LTW en acción:

Java

package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
        EntitlementCalculationService entitlementCalculationService =
                (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
        // the profiled aspect "weaves" the execution of this method
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin

package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")
    val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService
    // the profiled aspect "weaves" the execution of this method
    entitlementCalculationService.calculateEntitlement()
}

Solo nos queda una cosa por hacer. La introducción a esta sección indicó que con Spring puede habilitar LTW de forma selectiva por clase de cargador, y esto es cierto. Sin embargo, en este ejemplo utilizamos el agente Java (proporcionado con Spring) para habilitar el mecanismo LTW. Usamos el siguiente comando para ejecutar la clase Main mostrada anteriormente:

java-javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

Marca -javaagent es un indicador para configurar y activar agentes para instrumentar programas que se ejecutan en la JVM. Spring Framework incluye un agente, InstrumentationSavingAgent, empaquetado en spring-instrument.jar, que se proporcionó como el valor del argumento -javaagent en el ejemplo anterior.

La salida del programa Main se parece al siguiente ejemplo. (Introduje una declaración Thread.sleep(..) en la implementación calculateEntitlement() para que el generador de perfiles realmente capturara algún valor distinto de 0 milisegundos ( 01234 milisegundos no son la latencia introducida por AOP) La siguiente lista muestra el resultado que obtuvimos cuando ejecutamos nuestro generador de perfiles:

Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

Debido a que este mecanismo LTW se implementa utilizando AspectJ con todas las funciones, no nos limitamos a brindar asesoramiento a Spring. frijoles. La siguiente ligera variación en el programa Main da el mismo resultado:

Java

package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans.xml", Main.class);
        EntitlementCalculationService entitlementCalculationService =
                new StubEntitlementCalculationService();
        // the profiled aspect will "weave" the execution of this method
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin

package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main(args: Array<String>) {
    ClassPathXmlApplicationContext("beans.xml")
    val entitlementCalculationService = StubEntitlementCalculationService()
    // the profiled aspect will "weave" the execution of this method
    entitlementCalculationService.calculateEntitlement()
}

Observe que en el programa anterior cargamos el contenedor Spring y luego creamos una nueva instancia de StubEntitlementCalculationService completamente fuera del contexto Spring. Las sugerencias perfiladas todavía están vinculadas.

Por supuesto, el ejemplo está simplificado. Sin embargo, los conceptos básicos del soporte LTW de Spring se presentaron en el ejemplo anterior y el resto de esta sección detalla el significado de cada bit de configuración y aplicación.

El ProfilingAspect usado en este ejemplo puede ser básico, pero es bastante útil. Este es un buen ejemplo de un aspecto en tiempo de diseño que los desarrolladores pueden utilizar durante el desarrollo y luego excluirlo fácilmente de las compilaciones de una aplicación implementada en una prueba de aceptación del usuario (UAT) o en un entorno de producción.

Aspectos

Los aspectos que utilice en LTW deben ser aspectos de AspectJ. Puede escribirlos en el propio lenguaje AspectJ o escribir sus propios aspectos en el estilo @AspectJ. Entonces, sus aspectos serán aspectos válidos tanto de AspectJ como de Spring AOP. Además, las clases de aspecto compiladas deben estar disponibles en el classpath.

'META-INF/aop.xml'

El marco AspectJ LTW se configura utilizando uno o más archivos META-INF/aop.xml, que se encuentran en la ruta de clases de Java (ya sea directamente o, más típicamente, en archivos jar).

La estructura y el contenido de este archivo se describen en detalle en la documentación de referencia LTW de AspectJ. Dado que el archivo aop.xml está 100% escrito en AspectJ, no lo describiremos aquí.

Bibliotecas requeridas (JARS)

Para usar el motor LTW soporte Desde AspectJ a Spring Framework necesitarás al menos las siguientes bibliotecas:

  • spring-aop.jar

  • aspectjweaver.jar

Si está utilizando el agente de activación de instrumentación proporcionado por Spring, también necesitará:

  • spring-instrument.jar

Configuración de Spring

Un componente clave del soporte LTW de Spring es la interfaz LoadTimeWeaver (en el paquete org.springframework.instrument.classloading) y sus numerosas implementaciones que se incluyen. con la distribución de primavera. LoadTimeWeaver es responsable de agregar uno o más java.lang.instrument.ClassFileTransformers a ClassLoader en tiempo de ejecución, lo que abre la puerta a todo tipo de funciones interesantes. modos de uso, uno de los cuales es el enlace de aspecto en el momento de la carga.

Si no está familiarizado con la idea de convertir archivos de clase en tiempo de ejecución, consulte la interfaz API javadoc para el paquete java.lang.instrument antes de continuar. Si bien esta documentación no es completa, al menos podrá ver las interfaces y clases principales (como referencia mientras lee esta sección).

Configuración de LoadTimeWeaver para un específico ApplicationContext puede ser tan simple como agregar una sola línea. (Tenga en cuenta que es casi seguro que necesitará usar ApplicationContext como contenedor Spring; BeanFactory generalmente no es suficiente ya que el soporte del motor LTW usa BeanFactoryPostProcessors).

Para habilitar la compatibilidad con LTW en Spring Framework, debe configurar LoadTimeWeaver, lo que normalmente se realiza utilizando la anotación @EnableLoadTimeWeaving, como se muestra a continuación:

Java

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin

@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

Como alternativa, si prefiere la configuración basada en XML, utilice elemento <context:load-time-weaver/>. Tenga en cuenta que el elemento está definido en el espacio de nombres context. El siguiente ejemplo muestra cómo utilizar <context:load-time-weaver/>:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:load-time-weaver/>
</beans>

La configuración anterior detecta y registra automáticamente una cantidad de beans de infraestructura específicos de LTW, como LoadTimeWeaver y AspectJWeavingEnabler . De forma predeterminada, LoadTimeWeaver es una clase DefaultContextLoadTimeWeaver que intenta complementar el LoadTimeWeaver detectado automáticamente. El tipo exacto de LoadTimeWeaver que será "detectado automáticamente" depende de su entorno de ejecución. La siguiente tabla muestra las diversas implementaciones de LoadTimeWeaver:

Tabla 13. DefaultContextLoadTimeWeaver LoadTimeWeaver
Tiempo de ejecución Implementación LoadTimeWeaver

Ejecución en Apache Tomcat

TomcatLoadTimeWeaver

Ejecución en GlassFish (limitado a la implementación de EAR)

GlassFishLoadTimeWeaver

Ejecutar en JBoss AS de Red Hat o WildFly

JBossLoadTimeWeaver

Ejecutar en WebSphere de IBM

WebSphereLoadTimeWeaver

Ejecución en WebLogic de Oracle

WebLogicLoadTimeWeaver

JVM comenzó con InstrumentationSavingAgent(java -javaagent:path/to/spring-instrument.jar) de Spring

InstrumentationLoadTimeWeaver

Regrese esperando que el ClassLoader subyacente siga las convenciones generales (es decir, addTransformer y, opcionalmente, el método getThrowawayClassLoader).

ReflectiveLoadTimeWeaver

Tenga en cuenta que la tabla enumera solo aquellos LoadTimeWeaver que se detectan automáticamente cuando se utiliza DefaultContextLoadTimeWeaver. Puede especificar exactamente qué implementación de LoadTimeWeaver usar.

Para especificar un LoadTimeWeaver específico usando la configuración de Java, implemente la interfaz LoadTimeWeavingConfigurer y anular el método getLoadTimeWeaver(). El siguiente ejemplo especifica ReflectiveLoadTimeWeaver:

Java

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {
    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}
Kotlin

@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {
    override fun getLoadTimeWeaver(): LoadTimeWeaver {
        return ReflectiveLoadTimeWeaver()
    }
}

Si está utilizando una configuración basada en XML, puede especificar el nombre de clase completo como el valor de weaver-class de atributo en el elemento <context:load-time-weaver/>. Nuevamente, el siguiente ejemplo especifica ReflectiveLoadTimeWeaver:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>

LoadTimeWeaver, que está definido y registrado por la configuración, se puede recuperar más tarde del contenedor Spring utilizando el conocido nombre loadTimeWeaver. Recuerde que LoadTimeWeaver existe solo como un mecanismo para que el marco LTW de Spring agregue uno o más ClassFileTransformers. El ClassFileTransformer real que hace LTW es la clase ClassPreProcessorAgentAdapter (del paquete org.aspectj.weaver.loadtime). Se pueden encontrar más detalles en el javadoc de la clase ClassPreProcessorAgentAdapter, ya que los detalles de cómo se produce el enlace están más allá del alcance de este documento.

Queda un último atributo de configuración por resolver considere: el atributo aspectjWeaving (o aspectj-weaving si está utilizando XML). Este atributo controla si el mecanismo LTW está habilitado o no. Toma uno de los tres valores posibles, siendo el valor predeterminado autodetect si falta el atributo. La siguiente tabla muestra tres valores posibles:

Tabla 14. Valores de atributos de enlace de AspectJ
Annotation value XML Value Explanation

ENABLED

enabled

El enlace de AspectJ está habilitado y el enlace de aspecto se produce en el momento de la carga según sea necesario.

DISABLED

disabled

El enlace durante el arranque está deshabilitado. El enlace de aspecto no se produce durante la carga.

AUTODETECT

automatic detection

Si el marco LTW en Spring puede encontrar al menos un archivo META-INF/aop.xml , entonces se iniciará el enlace de AspectJ. De lo contrario, la vinculación quedará deshabilitada. Este es el valor predeterminado.

Configuración específica del entorno

Esta sección final contiene todas las configuraciones y ajustes adicionales necesarios. cuando se utiliza el soporte LTW de Spring en entornos como servidores de aplicaciones y contenedores web.

Tomcat, JBoss, WebSphere, WebLogic

Tomcat, JBoss/WildFly, IBM WebSphere Application Server y Oracle WebLogic Server todos proporcionan un ClassLoader común para aplicaciones capaces de instrumentación local. El LTW nativo de Spring puede usar estas implementaciones de ClassLoader para proporcionar enlace desde AspectJ. Simplemente puede activar la vinculación en el momento del arranque. En particular, no necesita modificar el script de inicio de JVM para agregar -javaagent:path/to/spring-instrument.jar.

Tenga en cuenta que en el caso de JBoss Es posible que deba desactivar el escaneo del servidor de aplicaciones para que no cargue clases antes de que la aplicación se ejecute realmente. Una solución rápida es agregar un archivo a su artefacto llamado WEB-INF/jboss-scanning.xml con el siguiente contenido:

<scanning xmlns="urn:jboss:scanning:1.0"/>

Uso universal de Java

Si necesita instrumentar clases en entornos que no son compatibles con implementaciones LoadTimeWeaver específicas, una opción común La solución es el agente JVM. Para tales casos, Spring tiene InstrumentationLoadTimeWeaver, que requiere un agente JVM específico de Spring (pero extremadamente genérico), spring-instrument.jar, determinado automáticamente por la configuración general @EnableLoadTimeWeaving y <context:load-time-weaver/>.

Para usarlo, necesita iniciar una máquina virtual con un agente desde Spring, especificando los siguientes parámetros para JVM:

-javaagent:/path /to/spring-instrument.jar

Tenga en cuenta que esto requiere una modificación en el script de inicio de JVM, lo que puede impedir que se utilice en entornos de servidor de aplicaciones (dependiendo de su servidor). y políticas operativas). Sin embargo, en escenarios de implementación de aplicaciones, como aplicaciones Spring Boot independientes, una aplicación por JVM, normalmente debe controlar toda la configuración de JVM de todos modos.

Recursos adicionales

Puede obtener más información sobre AspectJ. se puede encontrar en el sitio web de AspectJ.

Eclipse AspectJ de Adrian Collier et al. (Addison-Wesley, 2005) proporciona una introducción completa y una referencia al lenguaje AspectJ.

La segunda edición de AspectJ in Action es muy recomendable.por Ramnivas Laddad (Manning, 2009) El libro se centra en AspectJ, pero también se exploran muchos temas generales de AOP (con bastante profundidad).