I have a spring-based Web Service. I now want to build a sort of plugin for it that extends it with beans. What I have now in web.xml is:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/*-configuration.xml</param-value>
</context-param>
My core app has main-configuration.xml which declares its beans. My plugin app has plugin-configuration.xml which declares additional beans. Now when I deploy, my build deploys plugin.jar into /WEB-INF/lib/ and copies plugin-configuration.xml into /WEB-INF/classes/ all under main.war.
This is all fine (although I think there could be a better solution), but when I develop the plugin, I don't want to have two projects in Eclipse with dependencies. I wish to have main.jar that I include as a library. However, web.xml from main.jar isn't automatically discovered. How can I do this? Bean injection? Bean discovery of some sort? Something else?
Note: I expect to have multiple different plugins in production, but development of each of them will be against pure main.jar
Thank you.
I think there is a simpler approach:
In your host application (the webapp) define something like the following contextConfigLocation parameter:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/META-INF/foo/*-configuration.xml</param-value>
</context-param>
The crucial parts are classpath*:as it results in scanning the entire classpath for config files matching the following pattern. If you only use classpath: the lookup will stop at the first file found. It also won't traverse JARs if not starred correctly.
The second crucial part is having at least one non-wildcarded basepath for the lookup to work. This is due to Classloader traits that only reliably returns all resources if they contain a base path (see the Javadoc on PathMatchingResourcePatternResolver for details).
What you now have to do with your plugin projects is place your plugin-configuration.xml in /META-INF/foo/, package that as JAR and place it into your classpath (in case you build with Maven just add the dependency).
On application start Spring will now also pickup all config files from your plugins and build the ÀpplicationContext` from em.
You also might wanna checkout another post I did on application modularity with Spring: How do you make a multi-module spring configuration?
Instead of /WEB-INF/classes/*-configuration.xml, try classpath:*-configuration.xml
You can also list configuration files, each on a new line.
You will have to make sure that main.jar ends up in WEB-INF/lib, if you're not using maven you can do this in eclipse by marking you webapp as dependent on the project that creates the main.jar, via the project properties.
Related
In my spring project we are using web dependency contains a configuration applicationContext.xml and security-config.xml. I have to change on both of them in my application how can I achieve this.
I'm expecting like this
compile (librariesdependencyname) {
exclude ('applicationContext.xml')
exclude ('common-security-config.xml')
}
If your team handling the jar. Just ask them to creating new version and without this configuration and resource files
OR
You can modify the context loader loaction and patter like below
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml,classpath*:/**/*-config.xml</param-value>
</context-param>
More information about the project setup will help with more accurate suggestions.
There can be multiple way to achieve.
Assuming you have following
Library has mentioned xml files
We want the code not the configurations
As #Araf mentioned, we can request the team to update the library and change the configuration
Restructure the code to provide Autoconfigurations files this way consumer of library can choose to load the configurations through autoload or not
Define your own xml files to load so that it will not load library configurations
One way to achieve AutoConfiguration mechanism mentioned at https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration
or as it is your own project you can try multi module project https://www.baeldung.com/spring-boot-multiple-modules
Choosing which package to load or exclude, we can use #ComponentScan https://www.baeldung.com/spring-component-scanning.
XML version of annotations should be available, I personally prefer the annotations as it will use code as configurations.
You can add a custom task in gradle and instead of adding your jar compile "dependency. Define it as "something" dependency. Access it object using configurations."something" . Refer example below
librariesdependency "<<package-librariesdependencyname>>"
jar.dependsOn 'customJar'
task customJar (type: Jar) {
archiveName = 'your-output.jar'
from zipTree(configurations.librariesdependency.singleFile)
exclude 'applicationContext.xml'
}
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.
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.
I have a multi-module maven project, including a seperate assembly-project. As i develop and run my application from eclipse (during development), i have specific configuration-files (e.g. log4j or other property-files) in my main-module (which contains the main-class). These files contain development-time-specific information. The assembly-project contains each of the config-files for production. The assembled product then should use these configs instead. This is my current setup:
MainModule/src/main/resources
+configA.properties
+log4j.properties
Module1/src/main/resources
+configB.properties
AssemblyProj/src/main/resources
+configA.properties
+configB.properties
+log4j.properties
And the generated project has this structure:
libs/
+MainModule.jar
+Module1.jar
configs/
+configA.properties
+configB.properties
+log4j.properties
the config-directory overlays the config-files in each *.jar because of the classpath, i.e.
java -cp configs/;libs/* My.Main.Class
Now the problem that i have, is that there are still all dev-configs included in each jar. Also i have kind of a bad feeling about using that overlay-classpath-method. Is there any practice on how to do this in a better manner?
Extract these resources into classifier-based dependencies for each of the mentioned modules. Then define <profiles/> that trigger their usage. In your assembly use the classifiers as necessary.
Summary
We have a central LDAP server that our deployed Java web app should connect to. Our JUnit tests depend on specific data in the LDAP repository, so they need to connect to an embedded ApacheDS LDAP server, primed with a sample data set. How do we make sure that the ApacheDS server doesn't start up when we deploy our webapp?
Details
We are using Spring security, and have the following line in ldap-context.xml to start up the embedded LDAP server:
<security:ldap-server root="dc=test,dc=com" port="33389" ldif="classpath:EmbeddedServerRoot.ldif" />
Currently, our web.xml references both this test context file and our top-level application-context.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:ldap-context.xml
classpath:application-context.xml
</param-value>
</context-param>
We need to make sure that ldap-context.xml is included when we run our JUnit tests, and when we run the webapp directly from eclipse (via WTP), but excluded when we package the war and deploy it to a server.
We're using maven as the build tool. We can fairly easily take care of this situation for our JUnit tests by making sure they include both spring context files in the context configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:ldap-context.xml", "classpath:application-context.xml" })
public class TestStuff {
}
Then, our web.xml would only include application-context.xml, except for one thing - this doesn't work when running from WTP - we need the embedded server to start up in that case as well. Any suggestions?
If you're using Maven, why not use the Assembly plugin to manage your environment deployments. It seems like your spring file is not that complex, so you can have a common spring file which doesn't have the ldap-context.xml reference, and then a test-specific version which does have the ldap reference. When assembly is configured and run, the environment specific file will overwrite the common version, and then you can deploy your packaged app.
An other possibility is to use some properties in the pom and a filtered spring bean file defining aliases for the beans to switch between environments. But you need to habe both beans in the config, but you will use the one or the other.