Spring Boot executable jar and external configuration file - java

I have a Spring Boot application that uses 3rd-party jar. This jar requires an xml config file, that must be provided by clients on runtime (individually) and cannot be pre-packaged. 3rd party lib loads that file using below sequence (I stripped ifs and null-checks):
FileConfigurator.class.getResource("/" + filename);
Thread.currentThread().getContextClassLoader().getResource("/" + filename);
Thread.currentThread().getContextClassLoader().getResource(filename);
I cannot change the way that lib loads the file (e.g. using Spring's Resource loading), so it must be on classpath. Therefore I seem to lose the possibility of executing it like java -jar my-spring-boot-app.jar, because -jar option prevents any additional classpath entries from being added. So I started running it like
java -classpath my-spring-boot-app.jar:./config/: org.springframework.boot.loader.JarLauncher
My directory structure is following:
|-- config
| |-- application.properties
| `-- 3rd-party-config.xml
|-- my-spring-boot-app.jar
But then Spring's autowiring started to fail: Additional application.properties file in config directory overrides some of settings and using above command causes app startup to fail:
Error creating bean with name 'ORBConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.company.app.communication.corba.orb.ORBConfig.serverName; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'application.corba.serverName' in string value "${application.corba.serverName}"
Field String serverName is annotated with #Value("${application.corba.serverName}"), the property is defined in application.properties file bundled within JAR and value injection works fine when additional application.properties is not present in config dir.
My actual question is: what is the advisable way of deploying and/or running Spring Boot application, to take advantage of executable Jar feature, provide additional classpath resources on runtime and still be able to override some (but not all) properties by classpath application.properties file?
Application is packaged using spring boot maven plugin and uses spring-boot-starter-parent parent POM.

One simple answer if you won't change the startup command:
move ./config/application.properties to ./config/config/application.properties
If there exist more than one classpath resources with same name, Spring Boot will load only one of them, in you case, Spring Boot load and prioritize property resources as following:
file:config/application.properties
classpath:application.properties which maybe resolved to either my-spring-boot-app.jar!/applcation.properties or ./config/application.properties
If your classLoader chosen ./config/application.properties as second property source. Bang!
Spring Boot's default configuration property resource path priority (highest to lowest precedence) is:
file:config/
file:
classpath:config/
classpath
The ordinary executable jar execution make those two configuration property fall into:
file:config/application.properties
classpath:application.properties (from jar)
And moving ./config/application.propertie to './config/config/application.properties' becomes:
classpath:config/application.properties
classpath:application.properties (from jar)
Both in the same order and have no ambiguous.

Related

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.

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.

Java Bean Validation 2.0 Hibernate Validator - Executable JAR with external XML-Configuration

Context
I'm developing a stand-alone application that reads input-data from a csv-file.
Until now I've used a self-written class to perform input validation,
and the method used for validating a single record from the input file was triggered
immediately after reading a single line. The method was called in the methode for reading the csv-file.
Since the input data will be retrieved by using RESTful Web Services in the future
and we'll forfeit using csv-files, I want to decouple the validation from the csv-reading.
I stumpled upon Bean Validation (JSR 380) and used Hibernate Validator to refactor the validation procedure.
I tried both, adding validation-annotations directly to the Bean class, and using a validation.xml file for configuration.
Both ways work when I run the application from Eclipse.
Problem
The application will be deployed as an executable JAR.
I would like to keep the option to change the validation constraints without the need to repack and redeploy the JAR.
So I'm trying to figure out, if there is a way to exclude the validation.xml (+ further configuration files) from the JAR and put it in some directory "close" to the JAR.
Something like this:
myApp/
|-- JAR
|-- conf/
|-- ValidationMessages.properties
|-- META-INF/
|-- validation.xml
|-- validation/
|-- constraints-myBean.xml
What I tried
Placing the META-INF/-folder in the same directory as the JAR
The JAR runs, but the validation doesn't work.
Referencing the absolute path to constraints-myBean.xml in validation.xml, specifically changing
<constraint-mapping>META-INF/validation/constraints-pbpRecord.xml</constraint-mapping>
to
<constraint-mapping>C:/path/to/myApp/META-INF/validation/constraints-pbpRecord.xml</constraint-mapping>
and including validation.xml in my JAR.
A ValidationException cancels the execution:
Exception in thread "main" javax.validation.ValidationException: HV000096: Unable to open input stream for mapping file C:/Users/wsteffler/Desktop/pbp_import_tool/pbp_orgunit_import/META-INF/validation/constraints-pbpRecord.xml.
at org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters.setMappingStreams(ValidationBootstrapParameters.java:291)
at org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters.<init>(ValidationBootstrapParameters.java:67)
at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.parseValidationXml(AbstractConfigurationImpl.java:595)
at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.buildValidatorFactory(AbstractConfigurationImpl.java:376)
at javax.validation.Validation.buildDefaultValidatorFactory(Validation.java:103)
at de.uniwuppertal.hisinone.orgunitimport.pbp.Main.main(Main.java:220)
Adding the parent-directory of META-INF/ to the classpath (with -classpath argument)
The JAR runs, but the validation doesn't work.
I've read the Bean Validation specification (6.5. Bootstrapping & 9. XML deployment descriptor) and the Hibernate Validator 6.1.0 Reference Guide (8. Configuring via XML), especially looking for solutions in the chapters in parentheses, and searched on StackOverflow and Google for solutions. Unfortunately I haven't found anything that would lead me to a solution so far.
Is it even possible to achieve bootstrapping an external validation.xml when executing a JAR?
If it is, how can I achieve this?
If you'll need more information or parts from my code, I'll update the question with the required information.
Hibernate Validator clearly doesn't support that. For sure the validation.xml needs to be in the jar. We could maybe support having the file: prefix to point to external constraint mappings. I would accept a patch if you were willing to work on that.
Not sure about the resource bundles though so that would be an half baked solution for you.
I wonder if you could just pass a carefully crafted class loader that can load resources from your external location (and delegate everything else to the standard class loader) and use externalClassLoader(ClassLoader) to pass it to HV when configuring the ValidatorFactory.

Why spring beans are not created in jar files, but in class files

I am using spring for some dependency injection via the annotations. The problem ist whenever I start the application and use the .jar ,created by gradle, in the classpath Im getting the following exception: "org.springframework.beans.factory.NoSuchBeanDefinitionException"
But if the /build/classes/main/ is in the classpath the beans are created and no exception is thrown.
So the beans are created in the build/classes/main/ but not in the build/libs/*.jar
Set #ComponentScan("classpath*:org.mypackage") to let Spring scan jars as well.

Grails config of Spring beans in different files

Grails have cofig for spring bean called resources.groovy. And as i understand from docs it allows you to include another file, using loadBeans(%path%)
I'm tried with this:
println 'loading application config ...'
// Place your Spring DSL code here
beans = {
loadBeans("classpath:security") //i'm tried with "spring/security" and "spring/security.groovy" also
}
but when grails is running it log following error:
Caused by: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Error evaluating bean definition script: class path resource [security] cannot be opened because it does not exist
Offending resource: class path resource [security]; nested exception is java.io.FileNotFoundException: class path resource [security] cannot be opened because it does not exist
at grails.spring.BeanBuilder.loadBeans(BeanBuilder.java:470)
at grails.spring.BeanBuilder.loadBeans(BeanBuilder.java:424)
at resources$_run_closure1.doCall(resources.groovy:13)
at resources$_run_closure1.doCall(resources.groovy)
... 45 more
Script security.groovy is exists at grails-app/conf/spring and compiled by grails maven plugin into target/classes/security.class.
Directory target/resources/spring is empty at this time
How i can configure Grails or grails-maven-plugin to copy this config files, not compile it into classes?
p.s. this problem also presents when i try to include config scripts using grails.config.locations = [ %path% ] inside conf/Config.groovy, my groovy scripts compiles into classes and because of it, grails config builder can't find them :(
Did you try:
println 'loading application config ...'
// Place your Spring DSL code here
beans = {
loadBeans("classpath:*security.groovy")
}
(this should load all Groovy files on the classpath ending with security.groovy and parse them into bean definitions).
Update: Found an interesting thread with this message as reference and my understanding is that one trick is to use ant in scripts/_Events.groovy to copy the .groovy file to the classesDirPath dir and then simply use:
beans = {
// load spring-beans for db-access via spring-jdbc-template
loadBeans('security.groovy')
// load some other spring-beans
...
}
This looks like a hack to get things working in both the war and when running run-app though. Not sure how things "should" be done (if this even makes sense).

Categories