Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot YML and StandAlone Tomcat 8 Server

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)
like image 534
Shivam Sinha Avatar asked Sep 01 '15 09:09

Shivam Sinha


1 Answers

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.

like image 135
Shivam Sinha Avatar answered Sep 23 '22 15:09

Shivam Sinha