Hi all!
I am not really sure whether this is a Hibernate or a Javassist issue: I will try to describe my findings about this topic: I have a multi-module .ear project where my entities are placed in separate libraries included in the .ear file's /lib/ folder. I follow the interface-implementation pattern with my entity classes, so I have an interface for each of them and a @Proxy added on the impl class. I include the entity classes in my persistence.xml through orm.xml files. I deploy my app to JBoss AS 7.0.2.Final, and I use its integrated hibernate-core-4.0.0.CR2 as a JPA2 persistence provider. I always get a lot of "org.hibernate.HibernateException: HHH000142: Javassist Enhancement failed" exceptions in the log (however these do not prevent my app from being successfully deployed). But the whole stuff is still very undeterministic.
I tried to investigate what causes the problem. Javassist complained about not being able to load the interface classes (but not for all of the entities, and not always for the same ones). I found out that the reason for this was that it tried to look them up with the org.hibernate:main module class loader. But that's not right, since a component integrated into JBossAS cannot have dependency on my app's classes. The reason why that classloader was chosen is hidden in javassist.util.proxy.ProxyFactory.getClassLoader0() method line #595 (javassist-3.12.0.jar), saying:
Code:
loader = interfaces[0].getClassLoader();
... where the interfaces array stores all the interfaces the proxy needs no implement. In here alongside my own interface the org.hibernate.proxy.HibernateProxy interface can also be found; which is part of hibernate-core - of course. In cases, where this is the first element in the interfaces array, then its classloader will be picked, which will be the org.hibernate:main module classloader. When my interface is the first element in the interfaces array - because it also happens sometimes -, then everything is fine, because my app's classloader will be picked to load the interfaces, which will easily do the job.
When I tried to find out what determines the order of that array, I found that it comes from the org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.getProxyFactory(Class, Class[]) method's second argument. That is called from org.hibernate.proxy.pojo.javassist.JavassistProxyFactory.postInstantiate(String, Class, Set, Method, Method, CompositeType), where it is initialized from the incoming third argument in line #59 (org.hibernate.proxy.pojo.javassist.JavassistProxyFactory.java):
Code:
this.interfaces = (Class[]) interfaces.toArray(NO_CLASSES);
The interfaces argument is a Set here, and it comes from: org.hibernate.tuple.entity.PojoEntityTuplizer.buildProxyFactory(PersistentClass, Getter, Setter), from the local variable called proxyInterfaces, which is initialized in line #159 (org.hibernate.tuple.entity.PojoEntityTuplizer.java):
Code:
HashSet<Class> proxyInterfaces = new HashSet<Class>();
and all the detected interfaces are added afterwards, including HibernateProxy.class. So the problem is, that the order of the interfaces in the array will be determined by the
hash codes of the interface classes. And that causes the undeterministic behaviour.
I think, the problem is that Javassist supposes, that picking the classloader of the first interface will be able to load all the other interfaces as well. The safe solution would be to load all interfaces with their own classloaders, but of source I can understand if for some reason Javassist needs to choose one classloader to do the enhancement with. A quick solution whould be to enforce some kind of order in that interfaces array, e.g. based on the class hierarchy order, and always placing HibernateProxy to the end of this array.
I think the second solution is less intrusive, it would only require to change proxyInterfaces to be a LinkedHashSet, and adding HibernateProxy.class at last (instead of adding it first, as it is implemented now).
What do you think?
Thanks for any help!
Best Regards,
Lajos Gáthy