Monday, February 20, 2012

Do not use static imports in Groovy/Grails

As per Groovy's documentation, static import is supported and can be used as in java.  However static import usage in Groovy can lead to unpredictable issues. I have been happily using static imports for some time until today when I found very strange issue. Let me demonstrate it with an example of what issue I am talking about.


Suppose we have a DateUtil class as mentioned below with some utility methods for playing with dates.

package com.test.util

class DateUtil{
    
    private static final log = LogFactory.getLog(this)
    
    static Date parse(String format, String strDate){
        Date date = null
        try {
            date = Date.parse(format, strDate)
        } catch (Exception e) {
        }
        date
    }
    
}

For the sake of simplicity to understand the actual problem, I have not added many methods in DateUtil class and have added just one i.e parse, which parses the given date in String format to actual Date object as per supplied format. I have intentionally marked log variable above with red color since the problem I am going to mention is related to this variable.

Now suppose we have another class UserService as mentioned below

package com.test.service

import static com.test.util.DateUtil.*

class UserService{
    
    def searchUsersCreatedAfter(String strDate){
        log.info("Searching for users created after : ${strDate}")
        Date date = parse("MM/DD/YYYY",strDate)
        Users.findAllByCreatedDateGreaterThan(date)
    }    
    
}


UserService is a Grails Service class and hence will have all the required dependencies auto injected. One of the variables that all service classes in Gails have access to is "log". So as per this understanding we have written a method in UserService "searchUsersCreatedAfter" which finds all users which were created after the supplied date. This method accepts date in String format, and depends on DateUtils parse method to convert date from String to Date object. As can be seen in UserService, we have statically imported DateUtil's static methods and so we no longer need to prefix DateUtil while using it's static methods.

Now if "searchUserCreatedAfter" method is called from outside and given a date say "01/01/2012", it should work fine and should return results as expected. So what's the problem? Well problem is in "log" variable. If you look into log file and locate for log statement "Searching for users created after : 01/01/2012", you will be shocked to see prefix "com.test.util.DateUtil" instead of "com.test.service.UserService" before the log statement. You must be wondering how come is that possible. "searchUsersCreatedAfter" method is written in UserService, so how come logs are showing DateUtil class.

Let's discuss what has happened over here. Under normal circumstances, log variable referred in UserService should be the one injected by Grails which would have correctly prefixed "com.test.service.UserService" before log statement. But that's not the case over here. "log" variable in UserService is actually static variable defined in DateUtil. The reason being when we have statically imported DateUtil with "*" suffix, all of it's static variables and methods get imported. As a result "log" variable also get's imported. Wait a minute, "log" variable is private in DateUtil. So how come that is accessible by UserService. Well, that's because there is a bug in Groovy which allows privately declared variables accessible from outside.Yes, it's hard to believe but that's the truth. So now since "log" variable used in UserService is actually referring to DateUtils "log" variable, we see "com.test.util.DateUtil" prefix in log statement.

Well, incorrect prefix in log statements might not look very serious problem at first. But this can get serious and might give unpredictable results with other variables. Apart from this, few times I have observed static variables and methods are not visible even when correctly imported. Not sure why, but I have seen such problems. So on the whole my advice is not to use static imports in Groovy.

2 comments:

  1. This is a well known feature/bug in Groovy since day 1. Very annoying but thankfully it'll be fixed in Groovy 3.0.

    Also, it's not a good idea to use static imports with (*) like that in the first place. You could do two things here -

    1. import static com.test.util.DateUtil as DateUtil
    You'll have two separate log and DateUtil.log

    2. import static com.test.util.DateUtil.parse
    Just the methods which you need imported. If there are too many, you can fall back on aliasing above.

    ReplyDelete
  2. Thanks a lot for clearing this up. We ran into this yesterday, unfortunately we only found this post AFTER having solved it as well. Hmmm. Maybe you should change the title to help people in need! :)

    ReplyDelete