I have following situation. JDK 17, Spring-Boot: 2.6.2. A gradle multi-project. One project is a library (java-library, no spring boot plugin). Another project is a spring boot application with spring boot plugin. Generally, the spring dependency management plugin is not used, gradle platform concept is used instead. Application project includes dependency to library project per "implementation(project(':.."
Library:
Library project and spring boot application project have different packages.
like:
library root package is a.b.c and application root package is a.b.d
Library project has in root package of the package hierarchy a configuration class (for example a.b.c.LibraryConfig) which is annotated with #Configuration annotation and with #ComponentScan annotation, which has attribute "basePackageClass" pointing to this configuration class:
#Configuration
#ComponentScan(basePackageClass = LibraryConfig.class)
public class LibraryConfig {
Inside of library hierarchy (so in packages a.b.c.*) are services and rest controllers.
Application:
Application has in root package of the package hierarchy (for example in a.b.d) application class and in sub-package "config" it has configuration class (would be for example a.b.d.config.AppConfig), which imports the configuration class from library:
#Configuration
#Import(a.b.c.LibraryConfig.class)
public class AppConfig {
Problem:
Classes from the library are there in runtime - we are able to load them.
No any component of the library is in context (not registered), neither services nor rest controllers.
What are we missing?
We have tried different constellations of imports and scans. Also added component scan to application class and set there particular packages. Nothing helped.
The problem was, that the main application was using spring boot indexing annotation processor and library - not. So after enabling the annotation processor for library problem was solved.
Related
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.
Currently I have quite a bit of services written in Spring Boot 2.1.3 (in mono-repo) and I have a common package that's used in most of the services.
So my packages are organized in the following:
root
root.common
root.serviceA
root.serviceA.<subpackages>
root.serviceB
root.serviceB.<subpackages>
...
root.serviceX
root.serviceX.<subpackages>
In each service I have Program.java where it's tagged with #SpringBootApplication (e.g. root/serviceA/Program.java)
The problem now I'm having is that I have a component in root.common (e.g. root/common/JSONSerializer.java) which needs to be loaded in all the services. I tried tagging the file with #JsonComponent but obviously it doesn't work because it's outside root.service* package.
I managed to get it working by manually adding #Import tag in the main file but that means that for each service I have to manually add #Import({root.common.JSONSerializer.class}) which is tedious and error prone. Is there a way to include this file in the component scanning process?
You can set each Program class by following way:
#SpringBootApplication(scanBasePackages = {"root.serviceA", "root.common"})
REFERENCES
SpringBootApplication documentation
I have some library jar lib.jar (made using spring boot but packaged as normal jar without spring boot plugin) which is made of spring boot and contains spring.components file generated by spring-context-indexer.
Now, I'm using this jar in my application which also has spring-context-indexer and its own spring.components file and uses some of the bean defined in lib.jar.
When I start my application, spring should register all beans defined in spring.components of lib.jar and spring.components of application. But spring isn't registering any of bean of lib.jar.
I tried using basePackages property of #SpringBootApplication but no results.
I even copied all entries of spring.components of lib.jar into spring.components of my application but no result.
Can anyone please help me?
TL;DR
If you're using Spring Data, #SpringBootApplication.scanBasePackages is not enough, you also need #EnableJdbcRepositories (or *Jpa* or whatsoever).
package application;
// without this annotation all Repository classes
// from library will be missing
#EnableJdbcRepositories({
"application",
"library"
})
#SpringBootApplication(
scanBasePackages = {
"application",
"library"
}
)
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
Some more info
Okay, maybe I'm a bit late, but I decided to investigate this case a bit.
That's what I've found as of 2 Feb 2022:
All META-INF/spring.components files are loaded in CandidateComponentsIndexLoader.doLoadIndex. You can use debug to check whether it sees file from lib or not
CandidateComponentsIndexLoader then creates CandidateComponentsIndex, which is then stored in the component scanner, for me it is AnnotationConfigServletWebServerApplicationContext.scanner.componentsIndex
Then in ClassPathScanningCandidateComponentProvider findCandidateComponents is called, which, if componentsIndex is not null, just gets components from that index by provided basePackage.
That's why missing basePackage is crucial.
I haven't dug into the Spring Data algorithms, but in my case Spring hadn't been generating library Repositories until I added the #EnableJdbcRepositories with packages.
P.S. All links represent files at the 5.3.15 tag, latest atm.
I'd like to get help in setting up a multi-module Maven project using Spring Boot.
Correct me if I'm wrong but I've read that Spring-Boot reads the start main Application (Annotated with #SpringBootApplication and ran with SpringApplication.run) and finds the necessary classes through reflection. Which means that it first accesses the start class and then proceeds to find the controllers, models, repositories. If so, how do I set up the dependency in the pom.xml of each module if I had a project structure like this:
app
--src
--pom.xml
core
--pom.xml
--models
----/pom.xml
--controllers
----/pom.xml
--repositories
----/pom.xml
pom.xml
Please have a look complete guide how to create multi module project in spring boot.
https://spring.io/guides/gs/multi-module/
Spring boot will component scan from the package of the class annotated with #SpringBootApplication. Component scannign means that it is looking through the classes under that package recursively, analyzing annotations, and wiring up anything it recognizes. This can include controllers, simple variables with #Value annotations, members with #Autowired, and a host of other things.
You can actually jump into the source for the #SpringBootApplication annotation and see that it expands to numerous other annotations, #ComponentScan being one of them.
If all of your modules are in a sub-hierarchy package wise from there, then they will be scanned properly anyway. Often though, sub-modules will be in a slightly different package hierarchy. In this case, you can explicitly specify #ComponentScan() in your code and inside the () you can list the base packages to component scan from.
Whether or not its a sub-module doesn't matter much at this point; its just like scanning classes in any other library you're including.
General Advice
Also, just FYI - Multi module projects can get a little hard to manage (speaking from numerous separate experiences). They can be very good if used properly though. If you're a beginner to Maven though, it may be wiser to keep separte, well-defined projects with a proper release cycle and just import them as normal dependencies.
So, I'm not for or against them, but just make sure you understand them well going in :).
I have a GitHub project where I configured a multimodule maven project:
https://github.com/cristianprofile/spring-boot-mvc-complete-example
This is Example project maven module structure:
Spring mvc rest maven module ---> service maven module ---> repository maven module
The main module should be configured like this (Spring mvc rest layer):
#SpringBootConfiguration
#EnableAutoConfiguration
//spring mvc module auto scan only its package
#ComponentScan(basePackageClasses = HelloWorldController.class)
//It needs Service bean so it will import ConfigurationService.class from
// Service maven module
#Import({ConfigurationService.class})
Complete class:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/main/java/com/mylab/cromero/controller/Application.java
It will only scan its package :
HelloWorldController.class --> com.mylab.cromero.controller;
This Rest layer use a service maven module so it is necessary to add dependency:
<dependency>
<groupId>com.mylab.cromero.core</groupId>
<artifactId>mylab-core-service-impl</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
Complete pom file:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/pom.xml#L16
ConfigurationService.class from service maven module autoscan its packages and it will import ConfigurationRepository.class (Repository maven module)
#Configuration
//It needs repository's bean so it will import ConfigurationRepository.class from
// Repository maven module
#Import(ConfigurationRepository.class)
//service layer module auto scan only its package
#ComponentScan(basePackageClasses = ConfigurationService.class)
public class ConfigurationService {
}
Complete Service maven module code:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/mylab-core/mylab-core-service-impl/src/main/java/com/mylab/cromero/service/ConfigurationService.java#L12
Service maven module layer has a dependency with maven repository module:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/mylab-core/mylab-core-service-impl/pom.xml#L38
Repository module will auto configure jpa and domain classed:
#Configuration
#EnableJpaRepositories(basePackages = "com.mylab.cromero.repository")
#EntityScan(basePackageClasses=Base.class)
#ComponentScan(basePackageClasses = BaseRepository.class)
public class ConfigurationRepository {
}
This may be an impossible task, but here goes...
Is it possible to register a spring bean, by (ONLY) adding a jar to the classpath of a spring-boot application?
Scenario: I would like to create a non-intrusive plugin jar, which when imported into a spring-boot project's classpath, will automatically be picked up and provide a service (e.g. via a RestController).
Constraints
I don't want to change or reconfigure the existing spring-boot application (i.e. no additional scan paths or bean config).
I don't have any knowledge of the target spring-boot application's package structure/scan paths.
I guess I was hoping that by default Spring scan's its own package structure (i.e. org.springframework.** looking for the presence of database libs, etc) and I could piggy-back off that - I haven't had any luck (so far).
I've setup an example project in github, to further clarify/illustrate my example and attempts.
** Solution Addendum **
This bit that got it working, was to add the following file, which points to an #Configuration config file...
plugin-poc\src\main\resources\META-INF\spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.thirdpartyplugin.PluginConfiguration
I think in such cases you would try to add a spring auto configuration that is annotated with #ConditionalOnClass to be only evaluated if the given class is on the classpath. This class can register the bean and would just be evaluated if the conditional evaluates to true
Here is the relevant part of the spring boot documentation : Creating your own auto-configuration