25

I need a configuration free, deployable war, myapp1.war that can retrieve the configuration files from the tomcat/lib folder. As I have other web applications coexisting on the same Tomcat: myapp2.war, myapp3.war, I need this layout:

tomcat/lib/myapp1/application.properties
tomcat/lib/myapp2/application.properties
tomcat/lib/myapp3/application.properties

This way I can build the war files without any properties files inside the war and deploy on any server.

I have read the Spring documentation but it explains how to set the location when running as a jar:

java -jar myapp.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

I cannot figure out how to do this for the case of multiple coexisting war files.

I would like to know if this is possible or should I give up on Spring Boot and go back to the traditional Spring MVC applications.

Daniel Mora
  • 2,589
  • 1
  • 25
  • 21
  • 1
    It doesn't matter how you set the `spring.config.location` property. Can be system or environment variable. Could even be JNDI or the servlet context. So it doesn't have to be a argument to your jar. However instead of using `application.properties` you might want to give the file another name instead of each app its own directory. You can then simply load it from a default, globally set location. – M. Deinum Dec 08 '16 at 11:11

3 Answers3

30

A solution could be to load application-{profile}.properties as @PropertySource annotations as this question suggests, but then the logging system wont work, as you can see in the documentation.

The logging system is initialized early in the application lifecycle and as such logging properties will not be found in property files loaded via @PropertySource annotations.

This means that your logging properties in application-{profiles}.properties like:

logging.config=classpath:myapp1/logback.xml
logging.path = /path/to/logs
logging.file = myapp1.log

will be ignored and the logging system wont work.

To solve this I have used the SpringApplicationBuilder.properties() method to load properties at the beginning, when the application is configured. There I set the 'spring.config.location' used by Spring Boot to load all the application-{profiles}.properties:

public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder springApplicationBuilder) {
        return springApplicationBuilder
                .sources(Application.class)
                .properties(getProperties());
    }

    public static void main(String[] args) {

        SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(Application.class)
                .sources(Application.class)
                .properties(getProperties())
                .run(args);
    }

   static Properties getProperties() {
      Properties props = new Properties();
      props.put("spring.config.location", "classpath:myapp1/");
      return props;
   }
}

Then I have moved the properties files from src/main/resources to src/main/resources/myapp1

.
├src
| └main
|   └resources
|     └myapp1
|       └application.properties
|       └application-development.properties
|       └logback.xml
└─pom.xml

In the pom.xml I have to set the scope of embedded tomcat libraries as "provided". Also, to exclude all properties files in src/main/resources/myapp1 from the final war and generate a configuration free, deployable war:

    <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
            <packagingExcludes>
              **/myapp1/
            </packagingExcludes>
        </configuration>
    </plugin>

Then in Tomcat I have

├apache-tomcat-7.0.59
 └lib
   ├─myapp1
   |  └application.properties        
   |  └logback.xml
   └─myapp2
     └application.properties
     └logback.xml

Now I can generate the configuration free war and drop it into the apache-tomcat-7.0.59/webapps folder. Properties files will be resolved using the classpath, independently for each webapp:

   apache-tomcat-7.0.59/lib/myapp1
   apache-tomcat-7.0.59/lib/myapp2
   apache-tomcat-7.0.59/lib/myapp3
Community
  • 1
  • 1
Daniel Mora
  • 2,589
  • 1
  • 25
  • 21
  • 2
    Very helpful answer. However, whatever I do I cant seem to get Spring to see the logback.xml in the myapp1 folder, even if I set logging.config=classpath:myapp1/logback.xml, it just ignores it. Have you found any way around this? – ChrisGeo Jun 22 '16 at 17:51
  • @ChrisGeo What do you have inside apache-tomcat-7.x.xx/lib/myapp1 ? Have you checked that no application.properties files are inside the generated war? – Daniel Mora Jun 23 '16 at 12:47
  • @DanielMora i tried with the same code what you have given here. But it is not working, don't know what i have missed in config. As a jar it is working well, but failed when deployed as war. I'm getting this exception >>>>> java.io.FileNotFoundException: class path resource [application.properties] cannot be opened because it does not exist >>>>> don't know how to fix this, cloud you please help me.. – Srikanth Feb 02 '17 at 10:23
  • Thanks! Solved my initial problem and helped me understand the difference between Spring-Boot-Apps-as-WARs and standalone Jars with the embedded Tomcat. – dribnif Feb 03 '17 at 15:59
  • 2
    I think it is worth noting that "_The default search path classpath:,classpath:/config,file:,file:config/ is always used, irrespective of the value of `spring.config.location`_" from [documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files) – coderatchet May 30 '17 at 06:10
  • Interesting solution. How can you manage the initialization (i.e., SpringApplicationBuilder.properties() ) in tests? Thanks in advance. – baronKarza Feb 16 '18 at 13:58
2

With Spring 4.2 and @Annotation config and tomcat on linux serveur

In your Application class set the @PropertySource like that :

@Configuration
@EnableWebMvc
@PropertySource(value = { "classpath:application-yourapp.properties"})
@ComponentScan(basePackages = "com.yourapp")
public class YourAppWebConfiguration extends WebMvcConfigurerAdapter {

    ...
}

Now you just need to include the property file in your classpath

In production

Deploy your .war files ( or anything ) on tomcat, and put your application-yourapp.properties anyway on your production machine. ( for exemple in /opt/applyconfigfolder/application-yourapp.properties" )

Then in your tomcat ( here tomcat 7 ) open bin\catalina.sh

You have this line

# Ensure that any user defined CLASSPATH variables are not used on startup,
# but allow them to be specified in setenv.sh, in rare case when it is needed.
CLASSPATH=

Just add the path of the folder which contains application.properties

CLASSPATH=:/opt/applyconfigfolder

If you have already some classpath define you can add it

CLASSPATH=:/opt/applyconfigfolder:/yourpath1:/yourpath2:

I haven't try with windows but I think there is no problem

In Dev ( with eclipse )

├src
| └main
|   └ ....
└config
| └application-yourapp.properties

instead of src/main/resources/application-yourapp.properties

Now in eclipse add your config folder to classpath, go to "Run Configurations" of your tomcat server ( or equivalent ) and add the folder Config to User Entries

enter image description here

Ok that's it, your application.properties is out of the application and your project run perfectly in dev environment.

amdev
  • 3,010
  • 3
  • 35
  • 47
  • Instead of mutating `bin/catalina.sh`: prefer to append to the file `bin\catalina.sh` (creating if it does not yet exist). `echo 'CLASSPATH=$CLASSPATH:/opt/applyconfigfolder' >> "$CATALINA_BASE/bin/setenv.sh"` – Birchlabs Jul 03 '17 at 11:41
  • 1
    correction: I mean to say "prefer to append to the file _`$CATALINA_BASE/bin/setenv.sh"`_. – Birchlabs Aug 09 '17 at 16:43
2

Daniel Mora gave a good solution but instead of using spring.config.location you can use spring.config.name (https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files), so you can have different properties file for different web apps in the same tomcat/lib directory:

    public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder springApplicationBuilder) {
        return springApplicationBuilder
                .sources(Application.class)
                .properties(getProperties());
    }
    public static void main(String[] args) {

        SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(Application.class)
                .sources(Application.class)
                .properties(getProperties())
                .run(args);
    }

   static Properties getProperties() {
      Properties props = new Properties();
      props.put("spring.config.name", "myapp1");
      return props;
   }
}

I think that the lib directory is for third party libraries not for storing configuration properties for your web apps. So I think that a better solution is to add an external folder as additional classpath folder using shared.loader property in conf/catalina.properties:

shared.loader=${catalina.base}/shared/configurations

You can put your application properties app1.properties, app2.properties, ecc.. in apache-tomcat-7.0.59/shared/configurations.

Before finding Daniel Mora solution of overridding configure method of SpringBootServletInitializer my solution was to add a context.xml in src/main/webapp/META-INF with this content:

<Context>
    <Environment name="spring.config.name" value="myapp1" type="java.lang.String" override="false" description="define the property file for srping boot application"/>
</Context>
  • One potential downside to using `spring.config.name` is that bundled properties get precedence over say external property files, and so it's not possible to override built-in properties from outside. Using `spring.config.location` or better still, `spring.config.alternate-location` allows property overriding because the provided locations take precedence. – Venkat Peri Jul 19 '19 at 08:16
  • Why you say that bundled properties get precedence over external property files? Can you make an example? Thank you – Salvatore Iamundo Sep 02 '19 at 16:10