springspring-bootaopaspectjload-time-weaving

AspectJ LTW + Spring Boot + Inbuilt Tomcat Illegal access: this web application instance has been stopped already


I m trying to add aspectJ LTW integration in our Springboot Application. I m trying to use java way of specifying TomcatInstrumenatbleClassLoader and TomcatLoadTimeWeaver instead of xml way.

Refereing Spring Document But when starting my app it errors out like below

org.apache.catalina.loader.WebappClassLoaderBase checkStateForResourceLoading
INFO: Illegal access: this web application instance has been stopped already. Could not load [META-INF/spring.factories]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [META-INF/spring.factories]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1432)
    at org.apache.catalina.loader.WebappClassLoaderBase.findResources(WebappClassLoaderBase.java:1003)
    at org.apache.catalina.loader.WebappClassLoaderBase.getResources(WebappClassLoaderBase.java:1110)
    at org.springframework.core.io.support.SpringFactoriesLoader.loadSpringFactories(SpringFactoriesLoader.java:143)
    at org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames(SpringFactoriesLoader.java:132)
    at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:450)
    at org.springframework.boot.SpringApplication.getBootstrapRegistryInitializersFromSpringFactories(SpringApplication.java:294)
    at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:285)
    at com.org.pack.app.Application.main(Application.java:37)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:108)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:108)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)
Caused by: java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [META-INF/spring.factories]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1432)
    at org.apache.catalina.loader.WebappClassLoaderBase.findResources(WebappClassLoaderBase.java:1003)
    at org.apache.catalina.loader.WebappClassLoaderBase.getResources(WebappClassLoaderBase.java:1110)
    at org.springframework.core.io.support.SpringFactoriesLoader.loadSpringFactories(SpringFactoriesLoader.java:143)
    at org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames(SpringFactoriesLoader.java:132)
    at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:450)
    at org.springframework.boot.SpringApplication.getBootstrapRegistryInitializersFromSpringFactories(SpringApplication.java:294)
    at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:285)
    at com.org.pack.app.ImportApplication.main(ImportApplication.java:37)

My code goes as follows:

@SpringBootApplication
@Configuration
@ComponentScan({<Our Depedencies>})
@EnableBatchProcessing
@EnableLoadTimeWeaving
@EnableCaching
public class Application {
    /**
     * Main method
     * 
     * @param args - arguments
     */
    public static void main(String[] args) {
        DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(new TomcatInstrumentableClassLoader());
        SpringApplication application = new SpringApplication(defaultResourceLoader, Application.class);
        application.run(args);
        // SpringApplication.run(Application.class, args);
    }
} 
@Configuration
public class MyConfiguration {

    /**
     * Load Time viewer for tomcat based app
     * 
     * @return - Instance of Intrumentaiton Load TIme Weaver
     */
    @Bean
    public LoadTimeWeaver loadTimeWeaver() {
        return new InstrumentationLoadTimeWeaver();
    }
}

Dependencies (excluding other dependencies): Multi-module project Parent Pom

<modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

Project Root POM

          <dependency>
               <groupId>org.apache.tomcat.embed</groupId>
               <artifactId>tomcat-embed-core</artifactId>
               <version>9.0.74</version>
          </dependency>
          <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.19</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.9.19</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-instrument</artifactId>
                <version>5.3.18</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-instrument-tomcat</artifactId>
                <version>4.3.30.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>5.3.20</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.3.20</version>
            </dependency>

Module's POM

<!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument-tomcat</artifactId>
        </dependency>

The document also says for tomcat 6.0 or higher the setting is not needed, but getting error like

org.springframework.beans.factory.BeanCreationException: Error creating bean with name '<our entity factory class>' defined in class path resource [com/org/pack/configuration/MyConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver' defined in org.springframework.context.annotation.LoadTimeWeavingConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.instrument.classloading.LoadTimeWeaver]: Factory method 'loadTimeWeaver' threw exception; nested exception is java.lang.IllegalStateException: ClassLoader [org.springframework.boot.loader.LaunchedURLClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-instrument-{version}.jar
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332)
    at com.org.pack.app.Application.main(Application.java:41)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:108)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)

Solution

  • Update 2024-03-19

    If you are running your applications as executable JARs on JRE 9+ (Spring Boot ones or other types), probably Agent Embedder Maven Plugin is what you want.

    It enables you to embed a java agent in your executable JAR and have it started automatically using the Launcher-Agent-Class JVM mechanism.

    Unique features added by this plugin and unavailable via the original JVM mechanism: You can

    Spoiler: I am the author of the plugin and also happen to be the current maintainer of AspectJ.

    P.S.: In the case of the AspectJ weaver, as the JVM starts the agent very early, weaving will be active without extra Spring configuration and it should work for all classloaders - no more ClassLoader [...] does NOT provide an 'addTransformer(ClassFileTransformer)' method errors as seen when hot-attaching the weaver via spring-instrument.jar.

    Updated answer after MCVE was provided

    Faulty aspect

    Firstly, your aspect was faulty, it would never have matched anything. The pointcut

    set(@com.example.TestAspect * *)
    

    was faulty, because TestAspect is not an annotation. The correct pointcut is

    set(@com.example.TestAnnotation * *)
    

    Faulty Java command line

    You used (single line, line breaks added for readability)

    java
      -jar target/aspectjlib-0.0.1-SNAPSHOT.jar
      -javagent:/Users/carvind/IssueREproduce/javaagents/spring-instrument.jar
      -javaagent:/Users/carvind/IssueREproduce/javaagents/aspectjweaver.jar
    

    There is a typo -javagent instead of javaagent in the first agent parameter. But the bigger problem is the argument order. Everything after -jar my.jar will be treated as program arguments for the main class. I.e., the -javaagent JVM arguments need to come before -jar.

    You actually do not need both agents either. Your application works with

    If you accept my pull request, pull and then rebuild all your modules, you can successfully run the application from the root directory as follows:

    $ java -javaagent:IssueREproduce/javaagents/spring-instrument.jar -jar IssueREproduce/aspectjlib/target/aspectjlib-0.0.1-SNAPSHOT.jar
    
      .   ____          _            __ _ _   
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \  
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / / 
     =========|_|==============|___/=/_/_/_/  
     :: Spring Boot ::        (v2.2.1.RELEASE)
    
    2024-01-09 10:03:56.975  INFO 20472 --- [           main] c.e.aspectjlib.AspectjlibApplication     : Starting AspectjlibApplication v0.0.1-SNAPSHOT on Xander-UB with PID 20472 (C:\Users\alexa\Documents\java-src\SO_AJ_SprintBootError_77763616\IssueREproduce\aspectjlib\target\aspectjlib-0.0.1-SNAPSHOT.jar start
    ed by alexa in C:\Users\alexa\Documents\java-src\SO_AJ_SprintBootError_77763616)
    2024-01-09 10:03:56.983  INFO 20472 --- [           main] c.e.aspectjlib.AspectjlibApplication     : No active profile set, falling back to default profiles: default
    [LaunchedURLClassLoader@1bce4f0a] info AspectJ Weaver Version 1.9.19 built on Wednesday Dec 21, 2022 at 06:57:22 PST
    [LaunchedURLClassLoader@1bce4f0a] info register classloader org.springframework.boot.loader.LaunchedURLClassLoader@1bce4f0a                                                                                                         
    [LaunchedURLClassLoader@1bce4f0a] info using configuration file:/C:/Users/alexa/Documents/java-src/SO_AJ_SprintBootError_77763616/IssueREproduce/aspectjlib/target/aspectjlib-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/META-INF/aop.xml
    [LaunchedURLClassLoader@1bce4f0a] info register aspect com.example.TestAspect
    2024-01-09 10:03:58.330  INFO 20472 --- [           main] c.e.aspectjlib.AspectjlibApplication     : Started AspectjlibApplication in 2.297 seconds (JVM running for 3.071)
    [LaunchedURLClassLoader@1bce4f0a] weaveinfo Join point 'field-set(java.lang.String com.example.TestModel1.field1)' in Type 'com.example.TestModel1' (TestModel1.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    [LaunchedURLClassLoader@1bce4f0a] weaveinfo Join point 'field-set(java.lang.Double com.example.TestModel1.field4)' in Type 'com.example.TestModel1' (TestModel1.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    set(String com.example.TestModel1.field1)
    set(Double com.example.TestModel1.field4)
    [LaunchedURLClassLoader@1bce4f0a] weaveinfo Join point 'field-set(java.lang.Integer com.example.TestModel2.field2)' in Type 'com.example.TestModel2' (TestModel2.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    [LaunchedURLClassLoader@1bce4f0a] weaveinfo Join point 'field-set(boolean com.example.TestModel2.field3)' in Type 'com.example.TestModel2' (TestModel2.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    set(Integer com.example.TestModel2.field2)
    set(boolean com.example.TestModel2.field3)
    

    Or after removing @EnableLoadTimeWeaving and another rebuild:

    $ java -javaagent:IssueREproduce/javaagents/aspectjweaver.jar -jar IssueREproduce/aspectjlib/target/aspectjlib-0.0.1-SNAPSHOT.jar
    [LaunchedURLClassLoader@7113b13f] info AspectJ Weaver Version 1.9.19 built on Wednesday Dec 21, 2022 at 06:57:22 PST
    [LaunchedURLClassLoader@7113b13f] info register classloader org.springframework.boot.loader.LaunchedURLClassLoader@7113b13f
    [LaunchedURLClassLoader@7113b13f] info using configuration file:/C:/Users/alexa/Documents/java-src/SO_AJ_SprintBootError_77763616/IssueREproduce/aspectjlib/target/aspectjlib-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/META-INF/aop.xml
    [LaunchedURLClassLoader@7113b13f] info register aspect com.example.TestAspect
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.2.1.RELEASE)
    
    2024-01-09 10:05:36.669  INFO 9836 --- [           main] c.e.aspectjlib.AspectjlibApplication     : Starting AspectjlibApplication v0.0.1-SNAPSHOT on Xander-UB with PID 9836 (C:\Users\alexa\Documents\java-src\SO_AJ_SprintBootError_77763616\IssueREproduce\aspectjlib\target\aspectjlib-0.0.1-SNAPSHOT.jar started
     by alexa in C:\Users\alexa\Documents\java-src\SO_AJ_SprintBootError_77763616)
    2024-01-09 10:05:36.674  INFO 9836 --- [           main] c.e.aspectjlib.AspectjlibApplication     : No active profile set, falling back to default profiles: default
    2024-01-09 10:05:37.699  INFO 9836 --- [           main] c.e.aspectjlib.AspectjlibApplication     : Started AspectjlibApplication in 1.712 seconds (JVM running for 2.836)
    [LaunchedURLClassLoader@7113b13f] weaveinfo Join point 'field-set(java.lang.String com.example.TestModel1.field1)' in Type 'com.example.TestModel1' (TestModel1.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    [LaunchedURLClassLoader@7113b13f] weaveinfo Join point 'field-set(java.lang.Double com.example.TestModel1.field4)' in Type 'com.example.TestModel1' (TestModel1.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    set(String com.example.TestModel1.field1)
    set(Double com.example.TestModel1.field4)
    [LaunchedURLClassLoader@7113b13f] weaveinfo Join point 'field-set(java.lang.Integer com.example.TestModel2.field2)' in Type 'com.example.TestModel2' (TestModel2.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    [LaunchedURLClassLoader@7113b13f] weaveinfo Join point 'field-set(boolean com.example.TestModel2.field3)' in Type 'com.example.TestModel2' (TestModel2.java:7) advised by before advice from 'com.example.TestAspect' (TestAspect.java)
    set(Integer com.example.TestModel2.field2)
    set(boolean com.example.TestModel2.field3)
    

    If you compare the log output, you see that aspectjweaver kicks in earlier than spring-instrument, which makes it more powerful and universal, if your aspects also weave Spring classes. It depends on the use case, if spring-instrument is enough or not. Here, both work fine.

    Original answer

    Simply use JVM parameter -javaagent:/path/to/aspectjweaver-1.9.x.jar. When starting Spring or Spring Boot apps from the console, I never managed to get them working without a Java agent on the command line. Your custom weaver bean does not work with all classloaders, as you can see from the error message.

    According to the hint in all release notes from AspectJ releases since 1.9.8 or so, you also need --add-opens java.base/java.lang=ALL-UNNAMED on the JVM command line on JDK 16+. Sorry, but this is not AspectJ's fault. More recent JDKs simply do not provide a proper replacement for the now restricted APIs the load-time weaver needs to do its job.