2010-09-01 18 views
13

Estoy usando las transacciones declarativas de Spring (la anotación @Transactional) en el modo "aspectj". Funciona en la mayoría de los casos exactamente como debería, pero para uno no. Podemos llamarlo Lang (porque así se llama en realidad).AspectJ Tiempo de carga weaver no detecta todas las clases

He podido identificar el problema con el telar del tiempo de carga. Al activar el registro de depuración y detallado en aop.xml, se enumeran todas las clases que se tejen. La clase problemática Lang de hecho no se menciona en los registros.

Luego puse un punto de interrupción en la parte superior de Lang, haciendo que Eclipse suspenda el hilo cuando se carga la clase Lang. ¡Este punto de ruptura se golpea mientras el LTW teje otras clases! Así que supongo que o bien trata de tejer Lang y falla y no produce eso, o alguna otra clase tiene una referencia que lo fuerza a cargar Lang antes de que realmente tenga la posibilidad de tejerlo.

No estoy seguro de cómo seguir para depurar esto, ya que no puedo reproducirlo en menor escala. ¿Alguna sugerencia sobre cómo continuar?


Actualización: Otras pistas también son bienvenidos. Por ejemplo, ¿cómo funciona LTW en realidad? Parece que hay mucha magia sucediendo. ¿Hay alguna opción para obtener aún más resultados de depuración del LTW? Actualmente tengo:

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

olvidé tom mencionó antes: primavera-agente está siendo utilizado para permitir LTW, es decir, la InstrumentationLoadTimeWeaver.


Basado en las sugerencias de Andy Clement, decidí inspeccionar si el transformador AspectJ alguna vez pasó la clase. Puse un punto de interrupción en ClassPreProcessorAgent.transform(..), y parece que la clase Lang nunca llega a ese método, a pesar de que lo carga el mismo cargador de clases que otras clases (una instancia de Jetty's WebAppClassLoader).

Luego pasé a poner un punto de interrupción en InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Ni siquiera ese es golpeado por Lang. Y creo que se debe invocar ese método para todas las clases cargadas, independientemente de qué cargador de clase estén usando. Esto comienza a verse así:

  1. Un problema con mi depuración. Posiblemente Lang no se carga en el momento en que Eclipse informa que es
  2. ¿Error de Java? Extraordinario, pero supongo que sucede.

siguiente pista: Me enciende -verbose:class y parece como si Langes se carga antes de tiempo - probablemente antes de añadir el transformador a la Instrumentación. Curiosamente, mi punto de corte Eclipse no capta esta carga.

Esto significa que Spring es un nuevo sospechoso. parece haber algún procesamiento en ConfigurationClassPostProcessor que carga clases para inspeccionarlas. Esto podría estar relacionado con mi problema.


Estas líneas en ConfigurationClassBeanDefinitionReader hace que la clase Lang para ser leído:

else if (metadata.isAnnotated(Component.class.getName()) || 
     metadata.hasAnnotatedMethods(Bean.class.getName())) { 
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); 
    return true; 
} 

En particular, metadata.hasAnnotatedMethods() llamadas getDeclaredMethods() en la clase, que carga todas las clases de parámetros de todos los métodos de esa clase. Supongo que este podría no ser el final del problema, porque creo que se supone que las clases están descargadas. ¿Podría la JVM almacenar en caché la instancia de clase por razones desconocidas?

Respuesta

7

OK, he resuelto el problema. Esencialmente, es un problema de primavera junto con algunas extensiones personalizadas. Si alguien encuentra algo similar, trataré de explicar paso a paso lo que está sucediendo.

En primer lugar, tenemos un BeanDefintionParser personalizado en nuestro proyecto. Esta clase tenía la siguiente definición:.

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 

    protected Class<?> getBeanClass(Element element) { 
     try { 
      return Class.forName(element.getAttribute("class")); 
     } catch (ClassNotFoundException e) { 
      throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e); 
     } 
    } 

// code to parse XML omitted for brevity 

} 

Ahora, el problema se produce después de todo, la definición de frijol han sido leído y BeanDefinitionRegistryPostProcessor comienza a surtir efecto En esta etapa, una clase llamada ConfigurationClassPostProcessor empieza a buscar a través de todas las definiciones de frijol, para buscar para clases de beans anotadas con @Configuration o que tienen métodos con @Bean.

En el proceso de lectura de anotaciones para un bean, utiliza la interfaz AnnotationMetadata. Para la mayoría de los beans normales, se utiliza una subclase llamada AnnotationMetadataVisitor. Sin embargo, al analizar las definiciones de bean, si ha reemplazado el método getBeanClass() para devolver una instancia de clase, como lo hicimos, en su lugar se usa una instancia de StandardAnnotationMetadata. Cuando se invoca StandardAnnotationMetadata.hasAnnotatedMethods(..), llama al Class.getDeclaredMethods(), lo que a su vez hace que el cargador de clases cargue todas las clases utilizadas como parámetros en esa clase. Las clases cargadas de esta manera no se descargan correctamente y, por lo tanto, nunca se tejen, ya que esto ocurre antes de que el transformador AspectJ se registre.

Ahora, mi problema era que tenía una clase de esta manera:

public class Something { 
    private Lang lang; 
    public void setLang(Lang lang) { 
     this.lang = lang; 
    } 
} 

Entonces, tuve un grano de clase Something que fue analizada usando nuestra costumbre ControllerBeanDefinitionParser. Esto desencadenó el procedimiento de detección de anotación incorrecta, que desencadenó una carga de clase inesperada, lo que significa que AspectJ nunca tuvo la oportunidad de tejer Lang.

La solución fue la de no anular getBeanClass(..), pero en lugar getBeanClassName(..) anular, que según la documentación es preferible:

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 

    protected String getBeanClassName(Element element) { 
     return element.getAttribute("class"); 
    } 

// code to parse XML omitted for brevity 

} 

Lección del día: No anule getBeanClass a menos que realmente quiere decir. En realidad, no intente escribir su propio BeanDefinitionParser a menos que sepa lo que está haciendo.

Fin.

1

Opción 1) Aspect J es de código abierto. Abrítelo y vea lo que está pasando.

Opción 2) Cambiar el nombre de la clase a la explosión, ver si se comienza a trabajar

No me sorprendería si hay codificación difícil de saltar "lang' allí, aunque no puedo decir por qué.

Editar -

al ver código como este en el origen de

 if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object 
      superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class); 
      superclassname = Utility.compactClassName(superclassname, false); 

} else { 
      superclassname = "java.lang.Object"; 
     } 

parece que están tratando de saltar tejido de java.lang.stuff .... no ven nada por sólo "lang" pero puede estar allí (o un error)

+0

Gracias por la respuesta. Interesante idea de que la cadena "Lang" podría causar un problema. Pero debería haber mencionado, hay otras clases (al menos una) que tampoco se tejen. El otro conocido se llama CommServer. Entonces dudo que ese sea el problema. Puede que tenga que empezar a buscar en la fuente de AspectJ. – waxwing

4

Si su clase no se menciona en la salida -verbose/-debug, eso me sugiere que no está siendo cargada por el cargador que usted cree que es. ¿Puedes estar 100% seguro de que 'Lang' no está en el classpath de un cargador de clases superior en la jerarquía? ¿Qué cargador de clases está cargando Lang en el punto en el momento en que activas tu punto de interrupción?

Además, no mencionas la versión de AspectJ, si estás en la versión 1.6.7 que tenía problemas con ltw para algo más que un aop.xml trivial. Deberías estar en 1.6.8 o 1.6.9.

¿Cómo funciona ltw en realidad?

En pocas palabras, se crea un weape AspectJ para cada cargador de clases que pueda querer tejer el código. A AspectJ se le pregunta si desea modificar los bytes de una clase antes de definirla en la máquina virtual. AspectJ mira cualquier archivo aop.xml que pueda 'ver' (como recursos) a través del cargador de clases en cuestión y los usa para configurarse. Una vez configurado, teje los aspectos según lo especificado, teniendo en cuenta todas las cláusulas de inclusión/exclusión.

Andy Clemente
Proyecto AspectJ plomo

+0

Tengo 1.6.9 de aspectjweaver y aspectjrt en mis libs de guerra. Lang.class.getClassLoader() devuelve la misma instancia de cargador de clases que otra clase que está tejida. Luego experimenté colocando un punto de interrupción en ClassPreProcessorAgent, y de hecho, Lang nunca pasa a su método transform(). Tal vez debería intentar implementarlo en Tomcat en su lugar. – waxwing

+0

Cuando mencionaste el problema de que se cargue "demasiado pronto", me recordó que lo he visto antes con Spring. Una búsqueda rápida de la primavera Jira trajo https://jira.springframework.org/browse/SPR-4963 que es un tipo similar de problema. Sugiere una retransformación de clase como respuesta (donde la clase sería conducida a través del proceso de carga de nuevo, utilizando los bytes originales, pero la segunda vez a través de AspectJ la verá), pero hasta donde sé, no hemos hecho nada. para eso en la primavera todavía, no creo que haya nada planeado para 3.1 tampoco. –

Cuestiones relacionadas