Creating and importing a custom Spring library whilst separating shared dependencies - java

Im looking to create a Spring library project to share across an internal team.
At a very basic concept level The library will send message events to a queue and my plan is to standardise this within a team across several Spring Boot Microservices send messages the same way.
My pom in the library project looks something like this
<artifactId>my-library</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
etc...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
I have a service in the library project that looks like this
public class EventService {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public void sendAuditEvent(AuditMessage auditMessage){
Set<ConstraintViolation<AuditMessage>> violations = validator.validate(auditMessage);
if(!isEmpty(violations)){
log.error("Unable to send audit message");
violations.stream().forEach( v-> log.error(v.getMessage()));
}
log.info("Found {} violations", violations.size());
// etc blah blah
return;
}
}
When I import the library into another project my thinking is that I can Autowire the EventService. By adding it in the pom and then
#ComponentScan({"my.library.package.eventlibrary.service"})
How do I prevent spring version locking? If the library is using spring 2.1.5.RELEASE today and the project that imports the library uses a different version would I not end up with potentially maven conflicts?
Also lets say the project that imports the library uses a lower version of hibernate api and the library has 6.0.16.Final. How would I prevent the project from using the newer one found one in the library classpath?
To clarify my question further is there a way I can separate the dependencies in the library from the project that uses it.

Pre Java 9. You can exclude the spring dependencies using maven when you declare the dependency to your module, same goes on for Hibernate. But you can't tell to your module to use a different hibernate version in a WAR.
If you want to work around this you can develop your library as independent micro service expose interface in the form of REST or Websocket if you want full duplex communication or something else JMS whatever....
Post Java 9 you can use java modularity to define the exact dependencies for your jar module. Check Project Jigsaw https://www.baeldung.com/project-jigsaw-java-modularity.
In your case in order to have different versions of the same library (hibernate). You would need two separate class loaders. To achieve this you would need to use layering read here http://openjdk.java.net/projects/jigsaw/spec/sotms/#layers
And here is the source code of many examples including ones that use layers. Focus on them : https://github.com/accso/java9-jigsaw-examples/tree/master/jigsaw-examples

You can try to exclude all transitive dependencies that your library can bring to projects that will use it.
To do this you should replace spring-boot-starter-parent with spring-boot-dependencies in dependencyManagement section and use provided scope for all dependencies which the library needs to work with and which will be exactly used by the projects, that will work with the library.
For example, a pom.xml of your library can be looks like this:
<!-- ... -->
<groupId>com.example</groupId>
<artifactId>library</artifactId>
<version>0.1.0</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<spring-boot.version>2.1.5.RELEASE</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- ... -->
Then you will be able to use your library in the different projects, that use for example the old Spring Boot:
<!-- ... -->
<groupId>com.example</groupId>
<artifactId>old-project</artifactId>
<version>0.13.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/>
</parent>
<!-- ... -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>library</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- ... -->
So this project will use hibernate-validator:5.3.6.Final from its spring-boot-starter-web.
Important notes - the code of your library should be 'compatible' with this version of Spring Boot. In other words, you should test your library with different versions of Spring Boot in which you are interested.
See my project as an example.

Might be not what you are looking for, but you can distribute your library as a spring-boot-starter auto configuration module (of course, if the clients are spring boot applications).
This way you can control your dependencies in an agile way and you give your clients more freedom in using the library.
In your particular case, if you need to send a message to a queue you for sure need to have a corresponding classes in classpath. With auto configuration you can have Class Conditions or Been Conditions based on which you can track if your clients have correct configurations in runtime. You can also fail the context loading if something is wrong (providing a meaningful error message).
Spring also provides tracking mechanisms of what could happen if a particular class/library is missing.

Related

How to make specific maven dependency versions mandatory

I am working on creating a common library for my team which can be used by different micro-services of our team. Common library will be service-starter which includes : specific spring boot, spring version and other compatible versions and dependencies. Is it possible to guardrail using maven such that our whole team must be on specific MUST versions of some maven dependencies like spring boot version should be common across team (We will also have other maven dependencies which can be overridden in respective micro-service pom if needed.)
You cannot stop your team from defining their own version in their project. However, you can add a <dependencyManagement>tag in your project which will define the versions you wish them to use so they won't have to decide.
But, again, nothing to stop them to override these.
You can create a parent pom that you want and put in <dependencyManagement> the dependencies that you want to be used by applications that use this parent pom.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
so if another application uses as parent pom the one you have declared above and uses as dependency
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
</dependency>
</dependencies>
(It does not contain any specific version), it would retrieve by default the version which was delcared in parent pom of 1.0
But the child pom application would always be free to declare their own version if they want, and use that instead, effectively overriding what was declared in parent pom
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
More about Dependency management in Maven

Unable to resolve #RestController annotation in spring 2.0.3

I am making a new springboot- maven project. The compiler is unable to resolve #RestController when I use below pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
However, when I change the version to 1.5.13, the compiler is able to resolve #RestController
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
I want to be using the latest version of spring boot, and use #RestController.
#RestController doesn't come from org.springframework.boot:spring-boot-starter-web, it comes from org.springframework:spring-web. Different versions of spring-boot-starter-web must probably have some transient dependency that provides it, but it's a bad practice to rely on it. If you use #RestController in your code, you should explicitly require the artifact that provides it:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
since i can't add a comment, i think the maven config is not your problem, check your #Configuration and #SpringBootApplication classes and their path scanning settings
another problem you could have: maybe one of your other dependencies still depends on springboot 1.5, you have to have a clean springboot 2.0 classpath
i run in problems like this as well, so keep on diggin, you'll find it ;-)
I had somewhat a similar issue and it was because I named my class RestController. Once I renamed it Controller, I could import the #RestController packages without issues.

Maven - how to work with multiple versions of dependencies?

I have project which used JIRA REST Java Client. It worked fine until I tried to integrate it with Spring Boot. Since that I am not able to invoke createWithBasicHttpAuthentication from AsynchronousJiraRestClientFactory without error. I get:
ClassNotFoundException: org.apache.http.util.Args
So I added HttpComponents Core blocking I/O(httpcore) dependency to my pom.xml, but I after that I got
ClassNotFoundException: org.apache.http.nio.NHttpMessageParserFactory
Which I resolved with adding HttpComponents Core non-blocking I/O(httpcore-nio) to pom.xml. Now I have
NoSuchMethodError: org.apache.http.nio.client.HttpAsyncClient.start()V
I've compared dependency:tree when project has spring boot parent and when it's commented out. It shown me that adding spring boot parent changes versions of my dependencies. You can check diff here( on left without spring boot, on right with spring boot)
It seems that JIRA REST Java Client need older versions of some dependencies.
How can I solve this problem?
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.0.RELEASE</version>
</parent>
...
<dependencies>
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-rest-java-client-core</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.3</version>
</dependency>
</dependencies>
...
I was able to fix runtime in my Spring Boot application by overriding these properties in my pom.xml
<properties>
<httpasyncclient.version>4.0-beta3-atlassian-1</httpasyncclient.version>
<httpclient.version>4.2.1-atlassian-2</httpclient.version>
</properties>
Note that there can be other problems if you decide to use http-client and/or httpassync client in your project (eg. using RestTemplate).
Atlassian should definitely upgrade the dependencies.

Why version number in maven dependency is skipped at times?

I am fairly new to maven's capabilities..
I have seen that in pom.xml where dependencies are put, at times, only groupID and artifact id are mentioned and version is skipped. why is this?
For example the below dependency is from springsource website http://spring.io/guides/gs/authenticating-ldap/
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>1.5.5</version>
</dependency>
</dependencies>
But elsewhere in stackoverflow it was also mentioned that version is not optional. I would be glad if someone could explain this.
Yes, version is not optional.
Consider a multimodule application which has 10 modules, say module1, module2.. module10.Assume that all of these 10 projects use the spring-boot-starter-web. In case these 10 modules are interdependent, you might want to use the same version of the spring-boot-starter-web in each of these 10.
Now just imagine how complicated it would be if you were to maintain the same version number in all of these 10 pom files and then update all of them when you want to use a newer version of spring-boot-starter-web. Wouldn't it be better if this information can be managed centrally?
Maven has got something known a <dependencyManagement/> tag to get around this and centralize dependency information.
For the example which you have provided, below set of links will help you understand how the version number is resolved even though it's not present in the pom which you are looking at.
Look at the parent tag of the pom you are looking at (https://github.com/spring-guides/gs-authenticating-ldap/blob/master/complete/pom.xml)
Now lets go to that parent and see if the versions are specified in the dependencyManagement section of that pom(https://github.com/spring-projects/spring-boot/blob/master/spring-boot-starters/spring-boot-starter-parent/pom.xml). No it's not defined there as well. Now lets look at the parent of parent. https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml. Oh yea, we have got the version numbers there.
Similar to dependencyManagement, plugins can be managed in the pluginManagement section of the pom.
Hope that explains it.
Refer : dependencyManagement, pluginManagement
A few additions to the excellent answer by coderplus:
In a multi-module project, it is considered to be a good practice to configure the artifacts used by the project in the dependencyManagement of the root pom.xml so that you don't have to write versions in child module pom.xmls (like in some dependencies in your example).
It is also considered to be a good practice to declare versions of the external libraries that you use as properties and then use these properties in dependencyManagement/dependencies/dependency/version. This is more or less done here:
<properties>
<logback.version>1.1.2</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
In a multi-module project, you should also declare your own artifacts in dependencyManagement.
But please do not write the version explicitly (like Spring people do here), use ${project.version} instead.
So it would have been better to write:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${project.version}</version>
</dependency>
instead of
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
here.
The whole purpose is DRY, don't repeat yourself. The more redundant declarations you have in your POMs, the harder they can hit. Hunting for the obsolete dependencies is so much fun.
If the <version> isn't specified then the <version> of <parent> is used. If there is no <version> in the <parent> then the <version> in the <parent> of the <parent> is used. etc.

two war modules integration

i have made two maven projects : the first is a generic authentification module with spring security with this structure :
ear
|...warModule
| |...ejbModule
|...ejbModule
the second is a CRM have this structure
ear
|...warModule
|...ejbModule
|...ejbModule
now i want to integrate both so i can manage the CRM security with my authentification project (control url access ,permissions ...)is there a way to do that ?
One way is that, you declare first project as dependency in your second project POM file.
For detail you can check: Maven Dependency Mechanism
Sample code to add Dependencies:
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact 1</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact 2</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>`
Second way and I would suggest to use Overlay approach to integrate multiple modules and another benefit to use overlay is can share common resources across multiple applications.
You have just to add a dependency declaration in one pom.xml file which point to the other artifact. For example lets say the downside lines are your CRM pom file:
<project>
<groupId>crm.group.id</groupId>
<artifactId>crm-artifact-id</artifactId>
<version>1.0</version>
<packaging>ear</packaging>
<dependencies>
<dependency>
<groupId>authentication.group.id</groupId>
<artifactId>authentication-artifact-id</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
It is just a sample and should be updated following your projects/modules group/artifact IDs.

Categories

Resources