jMar"s Blog DevSmash Developer Portal

Wednesday, February 6, 2008

Java, Reflection, Hibernate Proxy Objects, and Confetti

After spending the last 3 days scratching my head and pushing Google to its theoretical limits, I have just experienced the ever-satisfying thrill of success. It was one of those moments where a little hole appears in the clouds and a beam of light illuminates my desk while gold and silver confetti float down from the overhead sprinklers. It really was that good; I nearly made an announcement over the intercom.

So here's the setup: we have a web application running on a Weblogic Portal server and we're using Hibernate and Spring. Due to application growth and an expanding data model, we decided it was time to turn lazy loading on (with respect to Hibernate). Meanwhile I'm working on some form processing logic that (for the sake of this discussion) needed to be a runtime annotation consumer.

The section of code was to determine whether a form had values for all of the "Recommended Fields". The recommended fields were designated via a RecommendedField annotation in the model class. All this method knows is that the particular model object will extend MySuperClass. So it's pretty straight forward. Here's a basic outline of what I had going:

public static boolean isComplete(MySuperClass object) {

 Method[] methods = object.getClass().getMethods();
 for(Method method : methods) {
  RecommendedField rField = method.getAnnotation(RecommendedField.class);
  if( null != rField) {
   // do some logic, then return true or false 
  }
 }
 
 return true;
}

Nothing seems glaringly wrong in the code above, but to my consternation, method.getAnnotation() always returned null. Around this time I came to understand one of the side effects of Hibernate lazy loading. In order to allow lazy access to any relational objects, Hibernate returns a "proxy" object that essentially wraps and mimics the object that you wanted. This way when you call a getter, instead of throwing a NullPointerException, Hibernate can retrieve the result from the database.

Great... so what now? While the proxy object will suffice whenever you need to access a public property or method, it quickly becomes apparent that introspection and reflection present a problem. Since the proxy object is not truly an instance of the class that it is wrapping, metadata (such as annotations) are not there.

In truth, this realization was the key to solving the problem. Understanding that the annotations simply didn't exist on the object that I was inspecting prompted me to explore how to retrieve the non-proxy version of the object. Here is the solution that I ultimately came up with:

public static boolean isComplete(MySuperClass object) {
 /*
  *  The type casting below is necessary in order to read the annotations on the object's methods.
  *  The "MySuperClass" object that is passed into this method is really a Hibernate proxy object 
  *  that wraps the MySuperClass object (due to Hibernate's lazy loading).
  */
 if (object instanceof HibernateProxy) {
  object = (MySuperClass)((HibernateProxy)object).getHibernateLazyInitializer().getImplementation();
 }
  
 Method[] methods = object.getClass().getMethods();
 for(Method method : methods) {
  RecommendedField rField = method.getAnnotation(RecommendedField.class);
  if( null != rField) {
   // do some logic, then return true or false 
  }
 }
 
 return true;
}

*Cue confetti*

You'll notice that the commented addition is rather peculiar. It essentially takes the proxy object that was passed in and casts it as a HibernateProxy object. Calling getImplementation() returns a generic object, which I can then recast as a MySuperClass object (which is what it claimed to be in the first place). Since the object I am working with is now a real and true instance of MySuperClass, I can inspect the methods and their annotations without any problems.

There is a word of warning to throw in about this solution: since I am replacing the proxy object with the real object, my assumption is that it is now detached from the Hibernate session. Attempts at lazy loading on the object would most likely return null or throw a LazyLoadException (I have not tested to verify). In my case I don't need to access any more data, so it's not a problem. If your circumstances are different, you can simply create a new instantiation out of the casted object, or attempt to reattach the object to the Hibernate session when you're done.



14 comments:

Anonymous said...

hi. i read that in Hibernate, sometimes you have to fight the framework (i.e. it is one of its shortcomings). we are using jdbc currently and seems to work for us.

Anonymous said...

Thanks for the tip. It works great!!!

Eric said...

Thanks for the informative content. Saved me a lot of time, as I was attempting to architect a solution to convert Hibernate proxies to JAX-WS/JAXB objects via CXF. Works like a champ!

Jeremy Martin said...

@Eric
I have to admit, I wouldn't have thought of that particular solution, but I'm glad this was able to save you some time. I don't look back with too much fondness over that particular issue!

Anonymous said...

nice work!
I was looking for the part to identify hibernate proxies.

Anonymous said...

Thanks so much for the tip. I am working on a client/server disconnected model and this was what I was looking for. Saved me hours.

Anonymous said...

regarding to that article
for all the beginners in Hibernate ,

here you can find a nice video with step by step tutorial how to set up the environment In Hibernate

http://hibernatetutorial.com/Hibernate-tutorial-how-to-set-up-basic-Hibernate-development-environment.html

Alexander en Ilona said...

y tnx im using it too

Ali B said...

Thanks buddy :-) you're a legend!

Anonymous said...

Thank you so much!!! This is exactly what I was looking for!

Luiz said...

Thanks dude! This fits like a glove! Greetings from Brazil.

Anonymous said...

can i get the real object from proxy object without using hibernateproxy class?

Anonymous said...

Great help, thanks to you I can now evict a (now fully initialised) proxy object before entering into a nested conversation within seam, meaning no double nested parent flushing! Woo!

Anonymous said...

Thanks a lot, you saved me from having to pull out the rest of my hair!