How to avoid classloading issues with MyBatis in OSGI environment? - java

I am working on an Eclipse 3.7 RCP-based application with multiple modules. Module A is a bunch of libraries including mybatis-3.2.2.jar. Module B depends on module A (Require-Bundle in the manifest.mf) and has code that uses MyBatis to access data in a database. I have exported packages with mapper classes and XML in module B and imported them in module A. I am building SqlSessionFactory in the code, and it works fine if I add all Mapper classes by name, e.g.
configuration.addMapper(MyMapper.class);
however when I try to add all Mappers in the package:
configuration.addMappers(MyMapper.class.getPackage().getName());
MyBatis does not see them.
I tried changing the default classloader but this did not help.
Resources.setDefaultClassLoader(this.getClass().getClassLoader());
I suspect the problem has to do with visibility of classes in an OSGI environment. If that's the case, are there any ways to fix it in the application?

Have you tried
Resources.setDefaultClassLoader(Activator.class.getClassLoader()). I think that will use the OSGi class loader for the bundle. Hopefully that will help.

configuration.addMappers uses its own ResolverUtil that uses the Thread context class loader. (At least in mybatis3).
Best bet would be to write your own scanning code and use addMapper directly. There are my references and examples below:
http://grepcode.com/file/repo1.maven.org/maven2/org.mybatis/mybatis/3.1.1/org/apache/ibatis/session/Configuration.java?av=f#518
http://grepcode.com/file/repo1.maven.org/maven2/org.mybatis/mybatis/3.1.1/org/apache/ibatis/io/ResolverUtil.java#148
EDIT: Here are some for mybatis 3.2.2
http://grepcode.com/file/repo1.maven.org/maven2/org.mybatis/mybatis/3.2.2/org/apache/ibatis/io/ResolverUtil.java#147
http://grepcode.com/file/repo1.maven.org/maven2/org.mybatis/mybatis/3.2.2/org/apache/ibatis/binding/MapperRegistry.java#86
Same thing applies, though.

I ran into a similar problem with Spring Data JPA in a Felix OSGi environment. In that case I was able to override a factory class and add this into the offending methods:
ClassLoader pre = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(context.getClassLoader());
// add mappers here or call super method
} finally {
Thread.currentThread().setContextClassLoader(pre);
}
In this case "context" was a Spring context, but you should be able to get Module B's classloader from a BundleWiring.
Bundle bundle; //get this by symbolic name if you don't have a reference
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
bundleWiring.getClassLoader();
Hopefully the addMappers method doesn't need to access Module A's classloader in the same call. If that's the case, there's not a ton you'll be able to do unless there's a way to extend and inject a different Configuration or MapperRegistry class.

Related

Spring: Dynamic registrations of beans, rest-controllers, and more

I am new to Spring and would like to convert my existing applications to Spring Boot.
However, I am using a self-written module framework that allows me to add or remove components or additional functions of the application dynamically at runtime. The whole thing can be compared to plugin frameworks like PF4J or the plugin mechanism in Minecraft servers.
The advantage of this is obvious. The application is much more dynamic and certain parts of the program can be updated at runtime without having to restart the whole application.
Under the hood, a new ClassLoader is created for each module when it is loaded. The ClassPath of this ClassLoader contains the JAR file of the module. Afterwards, I load the respective classes with this ClassLoader and execute there an init method, which contains each module.
Now, I would like of course in connection with Spring that both the dependency injection in the modules functions, and that beans or, for example, rest controllers, which are in the modules, register with the module loading and unregister with the module unloading.
Example: I have a staff module. When I register it, the employee endpoint is registered and is functional. When I unload the module, the employee endpoint is removed again.
Now to my problem:
Unfortunately, I don't know how to implement this with Spring, or if something like this is even possible in Spring. Or are there even already other solutions for this?
I also read something about application contexts. Do I have to create a new application context for each module, which I then somehow "closed" when unloading the module?
I hope you can help me, also with code examples.
This post helped me a bit: https://hdpe.me/post/modular-architecture-with-spring-boot/
In short for each module a new ApplicationContext (e.g. AnnotationConfigApplicationContext) is created. If you want to share beans between the modules, you have to publish them to the main application context.
Beans can be registered at runtime by ((GenericApplicationContext) applicationContext).registerBeanDefinition(name, beanDefinition); at the main Application Context.
Another problem is that additional configurations are required, for example for #RestController or similar, in order for them to work. See other questions on StackOverFlow from me.

Class cast exception with MSSQL drivers [duplicate]

I have 2 different Java projects, one has 2 classes: dynamicbeans.DynamicBean2 and dynamic.Validator.
On the other project, I load both of these classes dynamically and store them on an Object
class Form {
Class beanClass;
Class validatorClass;
Validator validator;
}
I then go ahead and create a Validator object using validatorClass.newInstance() and store it on validator then I create a bean object as well using beanClass.newInstance() and add it to the session.
portletRequest.setAttribute("DynamicBean2", bean);
During the lifecycle of the Form project, I call validator.validate() which loads the previously created bean object from the session (I'm running Websphere Portal Server). When I try to cast this object back into a DynamicBean2 it fails with a ClassCastException.
When I pull the object back out of the session using
faces.getApplication().createValueBinding("#{DynamicBean2}").getValue(faces);
and check the class of it using .getClass() I get dynamicbeans.DynamicBean2. This is the class I want to cast it to however when I try I get the ClassCastException.
Any reason why I'm getting this?
I am not quite following your description of the program flow, but usually when you get ClassCastExceptions you cannot explain you have loaded the class with one classloader then try to cast it to the same class loaded by another classloader. This will not work - they are represented by two different Class objects inside the JVM and the cast will fail.
There is an article about classloading in WebSphere. I cannot say how it applies to your application, but there are a number of possible solutions. I can think of at least:
Change the context class loader manually. Requires that you can actually get a reference to an appropriate class loader, which may not be possible in your case.
Thread.currentThread().setContextClassLoader(...);
Make sure the class is loaded by a class loader higher in the hierarchy.
Serialize and deserialize the object. (Yuck!)
There is probably a more appropriate way for your particular situation though.
I was getting this problem after adding a dependency to spring-boot-devtools in my Springboot project. I removed the dependency and the problem went away. My best guess at this point is that spring-boot-devtools brings in a new classloader and that causes the issue of class casting problems between different classloaders in certain cases where the new classloader is not being used by some threads.
Reference: A dozer map exception related to Spring boot devtools
The class objects were loaded in different classloaders, therefore the instances created from in each of classes are seen as 'incompatible'. This is a common issue in a an environment where there are many different classloaders being used and objects are being passed around. These issues can easily arise in Java EE and portal environments.
Casting an instance of a class requires that the Class linked to the object being casted is the same as the one loaded by the current thread context classloader.
I got the A2AClassCastException problem when trying to create a List of objects from XML using Apache Commons Digester.
List<MyTemplate> templates = new ArrayList<MyTemplate>();
Digester digester = new Digester();
digester.addObjectCreate("/path/to/template", MyTemplate.class);
digester.addSetNext("/path/to/template", "add");
// Set more rules...
digester.parse(f); // f is a pre-defined File
for(MyTemplate t : templates) { // ClassCastException: Cannot cast mypackage.MyTemplate to mypackage.MyTemplate
// Do stuff
}
As stated above, the cause is that the digester doesn't use the same ClassLoader as the rest of the program. I ran this in JBoss, and it turned out that commons-digester.jar was not in JBoss's lib directory, but rather in a webapp's lib directory. Copying the jar into mywebapp/WEB-INF/lib also solved the problem. Another solution was to casll digester.setClassLoader(MyTemplate.class.getClassLoader()), but that feels like quite an ugly solution in this context.
Had the same my.package.MyClass cannot be cast to my.package.MyClass on WildFly 10.1 and, as I understand, I did the opposite to what #Emil Lundberg described in his answer.
I have added the module (which contains my.package.MyClass) to my.war/WEB-INF/jboss-deployment-structure.xml as a dependency
<dependencies>
...
<module name="my.package"/>
</dependencies>
and removed the corresponding jar from my.war/WEB-INF/lib, re-deployed the WAR and then the code worked as expected.
Thus, we made sure it solves the issue. Now, we need to make sure the issue won't come back, for example, when the updated version of WAR will be assembled and deployed.
For this, in the sources of those WAR, it is required to add <scope>provided</scope> for those jar in pom.xml, so that when my.war is re-assembled next time with the fix/enhancement code injected, it will not bundle this jar into my.war/WEB-INF/lib.
I had the same issue while using several JBoss instances on different machines. To bad I didn't stumble across this post earlier.
There were artifacts deployed on different machines, two of them declared class loaders with identical name.I changed one of the classloader names and everything worked fine => Beware of Copy&Paste!
Why doesn't the ClassCastException thrown mention the involved class loaders? - I think that would be very useful information.
Does anyone know if there will be anything like this available in the future? Needing to check the class loaders of 20-30 Artifacts is not that pleasant. Or is there something I missed in the exception text?
EDIT: I edited the META-INF/jboss-app.xml file and changed the name of the loader, the idea is to have a unique name. At work we use the artifact id(unique) combined with the version inserted by maven({$version}) during the build. Using dynamic fields is only optional but helps if you want to deploy different versions of the same application.
<jboss-app>
<loader-repository>
com.example:archive=unique-archive-name-{$version}
</loader-repository>
</jboss-app>
You can find some info here: https://community.jboss.org/wiki/ClassLoadingConfiguration
I had the same issue, and I finally found a workaround on java.net :
Copy all org.eclipse.persistence jar files from glassfish4/glassfish/modules to WEB-INF/lib. Then go in your glassfish-web.xml, and set class-delegate to false.
Worked for me !
I had a similar issue with JAXB and JBoss AS 7.1. The issue and solution are described here: javax.xml.bind.JAXBException: Class *** nor any of its super class is known to this context. The exception that was given was org.foo.bar.ValueSet cannot be cast to org.foo.bar.ValueSet
I had the same issue on a wildfly EJB, The EJB was returning a list of Objects and has an remote and a local interface. I used the Local interface by mistake what was working just fine up until the point you try to cast the objects in the list.
Local/Remote interface:
public interface DocumentStoreService {
#javax.ejb.Remote
interface Remote extends DocumentStoreService {
}
#javax.ejb.Local
interface Local extends DocumentStoreService {
}
The EJB bean:
#Stateless
public class DocumentStoreServiceImpl implements DocumentStoreService.Local, DocumentStoreService.Remote {
The correct spring wrapper around the EJB:
<bean id="documentStoreService" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="java:global/dpc/dpc-ejb/DocumentStoreServiceImpl!santam.apps.dpc.service.DocumentStoreService$Remote"/>
<property name="businessInterface" value="santam.apps.dpc.service.DocumentStoreService$Remote"/>
<property name="resourceRef" value="true" />
</bean>
Note the $Remote, You can change this to $Local and it will find the Local interface just fine, and also execute the methods without any issue (from a separate application on the same container), but the model objects are not marshaled and are from a different class loader if you use the local interface by mistake.
Another option:
Happened to me in weblogic, but I guess it can happen in other servers as well - if you do (just) "Publish" and therefor some of your classes are re-loaded. Instead do "Clean" so all the classes will re-loaded together.
I had same problem with an EJB lookup from another EJB.
I solved adding #Remote(MyInterface.class) to EJB class configuration

Forcing external java (jar project) library beans to be managed by Spring fabric

I have two project the first is a spring boot app and the second one i.e. java static library project that is not dependent on anything else except java. In the past those two projects were one project however i separated them since they represent two different logical components, and that java library is used in other sub projects as well. Now since the statical library is simply a jar i can not instantiate classes of that jar based on the interface name provided such as we do it in spring for example if my interface was located in path:
edu.university.ServiceLayer.StudentInterface
i would easily do:
Student object = (Student) applicationContext.getBean("StudentInterface");
and that gives me the student object
Now i would like to do the same with the external java library. Since i have never done this, my question is what would be the best way to do it if keeping in mind that i would like to keep that library not dependent on anything else except java.
In my spring boot project in order to do that i needed simply need to annotate the selected bean with the correct artifcat i.e. #Component, #Repository #Service etc. and those beans are then automatically managed by the spring fabric. I.e. I can then seen them by printing the BeanDefinitionNames() of the applicationContext i.e.
String[] beanNames = applicationContext.getBeanDefinitionNames();
But that trick does not work with external jar. Now what would be the best compromise for this constellation, i.e. shell i really add spring dependency to my jar java library or is there any magical way i can do it without adding those dependency to my library. Again my target is to allow spring to manage selected beans of the external library i.e. i would like them to appear under:
applicationContext.getBeanDefinitionNames()
is there any pattern-like way that is used to accomplish this?
many thanks for the ideas.
This doesn't relate to Spring Integration at all.
Plus you have to read more documentations.
Any class available in the CLASSPATH can be instantiated as a bean in the Spring Container, e.g.
#Bean
public Foo foo() {
return new Foo();
}
When that Foo is in your jar. No need to modify them for those stereotype annotations.
BTW, to be more clear you even can create beans for Java native classes:
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Date now() {
return new Date();
}

Using SnakeYaml under OSGi?

Does SnakeYaml work within an OSGi framework? I've modified the MANIFEST & such so that it deploys correctly, but but trying to load a document into a JavaBean object structure is failing with "Class not found" exceptions.
Thanks.
Sometimes it is as simple as adding manifest headers to make a jar play nice in the OSGi sandbox. Sometimes jars/libraries do "naughty" things in the context of OSGi. A golden rule is to avoid using "Class.forName()" due to the way OSGi uses classloaders, otherwise perfectly valid in a single class loader environment. I pulled down the source to SnakeYaml and they're bean based loader makes use of Class.forName.
The good news is that there appears to be a constructor, CustomClassLoaderConstructor, that let's you use your own classloader and you use this when you make the core Yaml parser object. The key is getting the right class loader. You'll want to use the classloader of the bundle in which you're using Yaml BUT you'll need to make sure than ANY CLASS that will be created is imported into that bundle. The import will make sure all of the objects needed are in the classloader tree that OSGi creates.
See this question for created a classloader based on a bundle.
For anyone that stumbles across this, newer versions of snakeyaml are already a osgi bundle. No need to fiddle at file MANIFEST.MF.
You must just use a CustomClassLoaderConstructor like this:
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
CustomClassLoaderConstructor constructor = new CustomClassLoaderConstructor(this.getClass().getClassLoader());
Config config = new Yaml(constructor).loadAs(in, Config.class);
Code tested with org.yaml.snakeyaml;bundle-version="1.25.0"

Reliably finding annotations on class loaded with URLClassLoader

I have a code generator that uses URLClassLoader to load classes on a specified path, scan them for annotations, and then using Reflection on the fields/methods, generate DTOs.
It works great, in the test app.
When I put it into the Maven MOJO, I suddenly lose the ability to see the javax.persistence.Entity annotations on the classes. It loads them, it can see all the fields, but the Entity annotation is no longer visible.
I am assuming this is something to do with Classpath issues - is it? Neither the test app (a main() function in the plugin itself) or the MOJO are part of the project that the scanned classes are from. But one works and the other doesn't.
I have a little bit of debug code that prints out all of the annotations on the class when it examines them, and in the non-running version it finds literally none.
Any ideas how I debug the problem/solve it?
The problem turned out to be pretty simple, although I'm not sure why it worked fine in one case and not in another.
My URLClassLoader creation didn't specify a parent classloader. So, I assume it couldn't find anything. As soon as I used
loader = new URLClassLoader(classUrls, Thread.currentThread().getContextClassLoader());
for the classloader, it all started working just fine. I'm pretty ignorant when it comes to the ins and outs of classloaders, so this wasn't obvious. Especially since the example I was following didn't specify a parent either.
When you scan loaded class for annotations, you can't see the annotations which can't be found in classpath. That is, to read JPA's Entity annotation your code generator should have a JPA API in classpath (javax.persistence:persistence-api:1.0 in Maven).
However, if you use classloader to load external classes and scan them for annotations, you may face other problems with missed dependencies and execution of static initializers. May be, the better approach is to use bytecode manipulation libraries, such as ASM, to scan classes without loading them.
Annotations have a persistence level associated with them. Some don't survive compile time (i.e. they are not put into the .class files.) Check to see that the ones you are referencing are not of this type.
I am assuming this is something to do with Classpath issues
My bet is that the persistence-api artifact is declared with a provided scope and isn't listed in the class path passed to the plugin. Can you confirm this?

Categories

Resources