Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to setup load-time weaving with Spring and Tomcat WITHOUT using the -javaagent on the command line

I am using Spring 3.2.9, Tomcat 6.0.44

I am trying to configure my application's Spring instrumentation provider (e.g. spring-instrumentation.jar) for load-time weaving, when it is deployed on Tomcat.

I have a requirement to NOT use: "-javaagent:/path/path/spring-instrument.jar" on the command line to do the configuration.

I've read that I can configure the Spring instrumentation by modifying the <Context> element of my application's Tomcat configuration (in either Tomcat's server.xml or my web app's context.xml). Adding the appropriate <Context> element to the server.xml results in my application being correctly configured to run with Spring's instrumentation provider. Adding it to the context.xml (see below) does NOT result in a working setup.

I have a META-INF/aop.xml file, looks like this:

    <aspectj>
        <weaver options="-verbose -showWeaveInfo -debug">
            <include within="com.mv.xx.services..*"/>
            <exclude within="com.mv.xx.aop..*"/>
        </weaver>
        <aspects>
            <aspect name="com.mv.xx.aop.MyAspect"/>
        </aspects>
    </aspectj>

I also specify that I want to use load-time weaving by adding this to my Spring context config:

 <context:load-time-weaver />

And I add this jar to my application's classpath: spring-instrument-tomcat.jar

WHAT I HAVE TRIED:

When starting Tomcat, If I identify the location of the spring-instrument.jar on the command line using the -javaagent parameter like this:

-javaagent:/path/path2/spring-instrument-3.2.9.RELEASE.jar

Everything works fine.


Next I removed "-javaagent:/path/path2/spring-instrument-3.2.9.RELEASE.jar" from the command line. In Tomcat's server.xml file (located in $CATALINE_HOME/conf), I added a a <Context> element to the <Host> element, like this:

<Context path="/myApp" docBase="myApp">
    <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>

With this configuration everything behaves properly. However I have a requirement to not modify Tomcat's server.xml, since I don't have control over the server.xml (DevOps does, and is reluctant to modify it).


Next I removed the <Context> element from Tomcat's server.xml. According to the Spring docs, I can add a /META-INF/context.xml to my webapp, and put the <Context> element that used to be in Tomcat's server.xml into the context.xml, like so:

        <Context>
            <Context path="/myApp" docBase="myApp">
                <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
            </Context>
        </Context>

However when I restart Tomcat, I get an error message in the logs saying:

    SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.weaving.AspectJWeavingEnabler#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver': Initialization of bean failed; nested exception is java.lang.IllegalStateException: ClassLoader [org.apache.catalina.loader.WebappClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar

After digging around, I read something that suggested that I modify the <context:load-time-weaver/> element in my Spring config, like this:

        <context:load-time-weaver weaver-class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />

And add the jar containing InstrumentationLoadTimeWeaver.class to my classpath.

However when I do that, I get this error message in the logs:

        SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
        java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
    etc....

Can anyone explain how to setup load-time weaving with Spring and Tomcat WITHOUT using the -javaagent on the command line, and WITHOUT modifying the server.xml?

like image 592
StvnBrkdll Avatar asked Mar 14 '16 17:03

StvnBrkdll


2 Answers

This is the code that I managed to use in order to removed the exception that you mentioned. Basically you have to implement the LoadTimeWeavingConfigurer and override the method getLoadTimeWeaver().

@Configuration
@ComponentScan(basePackages = "org.myproject")
@EnableAspectJAutoProxy
@EnableSpringConfigured
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class Config implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }

    @Bean
    public InstrumentationLoadTimeWeaver loadTimeWeaver()  throws Throwable {
        InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
        return loadTimeWeaver;
    }

}
like image 118
Gui Avatar answered Sep 30 '22 05:09

Gui


I used invesdwin-instrument to perform that. It allows you to use Load time weaving instrumentation dynamically so that you don't have to use any javaagent.

It took me a bit of effort to make it work with Tomcat 8.5 though. But it finally work using this configuration with Spring Boot :

@SpringBootApplication
@EnableLoadTimeWeaving // instead of @ImportResource(locations = "classpath:/META-INF/ctx.spring.weaving.xml")
public class MySpringBootApplication {
    public static void main(final String[] args) {
        DynamicInstrumentationLoader.waitForInitialized(); //dynamically attach java agent to jvm if not already present
        DynamicInstrumentationLoader.initLoadTimeWeavingContext(); //weave all classes before they are loaded as beans
        SpringApplication.run(MySpringBootApplication.class, args); //start application, load some classes
    }
}

It should also work with previous version of Tomcat.

Regards

like image 29
teikitel Avatar answered Sep 30 '22 05:09

teikitel