Spring boot environment specific properties for dependencies - java

I am curious what is the best practice for environment (or even server) specific properties for dependencies.
This is not for properties that are managed by the applicationContext, as I am aware of the {env}-application.properties convention that Spring supports.
I will give a small example to elaborate:
My project, is project A. In A, we depend on Project B
A's pom.xml
<dependency>
<groupId>com.B</groupId>
<artifactId>B</artifactId>
</dependency>
Artifact B has a dependency on B.properties, which it does not provide and it must be on the classpath. I am unable to refactor Artifact B.
Contents of B.properties are:
b.someproperty=${property.placeholder}/b/dir
Contents of application.properties:
property.placeholder=default
Contents of dev-application.properties:
property.placeholder=dev
So when I run my spring boot app with -Dspring.profiles.active=dev, b.someproperty must resolve to dev/b/dir
What I have tried so far (that worked):
1) Externalized properties that sit on the server, with the placeholders already resolve and simply adding them to the classpath at runtime (So each server would have it's own B.properties file with no placeholders, and this would not be part of the build/deploy process)
2) Class within the spring bootable jar which takes the properties in b.properties, resolves all the placeholders, and writes it out to a file on the server and then adds this file to the classpath. So running the app with -Dspring.profiles.active=dev would generate b.properties on the server with no placeholders, and everything is contained within the jar.
Neither of these solutions are very clean, or good (imo). Is it possible to resolve the placeholders as the properties are being used, even if they are not being managed by the application context?
Any insight or criticism of my current solutions appreciated.

Related

Reference SQL scripts in maven dependency using Quarkus Flyway

I want Quarkus to execute a flyway migration based on some SQL scripts that I have stored in a separate repository, then packaged into a jar file and published to a private Nexus instance.
I believe I can point to a specific location using this application property:
quarkus.flyway.locations=/some/path
But given this dependency:
<dependency>
<groupId>com.myorganisartion.db</groupId>
<artifactId>myschema</artifactId>
<version>18.0.0</version>
</dependency>
What would the value of the flyway.locations property be?
Assume that the folder contains just one folder, containing the .sql files, called myschema.
Thanks in advance!
I've tried googling around and look into quarkus example app, but no luck.
I see that you can reference the classpath in the property value, but I'm not sure what to put after that, and why?
It seems I forgot to reference the datasource:
quarkus.flyway.myschema.locations=classpath:myschema/baseline,classpath:myschema/migrations,

PropertiesLauncher command line arguments not working with Spring Boot executable Jar

So I have basic multi-module Spring Boot project. The goal, that I had was to build executable jar and pass additional properties with the help of -Dloader.path=....
For some reason (if I understand purpose of this argument) loader.path is being ignored completely.
My project structure is following:
\-
|--conf
|---default
|--pets-api
|--pets-app (this module contains the Main-Class)
|--pets-domain
|--pets-infrastructure
Since no custom active profile is being passed it uses "default". Jar contains application-default.propeties file, that has single configuration server.servlet.context-path=/v1.
/conf/default location has 2 properties files:
application.properties
random.properties - this is bind to #ConfigurationProperties(prefix = "...") inside application
When I run it normally everything is fine java -jar pets-app-0.0.1-SNAPSHOT.jar. It just uses application-default.properties file and that is it.
Now when I am trying to utilize -Dloader.path argument as in java -Dloader.path=PATH/TO/conf/default -jar pets-app-0.0.1-SNAPSHOT.jar it starts application same as before, as if I am not adding 2 more file to classpath.
What is used:
Java 17
Spring Boot 2.6.12
Gradle
Did anyone come across this as well? Any suggestion on how to resolve it?
PS. If there is need to see the code, I can upload it to GitHub.

Possible to ignore configuration classes on the classpath?

I have a Spring Boot application that works as expected when ran with embedded tomcat, but I noticed that if I try to run it from an existing tomcat instance that I'm using with a previous project then it fails with a NoClassDefFoundError for a class that I don't use anywhere in my application.
I noticed in the /lib directory I had a single jar that contained a few Spring annotated classes, so as a test I cleaned out the /lib directory which resolved the issue. My assumption is that Spring is seeing some of the configurations/beans/imports on the classpath due to them existing in the /lib directory and either trying to autoconfigure something on its own, or is actually trying to instantiate some of these classes.
So then my question is - assuming I can't always fully control the contents of everything on the classpath, how can I prevent errors like this from occurring?
EDIT
For a little more detail - the class not being found is DefaultCookieSerializer which is part of the spring-session-implementation dependency. It is pulled into one of the classes in the jar located in /lib, but it is not any part of my application.
Check for features provided by #EnableAutoConfiguration. You can explicitly configure set of auto-configuration classes for your application. This tutorial can be a good starting point.
You can remove the #SpringBootApplication annotation from the main class and replace it with an #ComponentScan annotation and an #Import annotation that explicitly lists only the configuration classes you want to load. For example, in a Spring boot MVC app that uses metrics, web client, rest template, Jackson, etc, I was able to replace the #SpringBootApplication annotation with below code and get it working exactly as it was before, with all functional tests passing:
#Import({ MetricsAutoConfiguration.class,
InfluxMetricsExportAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class,
WebClientAutoConfiguration.class,
RestTemplateAutoConfiguration.class,
RefreshAutoConfiguration.class,
ValidationAutoConfiguration.class
})
#ComponentScan
The likely culprit of mentioned exception are incompatible jars on the classpath.
As we don't know with what library you have the issue we cant tell you the exact reason, but the situation looks like that:
One of Spring-Boot autoconfiguration classes is being triggered by the presence of class on the classpath
Trigerred configuration tries to create some bean of class that is not present in the jar you have (but it is in the specific version mentioned in the Spring BOM)
Version incompatibilities may also cause MethodNotFound exceptions.
That's one of the reasons why it is good practice not to run Spring Boot applications inside the container (make jar not war), but as a runnable jar with an embedded container.
Even before Spring Boot it was preferred to take account of libraries being present on runtime classpath and mark them as provided inside your project. Having different versions of the library on a classpath may cause weird ClassCastExceptions where on both ends names match, but the rest doesn't.
You could resolve specific cases by disabling autoconfiguration that causes your issue. You can do that either by adding exclude to your #SpringBootApplication or using a property file.
Edit:
If you don't use very broad package scan (or use package name from outside of your project in package scan) in your Spring Boot application it is unlikely that Spring Boot simply imports configuration from the classpath.
As I have mentioned before it is rather some autoconfiguration that is being triggered by existence of a class in the classpath.
Theoretical solution:
You could use maven shade plugin to relocate all packages into your own package space: see docs.
The problems is you'd have face:
Defining very broad relocation pattern that would exclude JEE classes that need to be used so that container would know how to run your application.
Relocation most likely won't affect package names used as strings in the Spring Boot annotations (like annotations #PackageScan or #ConditionalOnClass). As far as I know it is not implemented yet. You'd have to implement that by yourself - maybe as some kind of shade plugin resource processor.
When relocating classes you'd have to replace package names in all relevant configuration located in the jars. Possibly also merge some of those.
You'd also have to take into account how libraries that you use, or spring uses use package names or files.
This is definitely not a trivial tasks with many traps ahead. But if done right, then it would possibly allow you to disregard what is on the containers classpath. Spring Boot would also look for classes in relocated packages, and you wouldn't have those in ordinary jars.

Overriden application.yml in multi-module Spring Boot app

There is a Spring Boot 2 app with such a structure:
parent-module
module-1
src
main
java
resources
- application.yml
module-2
src
main
java
resources
- application.yml
Also, module-1 depends on module-2, specified in pom.xml dependencies section.
The problem is that when I specify some properties in module-2's application.yml - they are not visible in main module-1's components (via #Value annotation).
As was answered here seems like module-1's application.yml overrides module-2's application.yml. There is a workaround - if I use name application.yaml in module-2 everything works fine, but I'm going to add more modules and, finally, it's dirty hack.
What I'm doing wrong? Should such an hierarchy of property files specified somehow?
I will be happy to provide more details if it's needed.
Thank you!
Spring Boot is a runtime framework. I understand that your modules are not spring-boot applications by themselves (you can't make a dependency on a spring boot application packaged with spring boot maven plugin, because it produces an artifact that is not really a JAR from the Java's standpoint although it does have *.jar extension).
If so, they're probably regular jars. So you should have a "special" module that assembles the application. This special module lists both 'module1' and 'module2' in <dependency> section and should contain a definition of spring-boot-maven-plugin in its build section (assuming you're using maven). But if so you shouldn't really have more than one application.yml - it will be misleading. Instead, put the application.yml to the src/main/resources of that "special" module.
If you really have to for whatever reason work with multiple application.yaml files, make sure you've read this thread
I know, this is already a well-aged post.
I just came accross the same issue and the best solution I found was to import the module-specific configurations with the spring.config.import directive as described here.
In this case you still have your module specific configuration in property or yaml files within that specific module and do not have too much unwanted dependencies in your project setup.
application.yml is, as the name indicates, an application-level file, not a module-level file.
It is the build script that assembles the final application, e.g. the .war file, that needs to include a application.yml file, if any.
If modules need properties, and cannot rely on the defaults, e.g. using the : syntax in #Value("${prop.name:default}"), they need to provide a module-level property file using #PropertySource("classpath:/path/to/module-2.properties").
Note: By default, #PropertySource doesn't load YAML files (see official documentation), but Spring Boot can be enhanced to support it. See #PropertySource with YAML Files in Spring Boot | Bealdung.
Alternative: Have the application-level build script (the one building the .war file) merge multiple module-level build scripts into a unified application.yml file.

How to externalize application.properties files in Spring Boot to e.g. external dependent JAR?

I have a simple Maven module (not a Spring Boot application) in which I have placed my application.properties file.
I have 6-7 Spring Boot applications and I don't want to have an application.properties file in each and every application directory. I prefer it, if it is at one single place (external Maven module).
I am adding the maven module as a dependency in each of those Spring Boot application poms.
But, when I run those applications, it is not able to auto-detect the application.properties file because it is coming from a dependent jar not present physically in each of their application directories.
Is there any way to make this possible? I would like to avoid having properties files in 6-7 different locations, because that becomes tough to manage and handle.
Thank you in advance!
Consider using Spring Cloud Config that provides server and client-side support for externalized configuration in a distributed system. It requires some small effort, but it is very useful in long term. Config server manages configuration files (.properties or .yml), you can still use different config per profile (e.g. application-test.properties, application-prod.properties etc.). Your application has a higher priority, so you can always override properties coming from config server if needed. Another cool feature is that config server can utilize Git repository, so you can easily version all your configuration files. It also supports encryption - any fragile data can be encrypted so only your application knows how to decrypt it.
Config server
Config server is nothing else than a simple Spring Boot application that can be implemented as:
#SpringBootApplication
#EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
with simple application.properties file included:
server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo
with dependency in pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
Config client
On client side you add a dependency to your pom.xml (or its equivalent in build.gradle if you use Gradle):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
and all you have to do is add a URL to config server to your application.properties (or application.yml if you use YAML insted):
spring.cloud.config.uri: http://myconfigserver.com
Config files structure
Now let's say you have set up Git repository for your configuration files. Let's assume that your applications are named like horus, venus, mercury etc. and you have 3 different profiles: dev, test and prod. You also have some configuration that is common for all applications. In this case your configuration files structure would look like this (I will use properties files here but it applies to YAML as well):
application.properties - common config for all apps no matter what profile they use
application-dev.properties - common config for all apps running with dev profile
application-test.properties - common config for all apps running with test profile
application-prod.properties - common config for all apps running with prod profile
horus.properties - horus app config for, common for all profiles
horus-dev.properties - horus app config for dev profile only
horus-test.properties - horus app config for test profile only
horus-prod.properties - horus app config for prod profile only
etc.
There are some additional options that can be set (like encryption, connection strategy (fail fast or ignore) etc.), everything is well described and documented in official documentation https://cloud.spring.io/spring-cloud-config/ Hope it helps you making a good choice for managing your configuration in distributed application environment. Config server is a solution that was invented to solve this problem.
While Szymon Stepniak's answer certainly is a "by-the-book" of Spring Boot answer, I understand your situation, and even tried to do what you try to do by myself. Indeed, you can't define application.properties in other "external modules".
Here is how I've solved it:
Create a configuration in the "common" module
Create a property file in src/main/resources. It shouldn't be named application properties, It's better to provide a unique name for it (at least this is how I've done it, so let's assume that the file is called application-common.properties)
Use #PropertySources annotation to define a property file and load it with configuration.
Here is an example:
package com.myapp.common;
#Configuration
#PropertySources({
#PropertySource("classpath:application-common.properties")
})
public class MyConfiguration {
// you don't really have to define beans
}
Now if you want this configuration to load automatically only because the dependency is defined in your spring boot module of your build system, I've found the best to utilize spring factories:
Create the file src/main/resources/META-INF/spring.factories
Place the following into this file:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.myapp.common.MyConfiguration

Categories