I have a multi-module build using Gradle. There are 2 INDEPENDENT (not using packages from project 1 to project 2) but related spring projects in it. 1 of the projects is working as expected but 2nd project is not scanning any beans either in the same package or in sub packages.
Below are the things that I have tried (including the comments). None of the things worked except ImportAutoConfiguration and mentioning the classes which I wanted to import.
#SpringBootApplication(scanBasePackages = {"io.comany.logs.functionality"})
//#ImportAutoConfiguration( classes = {Test.class, DecorationManager.class, Consumer.class})
//#ComponentScan(basePackages = {"io.comany.logs.functionality"})
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
I have verified that my package names are correct. The main class is present in the package io.comany.logs.functionality and the classes I want to scan are in the pacakge io.comany.logs.functionality.kafka
Edit : Copied the same project and created another standalone project, it still doesn't work. So the problem should ideally not be with gradle config.
Related
Within a Gradle multi-module project with the bootstrapping in its own module I'm unable to use MockMvc, because its need to reference the bootstrapping-module. I'm not sure if I have misconfigured something. The basic structure is:
module: a module containing some REST-Services and needs a testImplementation-Dependency on starter
starter: the bootstrapping-module which gets the spring-boot-plugin applied and depends on module
I have set up a minimal example on github using Spring-Boot 2.3.1.RELEASE and Gradle 6.4 with the following configuration:
./settings.gradle.kts
rootProject.name = "spring-multimodule-integrationtest"
include("starter", "module")
./build.gradle.kts
subprojects {
repositories {
jcenter()
}
dependencies {
apply(plugin = "java-library")
"testImplementation"("junit:junit:4.12")
}
}
./starter/build.gradle.kts
plugins {
id("org.springframework.boot") version "2.3.1.RELEASE"
}
dependencies {
implementation(project(":module"))
}
./module/build.gradle.kts
dependencies {
testImplementation(project(":starter"))
}
The starter-module contains only one a single class "Starter" referencing the module-module:
public class Starter {
public String info() { return "starter"; }
public static void main(String[] args) {
System.out.println(new Starter().info() + " and " + new Module().info());
}
}
The module-module (*sigh I should have chosen a different name for this module) contains only this implemenation-class:
public class Module {
public String info() { return "module"; }
}
Additionally, the module-module has the following test-class doing the integration-test:
public class IntegrationTest
{
#Test public void testSomeLibraryMethod() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
Starter.main(new String[0]);
assertEquals("starter and module\n", out.toString());
}
}
This code runs fine until the applying of the spring-boot-plugin within "./starter/build.gradle.kts". When the tasks "clean test" issued on the shell I get:
❯ ./gradlew clean test
> Task :module:test FAILED
de.kramhal.multi.IntegrationTest > testSomeLibraryMethod FAILED
java.lang.NoClassDefFoundError at IntegrationTest.java:17
Caused by: java.lang.ClassNotFoundException at IntegrationTest.java:17
1 test completed, 1 failed
This problem does not occur, when tests are executed within the IDE (IntelliJ to be exact).
I already tried unsuccessfully to use the spring-dependency-management as suggested in this answer (as well as in several other answers).
What have I done wrong?
First off, I would recommend restructuring your project so you don't have cyclic dependencies. As it is now, in order to build starter, you need to build module. And in order to test module, you need to build starter. Gradle can do it, but it is usually a smell.
In terms of troubleshooting: when you get a test failure like this, look at the test report as that has the full stack trace. You should see that it complains that it can't find the Starter class (Caused by: java.lang.ClassNotFoundException: de.kramhal.multi.Starter), which is of cause in the starter module.
You mentioned the spring-dependency-management plugin, but that is only relevant for managing Maven dependencies, and not project dependencies like this. So it is not helpful here.
I am not entirely sure if this is Windows specific or not as I remember there were some discussions around performance a while back when having a lot of classes. But I believe the java-library plugin will look for jar files in other projects, and not the folder for compiled classes. This is a problem for you since the spring-boot plugin will by default disable the standard jar task and instead create "fat" a jar file through the bootJar task. Because you need both the fat jar for packaging the application to run stand-alone but also the normal jar for consuming it as a dependency, you need to do some tweaks to the starter project (Kotlin DSL):
tasks {
jar {
enabled = true
}
bootJar {
archiveClassifier.set("boot")
}
}
This will enable the normal jar file, but because the name will conflict with the one produced by the bootJar task, you need to rename one of them. I chose to rename the bootJar one.
I don't know why the test works for you in IntelliJ as that should, by default, delegate everything to Gradle. But maybe you have an old version, or done some manual configuration to let IntelliJ both compile and run your tests.
I have 2 Projects.
All the API contract (interfaces) defines in the demo-parent project (spring boot application)
The implementation for those defined in the demo-child project (spring boot application)
demo-parent is a dependency for demo-child, defined in the pom.xml of demo-child
In demo-parent :
AccessAPI.java
public interface AccessAPI {
void call();
}
SpringDemoParentApplication.java
#SpringBootApplication
public class SpringDemoParentApplication {
#Autowired
private AccessAPI accessAPI;
public static void main(String[] args) {
SpringApplication.run(SpringDemoParentApplication.class, args);
}
#PostConstruct
void exec() {
accessAPI.call();
}
}
In demo-child I have the implementation for the service :
AccessAPIImpl.java
#Service
public class AccessAPIImpl implements AccessAPI {
#Override
public void call() {
System.out.println("Executing from AccessAPIImpl");
}
}
Goal trying to achieve :
I must be able to build these projects independently, and pass the demo-child project jar via classpath to pick the implementation and inject all the implementation when running of demo-parent .
Such as :
>java -jar demo-parent.jar -cp demo-child.jar
I expected that the implementation would be picked up from the demo-child and autoinjected but It is not working as expected.
Note : I dont want to add demo-child dependency on demo-parent, the implementations/dependency must be picked up at runtime.
Please check the Git repository :
https://github.com/anthonyvk/spring-demo-child
https://github.com/anthonyvk/spring-demo-parent
TL; TR:
java -cp demo-parent.jar -Dloader.path=demo-child.jar org.springframework.boot.loader.PropertiesLauncher
if the demo-child.jar is a Spring Boot application, i.e. if the jar is repacked by the Spring Boot plugin, you need to change the loader.path:
java -cp demo-parent.jar -Dloader.path='demo-child.jar!/BOOT-INF/classes' org.springframework.boot.loader.PropertiesLauncher
Explanation:
By default, Spring Boot uses JarLauncher, which looks only in BOOT-INF/lib/ inside the jar archive, -cp argument has no effect. The commands above switch to PropertiesLauncher, which honors loader.path parameter (-cp equivalent). For details see The Executable Jar Format
By the way, the project structure seems to be a bit problematic:
adding spring boot application (demo-parent.jar) as a dependency to demo-child.jar is tricky, as demo-parent.jar is repacked by the Spring Boot plugin and maven cannot find AccessAPI class
there are runtime circular dependencies, even if not explicitly specified in the POM
I don't know detailed requirements, but if possible, I would suggest:
extracting interfaces to a separate project, let's say demo-api
making demo-parent and demo-child depended on demo-api, not on each other
making demo-api and demo-child regular jar's, not Spring Boot applications (removing Spring Boot plugin)
Is there a way I can get all packages related to my application in runtime? (e.g. list of strings)
I am interested only in packages that are declared in my application and not interested in those that come from dependencies.
I tried to search them by the main class:
System.getProperty("sun.java.command")
But, in this case, I can only get a package of my main class.
I assume it may differ of application and how it's packed. But, now I am mostly working with Spring Boot apps that are packed as jar.
You should try in this way, specify the main class package and do filter
spring boot main class package com.main
Package[] p = Package.getPackages();
for(Package p1 : p) {
if(p1.getName().startsWith("com.main"))
System.out.println(p1.getName());
}
I have projects A and B that both have common project as a compile dependency defined in their build.gradle files like this:
dependencies {
compile project(":common")
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-cache')
compile("net.oauth.core:oauth:20090617")
compile("net.oauth.core:oauth-httpclient4:20090617")
compile('org.apache.httpcomponents:httpclient')
compile("com.atlassian.jira:jira-rest-java-client:2.0.0-m2")
compile("com.google.guava:guava:18.0")
compile('org.flywaydb:flyway-core')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
This common project has application.yml file with all kind of common information such as database connection properties, hibernate setup, etc. So I do not want to duplicate these files all over the other projects such as A and B.
In project A the main spring boot file looks like this:
#SpringBootApplication(scanBasePackageClasses = {CommonApp.class,
A.class})
public class A {
public static void main(String[] args) {
SpringApplication.run(A.class, args);
}
}
where CommonApp is a main class in the commmon project. This common main file is the following:
#SpringBootApplication
public class CommonApp {
public static void main(String[] args) {
SpringApplication.run(CommonApp.class, args);
}
}
Projects A and B compile just fine, but all yml files that are in the classpath of the common project are not visible from A and B, so I have not choice but to duplicate them manually in A and B
What is the better approach? Can spring boot common projects share resources with other projects?
Notice that ideally solution should not be gradle dependant as I would like to run unit and integration tests from Intellij IDEA which does not use gradle for running tests.
My app structure is
app
|-A
|-build.gradle
|-web
|-B
|-build.gradle
|-common
|-src/main/resources
|-application.yml
|-database.yml
|-web.yml
|-settings.gradle
|-build.gradle
A, B and common are all spring boot apps (common is a boot app too, but it's only used as a dependency for A, B).
If you want a non-gradle solution, you could always add a DAO-esque file to access the required values in the common project then jar your common project and add it as a dependency to projects A and B.
Edit: I apologize, I did not explain very well. When I say "DAO-esque file", what I mean to say is a file of getter (and setters if needed) that access the properties file. For example, have a getter file that is a singleton. On creation, create a static reference to the property file. When Project A (or B) needs a property, it can call to this file to get the property.
As for a gradle solution, it looks like your above is fairly close with your
compile project(":common")
code. Do you have a settings.gradle file that has the line
includeFlat "common"
and a reference to the common project in the same directory as Project A and B? For example
Project A
(...Project A's files...)
Project B
(...Project B's files...)
Common
(...Common's files...)
I have a myapp parent pom type maven project with myapp-core and myapp-web modules. myapp-core module is added as dependency to myapp-web.
All the classes in myapp-core module reside in root package com.myapp.core and all classes in myapp-web module reside in root package com.myapp.web
The main Application.java is also in com.myapp.web package. As my core module root package is different I am including common base package "com.myapp" for ComponentScan as follows:
#Configuration
#ComponentScan(basePackages="com.myapp")
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now the surprising thing is if I run this app using Run As -> Spring Boot App it is working fine. But if I run it as Run As -> Java Application it is failing with error saying it can't found beans defined in myapp-core module.
If I move my Application.java to com.myapp package it is working fine.
It should work even if i run it as Java Application also, right?
After enabling debug log level for spring and going through extensive logs I found that scanning for various components like JPA Repositories, JPA Entities etc are depending on the Application.java's package name.
If the JPA Repositories or Entities are not in sub packages of Application.java's package then we need to specify them explicitly as follows:
#Configuration
#ComponentScan(basePackages="com.sivalabs.jcart")
#EnableAutoConfiguration
#EnableJpaRepositories(basePackages="com.sivalabs.jcart")
#EntityScan(basePackages="com.sivalabs.jcart")
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
With the above additional #EnableJpaRepositories, #EntityScan I am able to run it using Run As -> Java Application.
But still not sure how it is working fine when Run As -> Spring Boot App!!
Anyway I think it is better to move my Application.java to com.myapp package rather than fighting with SpringBoot!
I have the same problem. Only adding the #EnableJpaRepositories annotation can solve the issue. I tried to define basePackages in #SpringBootApplication, to no avail.
I think the package of the Application class is fed to the scanning process of JpaRepositories, but other packages defined in #SpringBootApplication are ignored.
It looks like a bug/improvement of Spring Boot.
I had a similar issue with Redis repositories that was fixed in a similar way:
#Configuration
#EnableConfigurationProperties({RedisProperties.class})
#RequiredArgsConstructor
#EnableRedisRepositories(basePackages = {"com.example.another"})
public class RedisConfig {
private final RedisConnectionFactory redisConnectionFactory;
#Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}