Import spring context with specific profile - spring

In my java-spring-maven web-application I'm using multiple spring contexts (single context for each project).
For the sake of simplicity let's say I have 2 spring contexts: a.xml and b.xml, each belong to a project: project A and project B where A depends upon B.
a.xml is importing b.xml like this:
<import resource="classpath*:/b.xml" />
So when loading a.xml, b.xml is loaded as well.
Now, I have 2 spring profiles in my system: test and production that are used by my contexts (loading different property place holders for each profile).
So in a.xml I got:
<!-- Production profile -->
<beans profile="production">
<context:property-placeholder
location="classpath:props-for-a.properties"
ignore-unresolvable="true" ignore-resource-not-found="true" />
</beans>
<!-- Test profile -->
<beans profile="test">
<context:property-placeholder
location="classpath*:test-props-for-a.properties"
ignore-unresolvable="true" />
</beans>
And the same convention in b.xml (only loading different properties files).
My properties file are under src/main/resources (production files) and src/test/resources (test files).
Now I have this unit test that looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/a.xml" })
#ActiveProfiles(profiles = "test")
public class SomeTestClass {
#Test
public void testMethod() {
//Some Test
}
}
In this case, a.xml is loaded in "test" profile and as expected the desired files are loaded (test-props-for-a). b.xml is imported by a.xml but now I experience a strange experience, my properties values from the property file loaded from b.xml are not injected.
For example if I have this property in my property file:
connection-ip=1.2.3.4
and in my class I got:
#Value("${connection-ip}")
private String connectionIP;
The value of connectionIP will be "${connection-ip}" instead of "1.2.3.4".
Notice that the file location starts with classpath*, without the * I get a FileNotFoundException:
Caused by: org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [test-props-for-b.properties] cannot be opened because it does not exist
To be clear, the file test-props-for-b.properties resides under src/test/resources of project B. Why do I get this?
If I run a test that loads b.xml directly (not imported via a.xml) it works just fine, the test properties in the file test-props-for-b.properties are loaded as expected.
Am I doing something wrong? Could this be a bug? If so, can you suggest a workaround?
UPDATE
I removed the star (*) from the path to my properties file (test-props-for-b.properties) and debugged spring code. In class ClassPathResource this method throws the exception:
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else {
is = this.classLoader.getResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
Here are the variables values:
this.clazz is null.
this.path holds the file name test-props-for-b.properties
And this command returns null:
this.classLoader.getResourceAsStream(this.path);
Why is that? I can clearly see the file under src/test/resources of project B.

After investigating the issue this is my conclusion:
The file could easily be loaded if I could use the suitable class-loader, meaning that in order to load a file from project A, any class-loader of a class from project A could do the trick, same for project B. But when using the property-placeholder of spring the class-loader of ClassPathResource.java is used, and when it tries to load a resource from a different project it fails to do so (a ClassPathResource gets initialized per property-placeholder, hence per project in my case).
I don't really know how to fix it, currently I have implemented an ugly workaround by duplicating my test properties files (added file test-props-for-b.properties to src/test/resources folder of project A, this way I have it duplicated in src/test/resource of project B and A as well).
I'm still looking for a cleaner way.

Have you tried to define the spring profiles outside instead of #ActiveProfiles(profiles = "test")?
One example is to define it in maven surefire plugin in this way:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<configuration>
<systemPropertyVariables>
<spring.profiles.active>test</spring.profiles.active>
</systemPropertyVariables>
<!-- THE REST OF CONFIGURATIONS -->
</configuration>
</plugin>

Related

In Spring, can I autowire a property value simply by including that JAR in my WAR file?

I’m using Spring 3.2.11.RELEASE. I have a JAR file with a class that has the following
#Service(“myService")
public class MyServiceImpl implements MyService
{
…
#Value(“#{myProperties[‘my.properties.key’]}”)
private String myPropertiesValue;
When I include the JAR file in a WAR, I must put this in the WAR’s application context or else the autowiring fails …
<util:properties id=“myProperties" location="classpath:my_file.properties" />
The file “my_file.properties” is at the root of my JAR file. My question is, is there any way I can get the autowiring of the property to occur by simply including my JAR file in my WAR? I realize that adding ‘<util:properties id=“myProperties" location="classpath:my_file.properties" />’ is not that hard, but when I include the above JAR in dozens of projects, it is easy to forget to include the “util” declaration in one or two, causing those applications to fail to deploy.
There is no other conceptually different way. Either you include the properties or you can import full xml which defines part of the application context specific to this jar:
<import resource="classpath:my_jar_config.xml" />
where my_jar_config.xml contains all beans and the <util:properties> specific to this jar.

Can I start a Spring Boot WAR with PropertiesLauncher?

I have a Spring Boot 1.2 app packaged as a WAR because I need to be able to deploy the app in an app server.
I also want to configure an external path which will contain jars to be added to the classpath. After reading the Launcher documentation, I configured the build to use PropertiesLauncher to this end :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>ZIP</layout>
</configuration>
</plugin>
I tried to start the app with various combinations of this additional system property : -Dloader.path=lib/,lib-provided/,WEB-INF/classes,<my additional path>
But I always end up with this error :
java.lang.IllegalArgumentException: Invalid source folder C:\<path to my war>\<my war>.war
at org.springframework.boot.loader.archive.ExplodedArchive.<init> ExplodedArchive.java:78)
at org.springframework.boot.loader.archive.ExplodedArchive.<init>(ExplodedArchive.java:66)
at org.springframework.boot.loader.PropertiesLauncher.addParentClassLoaderEntries(PropertiesLauncher.java:530)
at org.springframework.boot.loader.PropertiesLauncher.getClassPathArchives(PropertiesLauncher.java:451)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:609)
I looked at the source code and it seems that PropertiesLauncher can only handle jar archives (ending with ".jar" or ".zip") and "exploded archives" (not ending with the former)
Is it possible to do achieve what I want ? Am I doing it wrong ?
If it's not possible, which alternative is there ?
If somebody end up here this might be useful:
java -cp yourSpringBootWebApp.war -Dloader.path=yourSpringBootWebApp.war!/WEB-INF/classes/,yourSpringBootWebApp.war!/WEB-INF/,externalLib.jar org.springframework.boot.loader.PropertiesLauncher
(Spring-Boot 1.5.9)
https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/executable-jar.html#executable-jar-launching
In Spring Boot 1.2, PropertiesLauncher handles .jar and .zip files as "jar archives" and everything else as "exploded archives" (unzipped jars). It does not properly handles .war
Here's the alternative I found :
I eventually switched back to the regular war launcher and I managed to configure a folder which jar contents are added to the classpath using a SpringApplicationRunListener such as this (pseudo-code for concision) :
public class ClasspathExtender implements SpringApplicationRunListener {
public void contextPrepared(ConfigurableApplicationContext context) {
// read jars folder path from environment
String path = context.getEnvironment().getProperty("my.jars-folder");
// enumerate jars in the folder
File[] files = new File(path).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) { return name.endsWith(".jar"); }
});
URL[] urls = // convert files array to urls array
// create a new classloader which contains the jars...
ClassLoader extendedClassloader = new URLClassLoader(urls, context.getClassLoader());
// and replace the context's classloader
((DefaultResourceLoader) context).setClassLoader(extendedClassloader);
}
// other methods are empty
}
This listener is instanciated by declaring it in a META-INF/spring.factories file :
org.springframework.boot.SpringApplicationRunListener=my.ClasspathExtender
This worked for me (Spring Boot 1.3.2)
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>WAR</layout>
</configuration>
</plugin>

Reuse resources and context in another project

I have a project as following:
Project
Project-A/
src/test/java/
someTests.java
src/test/resources
database/
create.sql
insert.sql
spring/
test-config.xml
project-B/
src/test/java
otherTests.java
pom.xml of Project-B:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>Project-A</artifactId>
</dependency>
And in someTests.java, my configuration is:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/webapp-config.xml", "classpath:spring/test-dao-config.xml" })
public class DacsDAOImplTest
I want to reuse test-config.xml, create.sql and insert.sql in otherTests.java.
I have already tried in otherTests.java
#ContextConfiguration(locations = {"classpath*:spring/test-dao-config.xml"})
But this doesn't seem to work...
Edit: Geoand's answer help, I have no more exception related to beans, but now I have a
FileNotFoundException: class path resource
[exception-messages.properties] cannot be opened because it does not
exist.
Since I don't understand exactly what changes have done, I don't know how to fix this...
exception-messages are used in webapp-config.xml as <context:property-placeholder location="classpath:exception-messages.properties" />
I think the problem is that although you are importing Project-A into Project-B, the of test code does not get included by Maven. Check out the solutions in this SO question to see if it helps at all
EDIT:
Once you get maven to be to import the test code as well, you will need to create .properties files under /test/resources that contain the test values of the properties you are using in the application

maven spring test application contexts

I want to use the applicationContext.xml in my src/main/resources directory from within my test harness in src/test/java. How do I load it? I have tried:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestService {
...
}
but get a file not found error. I'm using Maven and Spring. Thanks.
Try this (note the asterisk):
#ContextConfiguration("classpath*:applicationContext.xml")
The Maven test classpath uses the files in target/test-classes. That folder contains Java classes from src/test/java and resources from src/test/resources.
The way to go is to create a test specific app context and store it under src/main/resources.
You may try to reference the file directly using file: i.e. something like file:src/main/resources/applicationContext.xml but to me this is an ugly hack.
Also, you can of course use the Maven resources plugin to copy applicationContext.xml prior to test execution.
Here's how I do it, it may or may not be the best way for you. The main thing is it works in both Eclipse and Maven:
Keep exactly one copy of each applicationContext-xxx.xml file per project. NEVER copy-and-paste them or their contents, it'll create a maintenance nightmare.
Externalize all environmental settings to properties files (e.g. database-prod.properties and database-test.properties) and place them in src/main/resources and src/test/resources respectively. Add this line to your app contexts:
<context:property-placeholder location="classpath:**/*.properties"/>
Create a superclass for all test classes and annotate it with a context configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContext.xml"})
#Ignore
public class SpringEnabledTest {
// Inheritable logger
protected Logger logger = LoggerFactory.getLogger(this.getClass());
}
Add <additionalClasspathElements> to your maven-surefire-plugin configuration to make sure surefire picks up appContext and the right properties files. I've created an example here.
Add the location(s) of the app context files and src/test/resources to your Eclipse classpath so you can execute unit tests in Eclipse as well.
NEVER add src/main/resources to your Eclipse classpath, it's only a convenient place for Maven to package additional source files, it should have no bearing on Eclipse. I often leave this directory blank and create additional folders (e.g. env/DEV, env/UAT and env/PROD) outside of the src/ folder and pass a parameter to the build server and let it know from which folder it needs to copy files to src/main/resources.
Add the src folder to the classpath of your testing tool. If it's in Eclipse, I think you can do it from the project properties. You may have to change it to classpath:**/applicationContext.xml as well.

Spring cannot find bean xml configuration file when it does exist

I am trying to make my first bean in Spring but got a problem with loading a context.
I have a configuration XML file of the bean in src/main/resources.
I receive the following IOException:
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [src/main/resources/beans.xml]; nested exception is
java.io.FileNotFoundException: class path resource [src/main/resources/beans.xml] cannot
be opened because it does not exist
but I don't get it, since I do the following code test:
File f = new File("src/main/resources/beans.xml");
System.out.println("Exist test: " + f.exists());
which gives me true! resources is in the classpath. What's wrong?
Thanks, but that was not the solution. I found it out why it wasn't working for me.
Since I'd done a declaration:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
I thought I would refer to root directory of the project when beans.xml file was there.
Then I put the configuration file to src/main/resources and changed initialization to:
ApplicationContext context = new ClassPathXmlApplicationContext("src/main/resources/beans.xml");
it still was an IO Exception.
Then the file was left in src/main/resources/ but I changed declaration to:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
and it solved the problem - maybe it will be helpful for someone.
Edit:
Since I get many people thumbs up for the solution and had had first experience with Spring as student few years ago, I feel desire to explain shortly why it works.
When the project is being compiled and packaged, all the files and subdirs from 'src/main/java' in the project goes to the root directory of the packaged jar (the artifact we want to create). The same rule applies to 'src/main/resources'.
This is a convention respected by many tools like maven or sbt in process of building project (note: as a default configuration!). When code (from the post) was in running mode, it couldn't find nothing like "src/main/resources/beans.xml" due to the fact, that beans.xml was in the root of jar (copied to /beans.xml in created jar/ear/war).
When using ClassPathXmlApplicationContext, the proper location declaration for beans xml definitions, in this case, was "/beans.xml", since this is path where it belongs in jar and later on in classpath.
It can be verified by unpacking a jar with an archiver (i.e. rar) and see its content with the directories structure.
I would recommend reading articles about classpath as supplementary.
Try this:
new ClassPathXmlApplicationContext("file:src/main/resources/beans.xml");
file: preffix point to file system resources, not classpath.
file path can be relative or system (/home/user/Work/src...)
I also had a similar problem but because of a bit different cause so sharing here in case it can help anybody.
My file location
How I was using
ClassPathXmlApplicationContext("beans.xml");
There are two solutions
Take the beans.xml out of package and put in default package.
Specify package name while using it viz.
ClassPathXmlApplicationContext("com/mypackage/beans.xml");
src/main/resources is a source directory, you should not be referencing it directly. When you build/package the project the contents will be copied into the correct place for your classpath. You should then load it like this
new ClassPathXmlApplicationContext("beans.xml")
Or like this
new GenericXmlApplicationContext("classpath:beans.xml");
This is because applicationContect.xml or any_filename.XML is not placed under proper path.
Trouble shooting Steps
1: Add the XML file under the resource folder.
2: If you don't have a resource folder. Create one by navigating new by Right click on the project new > Source Folder, name it as resource and place your XML file under it.
use it
ApplicationContext context = new FileSystemXmlApplicationContext("Beans.xml");
You have looked at src directory. The xml file indeed exist there. But look at class or bin/build directory where all your output classes are set. I suspect you will need only resources/beans.xml path to use.
I suspect you're building a .war/.jar and consequently it's no longer a file, but a resource within that package. Try ClassLoader.getResourceAsStream(String path) instead.
Note that the first applicationContext is loaded as part of web.xml; which is mentioned with the below.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>myOwn-controller</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Where as below code will also tries to create one more applicationContext.
private static final ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
See the difference between beans.xml and applicationContext.xml
And if appliationContext.xml under <META-INF/spring/> has declared with <import resource="beans.xml"/> then this appliationContext.xml is loading the beans.xml under the same location META-INF/spring of appliationContext.xml.
Where as; in the code; if it is declared like below
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
This is looking the beans.xml at WEB-INF/classes OR in eclipse src/main/resources.
[If you have added beans.xml at src/main/resources then it might be placed at WEB-INF/classes while creating the WAR.]
So totally TWO files are looked up.
I have resolved this issue by adding classpath lookup while importing at applicationContext.xml like below
<import resource="classpath*:beans.xml" />
and removed the the line ClassPathXmlApplicationContext("beans.xml") in java code, so that there will be only one ApplicationContext loaded.
In Spring all source files are inside src/main/java. Similarly, the resources are generally kept inside src/main/resources. So keep your spring configuration file inside resources folder.
Make sure you have the ClassPath entry for your files inside src/main/resources as well.
In .classpath check for the following 2 lines. If they are missing add them.
<classpathentry path="src/main/java" kind="src"/>
<classpathentry path="src/main/resources" kind="src" />
So, if you have everything in place the below code should work.
ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring-Module.xml");
Gradle : v4.10.3
IDE : IntelliJ
I was facing this issue when using gradle to run my build and test. Copying the applicationContext.xml all over the place did not help. Even specifying the complete path as below did not help !
context = new ClassPathXmlApplicationContext("C:\\...\\applicationContext.xml");
The solution (for gradle at least) lies in the way gradle processes resources. For my gradle project I had laid out the workspace as defined at https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_project_layout
When running a test using default gradle set of tasks includes a "processTestResources" step, which looks for test resources at C:\.....\src\test\resources (Gradle helpfully provides the complete path).
Your .properties file and applicationContext.xml need to be in this directory. If the resources directory is not present (as it was in my case), you need to create it copy the file(s) there. After this, simply specifying the file name worked just fine.
context = new ClassPathXmlApplicationContext("applicationContext.xml");
Beans.xml or file.XML is not placed under proper path. You should add the XML file under the resource folder, if you have a Maven project.
src -> main -> java -> resources
I did the opposite of most. I am using Force IDE Luna Java EE and I placed my Beans.xml file within the package; however, I preceded the Beans.xml string - for the ClassPathXMLApplicationContext argument - with the relative path. So in my main application - the one which accesses the Beans.xml file - I have:
ApplicationContext context =
new ClassPathXmlApplicationContext("com/tutorialspoin/Beans.xml");
I also noticed that as soon as I moved the Beans.xml file into the package from the src folder, there was a Bean image at the lower left side of the XML file icon which was not there when this xml file was outside the package. That is a good indicator in letting me know that now the beans xml file is accessible by ClassPathXMLAppllicationsContext.
This is what worked for me:
new ClassPathXmlApplicationContext("classpath:beans.xml");
If this problem is still flummoxing you and you are developing using Eclipse, have a look at this Eclipse bug: Resources files from "src/main/resources" are not correctly included in classpath
Solution seems to be look at properties of project, Java build path, source folders. Delete the /src/main/resources dir and add it again. This causes Eclipse to be reminded it needs to copy these files to the classpath.
This bug affected me when using the "Neon" release of Eclipse. (And was very frustrating until I realized the simple fix just described)
I was experiencing this issue and it was driving me nuts; I ultimately found the following lying in my POM.xml, which was the cause of the problem:
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
I was not sure to write it but maybe someone save a few hours:
mvn clean
may do the job if your whole configuration is already perfect!
I have stuck in this issue for a while and I have came to the following solution
Create an ApplicationContextAware class (which is a class that implements the ApplicationContextAware)
In ApplicationContextAware we have to implement the one method only
public void setApplicationContext(ApplicationContext context) throws BeansException
Tell the spring context about this new bean (I call it SpringContext)
bean id="springContext" class="packe.of.SpringContext" />
Here is the code snippet
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
public static ApplicationContext getApplicationContext() {
return context;
}
}
Then you can call any method of application context outside the spring context for example
SomeServiceClassOrComponent utilityService SpringContext.getApplicationContext().getBean(SomeServiceClassOrComponent .class);
I hope this will solve the problem for many users
I am on IntelliJ and faced the same issue. Below is how i resolved it:
1. Added the resource import as following in Spring application class along with other imports: #ImportResource("applicationContext.xml")
2. Saw IDE showing : Cannot resolve file 'applicationContext.xml' and also suggesting paths where its expecting the file (It was not the resources where the file applicationContext.xml was originally kept)
3. Copied the file at the expected location and the Exception got resolved.
Screen shot below for easy ref:
But if you would like to keep it at resources then follow this great answer link below and add the resources path so that it gets searched. With this setting exception resolves without #ImportResource described in above steps:
https://stackoverflow.com/a/24843914/5916535
Sharing my case and how I debugged it, maybe helps someone:
this will only be relevant if you have first checked you actually have the resources folder in correct place and correctly named
create some temporary folder somewhere, preferably out of any git projects (e.g. mkdir playground) and move there (cd playground)
copy the java archive there (e.g. cp /path/to/java.war .) that is missing that beans.xml
unpack it (e.g. unzip java.war on ubuntu)
find if there's any .xml files in there (for example in WEB-INF/classes) (the unpacking process should show a list of files being unpacked, most of them will probably be other dependencies as archives, these are not relevant)
if you don't see a beans.xml, just read the other .xml files (e.g. cat root-config.xml), you might find something like root-config.xml there or similar, in there you might either have some other <import resource="somethingelse.xml"> records or nothing.
if this is the case, this means you do have that file (root-config.xml here) present in the project or if not, continue going up parent projects to where the archive is getting packaged from. Find that file, add <import resource="beans.xml"> and run mvn package.
Now verifying the fix by doing the steps in 1.-5. should result in that file (root-config.xml here) in the newly packaged archive having the beans.xml defined and once you deploy it, it should work.
Make sure that beans.xml is located in the resources folder.

Resources