0

So I am trying to migrate a spring mvc (sp4) application to spring-boot application but I want to take a "bottom up" approach since I already have my configuration files made/tested and I know they work I just want to convert to a barebones spring-boot-web application where my existing configs will work on an embedded tomcat server.

All the solutions I have read so far in the documentation take a "top down" approach where they suggest to import all the starter jars you need and @EnableAutoConfiguration and "turn off" configs you don't need over time. I think this "top down" approach works great if you're starting from scratch but not if you're migrating an existing application.

Problem: That being said i'm trying to migrate my existing sp4 mvc app via a "bottom up" approach without @EnableAutoConfiguration... but i'm having trouble creating a barebones spring-boot-web application with my existing configs.

My configuration:

that implements a WebApplicationInitializer and @Override(s) the startup method

public class FooWebAppWebApplicationInitializer implements WebApplicationInitializer {

    public static final String SERVLET_NAME = "foo-web-app";

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class);

        // Manage the lifecycle of the root application context
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(SpringMvcConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(SERVLET_NAME, new DispatcherServlet(dispatcherContext));

        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        //Spring security config
        FilterRegistration.Dynamic springSecurityFilterChain = 
                servletContext.addFilter(
                        "securityFilter", new DelegatingFilterProxy("springSecurityFilterChain")
                        );

        springSecurityFilterChain.addMappingForServletNames(null, false, SERVLET_NAME);

        servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);

    }

Note: With SpringMvcConfig.class contains an @EnableWebMvc annotation.

Note: no @Component scanning is used... all beans are EXPLICITLY declared in config classes.

Spring Boot Runner:

@Configuration
public class SpringBootRunner {

    public static void main(String[] args) {

        //SpringApplicationBuilder sab = new SpringApplicationBuilder();
        SpringApplication springApplication 
                    = new SpringApplication(RootConfig.class,
                                            SpringMvcConfig.class);

        springApplication.run(args);
        //SpringApplication.(SpringBootRunner.class, args);
    }

    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.setPort(9000);
        factory.setSessionTimeout(10, TimeUnit.MINUTES);
        //factory.setErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
        return factory;
    }

}

Maven pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.foo</groupId>
    <artifactId>foo-web-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java-version>1.8</java-version>
    </properties>

    <dependencies>

       //... omitted application specific dependencies 
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
      </dependencies>
    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
         </plugins>
</build>

...

Things i've tried...

  1. As the spring boot documentation states: Convert the existing WebApplicationInitializer -> SpringBootServletInitializer and copy/paste the contents

    • tried passing the SpringBootServletInitializer to my runner (Result: server starts but my config was ignored)

    • tried @Override the configure method explicitly passing in the root / servlet context

.

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    application.sources(RootConfig.class);
    application.child(SpringMvcConfig.class);
    return application.sources(SpringBootServletInitializer.class);
}

that again resulted in the tomcat server starting but my config being ignored.

  1. Tried to pass the root and mvc configs directly to the SpringBootRunner resulted in... the configs loading in the logger but threw the following exception

.

Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]

Question: Tried various other approaches still with no luck. Tired of guessing. I really couldn't find an example of what I am trying to achieve anywhere. Does anyone know of a sample project or know what runner/config I need to achieve my desired "bottom up" approach for a migration to spring boot?

I just want to migrate my existing sp4 mvc config to a barebones spring-boot-web app and enable boot starters 1 by 1 after running my existing unit/integration tests on each starter jar I am migrating to.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Selwyn
  • 3,118
  • 1
  • 26
  • 33
  • The approach to take depends on what do you want? Do you want to use en embedded container or do you want to create a war file and deploy that. – M. Deinum Jan 04 '16 at 08:34

2 Answers2

0

Assuming you want to use an embedded container use the following. (Drop what you have now).

@SpringBootApplication
@Import(RootConfig.class, SpringMvcConfig.class)
public class SpringBootRunner extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRunner.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
      return application.sources(SpringBootRunner.class);
    }

}

The @Import will import your own configured beans. (Also put this class somewhere in your root package (i.e. com.your.app). Although there is a @SpringBootApplication annotation which enables auto configuration this will largely be disabled by the fact that you configure everything yourself (Spring Boot is smart enough to detect that).

To change the server port and session timeout just add an application.properties containing the following

server.port=9000
server.session.timeout=600 // in seconds!

This should start an embedded container or you should be able to deploy it to a war. Now you can step-by-step strip the things from your configuration you don't need.

Spring Boot will already add the Spring Security filter and HiddenHttpMethodFilter for you (that is then one thing you don't need to configure). The Spring Security configuration will be used from the config you provided.

As a final comment the Spring Boot starters are no more then a set of dependencies it doesn't do anything for auto configuration. So you can either include spring-boot-starter-web as a dependency or all of the spring-web dependencies by yourself. It is nothing more then convenience and it takes care of transitive dependency management as well.

You could even use the starters (or the Spring IO Platform bom) for dependency management without even using Spring Boot features.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • still getting that 'Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling' exception – Selwyn Jan 04 '16 at 11:13
  • Have you removed all the stuff you tried, have you removed your own `WebApplicationInitializer`? Also if you want a jar then make sure it is a jar and not a war (and remove the `extends SpringBootServletInitializer` and override method). – M. Deinum Jan 04 '16 at 11:14
  • Commented out all the lines of code in `WebApplicationInitializer`. Removed `extends SpringBootServletInitializer` and `Override` still getting the same exception. – Selwyn Jan 04 '16 at 11:19
  • looks like it successfully maps all the spring mvc defined controllers but 'chokes' write after that on this exception. http://prntscr.com/9lx6k5 – Selwyn Jan 04 '16 at 11:32
  • what do you mean? I'm not even sure what the exception means. This is why I wanted to do the 'bottom up' approach to isolate migration 1 by 1 to determine what could be the cause. – Selwyn Jan 04 '16 at 11:45
  • Adding the simple class above and removing your custom `WebApplicationInitializer` should be all you need to do. That should give you all your controllers and beans in your configuration. You have tried a lot, even tried to configure the embedded container by hand. The issue is in your configuration or how you are bootstrapping things, it has nothing to do with starters as starters are nothing more then dependency management things there is no code or whatsoever in those (all that code is in spring boot). Also pleas add the stack trace as text to your question not as an linked image/snippet. – M. Deinum Jan 04 '16 at 11:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99689/discussion-between-selwyn-jacobs-and-m-deinum). – Selwyn Jan 04 '16 at 11:50
  • @M Deinum thanks for the help I was able to replicate this and I filed the following issue: FYI I went ahead and created the following issue: https://github.com/spring-projects/spring-boot/issues/4875 – Selwyn Jan 04 '16 at 15:08
0

So... turns out many of the scenarios I was trying should of worked but I got 'gotcha-ed' by this issue:

How do I add method based security to a Spring Boot project?

Basically if you have a custom PermissionEvaluator interface bean defined in the SAME config class as spring security config... you will get these cryptic exceptions and your embedded tomcat will fail to start.

Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]

The work around is to defined the custom the PermissionEvaluator beans in their own SEPARATE config class files.

Community
  • 1
  • 1
Selwyn
  • 3,118
  • 1
  • 26
  • 33