Spring Boot YML and StandAlone Tomcat 8 Server

11,642

Credit: @M. Deinum

There are two options for passing spring profile args into Tomcat 8.

1. Set it as environment variable

Tomcat allows you to set environment config in CATALINA_HOME/setenv.sh or CATALINA_BASE/setenv.sh that are called during the start process.

setenv.sh:
export SPRING_PROFILES_ACTIVE=dev

You might also want to create a src/main/resources/banner.txt with this line in it:

active profiles     :: ${spring.profiles.active}

It won't work in your IDE (it reads from your jar/war's MANIFEST.MF file, which you won't have if you're compiling normally), but it's REALLY handy in the environments you care about -- everything BUT your local environment!

2. Add it to the start script/command before the before the executing class

I modified CATALINA_HOME/catalina.sh added a declared variable:

SPRING_PROFILE="dev"

And at all the relevant executions added it to script:

  eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
      -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
      -Dcatalina.base="\"$CATALINA_BASE\"" \
      -Dcatalina.home="\"$CATALINA_HOME\"" \
      -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
      -Dspring.profiles.active="\"$SPRING_PROFILE\"" \
      org.apache.catalina.startup.Bootstrap "$@" start \
      >> "$CATALINA_OUT" 2>&1 "&"

Obviously this isn't the recommended approach. But it works! If you do have the exact replicable steps for doing it the recommend approach feel free to post the answer and if it works i'll accept it.

Share:
11,642
Shivam Sinha
Author by

Shivam Sinha

Updated on June 09, 2022

Comments

  • Shivam Sinha
    Shivam Sinha almost 2 years

    I have the following directory structure/config file:

    src/main/resource/config: 
    application.yml 
    application-dev.yml 
    application-sit.yml
    

    Note according to the "Bootiful Configuration" https://spring.io/blog/2015/01/13/configuring-it-all-out-or-12-factor-app-style-configuration-with-spring:

    Spring Boot will read the properties in src/main/resources/application.properties by default. If a profile is active, it will also automatically reads in the configuration files based on the profile name, like src/main/resources/application-foo.properties where foo is the current profile. If the Snake YML library is on the classpath, then it will also automatically load YML files.

    Since snake YML jar is in class path if I set --spring.profiles.active=dev as a program arg in eclipse run configuration and use this as my main method Ever thing works as expected:

      public static void main(String[] args) {
            SpringApplication app = new SpringApplication(Application.class);
    
            SimpleCommandLinePropertySource source = new SimpleCommandLinePropertySource(args);
    
            // Check if the selected profile has been set as argument.
            // if not the development profile will be added
            addDefaultProfile(app, source);
    
            app.run(args);
        }
    
        /**
         * Set a default profile if it has not been set
         */
        private static void addDefaultProfile(SpringApplication app, SimpleCommandLinePropertySource source) {
            if (!source.containsProperty("spring.profiles.active")) {
                app.setAdditionalProfiles(Constants.SPRING_PROFILE_DEVELOPMENT);
            }
        }
    

    (Please note the main method reference above is from the following class used in my code: https://github.com/jarias/generator-jhipster-ember/blob/master/app/templates/src/main/java/package/_Application.java)

    Everything works as expected for spring.profile.active=dev. Which means that both: application.yml(loaded by default) and application-dev.yml(active profile) property files are loaded and excludes application-sit.yml since sit isn't an active profile.

    This embedded container works great for dev testing. However I want to release this into production by generating a war and deploy it to a standalone Tomcat8 Server.

    For that I created an implementation of WebApplicationInitializer which is required by Tomcat8 server to automatically detect, bootstrap and start spring application on the standalone server.

    @Configuration
    public class WebAppInit implements WebApplicationInitializer {
    
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
            }
    }
    

    After deploying the war I receive the following error I attempt to start the standalone server and receive the following error :

    Caused by: org.springframework.beans.factory.enter code hereBeanCreationException: Could not autowire field: private java.lang.String com.titlefeed.config.db.DbConfigJPA.databaseUrl; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder spring.data.postgres.uri' in string value "${spring.data.postgres.uri}"

    Which implies the Tomcat Server/Spring isnt loading the application-dev.yml since that contains the properties: spring.data.postgres.uri

    So I attempted the following two solutions

    1. added -Dspring.profiles.active=dev to JAVA_OPTS in tomcat/bin/catalina.sh
    2. added spring.profiles.active=dev to tomcat/conf/catalina.properties

    And neither of them worked. How can I get the standalone tomcat server to load the yml file associated with the spring.profiles.active property.

    It works fine for the embedded springboot server started from eclipse but doesnt for an standalong server ?

    EDIT1: M. Deinum - Implemented your suggested solution below however still got the following error:

    Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.data.postgres.uri' in string value "${spring.data.postgres.uri}

    It seems like the -Dspring.profiles.active=dev isn't getting set.

    @Configuration
    public class WebAppInit extends SpringBootServletInitializer {
    
     @Override
    
        protected WebApplicationContext createRootApplicationContext(
                ServletContext servletContext) {
               log.info("Properly INITALIZE spring CONTEXT");
               ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
               servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
               return super.createRootApplicationContext(servletContext);
        }
    
    }
    

    EDIT 2 ACV: - Adding "--spring.profiles.active=dev" as apart of JAVA_OPTS variable in the startup script: tomcat/bin/catalina.sh is not a viable option

    E.g:

     JAVA_OPTS="$JAVA_OPTS --spring.profiles.active=dev ...etc
    

    Gives the following error:

    Unrecognized option: --spring.profiles.active=dev Error: Could not create the Java Virtual Machine."

    EDIT 3: Amended application.yml to include the following property

    spring:
      profiles:
        active: dev
    

    Redeployed the war. Went to the exploded tomcat directory location to ensure the property was present webapps/feedserver/WEB-INF/classes/config/application.yml

    And the issue still occurred.

    EDIT 4: Added application.properties under the tomcat exploded webdir: webapps/feedserver/WEB-INF/classes/application.properties:

    spring.profiles.active=dev
    spring.data.postgres.uri=jdbc:postgresql://localhost:5432/feedserver
    

    restarted tomcat and the issue still occurred.

    Its seems like its not picking up either application.properties or application.yml

    EDIT 5 Used the recommended way to start the spring boot server for an external container:

    @Configuration
    public class WebAppInit extends SpringBootServletInitializer {
    
     @Override
     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
         return application.sources(Application.class);
     }
    
    }
    

    Edit 6:

    I added -Dspring.profiles.active=dev to the start command args:

    /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/java -Djava.util.logging.config.file=/Users/shivamsinha/Desktop/Programming/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dlog4j.rootLevel=ERROR -Dlog4j.rootAppender=console -DENV=dev -Dlog4j.configuration=/WEB-INF/classes/properties/log4j.properties -DTOMCAT_DIR=WEB-INF/classes/ -Djava.endorsed.dirs=/Users/shivamsinha/Desktop/Programming/tomcat/endorsed -classpath /Users/shivamsinha/Desktop/Programming/tomcat/bin/bootstrap.jar:/Users/shivamsinha/Desktop/Programming/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/Users/shivamsinha/Desktop/Programming/tomcat -Dcatalina.home=/Users/shivamsinha/Desktop/Programming/tomcat -Djava.io.tmpdir=/Users/shivamsinha/Desktop/Programming/tomcat/temp org.apache.catalina.startup.Bootstrap -Dspring.profiles.active=dev start
    

    However I stil get the following exception in the logs:

    Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.titlefeed.config.db.DbConfigJPA.databaseUrl; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.data.postgres.uri' in string value "${spring.data.postgres.uri}"
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
        ... 68 more
    Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.data.postgres.uri' in string value "${spring.data.postgres.uri}"
        at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
        at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
        at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:204)
        at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:178)
        at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:175)
        at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:801)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:955)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
        ... 70 more
    
    02-Sep-2015 03:15:40.472 SEVERE [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive /Users/shivamsinha/Desktop/Programming/tomcat/webapps/feedserver-1.0.0.war
     java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/feedserver-1.0.0]]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:728)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:714)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:917)
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1701)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)