Here's the situation. I have 2 projects.
Project A
Imports mongo-java-driver
Has a ConfigurationA class that defines a Bean like so:
public #Bean MongoDatabase getMongoDatabase() {
MongoClient client = new MongoClient();
return client.getDatabase("mydb");
}
Project B
Imports Project A
<dependency>
<groupId>com.project</groupId>
<artifactId>project-a</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
Has a ConfigurationB class that imports ConfigurationA
Does not have import for mongo-java-driver
When I try to run project B I am getting the following error:
java.lang.IllegalStateException: Could not evaluate condition on org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration#propertySourcesPlaceholderConfigurer due to internal class not found. This can happen if you are #ComponentScanning a springframework package (e.g. if you put a #ComponentScan in the default package by mistake)
...
Caused by: java.lang.NoClassDefFoundError: com/mongodb/client/MongoDatabase
If I add an explicit import to Project B for mongo-java-driver, it works fine.
My question is: Why do I need the import in Project B? Is there anything I can do to prevent this? I don't want Project B to know about the dependencies in Project A.
Related
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 {
}
I am currently running into some problems with spring boot and multi maven project structure. I am using Spring Boot 4.3.1.
My project structure looks as follows:
parent
-- pom.xml
-- application
-- pom.xml
-- src
-- main
-- java
-- Application.java (annotated with #SpringBootApplication)
-- test
-- java
-- MyApplicationTest.java (annotated with #SpringBootTest)
-- library
-- pom.xml
-- src
-- main
-- java (...)
-- test
-- java
-- MyLibraryTest.java (annotated with #SpringBootTest)
application module has a dependency on library.
MyApplicationTest works perfectly fine, but running MyLibraryTest instead, I fail with the following error:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test at org.springframework.util.Assert.state(Assert.java:392)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.getOr FindConfigurationClasses(SpringBootTestContextBootstrapper.java:173)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.processMergedContextConfiguration(SpringBootTestContextBootstrapper.java:133)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:409)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:305)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:112)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:78)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:120)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:105)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:152)
My first guess is that library needs a dependency on application, but this causes a cycle.
Is there any solution to that problem?
How can I structure my application correctly?
thanks a lot for suggestions.
MyLibraryTest looks as follow:
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class MyLibraryTest {
#Autowired
private MyService service;
#Test
public void testMyService_Save() {...}
}
You need to make sure that the library module's pom.xml includes -
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>1.5.2.RELEASE</version>
</dependency>
which is required for the module to use the #SpringBootTest annotation. You might already be using the same in your app module but is not included in the library module.
Well post the Edit, found the question to be possibly a duplicate of Unable to find a #SpringBootConfiguration when doing a JpaTest
Also quoting from Thomas's answer from the same thread, here
The thing about #DataJpaTest and a few other annotations is that they
look for a #SpringBootConfiguration annotation in the current package,
and if they cannot find it there, they traverse the package hierarchy
until they find it.
For example, if the fully qualified name for your test class was
com.example.test.JpaTest and the one for your application was
com.example.Application, then your test class would be able to find
the #SpringBootApplication (and therein, the
#SpringBootConfiguration).
If the application resided in a different branch of the package
hierarchy, however, like com.example.application.Application, it would
not find it.
which seems to be the case for you, where you are trying to test an application in a different module itself. Hence the error that you see.
The following is clearly bad practice but my question is out of pure curiosity: If I have a Maven project with two versions of the same library in the dependencies (e.g. jetty server in the pom.xml fraction below), then I import a class in my code (e.g. java code below), which version is the class picked up from?
<project>
....
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.3.0.v20150612</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>8.1.17.v20150415</version>
</dependency>
....
</project>
And the java code:
package test.hello;
import org.eclipse.jetty.server.Server;
public class Hello {
Server server;
public Hello() {
server = new Server();
}
}
Is the server object created from the old version class or the new version class? Maven does not complain about any ambiguity. It just loads a class; I don't know which.
Maven has nothing to do with loading jars or classes. Maven only gathers all the dependancy. Its upto your classloader to decide what to load when.
If you have two classes with the same binary name, and you want to know which one of them you are loading, you can only inspect the way that classloader tries to resolve a class name.
So I have a spring controller that looks like:
#Controller // the org.springframework.stereotype.Controller
#RequestMapping("/test")
public class TestController extends BaseController {
...
}
And this works fine. However when I am trying to refactor the code, to move BaseController to an external project "common", the project build will fail with followint stacktrace:
SEVERE: Servlet /mee threw load() exception
java.io.FileNotFoundException: class path resource [ca/wherego/common/controllers/BaseController.class] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:157)
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:49)
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80)
I have tried two ways of using the external project:
1) Let maven uses it as:
<dependency>
<groupId>my.api</groupId>
<artifactId>common</artifactId>
<version>1.0.1</version>
<scope>system</scope>
<systemPath>${basedir}/lib/common-1.0.1.jar</systemPath>
</dependency>
and place the maven in the correct folder ( I don't have a private maven build, so I can't properly upload it as I use other public maven dependencies).
2) I tried to use the "common" project as the project dependency in the build path.
However none of the methods work.. Am I missing anything? Thanks!
Thanks all of you to read my question!
Months ago , I had build nexus to manage my maven repository and I build an empty web project.
My goal is to create an architecture for my company. When a new project comes, I can only configure my pom.xml to add my frame dependency. Well, my project's frame is springMVC+velocity+mybatis
I separated my Java source code into four models, groupId, artifectId has been named like below:
groupId is __com.myproject.framework__
parent pom's artifactId is com.myproject.framework-root.
sub models's artifactId are framework-core, framework-utils and so on.
This architecture can be used when I create an empty Java web project. I only configured the pom.xml with :
<dependency>
<groupId>com.myproject.framework</groupId>
<artifactId>framework-core</artifactId>
<version>1.0</version>
</dependency>
It's very good, I can reference this jar package very well!
But framework-core's Java code uses the Spring framework. Source code of myproject-framework-core jar package, is as below:
Action:
package com.hc360.buyer.action
#Controller
public class ActiveRecordAction extends BaseAction {
// ...
}
Service:
package com.hc360.buyer.service
#Service
public class BuyerInfoMainServiceImpl{
...
}
This means that I must configure path into my spring config of the new empty project, such as:
<context:component-scan base-package="com.hc360.buyer.service" />
<context:component-scan base-package="com.hc360.buyer.action" />
My question is how can I design my framework models or jar package?
Goal is that when I create a new Java web project and reference my myproject-framework-core jar package, I needn't have to configure my spring config like below:
<context:component-scan base-package="com.hc360.buyer.action" />
In my mind, the best way is I only need to reference my myproject-framework-core jar package in this new project's pom.xml, and then it works well.