Using Spring #PropertySource in a Maven submodule - java

In a Spring-boot application, I was having a single module and I was able to inject a configuration file, e.g. "my.properties", that was located in src/main/resources as follows:
#Configuration
#PropertySource("/my.properties")
public class MyConf{
}
Everything was ok, but then I created submodules and now I moved that configuration file in a submodule. When I start the main application I gedt the following exception:
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.myapp.MainApplication]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/home/jeanvaljean/workspace/mainmodule/secondarymodule/my.properties]
As I see, I can solve the issue by writing
#PropertySource("/src/main/resources/my.properties")
Doing this, the path is correct and the file can be loaded.
Anyway, that is an horrible solution, and I'm pretty sure that there is a more elegant and flexible alternative. Any solution?

Spring has a few different implementations of how to find a resource. By using the prefix classpath: you are telling Spring to search for the resource in all the classpath, rather than in the classes that are bundled with your application.
Depending on the ApplicationContenxt, Spring will use a different default Resource class. It looks like in your case, Spring was instantiating a FileSystemResource, which only finds files available on the filesystem with either relative or absolute paths (but not inside jars!). My rule of thumb is to never prefix something if it's in the same module/component/jar, and always prefix it with classpath: if I know it's in a different module/component/jar (some people get mad at this :).
You can read a more in the Spring Documentation - Resources

Related

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.

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.

Spring boot serving static resource

I work on spring boot application. I'm trying to serve static content with spring.
want to serve a resource stored in the /c:/frontend/files/ directory whenever a request comes in for the URL matching the pattern: /file/**:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/file/**")
.addResourceLocations("file:///C:/frontend/files/" );
}
but when i try to access to this resource using this url: http://localhost:9999/file/app.min.js
I have this problem
There was an unexpected error (type=Not Acceptable, status=406).
Could not find acceptable representation
I resolved the problem. it's related to "spring-cloud-config-server". I just delete this config: org.springframework.cloud spring-cloud-config-server
It sounds like your project's folder structure is wrong.
Code should go under src/main/java and resources (like your javascript) should go under src/main/resources. You have a few different options where you can actually serve the files from. This post on the spring.io blog has the following to say:
Spring Boot will automatically add static web resources located within any of the following directories:
/META-INF/resources/
/resources/
/static/
/public/
Another option you also have is using webjars.
Personally, I've found it easiest to put those kind of files under src/main/resources/public. It always works without any issues for me. The interesting thing is you can put a folder named /public anywhere in your project and spring-boot will serve files out of it. You have to be really careful that it's under src/main/resources/public though if you're using a build tool like maven, as when you come to build your .jar the files won't be in the right place otherwise.

Failed to import bean definitions from URL location [classpath:applicationContext-core.xml]

I'm working on a java project with spring on eclipse using Maven, and running on a Tomcat server v6.0. Everything was working fine since yesterday morning.
Here his my problem : I'm building my project, I got a build success. Then I start my Tomcat server and got this error :
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:applicationContext- core.xml]
Offending resource: ServletContext resource [/WEB-INF/applicationContext.xml]; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [applicationContext-core.xml]; nested exception is java.io.FileNotFoundException: class path resource [applicationContext-core.xml] cannot be opened because it does not exist
I found out similar problem on some website but none of them give me a solution that worked for me.
It seems that eclipse isn't able to find applicationContext-core.xml when I'm doing this :
<import resource="classpath:applicationContext-core.xml" />
However, I do have the needed jar file nad-core-0.0.1-SNAPSHOT.jar in WEB-INF/lib containing applicationContext-core.xml.
I even tried to add it manually to the classpath but I was still having the same problem.
I keep on looking for a solution, when suddendly it work again once after restarting Eclipse and building while Eclipse was still updating indexes and my project was having this strange status Hg status pending instead of default. Surprised by this result I decide to build again my project after restarting Eclipse and I got the error again and I enable to make it work again. It's quite annoying...
This looks to be a really random problem.
Thanks a lot for your help :)
As you've not specified you web application structure. I assume you've a simple web application at hand with the following structures
webapp
WEB-INF/classes/applicationContext.xml
WEB-INF/lib/nad-core-0.0.1-SNAPSHOT.jar/applicationContext-core.xml
Application context.xml refers to the applicationContext-core.xml file using the import tag. I did encounter a similar situation in my web application, here're the check lists that you should go through and may be one of them can apply to your situation.
Check the generated snapshot jar file for the applicationContext-core.xml file and make sure it is in the root directory of the jar. As silly as it sounds, this was the root cause of the issue I faced in my deployment.
Make sure your Maven Pom.xml file is configured to include this XML file from the resources folder. You can use the resource tags in the build phase of Maven to package them within the jar file itself.
You can try removing the import tag from application context.xml file and instead load both of them from Spring's webapplication context itself.
Add a context loader listener class from spring org.springframework.web.context.ContextLoaderListener
Add context-param contextConfigLocation with value classpath:applicationContext-core.xml,classpath:applicationContext.xml. Spring has the ability to dynamically sort out the dependencies before initiating the bean factory.
Hope this check list helps.
I get pretty much the same config, six years later, I got the same error.
I also restart Eclipse, and it solved the issue.

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