Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Springboot - DevTools - RestController not always mapped when Rebuild Project

I'm using SpringBoot 1.3.5 with maven.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>

And devtools

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

I'm using Intellij IDEA 2016.2, previously 2014 with same problem.

I'm running my springboot app from Intellij Idea, first launch everything is well loaded and works, I can access my static pages and my 2 Rest Controllers work.

2016-08-18 15:27:58.771  INFO 26626 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@469d0c02: startup date [Thu Aug 18 15:27:57 CEST 2016]; root of context hierarchy
2016-08-18 15:27:58.789  INFO 26626 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/authentication/introspect],methods=[GET]}" onto public com.myapp.models.TokenIntrospection com.myapp.resources.AuthenticationResources.introspectToken(java.lang.String)
2016-08-18 15:27:58.790  INFO 26626 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/configuration],methods=[GET]}" onto public com.myapp.models.AppConfiguration com.myapp.resources.ConfigurationResources.getConfiguration()
2016-08-18 15:27:58.792  INFO 26626 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-08-18 15:27:58.793  INFO 26626 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

Because simply "Make Project" doesn't work well for static reload, I use "Rebuild Project" and sometimes, when app restart, I don't have my controllers mapped, sometimes one is missing, sometimes both are missing.

I don't have any clue about this :(

EDIT

@Morfic solutions did not work, so I used the Intellij local server to serve static contents and gulp-livereload instead of spring-dev-tools.

IJ local server

I just had to manage REST calls in JS when I'm in dev mode because REST resources are on localhost:8080 but my statics on localhost:63342, and enable CORS in my springboot (with a flag in properties file to enable CORS or not).

@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {

    @Value("${cors.enabled}")
    private boolean corsEnabled;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        super.addCorsMappings(registry);
        if(corsEnabled) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("GET", "PUT", "POST", "DELETE", "OPTIONS")
                    .allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization")
                    .allowCredentials(true)
                    .maxAge(3600L);
        }
    }
}

So question still pending for a working solution.

like image 289
Maelig Avatar asked Aug 18 '16 13:08

Maelig


1 Answers

I just managed to reproduce this with a simple hello-world service and using Rebuild project a couple of times because it only reproduces once in a while. My hunch is that dev-tools figure out a change has occurred before IJ has a chance to completely clean & rebuild. Probably as soon as the resources are published and before the classes are compiled from what I've seen looking at the output dir, dev-tools starts reloading the classes which are not yet present...

Following this supposition, I looked at the logs and classes timestamps and there's about a 1s gap between the time dev-tools starts reloading the context and the time my classes are written to disk. Obviously none of my @PostConstruct logs appear and spring's autoconfig does not find my classes...

As a workaround, you could use a trigger-file. Either add it as a global config in your home-dir as suggested in the link, or in your application.properties file. Additionally since any change to this file (creation, deletion, modification) would trigger a restart, and Rebuild project cleans the output directory, you'd have to define an additional path to look for this file. Thus, suppose we have a regular IJ spring boot run configuration, with the following 2 in the application.properties:

# name of the file to trigger a restart
spring.devtools.restart.trigger-file=restarttrigger

# where else to look for it. Also . evaluates to the app's base dir
spring.devtools.restart.additional-paths=.

... once you see IJ completing the build process, go to the app root dir and add or delete your trigger file, depending on whether it's already there or not, which should cause a restart. I've tested this a few times with no attempt failing so far. Below a short vide demo of the manual restart process:

IJ - boot dev tools manual restart

There are a couple of ways to automate this process. Aside from defining an artefact in IJ and using a post-process ant task to generate file, you can use maven (which you're already using) to generate such a file, but the downside is that you have to use maven compile instead of Rebuild project because IJ will not invoke maven when doing the rebuild (or I haven't found out how to do that yet). Please find below a simple configuration based on the fact that:

(Note: In Maven 2.0.5 and above, multiple goals bound to a phase are executed in the same order as they are declared in the POM, however multiple instances of the same plugin are not supported. Multiple instances of the same plugin are grouped to execute together and ordered in Maven 2.0.11 and above).

As such, the compiler plugin is by default bound to the compile phase, so we add a small task to generate the trigger-file using the application.properties file (or anything else)

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <!-- first, compile all we need -->
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
        </plugin>
        <plugin>
            <!-- then, generate the trigger-file so dev-tools will restart -->
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.8</version>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <configuration>
                        <tasks>
                            <copy file="${project.basedir}/src/main/resources/application.properties"
                                  toFile="${project.basedir}/restarttrigger" overwrite="true" />
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Further update:

Looking at the sources of FileSystemWatcher.scan(), there's a do-while loop which can be interpreted as: while there are still changes ongoing on the file system since the previous check, wait for a (configurable) time and verify again

private void scan() throws InterruptedException {
    Thread.sleep(this.pollInterval - this.quietPeriod);
    Map<File, FolderSnapshot> previous;
    Map<File, FolderSnapshot> current = this.folders;
    do {
        previous = current;
        current = getCurrentSnapshots();
        Thread.sleep(this.quietPeriod);
    }
    while (isDifferent(previous, current));
    if (isDifferent(this.folders, current)) {
        updateSnapshots(current.values());
    }
}

As per the documentation, the quietPeriod is configurable through the spring.devtools.restart.quiet-period property, but also as per the above mention source, it must be a value smaller than pollInterval configurable through spring.devtools.restart.poll-interval. Thus, playing around with the settings, I got a decent result with:

# Amount of time (in milliseconds) to wait between polling for classpath changes.
spring.devtools.restart.poll-interval=3000

# Amount of quiet time (in milliseconds) required without any classpath changes before a restart is triggered.
spring.devtools.restart.quiet-period=2999

In the end, you should be able to adjust those values to what suits you best.

Nonetheless, if the sources you're modifying are static resources such as FE GUI pages and depending on your requirements, perhaps it's better to use a tool which serves them from their location, such as node or a similar simple http server...

like image 111
Morfic Avatar answered Nov 07 '22 16:11

Morfic