Duplicate classes in a web application - java

I have a maven Spring Boot web application with quite a few (transitive) dependencies.
There are .jar files in the WEB-INF/lib folder inside the built .war.
For example:
hibernate-jpa-2.1-api-1.0.0.Final.jar
javax.persistence-2.1.0.jar
Both of these contain a class called javax/persistence/JoinColumn.class (with different file sizes).
At runtime, which "version" is going to be referenced when I use it in the code?
Can this type of dependency cause problems later?

To answer your question:
You don't know.
Yes, this will cause problems later.
For a more elaborate answer, check https://stackoverflow.com/a/5474838/7430994

Related

JBoss classloading IncompatibleClassChangeError

I'm migrating a project from JBoss 5 to 6.4. In the process I've updated several jar files in my build. I had several ClassCastExceptions which I resolved by taking the version of the specific classes from a jar in the JBoss system modules and using that in my app build. I've done so with two jars, jbossweb-7.5.26.Final-redhat-1.jar and jboss-servlet-api_3.0_spec-1.0.2.Final-redhat-2.jar which contain the classes org.apache.catalina.connector.Request and javax.servlet.http.HttpServletRequest respectively. I'm using both of those jars as libs in my app build and all is compiling without error or warning. I've confirmed that when I remove those libs the app fails to compile as it can't find the included refernced classes, so I know I'm not actually referencing some other copy of those classes in my build. The app is deploying fine, but when I try hitting it I get an exception
JBWEB001018: An exception or error occurred in the container during
the request processing: java.lang.IncompatibleClassChangeError: Class
org.apache.catalina.connector.Request does not implement the requested
interface javax.servlet.http.HttpServletRequest
When I look at the class definitions from the jars in my editor (JDeveloper 12c) I can see that org.apache.catalina.connector.Request as defined in jbossweb-7.5.26.Final-redhat-1 does in fact implement javax.servlet.http.HttpServletRequest as defined in jboss-servlet-api_3.0_spec-1.0.2.Final-redhat-2.jar.
I turned on verbose logging of classloading (JAVA_OPT -verbose:class), and confirmed that as JBoss is loading those two classes they are in fact being loaded from those two jar files, not some other one I didn't know about. I can see in my developer tool that the .class files I'm including in my project are from those same two jar files.
Possibly relevant, the classloading logs say JBoss is loading org.apache.catalina.connector.Request from RedHat/JBoss/EAP-6.4.0/modules/system/layers/base/.overlays/layer-base-jboss-eap-6.4.18.CP/org/jboss/as/web/main/. I'm not sure why it's using that copy/location rather than RedHat\JBoss\EAP-6.4.0\modules\system\layers\base\org\jboss\as\web\main.
So what gives? What I read on the error would suggest to me that jboss-servlet-api_3.0_spec-1.0.2.Final-redhat-2.jar was compiled against a different version of HttpServletRequest than jbossweb-7.5.26.Final-redhat-1 includes.
Is that interpretation or the error correct? Does JBoss 6.4 really ship with incompatible jar files in its system modules? If so what versions of these jars do I need and how to I check compatibility? And can I safely switch to a different jar file without introducing more incompatibilities with other jars? What other causes might I be missing? Any help much appreciated here.
1/ You shouldn't depend on servlet API implementation
2/ You shouldn't include those implementation classes in your application are they are provided by the server itself
3/ You shouldn't put API jars or their implementation in your application or if you do this you need to exclude those modules from your application using a jboss-deployment.xml
Looks like the issue was that I was not using the modules.xml correctly. I needed to add dependencies for the two packages in my module.xml file
<dependencies>
<module name="org.jboss.as.web"/>
<module name="javax.servlet.api"/>
</dependencies>
I had previously added those modules to my war file's jboss-deployment-structure.xml and that did not do the trick and left me with ClassNotFoundExceptions. My Jboss-deployment-structure.xml now just references my own custom module as a dependency, which in turn has dependencies of its own.
I still don't understand what was causing the previous error, as it's all the same versions of the jar files in question so I don't know where a conflict was, but this got the error to go away and no more ClassNotFoundExceptions.

How do I place files on the classpath?

I'm trying to implement a custom view in Spring-Boot-Admin. The documentation states "The JavaScript-Bundle and CSS-Stylesheet must be placed on the classpath at /META-INF/spring-boot-admin-server-ui/extensions/{name}/".
I think I've got the JS and CSS stuff, but where exactly do I have to put them now in my Spring Boot application? This is probably really simple but I don't understand it.
https://docs.oracle.com/javase/tutorial/essential/environment/paths.html
This is the Oracle documentation on the classpath, and is essential reading for any Java developer.
If you are using Spring Boot, I assume you are also using Maven/Gradle or some equivalent. If you follow their standard project structure, src/main/resources will be on the classpath and you can put your js/css there.
However, before going further I strongly advise you to read the above article as it really is quite fundamental to how the Java language works.
They should go to the 'resources/static' folder.
Anything placed in src/main/resources will be placed in your classpath if you are using a tool like Maven/Gradle.
Depending on how you setup your project, you may need to create a fat jar in order for Spring Boot Admin to pick up the files. You can look at the contents of your jar to see if that is necessary. Some IDEs like IntellliJ will create a fat jar for when you run your project from the IDE, but the jars created for deployment are not fat jars.

EJBs in libraries - Reusing EJBs

Let's say I want to create a library which I will use in future projects but I also want to include EJBs in that library referencing other EJBs etc.. That library would also contain simple java classes. What is the best way to do that? How do I define the dependencies in this case? I thought I would define them with annotations. If the user of the library wants to configure other dependencies he will be able to do so by overriding them inside the ejb-jar.xml of his project. Has anyone done something like this in the past? How would you go about it when developing in Eclipse?
Basically my problem is that as far as I can tell if I simply create an ejb-jar which I am going to include in all my projects the ejb container is going to instantiate my e.g. MDBs at deployment time even if I don't need all of the MDBs that are contained inside my library but only some of them.
Is a solution to not define MDBs as EJBs with annotations or inside the ejb-jar.xml but only their dependencies?
What about the session beans? Will they be automatically instantiated even if I don't use them inside a project?
EAR files. Although not very common, you could include your library and its dependencies in an EAR file and distribute that.
What I'd like to do is to distribute the library in its own Jar file along with a documentation of dependencies (e.g.: a Maven' POM file or ivy's xml file). Either way, you'll need a dependency manager.
There is also an option to build a fat JAR file in which all the dependencies are exploded. I don't really like that. If I have to include dependencies, I'd go with EAR files.

Spring jars in tomcat lib folder

I am completely confused over how the class loading happens in tomcat. So please bear with me if my question sounds stupid.
We deploy multiple spring webapps on single tomcat server. To reduce the memory footprint, we thought of having spring, hibernate and database driver jars in tomcat lib folder so that those are shared by all webapps.
I started first by marking spring dependencies as provided and copied those jars to tomcat lib. But on server startup I started getting multiple ClassNotFoundErrors, like for commons-logging, commons-fileupload, jackson, so I had to move these jars as well to tomcat lib.
But then this started feeling fishy. What if in future I add another spring dependency to my project, say spring-data-cassandra. Would I need to move it's dependent jars as well? This might be unending. Also I might get CNF errors at runtime.
I tried to follow this link and brought back spring-context and spring-web back to application war. But it didn't work, got ClassNotFound on some class during WebApplicationIntializer initialization. I tried to understand the order of classes getting loaded in tomcat, but could not understand much.
Then I found a complete different explanation for JDBC driver loading which kinda contradicts to all other explanations and left me completely confused.
As I read more, I think it is not a right approach to move spring jars to tomcat lib, but still haven't got a good reasoning. And then why JDBC driver works? Can someone please explain? Also does classloader for each webapp creates a copy of each class?
Edit 1: I came to know that few dependencies are optional in spring jars, and will be required if are in use in my webapp. So spring-web depends on jackson libraries but is optional for my app. So I need to find out which all jars are required for my project and are also required by spring, those jars need to be moved to tomcat lib.
I will try to explain what I know.
When you deploy a WAR on your tomcat, the class loading will happen this way :
look for the class to load in your WAR classLoader
if not found move to parent (tomcat /lib folder)
What happen in your case is that spring also have a lot of dependencies, if you package it in your war, its dependencies would have been packaged as well and everything would have worked fine. But since you defined spring as provided, all its dependencies are considered provided as well, and when you put it in /lib folder, spring is accessible, but its dependencies are not.
What you need to do is put all spring dependencies and the dependencies of dependencies (etc.) in lib folder as well. Another solution is to define an intermediary WAR in your classloading hierarchy which will contains all your common libs.
If you prefer to create this type of thin WAR, one solution would be to first gather all runtime/provided dependencies and then copy them explicitly under Tomcat's common (or shared) library directory:
If under a Maven project, gather everything (including transitive dependencies) under target/dependency:
mvn dependency:copy-dependencies -DincludeScope=runtime
mvn dependency:copy-dependencies -DincludeScope=provided
Afterwards, copy to library. For example:
cp target/dependency/*.jar /usr/local/tomcat/lib/

Where to put a shared library in JBoss AS 7

I have multiple web applications each using spring-hibernate and other open source libraries and portlets, so basically now each war file includes those jar files. How do I move these jars to a common location so that I don't have to put these in each war file? My jars are places in D:/ directory.
I tried creating modules but no success. e.g. if I added jar
<resources>
<resource-root path="mylib.jar"/>
</resources>
and mylib.jar needs another ABC class. That ABC class is in my WAR class-path. Here I get exception while loading this module. mylib.jar could not find ABC class and throws exception.
If those libraries are reused in several applications, probably the best solution would be to create JBoss modules.
For example, OJDBC library is used in several projects I'm developing. So, I added a new module to JBoss 7: https://community.jboss.org/wiki/CreateAModuleForOracleDatasourceInJBoss711Final (it's just an example).
But you said, that you tried creating modules, but with no luck. What was the problem? Did you get some errors?
EDIT
Answer updated in connection with updated question.
So, if I understood correctly, we can divide your libraries into two categories:
First category is "standard libraries": Spring, Hibernate, Log4j etc. So, these libraries might be added as modules into JBoss AS and reused in every WAR (scope=provided in Maven's dependency).
Any other non-standard libraries (i.e. written by yourself) might be added as modules as well. If these libraries require some other dependencies - these dependencies must be listed in module's XML file, as described in: https://docs.jboss.org/author/display/MODULES/Module+descriptors
Hope this helps at least a bit :)

Categories

Resources